6. ActiveX控制項 

 

有許多人把Visual Basic的成功歸功於自訂控制項(Custom Control)。Visual Basic可接受各家廠商的自訂控制項產品,因此我們不必受限於某一家特定廠商,可自由的選擇市面上最好的工具。

現在Visual Basic更進一步地讓發展者可以自行創造自己的自訂控制項(稱為ActiveX控制項)。用Visual Basic發展出來的ActiveX控制項和用C發展出來的控制項在外觀上和行為上完全一樣,事實上,使用Microsoft C++ 的程式設計師也可以使用Visual Basic的ActiveX控制項。

ActiveX控制項應用的範圍極廣,幾乎到處都可以使用,例如,用Microsoft Internet Explorer瀏覽器的網頁、Microsoft Excel 97和Word 97的文件、Microsoft Access和Visual FoxPro資料庫應用程式、Visual Basic、Visual J++ 和Visual C++ 的檔案等等,都可以使用Visual Basic的ActiveX控制項。

本章要告訴你如何建立ActiveX控制項,並且要討論關於ActiveX控制項的程式設計課題。我們把本章裡的範例程式放在隨書光碟裡,以便利讀者隨時參閱。

如何建立ActiveX控制項?
 

「ActiveX控制項界面精靈」可以讓你以現有的控制項為基礎,幫你自動建立一個新的控制項。但是經由這種方式所產生的控制項,初學者對它的程式碼可能很難了解,因此,在這裡我們教你如何自己一步一步地建立控制項,一旦你了解了ActiveX控制項的細節後,你必定更能掌握「ActiveX控制項界面精靈」所產生的程式。

設計ActiveX控制項的步驟
 

要建立ActiveX控制項,請按照下列的步驟進行:

首先,建立一個ActiveX控制項專案。

  1. 在UserControl設計視窗中,用工具箱中的繪圖工具和控制項來擺設外觀,就像你在設計表單時所做的工作一樣。
  2. 在UserControl的程式碼窗中加入一個Resize事件程序。當使用者改變這個控制項的大小時,你可以隨著改變而調整控制項的外觀。
  3. 加入屬性、物件方法和事件,這些構成了這個控制項的程式界面。
  4. 撰寫控制項功能的程式,這個程式決定了控制項的行為。
  5. 加入一個標準執行檔專案,以便對這控制項進行偵錯。
  6. 從「檔案」功能表中選擇「製成」,把這個控制項編譯成OCX檔。

接下來,我們要藉著建立一個叫做Blinker(BLINKER.VBD)的示範控制項,來詳細地討論這幾個步驟。偵錯和編譯會在接下來的兩節中討論。

這個Blinker控制項被用來使想要強調的視窗或控制項在螢幕上產生閃爍的效果。

建立ActiveX控制專案
 

要建立一個ActiveX控制項專案有以下幾個步驟:

  1. 從「檔案」功能表中擇建立新專案,「建立新專案」對話方塊會顯示出來。
  2. 點選兩下ActiveX控制項,Visual Basic會建立一個專案,並顯示UserControl設計視窗。
  3. 從「專案」功能表中,選擇「Project1屬性」,這時「專案屬性」對話方塊就會出現。
  4. 在「專案名稱」欄中輸入VB6WkSamp ,在「專案描述」欄中輸入"VB6 Workshop Blinker Sample Control",按下「確定」。專案名稱會被用來當作OCX檔的檔名,而專案描述中的文字會在「設定使用元件」對話方塊中出現。
  5. 在UserControl屬性視窗中,把Name屬性改為Blinker,這是控制項的物件類別名稱,當你使用這個控制項在你的表單上產生許多控制項的物件執行實體時,這些物件執行實體會被取名為Blinker1、Blinker2...以此類推。
  6. 從「檔案」功能表中選擇「儲存專案」,把Blinker控制項存成BLINKER.CTL,把專案存成BLINKER.VBP。

描繪使用者界面
 

Blinker控制項的功能並不複雜,在Blinker控制項裡,我們只需要放三個Visual Basic提供的控制項:上下控制項(UpDown Control)、文字方塊控制項和計時器控制項﹛﹜imer Control﹜,如圖6-1。


 

 圖6-1 Blinker控制項的視覺界面,包括一個ActiveX控制項和兩個基本控制項

以下的五個步驟告訴你如何建立Blinker控制項的視覺界面:

  1. 從「專案」功能表中選取「設定使用元件」,「設定使用元件」對話方塊會出現。
  2. 在「控制項」頁籤下,選取Microsoft Windows Common Controls-2 6.0(MSCOMCT2.OCX),然後按下「確定」。Visual Basic會加入UpDown控制項及其他的控制項到工具箱中。
  3. 在UserControl設計視窗中產生一個文字方塊控制項,在文字方塊控制項的Name屬性中鍵入txtRate,把Text屬性內容改為0。
  4. 在文字方塊控制項旁邊產生一個UpDown控制項,把它的Name屬性改為updnRate。
  5. 產生一個Timer控制項,設定它的Name屬性為tmrBlink。

調整控制項的大小
 

當使用者把Blinker控制項放在他們設計的表單上時,他們可以拉大或縮小Blinker控制項,因此,Blinker控制項本身必須能調整它的視覺界面,以免Blinker控制項看起來像變形了一樣。這個調整Blinker控制項外觀的工作,必須要靠我們自己寫程式才可以達成,沒有其他簡便的方法可以替代。

改變Blinker控制項大小時,UserControl_Resize事件程序會被驅動,因此,我們要把調整外觀的程式碼放在這裡事件程序中,以下就是這個程式。

`Code for control's visual interface
Private Sub UserControl_Resize()
    `Be sure visible controls are
    `positioned correctly within the
    `UserControl window
    updnRate.Top = 0
    txtRate.Top = 0
    txtRate.Left = 0
    `Resize visible controls
    `when user control is resized
    updnRate.Height = Height
    txtRate.Height = Height
    `Adjust UpDown control's width up
    `to a maximum of 240 twips
    If Width > 480 Then
        updnRate.Width = 240
    Else
        updnRate.Width = Width \ 2
    End If
    `Set width of text box
    txtRate.Width = Width - updnRate.Width
    `Move UpDown control to right edge of
    `text box
    updnRate.Left = txtRate.Width
End Sub

從程式裡你可以看到,我們用了一點小技巧來調整UpDown控制項的大小。我們不把UpDown控制項的大小固定,如果Blinker的寬度小於480 Twips的時候,我們讓UpDown控制項隨著Blinker控制項的大小變化按比例調整寬度,當Blinker寬度超過480 Twips時,則UpDown控制項的寬度會固定在240 Twips。

 加入Blinker控制項的屬性、物件方法和事件 
 

除了一般控制項都有的尺寸、位置和可見性等標準屬性外,Blinker控制項有兩個較特別的屬性:TargetObject和Interval。TargetObject用來設定Blinker控制項閃爍的目標物件,而Interval用來設定目標物件在每秒鐘內該閃爍幾次。在事件方面,Blinker控制項包括了一個Blinked事件,它發生在目標物件閃爍的動作結束之後,另外,在物件方法方面,Blinker控制項有一個Blink物件方法,它用來設定TargetObject和Interval屬性。

以下就是TargetObject、Interval、Blinked和Blink的定義:

Option Explicit

`Windows API to flash a window
Private Declare Function FlashWindow _
Lib "user32" ( _
    ByVal hwnd As Long, _
    ByVal bInvert As Long _
) As Long

`Blinked event definition
Public Event Blinked()

`Internal variables
Private mobjTarget As Object
Private mlngForeground As Long
Private mlngBackground As Long
Private mblnInitialized As Boolean

`Public error constants
Public Enum BlinkerErrors
    blkCantBlink = 4001
    blkObjNotFound = 4002
End Enum

`Code for control's properties and methods
`~~~.TargetObject
Public Property Set TargetObject(Setting As Object)
    If TypeName(Setting) = "Nothing" Then Exit Property
    `Set internal object variable
    Set mobjTarget = Setting
End Property

Public Property Get TargetObject() As Object
    Set TargetObject = mobjTarget
End Property

`~~~.Interval
Public Property Let Interval(Setting As Integer)
    `Set UpDown control--updates TextBox and
    `Timer controls as well
    updnRate.Value = Setting
End Property

Public Property Get Interval() As Integer
    Interval = updnRate.Value
End Property

`~~~.Blink
Sub Blink(TargetObject As Object, Interval As Integer)
    `Delegate to TargetObject and Interval properties
    Set Me.TargetObject = TargetObject
    Me.Interval = Interval
End Sub

TargetObject Property Set屬性程序把閃爍的目標物件指定給一個內部的物件變數mobjTarget,而Interval屬性用來設定和讀取UpDown控制項的設定值,當這個值改變時,文字方塊控制項txtRate裡面的值也會跟著改變,同時也因而改變了計時器控制項tmrBlinker的Interval屬性值。Blinked事件在這裡定義,但事實上它在Timer事件裡被觸發,我們接下來要介紹計時器控制項和文字方塊控制項的事件程序。

撰寫程式定義控制項的行為
 

到目前為止,我們還沒有談到Blinker控制項會做哪些事,Blinker的行為──閃爍另一個控制項或視窗──被定義在三個程序裡:UpDown控制項的Change事件程序、計時器控制項的Timer事件程序和文字方塊控制項的Change事件程序。以下就是這兩個程序的內容。

`Code for control's behavior
Private Sub updnRate_Change()
    `Update the text box control
    txtRate.Text = updnRate.Value
End Sub

Private Sub tmrBlink_Timer()
    On Error GoTo errTimer
    `Counter to alternate blink
    Static blnOdd As Boolean
    blnOdd = Not blnOdd
    `If the object is a form, use FlashWindow API
    If TypeOf mobjTarget Is Form Then
        FlashWindow mobjTarget.hwnd, CLng(blnOdd)
    `If it's a control, swap the colors
    ElseIf TypeOf mobjTarget Is Control Then
        If Not mblnInitialized Then
            mlngForeground = mobjTarget.ForeColor
            mlngBackground = mobjTarget.BackColor
            mblnInitialized = True
        End If
        If blnOdd Then
            mobjTarget.ForeColor = mlngBackground
            mobjTarget.BackColor = mlngForeground
        Else
            mobjTarget.ForeColor = mlngForeground
            mobjTarget.BackColor = mlngBackground
        End If
    Else
        Set mobjTarget = Nothing
        GoTo errTimer
    End If
    `Trigger the Blinked event
    RaiseEvent Blinked
    Exit Sub
errTimer:
    If TypeName(mobjTarget) = "Nothing" Then
        Err.Raise blkObjNotFound, "Blinker control", _
        "Target object is not valid for use with this control."
    
    Else
        Err.Raise blkCantBlink, "Blinker control", _
        "Object can't blink."
    End If
End Sub

Private Sub txtRate_Change()
    `Set Timer control's Interval property
    `to match value in text box
    If txtRate = 0 Then
        tmrBlink.Interval = 0
        tmrBlink.Enabled = False
        mblnInitialized = False
        `If blinking is turned off, be sure object 
        `is returned to its original state
        If TypeOf mobjTarget Is Form Then
            FlashWindow mobjTarget.hwnd, CLng(False)
        ElseIf TypeOf mobjTarget Is Control Then
            mobjTarget.ForeColor = mlngForeground
            mobjTarget.BackColor = mlngBackground
        End If
    Else
        tmrBlink.Enabled = True
        tmrBlink.Interval = 1000 \ txtRate
    End If
End Sub

UpDown控制項的Change事件程序只是單純地把UpDown控制項的值複製到文字方塊控制項中。計時器控制項的Timer事件程序在處理目標物件或視窗的閃爍動作;如果閃爍的對象是一個視窗,那麼Timer事件程序會呼叫FlashWindow API函式;如果閃爍的對象是一個控制項,那麼它用交換其前後景顏色的方式來達到閃動的目的。文字方塊控制項的Change事件程序負責處理閃爍的頻率。


參考資料:

請參閱 第十二章"對話方塊、視窗和其他表單" 中對FlashWindow API函式的討論。


如何對控制項進行偵錯?
 

建好ActiveX控制項後,下一步就是測試它。從「執行」功能表中選取「開始」時,Visual Basic通常對ActiveX偵錯測試的方式是把它顯示在Internet Explorer裡,如圖6-2所示。透過這種方式,你可以看到這個控制項執行的情形,但這並不是個偵錯的好方法,因為不能很容易地測試控制項的屬性和物件方法。


 

 圖6-2 Visual Basic將執行的ActiveX控制項顯示在Internet Explorer中

若要能夠完全地對ActiveX控制項進行偵錯,請按照以下幾個步驟建立一個包含兩個專案的專案群組,其中一個是ActiveX控制項專案,另一個則是測試用的專案:

  1. 如果ActiveX控制項還在IDE中,請從「檔案」功能表中選取「儲存專案」。
  2. 從「檔案」功能表中選取「建立新專案」。
  3. 在「建立新專案」對話方塊中點選兩下「標準執行檔」,產生一個只含單張表單的專案,以供你測試ActiveX控制項。
  4. 從「檔案」功能表中選取「新增專案」。
  5. 在「新增專案」的「開啟」頁籤中選取你的ActiveX控制項專案,按下「開啟舊檔」。
  6. 如果UserControl視窗是開啟的,關閉它。這個視窗關閉後,「工具箱」中的ActiveX控制項就會在啟用狀態下以供使用。
  7. 從「工具箱」中點選該控制項,然後在測試專案的表單上產生一個這樣的控制項。
  8. 在測試專案中寫些程式,以測試該控制項的屬性及物件方法。
  9. 執行測試專案。

以下這段程式碼測試一個名為blnkText的Blinker控制項,這個控制項和另一個叫txtStuff的文字方塊在同一張表單上。

Option Explicit

Private Sub Form_Load()
    `Set the object to blink
    blnkTest.Blink txtStuff, 1
End Sub

Private Sub Form_Click()
    `Stop the blinker
    blnkTest.Interval = 0
End Sub

Private Sub blnkTest_Blinked()
    Static intCount As Integer
    intCount = intCount + 1
    Caption = "Blinked " & intCount & " times"
End Sub

圖6-3中顯示的是測試中的Blinker控制項。


 

 圖6-3 測試中的Blinker控制項

在程式執行中進行偵錯時,你可以從測試專案裡追蹤程式的執行,一直追蹤到ActiveX控制項裡的程式碼。用「偵錯」功能裡的「逐行」、「逐程序」來設定中斷點和使用「監看式」可以幫助你進行偵錯,如圖6-4。

在測試程式執行以前,Blinker控制項中的部份程式碼就已經被執行了,這些程式碼在Blinker控制項的UserControl_Resize事件程序裡。如果要看到這些程式碼被執行的情況,你可以在製作測試用的表單之前,在UserControl_Resize程序裡設定幾個中斷點。當你在表單上產生一個Blinker控制項時,Visual Basic會把程式執行停留在中斷點上。


 

 圖6-4 對Blinker控制項進行偵錯

ActiveX控制項中的程式碼可以隨時修改,但是當你打開了UserControl設計視窗時,Visual Basic就不讓你動用表單上以及工具箱中的ActiveX控制項了,你必須關掉UserControl設計視窗才能夠繼續使用ActiveX控制項。

如果在測試用的表單上產生了一個ActiveX控制項,然後又在這個控制項上加入一些設計階段的屬性(稍後會討論如何產生設計階段屬性),那麼Visual Basic會禁止你動用表單上的這個控制項。結束ActiveX控制項中正在執行的程式,也會有相同的結果。你可以藉由執行測試專案來使ActiveX控制項恢復可用狀態。


注意:

當ActiveX控制項被初始化(Initialize)時,在表單上某些其他控制項的屬性可以被ActiveX控制項中的程式使用,有些屬性則不行。如果在表單上先拉出一個控制項,然後才拉出ActiveX控制項,那麼在ActiveX控制項的程式中引用前面這個控制項的屬性,可能會造成程式在執行時沒有任何反應,這是Visual Basic本身的一個缺陷。


如何對控制項進行編譯和註冊?
 

在編譯一個ActiveX控制項之前,你應該決定好要把這控制項編譯成機器碼還是虛擬碼。機器碼的執行速度比較快,而含虛擬碼的OCX檔比較小。

以Blinker控制項為例,Blinker控制項的行為靠Timer事件程序執行,因此,執行速度不是個重要的考慮因素。另一方面,Blinker控制項的機器碼OCX檔和虛擬碼OCX檔的檔案大小差別也只有5K而已,因此,從速度與檔案大小看來,機器碼OCX與虛擬碼OCX兩者並沒有什麼差別。

不過,如果這個OCX檔是放在Internet上面讓使用者下載的話,5K就是個很大的差別了。一般而言,要放在Internet上的ActiveX控制項,應該要編譯成虛擬碼。

設定編譯選項和進行程式編譯的步驟如下:

  1. 從「檔案」功能表中,選擇「製成Blinker.OCX」,這時「製成執行檔」對話方塊會出現。
  2. 在「製成執行檔」對話方塊裡點選「選項」,叫出「專案屬性」的對話方塊。
  3. 「專案屬性」對話方塊出現後,點選「編譯」頁籤,然後選擇編譯方式──機器碼或是虛擬碼。如果你要對程式進行最佳化編譯,你也要在這裡選取最佳化的選項。最後,按「確定」。
  4. 回到「製成執行檔」對話方塊後,設定要存入的檔名及磁碟目錄,按下「確定」,這時Visual Basic就會開始編譯。

一旦控制項被編譯完畢之後,Visual Basic自動會向系統註冊這個ActiveX控制項。控制項經過註冊之後,Visual Basic會把它加到「設定使用元件」對話方塊中的「控制項」頁籤下的選項中。(不過必須在另一個新專案裡才看得到這個控制項在選項中)。請看圖6-5。

如果你的ActiveX控制項包含在預備要散發的應用程式中,那麼當你在另一部機器上安裝應用程式時,「封裝暨部署精靈」會在待安裝的機器上向系統註冊你的ActiveX控制項。如果散發的應用程式不附帶安裝程式,你必須安裝Visual Basic的執行階段動態連結程式庫(Runtime DLL)、MSCOMCT2.OCX和BLINKER.OCX,然後用REGOCX32.EXE公用程式來對MSCOMCT2.OCX和BLINKER.OCX進行註冊。你可以在Visual Basic光碟中的 \COMMON\TOOLS\VB\REGUTILS目錄下找到REGOCX32.EXE。以下這一行就是向系統註冊Blinker控制項的命令:

REGOCX32.EXE  BLINKER.OCX


 

 圖6-5 「設定使用元件」對話方塊列出所有已註冊的控制項

如何產生設計階段的屬性?
 

Blinker控制項的TargetObject和Interval屬性可以在程式執行階段加以設定,我們也可以讓這兩個屬性在設計階段就被設定,也就是說,我們可以在「屬性視窗」裡看到他們。如果在「屬性視窗」裡設定這些屬性項目,我們要在UserControl的ReadProperties和WriteProperties事件程序中,使用PropertyBag物件和它的兩個物件方法── ReadProperty及WriteProperty,如下所示:

'Make Interval a design-time property
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    updnRate.Value = PropBag.ReadProperty("Interval", 0)
End Sub

Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    PropBag.WriteProperty "Interval", updnRate.Value, 0
End Sub

這段程式把Interval屬性放進了Blinker控制項的「屬性視窗」裡,如圖6-6,接下來我們需要加入Property_Changed陳述式到Blinker控制項的Interval Property Let程序裡;這樣Visual Basic才會把在設計階段取得的Interval屬性內容存起來。以下就是修改後的Property Let Interval程序:

`~~~.Interval
Public Property Let Interval(Setting As Integer)
    `Set UpDown control--updates TextBox and
    `Timer controls as well
    updnRate.Value = Setting
    `Update design-time setting
    PropertyChanged "Interval"
End Property


 

 圖6-6 用PropertyBag物件在「屬性」視窗中加入屬性項目

最後,我們需要確定在設計階段對Interval所作的改變不會引起Timer事件(回頭看一下txtRate_Change程序,看看我們原來是如何在執行階段觸發Timer事件的)。我們可以用UserControl物件的Ambient.UserMode屬性來判斷控制項是在執行階段還是設計階段;如果UserMode是False,那麼表示控制項正處於設計階段,反之,則在執行階段。以下是已經修改過的txtRate_Change程序。如果我們在設計階段把Interval設定成非零的值,這個修改後的程序就不會產生錯誤。

Private Sub txtRate_Change()
    `Exit if in design mode
    If Not UserControl.Ambient.UserMode Then Exit Sub
    `Set Timer control's Interval property
    `to match value in text box
    If txtRate = 0 Then
        tmrBlink.Interval = 0
        tmrBlink.Enabled = False
        mblnInitialized = False
        `If blinking is turned off, be sure object 
        `is returned to its original state
        If TypeOf mobjTarget Is Form Then
            FlashWindow mobjTarget.hwnd, CLng(False)
        ElseIf TypeOf mobjTarget Is Control Then
            mobjTarget.ForeColor = mlngForeground
            mobjTarget.BackColor = mlngBackground
        End If
    Else
        tmrBlink.Enabled = True
        tmrBlink.Interval = 1000 \ txtRate
    End If
End Sub

注意:

在ActiveX控制項的Initialize事件中,UserMode屬性無法被使用。


要讓TargetObject成為設計階段屬性,我們可要花費多一點心思了,因為Visual Basic的「屬性視窗」目前並不能以物件作為屬性的設定值,也就是說,Visual Basic不能把物件當作設定值選項讓使用者選擇,因此,我們要用一個新屬性來接收一串字串,然後利用這字串來設定TargetObject屬性內容。這個新屬性TargetString可以在「屬性」視窗中出現,以下是它的程序內容:

`~~~.TargetString
Public Property Let TargetString(Setting As String)
    If UserControl.Parent.Name = Setting Then
        Set TargetObject = UserControl.Parent
    ElseIf Setting <> "" Then
        Set TargetObject = UserControl.Parent.Controls(Setting)
    End If
End Property

Public Property Get TargetString() As String
    If TypeName(mobjTarget) <> "Nothing" Then
        TargetString = mobjTarget.Name
    Else
        TargetString = ""
    End If
End Property

我們還需要把PropertyChanged陳述式加入到控制項的TargetObject屬性裡,如下所示:

'~~~~~~.TargetObject
Public Property Set TargetObject(Setting As Object)
    If TypeName(Setting) = "Nothing" Then Exit Property
    'Set internal object variable
    Set mobjTarget = Setting
    'Property has changed
    PropertyChanged "TargetObject"
End Property

要把TargetString加到屬性視窗中,請修改UserControl控制項的ReadProperties和WriteProperties事件程序:

`Get design-time settings
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    updnRate.Value = PropBag.ReadProperty("Interval", 0)
    TargetString = PropBag.ReadProperty("TargetString", "")
End Sub

`Save design-time settings
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    PropBag.WriteProperty "Interval", updnRate.Value, 0
    PropBag.WriteProperty "TargetString", TargetString, ""
End Sub

如何顯示「屬性頁」對話方塊?
 

「屬性頁」對話方塊讓你不用透過「屬性」視窗就可以設定ActiveX控制項的設計階段屬性。這樣有什麼好處?你可以把控制項中相關的重要的屬性集合在一張「屬性頁」裡,方便地修改其內容,而不必在「屬性」視窗中眾多的屬性項目裡辛苦地尋找你要設定的屬性。

你也可以利用「屬性頁」列出可供使用者選擇的屬性設定值。例如Blinker控制項的「屬性頁」列出了可供TargetString設定的屬性設定值(表單上的物件),如圖6-7。

先確定你在「專案」視窗中選到的是這個ActiveX控制項的專案,而不是測試專案,然後再增加一個「屬性頁」到ActiveX控制項專案裡。首先,在「專案」功能表裡點選「新增屬性頁」,然後點選兩下「屬性頁」圖像。在「屬性」視窗中把這個新屬性頁物件的Name屬性和Caption屬性加以修改,屬性頁的Caption屬性內容會在執行時顯示在「屬性頁」的頁籤標題上。


 

 圖6-7 Blinker控制項的「屬性頁」

接下來,我們要把這個屬性頁和ActiveX控制項連結在一起。第一步先打開UserControl設計視窗並且選擇這個控制項,在「屬性」視窗裡點選兩下PropertyPages屬性,這時會有一個「連結至屬性頁」對話方塊出現,如圖6-8,選擇你要的屬性頁,按下「確定」。

當你把ActiveX控制項和屬性頁連結在一起之後,把UserControl設計視窗關掉,點選在測試表單上的Blinker控制項。你可以看到Blinker的「屬性」視窗中的屬性項目裡多了一個「自訂」屬性。點選兩下「自訂」屬性,你就可以看到你剛剛增加屬性頁了。


 

 圖6-8 利用「連結至屬性頁」對話方塊將屬性頁和控制項連結在一起

在屬性頁的設計階段,你可以在屬性頁上面加入各種控制項以及屬性頁的事件程序,就好像在設計一張表單一樣。屬性頁中有一個內建的SelectedControls集合物件,透過這個集合物件你可以取得屬性頁所連結的ActiveX控制項,進而取得這個ActiveX控制項(Blinker)裡的屬性。另外,可以利用屬性頁的SelectionChanged事件程序來對屬性頁上的控制項作資料初值化的工作。以下的程式利用屬性頁設定Blinker屬性頁上的Interval和TargetObject屬性。

Blinker屬性頁包含了一個文字方塊控制項txtInterval和一個下拉式清單方塊(ComboBox Control) cmbTargetString。

Private Sub PropertyPage_SelectionChanged()
    `Set property page interval to match
    `control's setting
    txtInterval = SelectedControls(0).Interval
    `Build a list of objects for TargetString
    Dim frmParent As Form
    Dim ctrIndex As Control
    Dim strTarget As String
    `Get form the control is on
    Set frmParent = SelectedControls(0).Parent
    strTarget = SelectedControls(0).TargetString
    If strTarget <> "" Then
        `Add current property setting to 
        `combo box
        cmbTargetString.List(0) = strTarget
    End If
    If frmParent.Name <> strTarget Then
        `Add form name to combo box
        cmbTargetString.AddItem frmParent.Name
    End If

    `Add each of the controls on the form to
    `combo box
    For Each ctrIndex In frmParent.Controls
        `Exclude Blinker control
        If TypeName(ctrIndex) <> "Blinker" Or _
            ctrIndex.Name = strTarget Then
            cmbTargetString.AddItem ctrIndex.Name
        End If
    Next ctrIndex
    `Display current TargetString setting
    cmbTargetString.ListIndex = 0
End Sub

注意:

SelectedControls集合物件在屬性頁的Initialize事件中不能被使用。


在上面的程式中,有幾點要特別討論一下。SelectedControls(0) 會傳回目前選到的物件── Blinker控制項。另外,我們必須加上一個Parent屬性到Blinker控制項裡,這樣才能存取Blinker控制項收納器的資訊。以下是Blinker控制項的Parent屬性程序:

`~~~.Parent
Public Property Get Parent() As Object
    Set Parent = UserControl.Parent
End Property

接下來工作是:用屬性頁的ApplyChanges事件程序把屬性頁裡的設定內容寫到ActiveX控制項的屬性裡加以儲存。以下這段程式告訴你如何完成這件工作:

Private Sub PropertyPage_ApplyChanges()
    `Save settings on the property page
    `in the control's properties
    SelectedControls(0).Interval = txtInterval.Text
    SelectedControls(0).TargetString = _
        cmbTargetString.List _
        (cmbTargetString.ListIndex)
End Sub

如果使用者更動了任何一項屬性頁上的設定,我們必須通知屬性頁本身。這要靠Visual Basic裡一個內建的Changed屬性來達成。當使用者在「屬性頁」上按下了「確定」或「套用」按鈕時,Changed屬性會告訴Visual Basic去套用目前屬性頁上的設定,我們在屬性頁中控制項的事件程序裡直接使用Changed屬性,達到相同的效果。以下的程式告訴你,屬性頁上任何一個設定被更動時,我們如何通知屬性頁。

Private Sub txtInterval_Change()
    Changed = True
End Sub

Private Sub cmbTargetString_Change()
    Changed = True
End Sub

使用者可以用三種方式將屬性頁顯示出來:把滑鼠游標到移表單的ActiveX控制項上,按下滑鼠左鍵,然後選擇「屬性」;按兩下「屬性」視窗中的(自訂)屬性項目;按二下(自訂)屬性內容空格旁邊的"... "按鈕(Ellipsis)。

以下的幾個步驟教你如何加上一個"..."按鈕到屬性視窗的某個屬性項目上:

  1. 選擇ActiveX控制項的程式碼視窗。
  2. 從「工具」功能表單中選擇「程序屬性」,「程序屬性」對話方塊即會出現。
  3. 選擇「進階」。
  4. 從「名稱」下拉式清單中選擇屬性名稱,在「瀏覽屬性時使用此頁」下拉式清單中選擇你要的屬性頁,如圖6-9。
  5. 按下「確定」。


 

 圖6-9 使用「程序屬性」對話方塊將屬性頁與某個屬性連結

現在你可以在「屬性」視窗中按下TargetString屬性項目的"..."按鈕,就可以看到在圖6-10裡的屬性頁。當使用者以上述方式顯示屬性頁時,屬性頁應該要把駐點(Focus)放在相關的控制項上。以下屬性頁的EditProperty事件程序告訴你如何在屬性頁的控制項上設定駐點。

Private Sub PropertyPage_EditProperty(PropertyName As String)
    `Set focus on the appropriate control
    Select Case PropertyName
        Case "TargetString"
            cmbTargetString.SetFocus
        Case "Interval"
            txtInterval.SetFocus
        Case Else
    End Select
End Sub


 

 圖6-10 按下"..."按鈕以呼叫「屬性頁」

如何以非同步的方式載入屬性?
 

在網頁中使用的ActiveX控制項需要用非同步(Asynchronous)方式載入屬性的設定值,這樣可以讓網頁瀏覽器一邊顯示網頁的內容,一邊下載圖形和其他較大的資料項。

圖6-11裡是一個AsynchronousAnimation控制項,它是由Microsoft Windows Common Control-2 6.0 (MSCOMCT2.OCX)裡的Animation控制項改良而來的。


 

 圖6-11 AsynchronousAnimation正在播放一個AVI檔

AsynchronousAnimation控制項的AVIFile屬性可以接受一個代表檔名或URL的字串,而AsyncRead方法則用來傳輸檔案,把讀入的檔案存放在Windows的Temp目錄下,檔名由Visual Basic自動指定。以下就是AVIFile的屬性程序。

Option Explicit

Dim mstrAVISourceFile As String
Dim mstrTempAVIFile As String

`~~~.AVIFile
Property Let AVIFile(Setting As String)
    If UserControl.Ambient.UserMode _
        And Len(Setting) Then
        AsyncRead Setting, vbAsyncTypeFile, "AVIFile"
        mstrAVISourceFile = Setting
    End If
End Property

Property Get AVIFile() As String
    AVIFile = mstrAVISourceFile
End Property

當檔案傳輸完畢後,AsyncRead方法會驅動AsyncReadComplete事件,我們可以把所有非同步執行的事件放在AsyncReadComplete事件程序中處理。我們把AsyncProp.PropertyName的值放在Select Case陳述式裡作條件判斷,讓每一個非同步的屬性執行它們所應執行的程式碼。在我們以下的範例中,AsyncReadComplete事件程序會利用一個叫作aniControl的Animation控制項開啟一個AVI檔。

`General event handler for all async read complete events
Private Sub UserControl_AsyncReadComplete _
    (AsyncProp As AsyncProperty)
    Select Case AsyncProp.PropertyName
        `For AVIFile property
        Case "AVIFile"
            `Store temporary filename
            mstrTempAVIFile = AsyncProp.Value
            `Open file
            aniControl.Open mstrTempAVIFile
            `Play animation
            aniControl.Play
        Case Else
    End Select
End Sub

當ActiveX控制項結束它們所有的動作後,要確定把暫存檔清理掉。一個好的Internet應用程式不應該在使用者的機器上留下任何暫存檔。以下的程式用來關閉aniControl檔並且刪除掉暫存檔。

Private Sub UserControl_Terminate()
    `Delete temporary file
    If Len(mstrTempAVIFile) Then
        aniControl.Close
        Kill mstrTempAVIFile
    End If
End Sub

要使用AsynchronousAnimation控制項,只要在表單上拉出這個控制項,並在事件程序中設定AVIFile屬性即可。以下的例子在用這個控制項開啟播放Visual Basic光碟裡的"檔案搜尋"動畫。

Private Sub Form_Load()
    aaniFindFile.AVIFile = _
    "d:\common\graphics\avis\findfile.avi"
End Sub

參考資料:

請參閱 第八章"建立Internet元件" ,本章告訴你如何把AsynchronousAnimation控制項內嵌在網頁裡。


如何建立一個與資料庫連結的控制項?
 

如果ActiveX控制項的屬性提供資料連結(Data Binding)功能,我們就可以用這個ActiveX控制項顯示資料錄中的資料。所謂 " 資料連結 " 是指控制項屬性與資料錄或查詢的資料欄之間的關係。例如,我們可以把前一節中的Blinker控制項的Interval屬性連上資料庫中的某個數值型別的欄位,這種屬性與資料欄之間的關係就是資料連結。如果要在控制項的屬性上加上資料連結功能,請遵循以下這幾個步驟:

  1. 將控制項所屬的專案載入Visual Basic發展環境中。
  2. 從「工具」功能表中選取「程序屬性」。
  3. 在「程序屬性」對話方塊中點選「進階」按鈕,如 圖6-9 。
  4. 在「名稱」中選擇欲連結的屬性。
  5. 在「資料連結」選項群中選取「屬性具資料連結功能」,按照下列指示選取其它選項:
    • 如果屬性是該控制項主要的資料連結屬性,請選取「此屬性會連結到DataField」。如果控制項中有一個以上的資料連結屬性,應該在最主要的連結屬性中選取以上這個選項,因為同一個控制項中只能有一個屬性可以用這個選項。
       
    • 如果要在設計階段把該屬性顯示在「資料連結」對話方塊中,請選取「設計階段時,會顯示於DataBindings集合物件」;如果只要在執行階段設定此一屬性,那麼不要選這個選項。
       
    • 如果使用者或程式可以改變顯示在屬性中的資料,請選取「在屬性值改變前,先呼叫CanProperty Change」,這個選項讓我們可以在屬性值改變時做一些資料驗證的工作。
       
    • 如果選取了「即時更新」,那麼當屬性值有所改變時,連結的資料欄也會跟著更新。
       

如圖6-12,Employee控制項是一個很簡單的資料連結控制項,它連結Visual Basic提供的NWIND資料庫。


 

 圖6-12 Employee控制項用來顯示NWIND資料庫的Employee資料表

Employee控制項(DatCtl.VBP)包含了幾個標籤和文字方塊,用以顯示NWIND資料庫中的Employee資料表,其中FirstName、LastName、HireDate和Notes等屬性的程式碼如下:

`Properties section
Public Property Get FirstName()
    FirstName = lblFirstName.Caption
End Property

Public Property Let FirstName(Setting)
    lblFirstName.Caption = Setting
    PropertyChanged FirstName
End Property

Public Property Get LastName() As String
    LastName = lblLastName.Caption
End Property

Public Property Let LastName(Setting As String)
    lblLastName.Caption = Setting
    PropertyChanged LastName
End Property

Public Property Get HireDate() As String
    HireDate = lblHireDate.Caption
End Property

Public Property Let HireDate(Setting As String)
    lblHireDate.Caption = Setting
    PropertyChanged HireDate
End Property

Public Property Get Notes() As String
    Notes = txtNotes.Text
End Property

Public Property Let Notes(Setting As String)
    txtNotes.Text = Setting
    PropertyChanged Notes
End Property

以上的每個Property Let屬性程序都有一行PropertyChanged指令,根據Visual Basic文件所述,你應該在所有資料連結的屬性中加入這一行,儘管這些屬性只能在執行時期才能使用。

如果要使用Employee控制項,請看以下幾個步驟:

  1. 建立一個新的標準執行檔專案。
  2. 把DatCtl.VBP專案加進來,建立專案群組。
  3. 在表單上產生一個Data控制項,把它的DatabaseName設為NWIND.MDB,RecordSource屬性設為Employee。
  4. 在表單上產生一個Employee控制項,在DataBindings屬性上按下「... 」按鈕,叫出「資料連結」對話方塊,如圖6-13。
     

     圖6-13 利用「資料連結」對話方塊把屬性與資料欄加以連結
  5. 從「屬性名稱」中選取FirstName,「資料來源」選擇Data1,「資料欄」則選FirstName。
  6. 為每一個Employee的屬性重複步驟5,最後才按下「確定」。
  7. 執行這個專案。當你用Data控制項的按鈕移動資料錄時,Employee控制項就會顯示NWIND資料庫的資訊,如圖6-14。


 

 圖6-14 以Employee控制項和Data控制項顯示NWIND.MDB中的資料錄

如何使用DataRepeater控制項?
 

DataRepeater控制項是一種收納器(Container),用以收納具備資料連結功能的ActiveX控制項。要使用DataRepeater控制項,你必須先建好編譯過的資料連結控制項,就像前一節中的Employee控制項。

在顯示資料庫中的資料時,DataRepeater控制項讓你可以用捲動式的清單取代一般表單(一次一筆)的界面,有點像FlexGrid控制項,但卻可以在格子內包含其他的控制項。從圖6-15中你可以看到一般表單界面、FlexGrid界面和DataRepeater界面的差異。


注意:

DataRepeater控制項必須使用相容的資料來源,如ADO Data控制項;DataRepeater控制項無法與Data控制項一起使用。


請按照以下步驟使用DataRepeater控制項:

  1. 從「專案」功能表中選取「設定使用元件」。
  2. 在「設定使用元件」對話方塊中選取「Microsoft ADO Data Cnotrol 6.0」,然後按下「確定」,Visual Basic便會把ADO Data控制項和DataRepeater控制項加到工具箱中。
     

     圖6-15 一般表單、FlexGrid控制項和DataRepeater控制項提供三種不同的界面
  3. 在一張表單上建立一個DataRepeater控制項。
  4. 在同一張表單上建立一個ADO Data控制項。
  5. 按下ADO Data控制項中ConnectionString屬性的「... 」按鈕,叫出「屬性頁」對話方塊。
  6. 選取「使用ODBC資料來源名稱」,再從下拉式清單中選擇一個資料來源(跳過步驟7到步驟11),或是按下「新增」按鈕新增一個新的資料來源。(步驟7到步驟11解釋如何產生一個ODBC Microsoft Access資料來源。)
  7. 在「建立新資料來源」對話方塊中選擇「系統資料來源(只套用在這部機器)」,按下「下一步」按鈕。
  8. 選取「Microsoft Access Driver(*.MDB)」,按「下一步」,再按「完成」。
  9. 在「ODBC Microsoft Access 97設定」對話方塊的「資料來源名稱」中填入資料來源的名稱。
  10. 按下「選取」按鈕,選擇一個Access的資料庫,按下「確定」以關閉「ODBC Microsoft Access 97設定」對話方塊。
  11. 在「屬性頁」對話方塊中選取「使用ODBC資料來源名稱」,從下拉式清單中選取步驟9中所新增的資料來源,按下「確定」。
  12. 在ADO控制項的RecordSource屬性欄按下「...」按鈕,叫出「屬性頁」對話方塊。
  13. 「命令類型」選擇「2-adCmdTable」,在「資料表或預存程序的名稱」清單中選擇一個資料表,按下「確定」。
  14. 在表單上點選DataRepeater控制項,將它的DataSource屬性設成步驟4的ADO Data控制項的名稱。
  15. DataRepeater控制項的Repeated Control Name屬性設為ActiveX控制項的名稱。
  16. 在DataRepeater控制項的(自訂)屬性欄按下「... 」鈕,叫出「屬性頁」對話方塊,然後點選「重複項連結」頁籤,如圖6-16。
  17. 在「屬性名稱」中選擇一個屬性名稱,然後在「資料欄」中選擇一個資料欄。按下「新增」。
  18. 為ActiveX控制項的每一個屬性重複步驟17,然後按下「確定」。


 

 圖6-16 利用DataRepeater的「屬性頁」對話方塊將重複的控制項屬性連結至資料欄

執行這個專案時,你可以用ADO Data控制項的箭號按鈕或是DataRepeater控制項的捲軸來移動資料錄。

如何建立一個收納器控制項?
 

如果你把某個使用者控制項的ControlContainer屬性設為True,所有放在這個控制項上的物件可以一起被移動或改變大小,Microsoft Tabbed Dialog控制項和DataRepeater控制項就是屬於收納器(Container)控制項。圖6-17所顯示的是一個由Shape和Label控制項所建立的簡單收納器控制項。


 

 圖6-17 這個Container控制項的ControlContainer屬性被設為True,它具備類似Frame控制項的功能

以下這段程式告訴你Container控制項(Container.VBP)如何處理其形狀大小的改變:

`User interaction section
Private Sub UserControl_Resize()
    `Resize the frame to match control
    shpFrame.Width = UserControl.ScaleWidth - shpFrame.Left
    shpFrame.Height = UserControl.ScaleHeight - shpFrame.Top
End Sub

Container的Caption屬性初設值由Extender物件設定。Extender物件讓所有的物件可以存取Visual Basic或收納器控制項的內建屬性,它可以被使用在InitProperties程序裡,但是如果你在更早的階段(如控制項的Initialize事件中)使用Extender物件,Visual Basic會產生一個執行階段的錯誤。當使用者在表單上建立一個Container控制項時,以下這段程式碼會把Visual Basic自動指定的名稱顯示在Container控制項的標題上。

`Control maintenance section
Private Sub UserControl_InitProperties()
    `Display appropriate caption
    lblTitle.Caption = Extender.Name
End Sub

Container的Caption屬性可以設定或是傳回lblTitle的Caption屬性。在設計階段,Container的Caption屬性是一個可讀可寫的屬性,因此,它會有Property Let、Property Get、Read Properties和Write Properties等程序,如下所示:

Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    lblTitle.Caption = PropBag.ReadProperty("Caption")
End Sub

Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    PropBag.WriteProperty "Caption", lblTitle.Caption
End Sub

`Properties section
Public Property Get Caption() As String
    Caption = lblTitle.Caption
End Property

Public Property Let Caption(Setting As String)
    lblTitle.Caption = Setting
End Property

Container的Controls屬性會傳回Container物件所包含的集合物件,這是一般的Frame控制項所欠缺的功能,我們特別把它放在範例中,告訴你如何使用ContainedControls集合物件。當你把Container的ControlContainer屬性設為True時,Visual Basic就會自動建立一個ContainedControls集合物件,其他種類的控制項不會有這樣的集合物件。以下就是唯讀的Controls屬性的程式內容:

`Read-only property
Public Property Get Controls() As Collection
    Set Controls = UserControl.ContainedControls
End Property