10.API函式

 

V isual Basic有一個強大的功能,它可以呼叫在動態連結程式庫(Dynamic Link Library,DLL)裡的函式,這些函式包括Microsoft Windows提供的應用程式設計界面 (Application Programming Interface,API)。這些Windows API函式以及在其他DLL裡的函式,大大的延伸了Visual Basic的能力,也因此Visual Basic才會凌駕於許多程式語言之上。

在本章中,我們要告訴你如何使用Visual Basic的換行接續字元,讓函式的宣告部分能讓人易讀易懂,我們也將示範幾個常用函式,作為你學習使用API的第一課,另外,我們也會詳細地討論幾個簡單的範例以及一些開始使用API函式時常碰到的陷阱。

如何呼叫API函式?
 

在使用API函式之前,你必須要在你的程式裡先宣告這些API函式。

宣告
 

API函式並不是Visual Basic內部的函式,因此,你必須在你的程式中明確加以宣告才能使用它們,你可以在Visual Basic的線上說明裡找到Declare陳述式的完整語法。在這裡我們要告訴你幾個秘訣。

有些API函式的宣告部份十分冗長,在以前的作法裡,你不是必須忍受這種冗長的宣告,就是得想盡辦法把它擠成一行,例如,以下是GetTempFileName函式的標準宣告,在Visual Basic 4以前的版本裡你必須把整個宣告全部放在一行裡面:

Private Declare Function GetTempFileName Lib "kernel32" Alias
"GetTempFileNameA" (ByVal lpszPath As String, ByVal lpPrefixString
As String, ByVal wUnique As Long, ByVal lpTempFileName As String)
As Long

這裡有一個簡化後的宣告,它縮短了這一長串的宣告,但它的缺點是很難讓人看懂:

Private Declare Function GetTempFileName& Lib "kernel32" Alias
"GetTempFileNameA" (ByVal Pth$, ByVal Prf$, ByVal Unq&, ByVal Fnm$)

現在Visual Basic的換行接續字元(底線符號 _)讓你可以使用較長、較能讀懂的參數名稱,並且把一長串的函式宣告分成好幾行較短的宣告格式。以下是上述的宣告部份用換行接續符號改變後的結果:

Private Declare Function GetTempFileName _
Lib "kernel32" Alias "GetTempFileNameA" ( _
    ByVal lpszPath As String, _
    ByVal lpPrefixString As String, _
    ByVal wUnique As Long, _
    ByVal lpTempFileName As String _
) As Long

在本書中,所有API函式的宣告都採用這個格式。

32位元函式的宣告
 

在前面的例子中,GetTempFileName函式的別名是GetTempFileNameA,這表示GetTempFileNameA在DLL中還有另一個名稱叫做GetTempFileNameA。在Windows裡,與字串變數相關的32位元函式是參照16位元版本的函式命名而來的,這些32位元函式雖然已經以32位元編碼格式重新修改編譯過了,但它們在內部仍然使用ANSI字串,因此函式名稱末尾有一個"A"。請注意,在32位元版本的Visual Basic裡,Windows API函式名稱的字母大小寫不能互相替代。


注意:

在Windows NT環境下,Microsoft提供了另一組32位元的API函式,這些函式使用的字串在系統DLL裡面是以Unicode來處理的,因此,這些函式在DLL裡的原始名稱都帶著一個字尾W。Window 95不支援Unicode版的函式。


如果想確定函式宣告格式是正確的,可以在WIN32API.TXT裡找到正確的格式,這個檔案存放在 \Program Files\Microsoft Visual Studio\Common\Tools\Winapi目錄,你可以用「複製」、「貼上」的技巧把函式的宣告放到應用程式裡,也可以用「API檢視員」自動地幫你完成工作。

「API檢視員」可以載入一個純文字API檔或是資料庫檔,讓你可以方便地瀏覽檔案的內容。在「API檢視員」裡,函式的宣告部份可以被選取、複製然後貼進程式碼視窗裡。你可以從Windows 95工作列的「開始」按鈕啟動在「Microsoft Visual Basic 6.0工具」功能群組的「API檢視員」,也可以從「增益集」功能表中執行「API檢視員」。如果要把「API檢視員」放進Visual Basic的「增益集」功能表中,請從「增益集」功能表中選取「增益功能管理員」,然後選取「Visual Basic 6 API檢視員」,按下「確定」離開。

在 第三十章"發展工具"裡, 我們會介紹如何建立一個Visual Basic環境下的增益功能(Add-in),讓這個增益功能幫你插入API函式的宣告到應用程式裡。

字串
 

傳字串給API函式時,要小心避開幾個陷阱。第一個是API函式不會替你產生字串的存放空間,你必須自己向系統要求分配足夠的空間給傳回的字串。例如,以下的程式使用GetWindowsDirectory API函式,這個函式會傳回Windows目錄的路徑,我們必須要分配足夠的空間給存放函式傳回值的strWinPath字串變數。在函式被呼叫前,我們用Space函式分配空間給strWinPath,但是如果strWinPath字串變數的長度不夠存放傳回值,API函式將不會傳回任何資料。

Option Explicit

Private Declare Function GetWindowsDirectory _
Lib "kernel32" Alias "GetWindowsDirectoryA" ( _
    ByVal lpBuffer As String, _
    ByVal nSize As Long _
) As Long

Private Sub Form_Click()
    Dim strWinPath As String
    Dim lngRtn As Integer
    Const MAXWINPATH = 144
    strWinPath = Space$(MAXWINPATH)
    lngRtn = GetWindowsDirectory(WinPath, MAXWINPATH)
    strWinPath = Left$(strWinPath, lngRtn) 'Truncate at the 0 byte
    Print strWinPath
End Sub

如果把上述的程式片斷放在一個應用程式裡執行,Windows目錄的路徑會被顯示在表單上,如圖10-1。


 

 圖10-1 GetWindows Directory函式傳回Windows目錄的路徑

另外,API函式傳回的字串以一個一位元組長的0表示字串的結尾。在以上的範例裡,函式會傳回字串長度,但或許其他與字串相關的API函式並不會傳回字串長度,要想知道字串長度的話,只有靠自己在字串裡尋找那個0字元。如果API函式不傳回字串長度,這裡有一個辦法可以截掉字串中多餘的空白:

strWinPath = Left$(strWinPath,InStr(strWinPath,Chr$(0)-1)

參考資料:

另請參閱 第三十章"發展工具" 的APIAddin應用程式,這個範例告訴你如何利用「選擇」、「複製」、「貼上」等編輯功能把API函式複製到你的應用程式中。


如何傳遞程序的位址給API函式?
 

傳遞Visual Basic程序位址給API函式是一個程式設計高手經常使用的高級技巧。Visual Basic現在增加了AddressOf運算子,讓你可以達成這個目的。這種技巧在C語言裡常被用到,這樣的C函式稱為Callback函式。

Visual Basic裡的Callback程序是指當API函式執行時會被呼叫的程序,被呼叫程序的參數由API函式來決定。例如,在以下的程式裡EnumChildWindows API函式會呼叫ChildWindowProc函式。這段程式碼應該要放在模組(BAS)中而不是表單裡,如果「專案屬性」裡的啟動物件被設為Sub Main,那麼當這個程式被執行時,Visual Basic開發展環境的視窗識別代碼(Window handle)會被顯示在「即時運算」視窗中。

Option Explicit

Private Declare Function GetActiveWindow _
Lib "User32" () As Long

Private Declare Function EnumChildWindows _
Lib "User32" ( _
    ByVal hWnd As Long, _
    ByVal lpWndProc As Long, _
    ByVal lp As Long _
) As Long

Sub Main()
    Dim hWnd As Long
    Dim lngX As Long
    'Get a handle to the active window
    hWnd = GetActiveWindow()
    If (hWnd) Then
        'Call EnumChildWindows API, which calls
        'ChildWindowProc for each child window and then ends
        lngX = EnumChildWindows(hWnd, AddressOf ChildWindowProc, 0)
    End If
End Sub

'Called by EnumChildWindows API function
Function ChildWindowProc( _
    ByVal hWnd As Long, _
    ByVal lp As Long _
) As Long
    'hWnd and lp parameters are passed in by EnumChildWindows
    Debug.Print "Window: "; hWnd
    'Return success (in C, 1 is True and 0 is False)
    ChildWindowProc = 1
End Function

EnumChildWindows函式傳遞dWnd和lp參數給ChildWindowProc函式,ChildWindowProc函式的宣告格式可以在Platform SDK說明主題中找到。按照程序命名的習慣,被呼叫的程序如果是以Proc作為程序名稱的結尾,則表示這個程序是個Callback程序,不能直接被叫用。

在對程式進行偵錯時,Callback程序會產生一些令人混淆的執行結果。以上面的程式為例,程式在Visual Basic開發環境下執行時會產生一組視窗的識別代碼,但如果把程式編譯後再加以執行,所得到的結果是另一組的識別代碼。使用AddressOf運算子時,請記住以下重點:

  1. AddressOf運算子只能用在程序模組 (BAS) 裡,而且只能和引數合併使用,這個限制防止了程式設計師傳遞物件裡的程序位址或表單裡的程序位址被混淆誤用。
  2. 被AddressOf運算子引用的程序和呼叫程序必須在同一個專案裡。

如何在宣告函式時應用ByVal、ByRef和As Any?
 

在32位元的Visual Basic裡,大部份函式宣告部份都已經不使用As Any了,取而代之的是確切的資料型別,如As Integer等等,不過,在這裡我們要討論一個特例── WinHelp API函式的16位元版本,這個版本使用As Any。

許多16位元API函式宣告部份都使用了As Any,這種設計的本意是讓程式設計師可以根據需要來傳遞各種不同的資料型別,例如,以下的程式使用了16位元的WinHelp函式,用來顯示Visual Basic說明檔。請看一下第四個參數,它宣告為As Any ( 第十七章"線上輔助功能" 會介紹32位元版的WinHelp函式),因此它可以接受各種不同資料型別的引數,這個引數可以是長整數也可以是字串的指標,它完全根據wCommand所收到的值來決定。

Option Explicit
Const vbHelpContents = 3

Private Declare Function WinHelp _
Lib "User32" Alias "WinHelpA" ( _    
   ByVal hWnd As Long, _
    ByVal lpHelpFile As String, _    
   ByVal wCommand As Long, _
    ByRef dwData As Any _
) As Long

Private Sub Form_Click()    
   Dim lngX As Long
    Dim lngY As Long
    lngX = WinHelp(Form1.hWnd, "metric.hlp", vbHelpContents, _
        ByVal lngY)
End Sub

依照慣例,所有的As Any參數都會以ByRef來宣告(筆者特意將By Ref寫在上面的例子裡,事實上如果不用ByVal或ByRef,Visual Basic都會預設為ByRef)。請仔細看一下上例中呼叫函式的地方,你會發現最後一個引數前面,我們加上了ByVal,把變數lngY傳給函式,這樣做的用意在於把長整數的值0傳給函式。如果必須在引數前面加上ByVal關鍵字來呼叫函式,要特別留意你是否以正確的方式在使用引數,錯用ByVal或ByRef可能會造成程式當掉的危險。

如何輕鬆地把API函式的宣告加到程式裡?
 

Visual Basic提供了一個很好用的「API檢視員」增益功能(APILOAD.EXE),讓程式設計人員可以方便地瀏覽或是複製一長串的API常數、Type結構和函式宣告。「API檢視員」預設的函式宣告關鍵字是Public,因此,如果要把API函式宣告放進表單、物件類別、使用者控制項(User Control)或使用者文件(User Document),必須在函式宣告的Declare陳述式前面加上Private關鍵字。

如何利用API函式取得系統資訊?
 

在前一節裡,我們討論了如何在應用程式裡使用API函式,在這節裡我們將要更進一步地討論如何利用API函式取得Windows的系統資訊。

許多的系統資訊以前只能透過API函式來取得,現在Visual Basic已經可以用SysInfo物件來取代API函式。SysInfo控制項提供了系統版本、系統平台和系統物件等資訊,因此,它可以讓我們用程式來分辨Windows 95和Windows NT的不同,另外,當我們把PCMCIA卡插入到筆記型電腦的時候,Plug-and-Play事件會被啟動,而SysInfo控制項也可以讓我們掌握這些事件。

如果要使用SysInfo控制項,要在「專案」功能表中選取「設定使用元件」,然後在「設定使用元件」對話方塊中核取Microsoft SysInfo Control 6.0,按下「確定」之後,就會看到這個控制項在「工具箱」裡。

以下的程式告訴你如何使用SysInfo控制項取得作業系統的相關資訊:

Option Explicit

Private Sub Form_Click()
Dim sMsg As String
    'Get platform and version
    Select Case sysOS.OSPlatform
        Case 0
            strMsg = "Unidentified"
        Case 1
            strMsg = "Windows 95, version " & CStr(sysOS.OSVersion)
        Case 2
            strMsg = "Windows NT, version " & CStr(sysOS.OSVersion)
    End Select
    'Display OS information
    Print strMsg
End Sub

圖10-2顯示程式執行的結果。


 

 圖10-2 使用SysInfo控制項顯示作業系統和版本

很可惜,SysInfo並不提供其他許多有用的系統資訊,包括系統顏色、CPU種類以及經過的時間 (Elapsed Time) 等等,為了補充這個不足之處,我們在接下來的幾個小節裡要討論如何使用Windows API函式取得這些資訊。

系統顏色資訊
 

你可以使用GetSysColor API函式來取得29項視窗元件的顏色資訊,GetSysColor函式會傳回一組RGB值,告訴你有關視窗標題、功能表和邊界等視窗元件的顏色。以下這段程式列出了所有的顏色常數並且告訴你如何用GetSysColor函式取得桌面的顏色。在SysColor列舉集合中的某些元素只適用在某些特定版本的Windows,例如COLOR_3DDKSHADOW只適用於Windows 95。

Option Explicit

Private Enum SysColor
    COLOR_SCROLLBAR = 0
    COLOR_BACKGROUND = 1
    COLOR_ACTIVECAPTION = 2
    COLOR_INACTIVECAPTION = 3
    COLOR_MENU = 4
    COLOR_WINDOW = 5
    COLOR_WINDOWFRAME = 6
    COLOR_MENUTEXT = 7
    COLOR_WINDOWTEXT = 8
    COLOR_CAPTIONTEXT = 9
    COLOR_ACTIVEBORDER = 10
    COLOR_INACTIVEBORDER = 11
    COLOR_APPWORKSPACE = 12
    COLOR_HIGHLIGHT = 13
    COLOR_HIGHLIGHTTEXT = 14
    COLOR_BTNFACE = 15
    COLOR_BTNSHADOW = 16
    COLOR_GRAYTEXT = 17
    COLOR_BTNTEXT = 18
    COLOR_INACTIVECAPTIONTEXT = 19
    COLOR_BTNHIGHLIGHT = 20
    COLOR_3DDKSHADOW = 21
    COLOR_3DLIGHT = 22
    COLOR_INFOTEXT = 23
    COLOR_INFOBK = 24
    COLOR_DESKTOP = COLOR_BACKGROUND
    COLOR_3DFACE = COLOR_BTNFACE
    COLOR_3DSHADOW = COLOR_BTNSHADOW
    COLOR_3DHIGHLIGHT = COLOR_BTNHIGHLIGHT
    COLOR_3DHILIGHT = COLOR_3DHIGHLIGHT
    COLOR_BTNHILIGHT = COLOR_BTNHIGHLIGHT
End Enum

Private Declare Function GetSysColor _
Lib "user32" ( _
    ByVal nIndex As Long _
) As Long

Private Sub Form_Click()
    Dim lngSystemColor As Long
    Dim intRed As Integer, 
   Dim intGreen As Integer, 
   Dim intBlue As Integer
    'Get color of desktop
    lngSystemColor = GetSysColor(COLOR_DESKTOP)
    'Set form's background color to same as desktop
    Me.BackColor = lngSystemColor
    'Split this color into its components
    ColorSplit lngSystemColor, intRed, intGreen, intBlue
    Print "R,G,B = "; intRed, intGreen, intBlue
End Sub

Function ColorSplit(lngRGBMix As Long, _
intR As Integer, intG As Integer, intB As Integer)
'Extract R, G, and B values from an RGB color
    intR = lngRGBMix And &HFF
    intG = (lngRGBMix \ &H100) And &HFF
    intB = (lngRGBMix \ &H10000) And &HFF
End Function

在這個例子裡,我們使用了ColorSplit程序來取得紅色、綠色及藍色的值,ColorSplit實際上是Visual Basic的RGB函式的反向運算函式,RGB函式將紅綠藍三個值合併為一個範圍從0到255的長整數,而ColorSplit函式則將這個合併值分為代表紅綠藍的三個值。圖10-3顯示程式執行結果。


 

 圖10-3 GetSysColor函式將表單的顏色改為桌面的顏色

參考資料:

另請參閱 第十四章"繪圖技巧" 有關處理顏色的部份。


CPU種類
 

以下的程式呼叫GetSystemInfo API函式,把傳回的系統資訊存放在Type結構SYSTEM_INFO裡面,然後把這個結構裡其中的兩個元素──處理器種類和處理器的數目──加以顯示。

如果仔細看一下SYSTEM_INFO的內容,你會發現SYSTEM_INFO包含了許多相當有用的資訊。

Option Explicit

Private Type SYSTEM_INFO
    dwOemID As Long
    dwPageSize As Long
    lpMinimumApplicationAddress As Long
    lpMaximumApplicationAddress As Long
    dwActiveProcessorMask As Long
    dwNumberOfProcessors As Long
    dwProcessorType As Long
    dwAllocationGranularity As Long
    dwReserved As Long
End Type

Private Declare Sub GetSystemInfo _
Lib "kernel32" ( _
    lpSystemInfo As SYSTEM_INFO _
)

Private Sub Form_Click()
    Dim Sys As SYSTEM_INFO
    GetSystemInfo Sys
    Print "Processor type: "; Sys.dwProcessorType
    Print "No. Processors: "; Sys.dwNumberOfProcessors
End Sub

圖10-4顯示程式執行的結果。


 

 圖10-4 用GetSystemInfo取得處理器的相關資訊

經過的時間
 

GetTickCount是Windows提供的API函式,它會傳回Windows啟動後到目前為止所經過的時間,這個傳回值以毫秒(millisecond)為單位,而其精準度遠超過Visual Basic的Timer函式所傳回結果的精準度。Timer函式所傳回的值是從午夜零時到目前為止所經過的時間,其精準度不到毫秒,而實際上Visual Basic在每秒鐘裡只更新這個值18.2次。

另一個GetTickCount API函式優於Timer函式的理由是:GetTickCount沒有午夜零時的限制。Timer在午夜零時會傳回零,而GetTickCount在電腦連續開機49.7天後才會歸零。在以下程式中,當你在表單上按下滑鼠左鍵時,GetTickCount函式會被呼叫,而從Windows被啟動一直到函式被呼叫這段期間所經過的時間會被顯示在表單上。以上這個處理會被重複9次,以便讓你了解GetTickTime函式每隔多久會傳回一次運算結果。

Option Explicit

Private Declare Function GetTickCount _
Lib "kernel32" ( _
) As Long

Private Sub Form_Click()    
     Dim i As Integer    
     Dim lngMS As Long
      Print "Time elapsed since Windows was started:"    
     For i = 1 To 10
            lngMS = GetTickCount        
            Do While lngMS = GetTickCount        
            Loop
            Print lngMS; " milliseconds"    
     Next i 
End Sub

圖10-5顯示了程式執行的結果。


 

 圖10-5 以GetTickCount函式傳回Windows啟動後所經過的時間

硬碟的資訊
 

我們可以用Windows提供的GetDriveType API函式來判斷使用者的電腦有幾個硬碟。以下的程式使用這個函式偵測系統上所有的硬碟。

Option Explicit

'GetDriveType return values
Const DRIVE_REMOVABLE = 2
Const DRIVE_FIXED = 3
Const DRIVE_REMOTE = 4
Const DRIVE_CDROM = 5
Const DRIVE_RAMDISK = 6

Private Declare Function GetDriveType _
Lib "kernel32" Alias "GetDriveTypeA" ( _
    ByVal nDrive As String _
) As Long

Private Sub Form_Click()    
   Dim i As Integer
   Dim lngDrive As Long
   Dim strD As String
   For i = 0 To 25  'All possible drives A to Z
        strD = Chr(i + 65) & ":\"        
        lngDrive = GetDriveType(strD)
        
        Select Case lngDrive        
        Case DRIVE_REMOVABLE
                Print "Drive " & strD & " is removable."        
        Case DRIVE_FIXED
                Print "Drive " & strD & " is fixed."        
        Case DRIVE_REMOTE
                Print "Drive " & strD & " is remote." 
         Case DRIVE_CDROM
                Print "Drive " & strD & " is CD-ROM."
         Case DRIVE_RAMDISK
                Print "Drive " & strD & " is RAM disk." 
         Case Else
          End Select    
   Next i
End Sub

圖10-6顯示的是程式的輸出結果。


 

 圖10-6 以GetDriveType函式傳回硬碟的種類

如何在ActiveX控制項中加入API函式?
 

Microsoft在設計SysInfo控制項時,並沒有把許多有用的資訊都包括在裡面,舉例來說,我們可以用SysInfo控制項來檢查使用者何時改變了系統的顏色,但是必須要用Windows API函式來判斷使用者改變了哪些顏色。現在,Visual Basic容許我們自己動手建立一些包含常用API函式的控制項,有了這種控制項,我們可以把API函式變成控制項的物件方法或者是屬性,在使用這些方法或屬性的時候,就不用在程式裡宣告冗長的API函式。

在ActiveX控制項裡加入API函式
 

API函式可以被包含在使用者控制項模組裡。你可以在一個ActiveX控制項專案的模組中宣告API函式,然後從Public程序中呼叫這個API函式。以下的程式告訴你TickControl控制項如何使用GetTicks方法來呼叫GetTickCount函式。

Option Explicit

'TickControl control module
'API declaration
Private Declare Function GetTickCount _
Lib "kernel32" ( _
) As Long

Public Function GetTicks() As Long
    'Return number of milliseconds since Windows started
    GetTicks = GetTickCount
End Function

請注意,API函式必須用Private關鍵字來宣告。GetTicks函式只是很單純地把GetTickCount函式包裝在裡面,以便它可被外界使用。


參考資料:

另請參閱 第六章"ActiveX控制項" ,本章對控制項的產生有更深入討論。


加強現有的控制項
 

如果要加強某個控制項的功能卻又苦無它的原始碼時,我們可以把這個控制項加到另一個控制項裡,使另一個控制項不僅包含原控制項的所有功能,也能擁有屬於自己的功能。如果採取這種方式來加強某個控制項的功能,切記必須在應用程式所執行的系統上安裝兩個控制項。

把某個既有物件放進另一個新物件,使新物件具備舊物件的屬性和方法,那麼舊物件類別就叫做新物件類別的基礎類別(Superclass),而新物件類別則稱為舊物件類別的衍生類別(Derived Class)。你可以透過物件類別的衍生行為替SysInfo控制項加入新的屬性和方法,用以加強SysInfo控制項的功能。

圖10-7所顯示的是一個UserControl控制項裡包含了一個SysInfo控制項,你可以在這個UserControl控制項裡加入SysInfo所沒有的功能。


 

 圖10-7 可以藉由物件類別的衍生來加強SysInfo控制項的功能

如果要對某個控制項進行控制項衍生的工作,必須複製基礎控制項中所有的屬性方法和事件,這是一件繁瑣又不得不做的打字工作。請看以下的程式碼。

'Delegate to SysInfo Properties
Public Property Get ACStatus() As Integer
    ACStatus = OldSysInfo.ACStatus
End Property
Public Property Get BatteryFullTime() As Long
    BatteryFullTime = OldSysInfo.BatteryFullTime
End Property
Public Property Get BatteryLifePercent() As Integer
    BatteryLifePercent = OldSysInfo.BatteryLifePercent
End Property
Public Property Get BatteryLifeTime() As Long
    BatteryLifeTime = OldSysInfo.BatteryLifeTime
End Property
Public Property Get BatteryStatus() As Integer
    BatteryStatus = OldSysInfo.BatteryStatus
End Property
Public Property Get OSBuild() As Integer
    OSBuild = OldSysInfo.OSBuild
End Property
Public Property Get OSPlatform() As Integer
    OSPlatform = OldSysInfo.OSPlatform
End Property
Public Property Get OSVersion() As Single
    OSVersion = OldSysInfo.OSVersion
End Property
Public Property Get ScrollBarSize() As Single
    ScrollBarSize = OldSysInfo.ScrollBarSize
End Property
Public Property Get WorkAreaHeight() As Single
    WorkAreaHeight = OldSysInfo.WorkAreaHeight
End Property
Public Property Get WorkAreaLeft() As Single
    WorkAreaLeft = OldSysInfo.WorkAreaLeft
End Property
Public Property Get WorkAreaTop() As Single
    WorkAreaTop = OldSysInfo.WorkAreaTop
End Property
Public Property Get WorkAreaWidth() As Single
    WorkAreaWidth = OldSysInfo.WorkAreaWidth
End Property

以上述這種方式複製屬性和方法,我們稱之為代理(Delegating)。你不需要把一些常用的屬性如Name和Parent也代理進來,因為收納器(指新物件)本身就有這些屬性。如果要複製SysInfo所有的事件到新的SysInfo控制項裡,要在新控制項裡用Event陳述式宣告每一個舊SysInfo的事件,然後在新控制項的事件程序裡用RaiseEvent來觸發這些事件。我們在這裡只列出精簡版的程式,完整的程式附在隨書光碟中。

'Event declarations
Event ConfigChangeCancelled()
Event ConfigChanged( _
    OldConfigNum As Long, _
    NewConfigNum As Long)
Event DeviceArrival( _
    DeviceType As Long, _
    DeviceID As Long, _
    DeviceName As String, _
    DeviceData As Long)
Event DeviceOtherEvent( _
    DeviceType As Long, _
    EventName As String, _
    DataPointer As Long)
Event DeviceQueryRemove( _
    DeviceType As Long, _
    DeviceID As Long, _
    DeviceName As String, _
    DeviceData As Long, _
    Cancel As Boolean)
'And so on...

'Raise all the SysInfo events on the user control
Private Sub OldSysInfo_ConfigChangeCancelled()
    RaiseEvent ConfigChangeCancelled
End Sub
Private Sub OldSysInfo_ConfigChanged( _
    ByVal OldConfigNum As Long, _
    ByVal NewConfigNum As Long)
    RaiseEvent ConfigChanged(OldConfigNum, NewConfigNum)
End Sub
Private Sub OldSysInfo_DeviceArrival( _
    ByVal DeviceType As Long, _
    ByVal DeviceID As Long, _
    ByVal DeviceName As String, _
    ByVal DeviceData As Long)
    RaiseEvent DeviceArrival(DeviceType, DeviceID, DeviceName, DeviceData)
End Sub
Private Sub OldSysInfo_DeviceOtherEvent( _
    ByVal DeviceType As Long, _
    ByVal EventName As String, _
    ByVal DataPointer As Long)
    RaiseEvent DeviceOtherEvent(DeviceType, EventName, DataPointer)
End Sub
Private Sub OldSysInfo_DeviceQueryRemove( _
    ByVal DeviceType As Long, _
    ByVal DeviceID As Long, _
    ByVal DeviceName As String, _
    ByVal DeviceData As Long, _
    Cancel As Boolean)
    RaiseEvent DeviceQueryRemove(DeviceType, DeviceID, DeviceName, _
        DeviceData, Cancel)
End Sub
'And so on...

每一個舊SysInfo控制項的事件都會在新控制項中驅動一個同名的事件。在複製(包裝)完舊SysInfo中所有的事件屬性和方法之後,就可以開始加入新增的屬性和方法到新控制項裡了。以下就是我們新增的函式:

'Declarations for system colors
Public Enum SystemColor
    COLOR_SCROLLBAR = 0
    COLOR_BACKGROUND = 1
    COLOR_ACTIVECAPTION = 2
    COLOR_INACTIVECAPTION = 3
    COLOR_MENU = 4
    COLOR_WINDOW = 5
    COLOR_WINDOWFRAME = 6
    COLOR_MENUTEXT = 7
    COLOR_WINDOWTEXT = 8
    COLOR_CAPTIONTEXT = 9
    COLOR_ACTIVEBORDER = 10
    'Constants omitted for brevity
End Enum
Private Declare Function GetSysColor _
Lib "user32" ( _
    ByVal nIndex As Long _
) As Long

Public Function GetSystemColor(Index As SystemColor)
    GetSystemColor = GetSysColor(Index)
End Function

SystemColor方法會傳回視窗物件的顏色設定值,這些視窗物件的代碼被定義在列舉集合SystemColor裡。在應用程式中使用這個控制項時,你可以在「瀏覽物件」中看到它們。


參考資料:

Visual Basic的「ActiveX控制項界面精靈」提供了一個更方便的方式建立控制項界面,你可以用「增益集」中的「增益功能管理員」把「ActiveX控制項界面精靈」加入到你的Visual Basic開發環境裡。