3. 內建控制項

Microsoft Visual Basic術語中,內建控制項(built-in controls)是指當開啟環境後在工具箱視窗中所有看得的控制項。 這一群重要的控制項包括有: Label、Textbox及 CommandButton控制項,且在每一個應用程式幾乎都用得到。如您所知,Visual Basic也可延伸使用Visual Basic套裝軟體所附的或其他協力廠商開發的商用、共享甚至是免費軟體之Microsoft ActiveX 控制項(像是OCX控制項或OLE自訂控制項)。雖然這些延伸的控制項通常會比內建控制項功能更強大,內建控制項仍有其不磨滅的優點。

基於以上幾點,可以了解如何善用內建控制項是非常重要的。本章中,我將集中介紹他們最為重要的屬性、方法及事件,同時也會講解如何只使用內建控制項來處理一般的的程式問題。

TEXTBOX 控制項
 

TextBox控制項在程式中提供使用者一個簡單的方式來輸入數值。因此,是大部份Windows應用程式最常使用到的控制項。TextBox控制項有許多的屬性及事件,因而也是最複雜的內建控制項之一。在這一節中,我將介紹TextBox控制項中最好用的屬性,並說明如何解決一些常會遇到的困難。

在表單上放置TextBox控制項後,必需設定一些基本的屬性,我在建立新的TextBox控制項時第一件會做的事是清空 Text 的屬性。如果是個多行的欄位,我也會設定 MultiLine 的屬性值為True。您可以將TextBox控制項的 Alignment 屬性設為靠左對齊、靠右對齊或置中對齊。TextBox控制項在顯示數值資料時靠右對齊特別好用,不過仍有幾點事項需要特別注意:當 Multiline 屬性值設為True時這個屬性也都能正確運作,不過在Microsoft Windows 98、Microsoft Windows NT 4 with Service Pack 3或其後的版本中,則只對單行的控制項有效,而在之前的Windows 9x 或Windows NT則不會產生錯誤,但是單行TextBox控制項會忽略 Alignment 屬性向左對齊。

要限定使用者不能改變TextBox控制項的內容,可以設定其 Locked 屬性值為True。通常會這樣做是當此控制項的內容是計算的結果或是用來顯示以開啟成唯讀資料庫中的欄位。大部份的情況下,用有邊框、背景顏色為白色的Label控制項也可以產生同樣的結果,但是鎖定的TextBox控制項另會允許使用者複製其值到剪貼簿上,而如果超過欄位寬度也可以捲動來看。

處理數值欄位時,會需要限定使用者能輸入字元的位數。要這樣做很簡單,只要用 MaxLength 屬性即可。值為0(預設值)表示可輸入任何位數,而其他正數值 N 則限定欄位的內容只有 N 個字元長度。

建立密碼欄位,則應設定 PasswordChar 屬性為字元字串,一般是用星號(*)。這樣子,程式仍可以讀取並修改TextBox控制項,但使用者則只會看到一排的星號。


注意

因為密碼保護的TextBox控制項實際上會使 Ctrl+X  Ctrl+C 這二個快速鍵功能失效,所以可避免有心人士竊取到其他使用者輸入的密碼。不過,如果應用程式有 編輯 功能表,且其中也有一般剪貼簿的指令,則您可選擇當焦點在密碼欄位時是否要使 複製  剪下 指令無效。


此外還可設定其他屬性可讓控制項比較好看:像是 Font 屬性。除此之外,也可以設定 ToolTipText 屬性來幫助使用者了解這個TextBox控制項是做什麼用的。另外要做一個無邊框的TextBox控制項,可以設定 BorderStyle 屬性值為0─沒有框線,不過在Windows應用程式這樣的控制項很少出現。一般說來,在設計階段不會對TextBox控制項做太多的事,而會在程式碼中再加以變化。

執行階段的屬性
 

Text 屬性是在程式碼中最常參照到的,且他也是TextBox控制項的預設屬性,所以很方便使用。其他常用的屬性如下:

  • SelStart 屬性可設定或傳回閃爍插入記號(即輸入文字會出現插入點)的位置。請注意在TextBox及其他控制項中的閃爍游標稱為插入記號(caret)而不是指滑鼠的游標(cursor)。當插入記號是在TextBox控制項的開頭,SelStart 會傳回0;而當在使用者輸入字串的後面時,SelStart 會傳回 Len(Text) 的值。可以修改 SelStart 屬性來計劃性移動插入記號。
     
  • SelLength 屬性會傳回文字中被使用者標示部份的字元數,如果沒有標示文字則會傳回0值。指派一個非零值到這個屬性則可以用程式碼來選取文字。很有趣地,甚至可以派一個大於目前文字長度的值到這個屬性中,也不會產生執行錯誤。
     
  • SelText 屬性會設定或傳回目前選取的文字部份,如果沒有文字被選取則會傳回空字串。可用這個屬性直接摘取標示文字而無需尋求 Text、SelStar 及 SelLength 等屬性。更好玩的事是也可以輸入一個新的值來取代目前的選取範圍,而如果沒有選取任何文字,則字串會插入在目前插入記號的位置上。
     

小秘訣

如欲新增文字到TextBox控制項中,應使用以下程式碼(而不是用連結運算元)以減少閃爍的情況並增進效能:

Text1.SelStart = Len(Text1.Text)
Text1.SelText = StringToBeAdded

要執行這些屬性典型的方式之一就是全部選取TextBox控制項的內容。通常會使用到是當插入記號出現在欄位上以便使用者能快速以新值來代換即存的值,或任何方向鍵來開始編輯:

Private Sub Text1_GotFocus()
    Text1.SelStart = 0 
    ' 用大一點的數值來選取所有的內容.
    Text1.SelLength = 9999
End Sub

通常會先設定 SelStart 屬性值再設定 SelLength 或 SelText 屬性。當指派新的值給 SelStart 屬性時,其他二個屬性也會相對地分別自動重設為0及空字串,因此會取代了先前的設定。

捕捉鍵盤活動(Trapping Keyboard Activity)
 

TextBox控制項支援 KeyDown、KeyPress 及 KeyUp 等在 第二章 曾介紹過的標準事件。最常會用到的是會防止使用者輸入無效的按鍵。這個預防措施中的典型範例是數值欄位,所以必需過濾掉非數值的按鍵:

Private Sub Text1_KeyPress(KeyAscii As Integer)
    Select Case KeyAscii
        Case Is < 32               ' Control鍵OK.
        Case 48 To 57              ' 這是數值
        Case Else                  ' 拒絕其他的按鍵
            KeyAscii = 0
    End Select
End Sub

應不去拒絕ANSI碼小於32的按鍵及其他重要按鍵像是 Backspace、Escape、Tab  Enter 。另外請注意一些控制按鍵如果TextBox不知應如何處理時則會發出嗶嗶聲(例如像單行的TextBox 控制項無法處理 Enter 按鍵時)。


注意

請勿認為 KeyPress 事件在任何條件下都會補捉到所有控制項。舉例來說,如果在表單上沒有一Default屬性值設為Ture的CommandButton控制項,則 KeyPress 事件只能處理 Enter 按鍵。而如果表單有預設指令按鈕,則按下 Enter 鍵的效果就如同按下那個按鈕一樣。同樣的,即使表單上有 取消 這個按鈕,在這個事件中也不能使用 Escape 按鍵。最後,僅在表單上沒有任何其他控制項且其 TabStop 屬性值為True時,KeyPress 事件才抓得到 Tab 控制鍵。


可以使用 KeyDown 事件程序來讓使用者能夠用向上或向下的方向鍵來增加或減少目前的值,請參考:

Private Sub Text1_KeyDown(KeyCode As Integer, Shift As Integer)
    Select Case KeyCode
        Case vbKeyUp
            Text1.Text = CDbl(Text1.Text) + 1
        Case vbKeyDown
            Text1.Text = CDbl(Text1.Text)  -1
    End Select
End Sub

說明

在TextBox唯讀控制項的執行中有一個錯誤。當 Locked 屬性值為True時, Ctrl+C 並不會將選取的文字複製到剪貼簿上,必需要在 KeyPress 事件程序寫這些程式碼來執行這個功能。


數值的確認常式
 

雖然在 KeyPress 或 KeyDown 事件程式中抓出無效按鍵,似乎是第一要務,但是對無經驗的使用者而言,會發現他們其實還是有其他許多的方法輸入無效的值。依據處理這些資料之方法的不同,應用程式可能會突然地結束並出現執行階段的錯誤,或甚至更糟,雖會正常運作但產生的是不正確的結果。所以真正需要的是防彈(bullet-proof)的方法來抓出無效的值來。

在提供這個問題的解決方案前,請容我解釋一下為什麼不能僅靠補捉無效按鍵來作確認這些事。試問萬一使用者是從剪貼簿上貼過來一個無效的值呢?您可能會回答:「可以堵住 Ctrl+V  Shift+Ins 的按鍵來防止使用者這麼做!」很可惜,Visual Basic的TextBox控制項提供了一個預設編輯功能表可讓使用者只要在上面按下滑鼠右鍵即可執行任何剪貼簿的動作。不過還好這仍是有個解決的方法:除了在TextBox收到按鍵值之前防堵按鍵,還可以在Change事件中防堵按鍵結果,並且如果其值未通過測試則予以拒絕。但這樣做會使程式碼的架構稍顯複雜:

' 表單級變數
Dim saveText As String
Dim saveSelStart As Long

Private Sub Text1_GotFocus()
    ' 當控制項收到焦點時儲存其值
    saveText = Text1.Text
    saveSelStart = Text1.SelStart
End Sub

Private Sub Text1_Change()
    ' 避免巢狀呼叫
    Static nestedCall As Boolean
    If nestedCall Then Exit Sub

    '測試控制項的值
    If IsNumeric(Text1.Text) Then
        ' If value is OK, save values.
        saveText = Text1.Text
        saveSelStart = Text1.SelStart
    Else
        '準備掌握巢狀呼叫
        NestedCall = True
        Text1.Text = saveText
        NestedCall = False
        Text1.SelStart = saveSelStart
    End If
End Sub

Private Sub Text1_KeyUp(KeyCode As Integer, Shift As Integer)
    saveSelStart = Text1.SelStart
End Sub

Private Sub Text1_MouseDown(Button As Integer, _
    Shift As Integer, X As Single, Y As Single)
    saveSelStart = Text1.SelStart
End Sub

Private Sub Text1_MouseMove(Button As Integer, _
    Shift As Integer, X As Single, Y As Single)
    saveSelStart = Text1.SelStart
End Sub

若控制項的值在Change事件程式中測試不過,則必需存回之前有效的值;這個動作會遞迴地觸發 Change 事件,然後取消迴圈呼叫。您可能覺得奇怪為什麼也需要防堵 KeyUp、MouseDown 及 MouseMove 事件:原因是您常會需要持續追蹤最後一個插入點的有效位置,以讓末端使用者能用方向鍵或滑鼠將其移動。

之前的程式碼片斷用到了 IsNumeric 函數來防堵無效的資料。請注意這個函數在實際上應用程式使用上並不是很穩定的。例如 IsNumeric 函數會將以下的字串誤判為有效數字:

123,,,123
345-
$1234     ' 如果那個欄位不是貨幣資料型態應該如何?
2.4E10    ' 如果我不想支援科學表示法要怎麼辦?

對於這個問題,筆者準備了另一個函數代替,當然您可以再予以修改成您個人所需的樣子。(例如:您可以增加其支援貨幣符號或接受以逗號為千分位號等等)。請注意當輸入的是空字串時,這個函數將會傳回True的值,所以如果不允許使用者讓欄位空白的話,需執行另外的測試:

Function CheckNumeric(text As String, DecValue As Boolean) As Boolean
    Dim i As Integer
    For i = 1 To Len(text)
        Select Case Mid$(text, i, 1)
            Case "0" To "9"
            Case "-", "+"
                ' 正/負號只允許放在第一個字元
                If i > 1 Then Exit Function
            Case "."
                ' 如果十進制就不允許有小數點
                If Not DecValue Then Exit Function
                ' 十進位址允許一個小數點
                If InStr(text, ".") < i Then Exit Function
            Case Else
                ' 所有其它的字元都不允許
                Exit Function
        End Select
    Next
    CheckNumeric = True
End Function

如果希望TextBox控制項能包含其他資料型態,則需再用到之前筆者曾介紹過的確認架構(包括了在 GotFocus、Change、KeyUp、MouseDownn 及 MouseMove 事件程序中所有的程式碼)並將呼叫副程式改為呼叫自訂的有效化副程式 IsNumeric。不過事情並不像其一開始表現的那樣簡單。假設現在您有一個資料欄位:您會使用 Change 事件中 IsDate 函數來確認值是否有效嗎?答案當然是否定的。事實上,當輸入第一個位數的日期值時,IsDate 會傳回False且因此副程式會不讓您輸入剩下的字元,這樣您也無法再輸入任何的值了。

這個例子解釋了為什麼一個按鍵的有效確認並不都是最符合需求最好的答案。因此,大部份的Visual Basic程式設計師較喜歡用欄位的有效確認,並只在使用者將輸入焦點到移動到表單上另一個欄位時再做測試。下一節將介紹欄位的有效確認。

CauseVAlidation屬性及VAlidate事件
 

Visual Basic 6終於提供了困擾程式設計師多年的確認問題之解決方案。如同您將看到的, Visual Basic 6的解決途徑非常簡潔;我很驚訝這個救星居然要到程式語言的第六版才出現。新的確認的重要特性是 Validate 事件及 CausesValidation 屬性。此二者會如下所述一起作用:當輸入焦點離開一控制項時,Visual Basic會檢查即將接受輸入焦點之控制項的 CausesValidation 的屬性。如果其屬性值為True,Visual Basic會在即將失出焦點的控制項中引發 Validate 事件,因此會讓程式設計者有機會來確認其內容,如果有必要,還能取消焦點的移動。

接下來試看看實務上的範例。假設現在表單上有五個控制項:一個要求輸入的欄位(TextBox控制項、txtRequired,且不能是空字串),一個值介於1到1000的數字欄位txtNumeric、及三個按鈕:OK、Cancel及Help(如圖3-1)。因為若使用者按下Cancel或Help按鈕時不需執行確認,所以要將其 CausesValidation 屬性值設為False。因為這個屬性的預設值為True,所以對於其他控制項則不需再修改。執行這個範例程式,在TextBox中輸入一些東西,然後移到第二個欄位。因為第二個欄位的 CausesValidation 屬性值為True,所以Visual Basic在第一個TextBox控制項中會引發 Validate 事件:

Private Sub txtRequired_Validate(Cancel As Boolean)
    ' 檢查欄位非空白
    If txtRequired.Text = "" Then
        MsgBox "Please enter something here", vbExclamation
        Cancel = True
    End If
End Sub

若 Cancel 參數值設為True,則Visual Basic會取消使用者的動作並將輸入焦點帶回到txtRequired控制項上:不會產生其他的 GotFocus 及 LostFocus 事件。另一方面,若在要求輸入的欄位中輸入東西,焦點就會跑到第二個欄位上(數字的文字方塊)。試在Help或Cancel按鈕上按一下:因為已將這些控制項中的 CausesValidation 屬性值皆設定為False,所以這時候不會引發 Validate 事件,不過按下OK按鈕則會執行數字欄位的 Validate 事件,在此可檢查其是否為無效字元及其有效範圍。


 

圖3-1 提供您試驗新的Visual Basic Validate特性之示範程式。
Private Sub txtNumeric_Validate(Cancel As Boolean)
    If Not IsNumeric(txtNumeric.Text) Then
        Cancel = True
    ElseIf CDbl(txtNumeric.Text) <1 Or CDbl(txtNumeric.Text) > 1000 Then
        Cancel = True
    End If
    If Cancel Then
        MsgBox "Please enter a number in range [1-1000]", vbExclamation
    End If
End Sub

在某些環境下,可能會需要在程式中確認有焦點的控制項是否有效,而不用等待使用者移動輸入焦點。可以直接在表單中發生的 ValidateControls 方法(此方法能促使輸入焦點控制項的Validate事件)中做處理。典型的方式是在使用者關閉表單時來做:

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    ' 指定的欄位不是有效值,不能關閉這個表單
    If UnloadMode = vbFormControlMenu Then
        On Error Resume Next
        ValidateControls
        If Err = 380 Then
            ' 指定的欄位無效
            Cancel = True
        End If
    End If
End Sub

檢查 UnloadMode 的參數是非常要緊的事;否則當使用者點選Cancel按鈕時,應用程式會誤執行 ValidateControls 的方法。請注意,若Cancel是設在有焦點的控制項 Validate 事件程序中,則 ValidateControls 會傳回錯誤值380。


注意

Visual Basic 6的確認架構有二個瑕玼:若表單上有CommandButton且其 Default 屬性值為True,當輸入焦點在另一控制項時按下 Enter 鍵變成點選CommandButton控制項,即使CommandButton的 CausesValidation 屬性值設為True,也不會引發 Validate 事件。唯一的解決方式就是從預設的CommandButton控制項中的Click事件程序中,呼叫用 ValidateControls 的方法

第二個瑕玼是:當從一 CausesValidation 屬性值為False的控制項移動焦點時,即使這個控制項收到了焦點且其 CausesValidation 屬性值True,也不會引發 Validate 事件。


新版Visual Basic 6的確認機制雖然很簡單且只需花一些功夫即可執行,但卻不一定完全能符合所有確認有效的需求。事實上,這個小技巧僅能啟動欄位的確認程序;至於對記錄的確認部份則無任何功效。也就是說,這只能保證特定欄位是正確,而不能說表單上所有的欄位內容都是正確有效的資料。執行示範程式,在第一個欄位輸入字串,然後按 Alt+F4 關閉表單,就能了解上面所說的意思了。程式碼並不會產生錯誤,甚至當第二個欄位中的不是有效數值也不會產生錯誤!不過還好,要建立一個一般的副程式讓表單上每一個控制項都能自行確認其值是否有效,並不是件難事:

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    ' 所有的欄位無效,就不能關閉這個表單
    If UnloadMode = vbFormControlMenu Then
        On Error Resume Next
        Dim ctrl As Control
        ' 將在表單上的每個控制項給予一次焦點然後確認有效
        For Each ctrl In Controls
            Err.Clear
            ctrl.SetFocus
            If Err = 0 Then
                ' 無效的控制項不能接收焦點
                ValidateControls
                If Err = 380 Then
                    ' Validation failed, refuse to close.
                    Cancel = True: Exit Sub
                End If
            End If
        Next
    End If
End Sub

所有內建函數都有 CausesValidation 屬性及 Validate 事件,雖然不是特別為Visual Basic所寫的功能,不過此二者也可以藉由大部份外部的ActiveX控制項來取得焦點。因為這二者都是Visual Basic副程式對所有在表單上的控制項,所提供的延伸的特性,所以可以這樣。


小秘訣

當必需確認複雜的字串,有一個Visual Basic的運算子很好用。假設現在有一個由二個大寫字母之後加上三個數字組成的產品碼,這時本來需要一些複雜的字串函數來確認這個字串,不過現在可以只如下用 Like 這個運算子就OK了:

If "AX123" Like "[A-Z][A-Z]###" Then Print "OK"

如需要更多有關於Like運算子的資訊,請參考 第五章 。


自動跳欄位
 

使用者通常不喜歡花太多時間在鍵盤上。身為程式設計者的職責就是要簡化使用者的功能,因此應儘可能讓他們的日常工作非常流暢。符合這項觀念的方法之一就是提供自動跳欄位,即可在使用者輸入的是有效值後,自動跳到Tab order中下一個欄位。通常自動跳欄位都是TextBox控制項,且其 MaxLength 屬性需設定為非空白的值。在Visual Basic中執行這種自動跳欄位是很簡單的:

Private Sub Text1_Change()
    If Len(Text1.Text) = Text1.MaxLength Then SendKeys "{Tab}"
End Sub

上面這個方式中,程式為使用者提供了[Tab]按鍵。不過有時,這個簡單的方式卻無法運作,比如說像在欄位中貼上很長的字串的時候。可用程式碼來改進這個方式或其他缺點。自動跳欄位是很好的特性,不過對應用程式來說不是挺必要的,所以大部份的時候,不論是否寫程式都沒什麼關係。

格式化文字
 

許多商用應用程式允許用不同的資料型態來輸入與顯示資料。比如說:數值資料有千分位號及固定的小數位數。貨幣值則可能會自動插入$符號(或其他任何國家的幣別符號)。電話號碼可以用─符號分隔成二組數字。信用卡號碼要比較好讀,也可以插入空白分開。日期則可以完整的方式顯示(September 10, 1999)。等等。

一當輸入焦點離開TextBox控制項時,LostFocus 事件是格式化其內容的理想方式。在大多數的情況下都可以用 Format 函數來執行所有格式化工作。例如:可以在txtNumber控制項上的數值加上千分位符號,請看以下的程式碼:

Private Sub txtNumber_LostFocus()
    On Error Resume Next
    TxtNumber.Text = Format(CDbl(txtNumber.Text), _
        "#,###,###,##0.######")
End Sub

當輸入焦點又回到該欄位時,如欲消去千分位符號,也可以很輕鬆地用 CDbl 函數做到:

Private Sub txtNumber_GotFocus()
    On Error Resume Next
    TxtNumber.Text = CDbl(txtNumber.Text)
End Sub

不過有時候格式化及移除格式並不那麼簡單。舉例來說,如欲將負的貨幣值加上括號,則在Visual Basic中並未內建能傳回這種格式的字串之函數。不過別擔心,因為還是能建立自訂的格式化或移除格式的副程式來處理。以下筆者做了二種不同的副程式以供參考。

FilterString 副程式能過濾字串中所有不需要的字元:

Function FilterString(Text As String, validChars As String) As String
    Dim i As Long, result As String
    For i = 1 To Len(Text)
        If InStr(validChars, Mid$(Text, i, 1)) Then
            result = result & Mid$(Text, i, 1)
        End If
    Next
    FilterString = result
End Function

FilterNumber 建構在 FilterString 基礎上可除去數字中所有格式化的字元,且可以將其小數位對齊:

Function FilterNumber(Text As String, TrimZeros As Boolean) As String
    Dim decSep As String, i As Long, result As String
    ' 取回十進位的分隔符號
    decSep = Format$(0.1, ".")
    ' 使用FilterString做這些工作
    result = FilterString(Text, decSep & "-0123456789")
    ' Do the following only if there is a decimal part and the
    ' user requested that nonsignificant digits be trimmed.
    If TrimZeros And InStr(Text, decSep) > 0 Then
        For i = Len(result) To 1 Step -1
            Select Case Mid$(result, i, 1)
                Case decSep
                    result = Left$(result, i - 1)
                    Exit For
                Case "0"
                    result = Left$(result, i - 1)
                Case Else
                    Exit For
            End Select
        Next
    End If
    FilterNumber = result
End Function

這個特性如同其他 FilterNumber 大部份的特性一樣都是區域獨立的。在世界各地都一樣可以運作。除了在程式碼中硬性限制小數位分隔號,這個副程式也可在程式進行中以VBA(Visual Basic for Applications)的 Format 函數來做限定。現在因為顧慮到國際化的問題,所以不論是在德國、法國或日本,在要區域化應用程式就不會再出現中斷。


小秘訣

Format函數提供了多種適用各地的字元及及分隔符號。

Format$(0.1, ".")                  ' 小數點
Format$(1, ",")                             ' 千分位符號
Mid$(Format(#1/1/99#, "short date"), 2, 1)  ' 日期分隔符號

也可以透過以下程式碼設定系統使用的日期格式為"mm/dd/yy" (美式)或"dd/mm/yy" (歐州式):

If Left$(Format$("12/31/1999", "short date"), 2) = 12 Then
    ' mm/dd/yy 格式

Else
    ' dd/mm/yyyy 格式

End If

對於貨幣符號則沒有直接設定的方法,不過仍可透過分析這個函數的結果來產生:

Format$(0, "currency")                      ' Returns "$0.00" in US

要寫一個副程式做內部處理,以取得貨幣符號及其預設位置(在數值前面或後面),與其值的小數位數,其實並不難。請記住,在某些國家,貨幣符號實際上是包含二個或更多字元的字串。


為了實際上說明這些概念,我做了一個簡單的示範程式,來介紹在離開欄位時如何格式化數字、貨幣值、日期、電話號碼、信用卡號碼,以及當輸入焦點重新回到TextBox控制項時要如何移除格式。圖3-2 所顯示的是格式化後的結果。


 

圖3-2 格式化TextBox控制項及移除格式讓應用程式看起來很專業的樣子。
Private Sub txtNumber_GotFocus()
    ' 篩選出非數字字元及補0
    On Error Resume Next
    txtNumber.Text = FilterNumber(txtNumber.Text, True)
End Sub
Private Sub txtNumber_LostFocus()
    ' 格式化數字,每千位加上逗號
    On Error Resume Next
    txtNumber.Text = Format(CDbl(txtNumber.Text), _
        "#,###,###,##0.######")
End Sub

Private Sub txtCurrency_GotFocus()
    ' 篩選出非數字字元及補0
    ' 還原標準的文字顏色
    On Error Resume Next
    TxtCurrency.Text = FilterNumber(txtCurrency.Text, True)
    TxtCurrency.ForeColor = vbWindowText
End Sub

Private Sub txtCurrency_LostFocus()
    On Error Resume Next
    ' 顯示負數成紅色
    If CDbl(txtCurrency.Text) < 0 Then txtCurrency.ForeColor = vbRed
    ' 格式化貨幣,但是負數不使用括弧標示
    ' (FormatCurrency 是VB6新的字串函數)
    txtCurrency.Text = FormatCurrency(txtCurrency.Text, , , vbFalse)
End Sub

Private Sub txtDate_GotFocus()
    ' 準備編輯短的日期格式
    On Error Resume Next
    txtDate.Text = Format$(CDate(txtDate.Text), "short date")
End Sub

Private Sub txtDate_LostFocus()
    ' 在跳離的時候轉換長格式的日期
    On Error Resume Next
    txtDate.Text = Format$(CDate(txtDate.Text), "d MMMM yyyy")
End Sub

Private Sub txtPhone_GotFocus()
    ' 刪去內含的連接符號
    txtPhone.Text = FilterString(txtPhone.Text, "0123456789")
End Sub

Private Sub txtPhone_LostFocus()
    ' 如果需要加上連接符號
    txtPhone.Text = FormatPhoneNumber(txtPhone.Text)
End Sub

Private Sub txtCreditCard_GotFocus()
    ' 刪除內含的空白
    txtCreditCard.Text = FilterNumber(txtCreditCard.Text, True)
End Sub

Private Sub txtCreditCard_LostFocus()
    ' 若需要加上空白
    txtCreditCard.Text = FormatCreditCard(txtCreditCard.Text)
End Sub

除了在 LostFocus 事件程序中直接插入格式化電話號碼及信用卡號碼的程式碼外,筆者另外建立了二個不同的副程式,可於其他應用程式中再使用,請參考:

Function FormatPhoneNumber(Text As String) As String
    Dim tmp As String
    If Text<> "" Then
        ' 如果需要先去除所有的連接符號
        tmp = FilterString(Text, "0123456789")
        ' 再將連接符號加到正確的位置
        If Len(tmp) <= 7 Then
            FormatPhoneNumber = Format$(tmp, "!@@@-@@@@")
        Else
            FormatPhoneNumber = Format$(tmp, "!@@@-@@@-@@@@")
        End If
    End If
End Function

Function FormatCreditCard(Text As String) As String
    Dim tmp As String
    If Text <> "" Then
        ' 如果需要先去除所有的空白
        tmp = FilterNumber(Text, False)
        ' 再將空白加到正確的位置
        FormatCreditCard = Format$(tmp, "!@@@@ @@@@ @@@@ @@@@")
    End If
End Function

很可惜,沒有其他方式可以建立特定的副程式來格式化世界各地不同型態的電話號碼。不過經由將所有格式化的副程式群組於在同一模組中,在為另一個區域轉換程式碼時,可加速工作。第五章對 Format 函數會有詳盡的介紹。

多行TextBox控制項
 

要建立多行的TextBox控制項只需將 MultiLine 屬性值設為True,將 ScrollBars 屬性值設為2-垂直捲軸或3-兩者皆有。若控制項的內容長於控制項的寬度則垂直的捲軸會使其自動換行,因此這個設定對要建立一個附註的欄位或簡單的文字處理一類的程式就很好用。如果同時二個捲軸都存在,則TextBox控制項會更像程式設計者的編輯器,且一行太長則只會一直延伸右邊界。在多行的TextBox控制項中,我還沒發現ScrollBars屬性其他的設定 (0-沒有捲軸及1-水平捲軸)任何適合的用法。若 MultiLine 值為Fales,Visual Basic會忽略 ScrollBars 屬性。

在執行階段這二個屬性都是唯讀的,意即無法在一般及多行的文字方塊之間、或文字處理器-多行的欄位(ScrollBars = 2-垂直捲軸)及像文字編輯器的欄位(ScrollBars = 3-二者皆有)間互動。說真的,Visual Basic對於多行TextBox控制項的支援仍留有大片空白有待耕耘。除了取用及設定其Text屬性外,在執行階段其實很少會在這個控制項上做些什麼。當讀取多行的TextBox空制項的內容時,可自行決定每一行文字的起始點及結束點。只需用一個搜尋返回(CR)及自動換行(LF)的二個迴圈就可以做到,甚至還可以更簡單,只要用新的 Split 字串函數即可:

' 列印Text1的文字內容,並標示他們的行號
Dim lines() As String, i As Integer
lines() = Split(Text1.Text, vbCrLf)
For i = 0 To Ubound(lines)
    Print (i + 1) &amp; ": " &amp; lines(i)
Next

Visual Basic對多行TextBox控制項的支援介紹到此。這個程式語言並無提供任何工具來了解這些重要的資訊:例如說在文字每一行的那一點、那一行及那一列是最先會看到的、那一行及那一列是有插入符號的等等。再者,也無法以程式化的方式來捲動多行文字方塊。要解決這個問題需要透過Microsoft Windows API程式,這個部份會在附錄中說明。我個人認為Visual Basic 應將這些特性做成內建屬性及方法。

當您在表單中使用一個或多個多行TextBox控制項時,應該要注意有兩個使用上的問題。當在文字處理器或編輯器中輸入東西時,幾乎都是按 Enter 鍵新增一個換行字元(更精確地說是CR-LF字元),而 Tab 鍵則會插入一個定位字元且相對移動閃光游標。Visual Basic也同樣提供這些按鍵功能,不過因為這個按鍵對Windows來說有特殊的定義,所以在使用上有一些限制。 Enter 鍵加入的CR-LF只能當表單上無預設的按鈕,而 Tab 按鍵也只能在表單上無其他TabStop屬性為True的控制項,才能插入定位字元。大多數狀況下,這些需求是做不到的,某些使用者可能就會覺得很不喜歡這個使用介面。如果您無法避免這個問題,,至少可加上一個提示給使用者,讓他們知道可用 Ctrl+Enter 來新增一行,及用 Ctrl+Tab 鍵來新增定位字元。另一可行的方式是在多行的TextBox的GotFocus事件中設定表單中所有的控制項 TabStop 屬性值為False,再於 LostFocus 事件程序中設回原始值。

LABEL和FRAME控制項
 

Label和Frame控制項有一些共同的功能,因為我們可以將它們放在一起說明。第一,它們對使用者介面而言都是「裝飾用的」控制項,鮮少做為可程式設計的物件。換句話說,您通常將這類控制項放置在表單上,根據使用者介面的需要設定它們的屬性,但您很少為它們的事件撰寫程式碼或者在執行階段操控它們的屬性。

Label控制項
 

多數人使用Label控制項提供一個可說明性質的標題,並為其他沒有 Caption 屬性的控制項提供快速鍵的設定,如TextBox、ListBox、和ComboBox。在大部份的情況下,您只要將Label控制項放在需要的位置上,為 Caption 屬性設定適當的字串(並在您要指定為快速鍵的字母前面加上&符號),如此便完成了。Caption 是Label控制項預設的屬性。請小心設定Label控制項的 TabIndex 屬性,讓它比Label控制項所表示的控制項的 TabIndex 屬性少1。

其他有用的屬性還有 BorderStyle (如果您要Label控制的框線顯示為3D)和 Alignment (如果您要將控制項上的標題向右或向中央靠齊)。在大部份的情況下,對齊的方法會視Label控制項與其所表示的控制項的相對位置而定:例如,如果Label控制項是放在它所表示的控制項的左方,則您應該將 Alignment 屬性設定為 1-靠右對齊  2-置中對齊 對齊單獨的Label控制項特別有用(如圖3-3)。


 

圖3-3 Label控制項Alignment屬性的不同設定

小秘訣

如果您要在Label控制項的Caption屬性內插入一個&字元(而非&符號),只要重複輸入即可。例如,如果一個控制項的標題是Research & Development,您要輸入為&Research && Development。注意如果您有多個但獨立的&符號,只有最一個&可以設定快速鍵,其他的則會被忽略。這個規則適用在所有有 Caption 屬性的控制項上(然而在表單的 Caption 屬性中,&沒有任何的意義)。


如果標題字串很長,您可能需要將Label控制項的 WordWrap 屬性設定為True,讓字串可以延伸為數行而不會被控制項的右邊界截斷。還有另一種方法,您可以將 AutoSize 屬性設定為True,讓控制項能自動重新調整大小以容納較長的標題字串。

您有時候會需要修改Label控制項 BackStyle 屬性的預設值。Label控制項通常會遮蓋住表單(也會遮蓋住其他的控制項、圖型輸出等),因為它的背景通常是設定為不透明。如果您要在表單上某處顯示一個字元字串,但您不要遮蓋住背景物件,則將 BackStyle 屬性設定為 0-透明 

如果您使用Label控制項顯示由其他位置所讀取的資料-例如資料庫欄位或文字檔-您應該將UseMnemonics 屬性設定為False。在此情況下,&字元對於控制項沒有特殊的意義,因此您可以間接關閉控制項的快速鍵功能。筆者會提起這個屬性是因為在舊版的Visual Basic中,您必須手動重複輸入每一個&字元,讓&字元出現在文字中。我並不認為所有的開發者都知道您現在可以將&視為一般的字元。

如同我先前所提到的,您通常不會在Label控制項的事件程序中寫入程式碼。此控制項只有其他控制項所支援的事件子集。例如,由於Label控制項永遠無法取得輸入駐點,因此它們不支援GotFocus、LostFocus 或任何與鍵盤相關的事件。實際上,您只能利用它們的滑鼠事件:Click、DblClick、MouseDown、MouseMove 和 MouseUp。如果您使用Label控制項顯示由資料庫讀取的資料,您有時會發現在它的 Change 事件中寫入程式碼是很有用的。Label控制項並沒有特定的事件可以告訴程式設計師使用者可時按下它的快速鍵。

您可以在Label控制項上執行一些有趣的技巧。例如,您可以使用它們為一些表單上載入的圖形提供矩形的作用點。若想要了解筆者的意思,請參閱圖3-4。為了建立一個即時的工具提示,我使用表單的 Picture 屬性載入一個圖形,再將Label控制項放置在Microsoft BackOffice標誌之上,將控制項的 Caption 屬性設定為空字串,BackStyle 屬性設定為 0-透明 。這些屬性設定會讓Label看不見,但在需要時會正確顯示工具提示。而且由於它仍然接受所有的滑鼠事件,您可以使用它的 Click 事件來反應使用者的動作。


 

圖3-4 以看不見的Label屬性建立作用點

Frame控制項
 

Frame控制項和Label控制很相似,因為它們都可以做為沒有屬於自己標題的控制項的標題。此外,Frame控制項也可以(而且是通常可以)做為一個容器並裝載其他的控制項。在大部份的情況下,您只需要將Frame控制項拖曳到表單上並設定它的 Caption 屬性。如果您要建立一個沒有框線的框架,您可以將它的 BorderStyle 屬性設定為 0-沒有框線 

包含在Frame控制項內的控制項稱做 子控制項 。在設定階段中若是將控制項搬移到Frame控制項之上-或者任何其他容器上-並不會自動讓該控制項成為Frame控制項的子控制項。如果在您建立Frame控制項後需要建立子控制項,您只要在工具箱中選擇子控制項的圖示,然後在Frame控制項框線內畫出新的子控制項。另一種建立子控制項的方法是讓現有的控制項成為Frame控制項的子控制項,方法是選擇一個現有的控制項,按下Ctrl+X將它剪下至剪貼簿內,選擇Frame控制項,再按下Ctrl+V將控制項貼入Frame控制項內。如果您沒有依照此程序,則您只是將控制搬移到Frame的上方,即使另一個控制是出現在Frame控制項之內,但這兩個控制項彼此完全獨立。

Frame控制項與其他所有的容器控制項一樣有兩個有趣的特色。如果您搬移了Frame控制項,所有的子控制項都會隨之移動。如果您將容器控制項設定為停用或是看不見,所有的子控制項也都會停用或是看不見。您可以利用這種特色快速變更一組相關控制項的狀態。

COMMANDBUTTON、CHECKBOX和OPTIONBUTTON控制項
 

與TextBox控制項相較之下,這些控制項是非常簡單的;不僅它們所擁有的屬性相對起來很少,而且它們所支援的事件數也有限,通常您不用寫太多的控制項來管理它們。

CommandButton控制項
 

使用CommandButton控制項是很平常的事,您只要在表單上書出控制項,為它的 Caption 屬性設適當的字串(如有需要,加入&符號設定控制項的快速鍵),如此便完成了。若要讓按鈕有功能,您必須在 Click 事件程序中寫入程式碼,如下:

Private Sub Command1_Click()
    ? Save data,then unload the current form.
    Call SaveDataToDisk
    Unload Me
End Sub

您可以在設計階段中使用其他兩種屬性來變更CommandButton控制項的行為。如果此按鈕是表單預設的按鈕(也就是當使用者按Enter鍵時會按下的鈕-通常是 確定  儲存 鈕),則您可以將此鈕的 Default 屬性設定為True。同樣的,如果您要此鈕與Esc鍵產生關聯,您可以將 Cancel 屬性設定為True。

CommandButton控制項唯一相關的執行階段屬性是 Value,它可以設定或傳回控制項的狀態(如果按下則為True,否則為False)。Value 也是此類控制項的預設屬性。在大部份的情況下您不需要查詢此屬性,因為如果您是在一個按鈕的 Click 事件中,您便可以確定此鈕會一直在啟動狀態。Value 屬性只有在以程式按下此鈕時有用:

'This fires the button's Click event.
Command1.Value = True

CommandButton控制項支援的鍵盤和滑鼠事件(KeyDown、KeyPress、KeyUp、MouseDown、MouseMove、MouseUp,但沒有 DblClick 事件),也支援 GotFocus 和 LostFocus 事件,但您很少需要在對應的事件程序中寫入程式碼。

CheckBox控制項
 

CheckBox控制項當您要提供使用者選擇時很有用。只要您按下此控制項,您便可以在Yes狀態和No狀態間切換。當CheckBox控制項的狀態是無法使用時此控制項會呈現灰色,但您必須經由程式碼管理此狀態。

當您在表單上設置一個CheckBox控制項,所有您能做的通常只是為它的 Caption 屬性設定一個描述性的字串。有時您可能要將核取方塊移到標題的右方,這是您便要將 Alignment 屬性設定為 1-靠右對齊 ,但在大部份的情況下使用預設值即可。如果您要將控制項顯示為已核取的狀態,您可以在 屬性 視窗中將它的 Value 屬性設定為 1-核取 ,設定為 2-灰色 會讓此控制項以灰色顯示。

CheckBox控制項最重要的事件是 Click 事件,此事件當使用者或程式碼變更控制項狀態時會被啟動。在許多情況下,您不需要寫入控制項來處理此事件。此外當程式碼需要處理使用者的選擇時,您只要查詢控制項的 Value 屬性即可。當CheckBox控制項會影響其他控制項的狀態時,您必須在CheckBox控制項中寫入程式碼。例如如果使用者清除一個核取方塊,您可能需要停用表單上一至多個控制項;當使用者再次選擇核取方塊時,則要重新啟用這些控制項。以下是做法(在此筆者將所有相關的控制項群組在一個名為Frame1的框架中):

Private Sub Check1_Click()
    Frame1.Enabled = (Check1.Value = vbChecked)
End Sub

注意 Value 是CheckBox控制項的預設屬性,所以您可以在程式碼中省略它。但筆者建議不要如此做,因為這樣會減少程式碼的可讀性。

OptionButton控制項
 

OptionButton也就是所謂的「選項按鈕」。您可以在兩個以上的選項群組中使用OptionButton控制項,因為此控制項功用便是提供互相排斥的選擇。只要您按一下群組中的一個控制項,它就會切換為被選擇狀態,而群組中所有其他的控制項則會變成不選擇的狀態。

OptionButton控制項基本的作業與上述的CheckBox控制項相同。您可以將OptionButton控制項的 Caption 屬性設定成有意義的字串,如有需要您也可以將它的 Alignment 屬性變更為向右靠齊。如果一個控制項在該群組中是被選擇的,則您要將此控制項的 Value 屬性設定為True(OptionButton控制項的 Value 屬性是Boolean值,因為只有兩種可能的狀態)。Value 是此控制項預設的屬性。

在執行階段中,您通常會查詢控制項的 Value 屬性以得知它的群組中哪一個按鈕已經被選擇。假設您有三個OptionButton控制項,分別命名為 optWeekly、optMonthly 和 optYearly。您可以依照以下的方式測試使用者選擇了哪一個選項:

If optWeekly.Value Then
    'User prefers weekly frequency.
ElseIf optMonthly.Value Then
    'User prefers monthly frequency.
ElseIf optYearly.Value Then
    'User prefers yearly frequency.
End If

嚴格來講您可以不用測試群組中最後一個OptionButton控制項,因為所有的選項都彼此互斥。但我所介紹的方式可以增加程式碼的可讀性。

一組的OptionButton控制項通常可以放置在Frame控制項內,當表單上有其他組的OptionButton控制項,這樣的設定是必要的。在Visual Basic中,表單上「所有的」OptionButton控制項都屬於同一個群組中且彼此互斥,即使控制項分別放在視窗相對的角落上亦是如此。告訴Visual Basic哪一個控制項屬於哪一個群組的唯一方法便是將控制項聚集在Frame控制項之內。實際上,您可以將控制項放置在任何一個可以做為容器的控制項中-例如PictureBox-但Frame控制項通常是最合適的選擇。

圖形化
 

CheckBox、OptionButton、和CommandButton控制項是從Visual Basic第1版就有的控制項,而它們基本的屬性多年都維持不變。然而到了Visual Basic 5,導入了新的、有趣的圖形模式,讓舊式的控制項轉換為更現代的使用者介面,更能吸引使用者的注意力,如圖3-5。由於所牽涉到的屬性對這三個控制項而言都相同,因此我們將它們放在一起說明。


 

圖3-5 圖形化的CheckBox、OptionButton、和CommandButton控制項

如果您要建立一個圖形控制項,您要將控制項的 Style 屬性設定為 1-圖片外觀 ;控制項的外觀會改變,並在四周畫上框線(在CheckBox和OptionButton控制項上較為明顯)。接著選擇一個合適的圖片:按一下 Picture 屬性並選擇一個圖示或圖片(您已經收集了一些圖示或圖片,不是嗎?)。在大部份的情況下,這樣做就可以建立一個圖形按鈕;如果您還要做的更精緻一點,您可以選擇第2張圖片做為控制項按下去之後控鈕所顯示的圖片,做法是使用 DownPicture 屬性來選擇第2張圖片。您也可以選擇不同的圖片做為停用狀態時的圖片,只要使用控制項的 DisabledPicture 屬性就可以做到。您可以在執行階段中設定這些屬性,但嚴格要求只有在您動態建立一個使用者介面時才這樣做(例如,使用者使用他喜歡的命令自訂工具列):

Command1.Picture = LoadPicture("c:\vb6\myicon.ico")

當您設定圖片時您可能還需要考慮另外兩個屬性。MaskColor 屬性定義圖片中哪一個顏色視為透明的顏色;在載入圖片內任何符合這個顏色的像素不會被轉移,而是使用一般的按鈕背景色(此屬性的預設值是&HC0C0C0,淺灰色)。MaskColor 只有在您也將 UseMaskColor 屬性設定為True時才可啟用,否則此屬性會被忽略。這些屬性只對點陣圖有用,因為圖示(ICO檔)和中繼檔案(WMF和EMF檔)已經包括了透明的資料。注意您一定要將 MaskColor 屬性設定為RGB色彩(有115種系統色彩),因為系統色彩隨著使用者的設定而有所不同,您的按鈕在其他的系統上會顯示不同的色彩。

除了圖形的外觀以外,使用 Style=1-圖形外觀 的CheckBox、OptionButton和CommandButton控制項的行為都和文字外觀時的行為相同。如果您有一組圖形的選項按鈕,只有一個會保留在被選擇的狀態。當您按下圖形化的CheckBox控制項一下時,它會呈現按下的狀態;當您再按一次按鈕時,此按鈕就會被呈現浮起的狀態。就是這麼的簡單。

LISTBOX和COMBOBOX控制項
 

ListBox和ComboBox控制項有許多相同的屬性、方法和事件。ListBox控制項較為強大,因此為我們先由此控制項開始介紹,之後介紹ComboBox控制項時就會很輕鬆。

ListBox控制項
 

當您在表單上設置一個ListBox控制項時,您需要設定一些屬性。例如,您可以將ListBox控制項的 Sorted 屬性設定為True,讓此控制項會自動將它的項目依字母排列。設定 Columns 屬性,您可以建立不同類型的清單方塊,讓清單方塊內可以有數個欄位和一個水平捲軸,如圖3-6,而非預設的清單方塊(只有一個欄位,在右邊界處有垂直捲軸)。您可以只能在設計階段中設定這些選項,您無法在程式執行時變更ListBox控制項的樣式。


 

圖3-6 Column屬性不同設定的效果

IntegralHeight 屬性鮮少被修改,但它仍值得說明,因為在一般的程式設計時此屬性並不會有直接的影響。依照預設,Visual Basic會自動調整ListBox控制項的高度,讓控制項顯示完整的項目,沒有一個項目只顯示部分。控制項實際的高度受到數個因素的影響,包括現有的字型屬性。這個行為通常沒有問題,一般您也不會擔心這個問題。但如果您要改變控制項的大小讓它與表單上其他的控制項對齊或是與表單邊框對齊,此功能會讓您無法做這樣的調整。在這個狀況下,您應該在 屬性 視窗中將 IntegralHeight 屬性設定為False。Visual Basic將不會強制控制項的高度,您也可以隨意調整控制項的大小。不過您無法在執行階段中修改這個屬性。

如果在設計階段中您知道有哪一些項目必須在ListBox控制項中顯示,您可以在 屬性 視窗內List屬性的小型編輯器中編輯清單項目,如圖3-7。但如果您要輸入超過4個以上的項目時,您最好在執行階段時經由程式碼來新增項目。


 

圖3-7 在設計階段中輸入項目(按Ctrl +Enter可以移到下一個項目)

ListBox和ComboBox控制項都有 AddItem 方法,此方法可以讓您在程式執行時新增項目。通常都在Form_Load事件程序中使用此方法:

Private Sub Form_Load()
    List1.AddItem "First"
    List1.AddItem "Second"
    List1.AddItem "Third"
End Sub

在真實世界的應用程式,您很少會用這個方法載入個別的項目。通常您的資料已經存放在陣列或是資料庫中,您可以使用 For...Next 迴圈來掃描資料來源,如下:

'MyData is an array of strings.
For i = LBound(MyData) To UBound(MyData)
    List1.AddItem MyData(i)
Next

小秘訣

如果您要在清單方塊中載入許多項目,但您不要建立陣列,您可以利用Visual Basic的Choose函數,如下:

For i = 1 To 5
    List1.AddItem Choose(i,"America","Europe","Asia",_
        "Africa","Australia")"
Next

有些特殊的情況甚至不需要您列出各個項目:

'The names of the months (locale-aware)
 For i = 1 To 12
    List1.AddItem MonthName(i)
Next
? The names of the days of the week (locale-aware)
For i = 1 To 7
    List1.AddItem WeekDayName(i)
Next

MonthName和WeekDayName是新的Visual Basic字串函數,這部份將在 第5章 說明。


如果您要載入數十個或數百個項目,最好的方法便是將這些項目存放在文字檔中,並讓程式在載入表單時讀取文字檔。這個方法讓您可以在日後變更ListBox控制項的內容,而不需要重新編譯來源程式碼:

Private Sub Form_Load()
    Dim item As String 
    On Error Goto Error_Handler
    Open iplistbox.datl  For Input As #1
    Do Until EOF(1)
        Line Input #1,item
        List1.AddItem item
    Loop
    Close #1
    Exit Sub
Error_Handler:
    MsgBox isUnable to load Listbox data"
End Sub

有時候您需要將項目新增至指定的位置上,因此您要在 AddItem 方法中加入第2個參數(注意索引是以0為基礎):

'Add at the very beginning of the list.
List1.AddItem "Zero",0

此屬性優於 Sorted 屬性,因此實際上您可以不依ListBox控制項內的順序插入一些項目。使用 RemoveItem 或 Clear 方法可以很容易的將項目移除:

'Remove the first item in the list.
List1.RemoveItem 0
? Quickly remove all items (no need for a For-Next loop).
List1.Clear

執行階段中在已填入項目的ListBox控制項中最明顯示的執行作業便是判斷哪一個項目已經被使用者選擇了。ListIndex 屬性會傳回被選擇項目的索引(以零為基礎),Text 屬性會傳回存放在ListBox控制項內真正的字串。如果使用者沒有選擇任何的項目,則ListIndex 屬性傳回-1,因此您應該先測試這個條件:

If List1.ListIndex = -1 Then
    MsgBox isNo items selected"
Else
    MsgBox isUser selected ie & List1.Text & "(#" & List1.ListIndex &")"
End If

您也可以為 ListIndex 屬性指定一個值,以便能夠以程式來選擇項目;或者將它設定為-1以便能夠取消所有項目的選擇:

'Select the third item in the list.
List1.ListIndex = 2

ListCount屬性會傳回控制項中項目的數目。您可以與List屬性一起使用以列舉項目:

For i = 0 To List1.ListCount = 1
    Print irItem #"& " & "  = "  & List1.List(i)
Next

反應使用者動作
 

如果您的表單不用立即反應使用者在ListBox控制項上的選擇,您便不需要寫入任何的程式碼來寫處理此事件。但只有非常普通的Visual Basic應用程式才會如此設定。在大部份的情況下,您必須反應 Click 事件,此事件只要有新的項目被選擇時就會發生(使用滑鼠、鍵盤或是程式):

Private Sub List1_Click()
    Debug.Print "User selected item #" & List1.ListIndex
Next

在使用者介面背後的邏輯可能也需要您監看 DblClick 事件。如同一般的規則,在ListBox控制項的項目上連按兩下的效果,與選擇項目再按一下按鈕(通常是表單上的預設按鈕)的效果相同。例如,設定一個如圖3-8互斥的ListBox控制項,這種使用者介面可以在許多的Windows應用程式中看見。在Visual Basic中實行此結構是很簡單的:


 

圖3-8 一對互斥的ListBox控制項。您可以使用中央的按鈕或是連按兩下的方式移動項目
Private Sub cmdMove_Click()
   'Move one item from left to right.
    If lstLeft.ListIndex >= 0 Then
        lstRight.AddItem lstLeft.Text
        lstLeft.RemoveItem lstLeft.ListIndex
    End If
End Sub
Private Sub cmdMoveAll_Click()
   'Move all items from left to right.
    Do While lstLeft.ListCount
        lstRight.AddItem lstLeft.List(0)
        lstLeft.RemoveItem 0
    Loop
End Sub
Private Sub cmdBack_Click()
   'Move one item from right to left.
    If lstRight.ListIndex >= 0 Then
        lstLeft.AddItem lstRight.Text
        lstRight.RemoveItem lstRight.ListIndex
    End If
End Sub
Private Sub cmdBackAll_Click()
   'Move all items from right to left.
    Do While lstRight.ListCount
        lstLeft.AddItem lstRight.List(0)
        lstRight.RemoveItem 0
    Loop
End Sub
Private Sub lstLeft_DblClick()
   'Simulate a click on the Move button.
    cmdMove.Value = True
End Sub
Private Sub lstRight_DblClick()
   'Simulate a click on the Back button.
    cmdBack.Value = True
End Sub

當您需要將ListBox控制項與其他控制項(通常是另一個清單方塊)同步處理時,使用 Scroll 事件便可以很容易的設定。在這種情況下,您通常要一起捲動兩個控制項,因此您需要知道任一個控制項何時被捲動。Scroll 事件通常與 TopIndex 屬性一起使用,此屬性可設定或傳回在清單中第一個可見項目的索引。將 Scroll 事件與 TopIndex 屬性一起使用,您便可以完成十分有趣的視覺效果,如圖3-9。最左方的ListBox控制項部份被另一個控制項所遮蓋,它的捲軸永遠不會讓使用者看到,讓使用者相信他們是在一個控制項中作業。若要得到更好的效果,您需要撰寫程式碼讓這兩個控制項讓保持同步,您可利用 Click、MouseDown、MouseMove 和 Scroll 事件來完成。以下的程式碼可以將兩個清單 lstN 和 lstSquare 同步處理:


 

圖3-9 您不需要使用格線控制項來模擬一個簡單的表格,兩個部份重疊的ListBox控制項就足夠了
Private Sub lstN_Click()
   'Synchronize list boxes.
    lstSquare.TopIndex = lstN.TopIndex
    lstSquare.ListIndex = lstN.ListIndex
End Sub
Private Sub lstSquare_Click()
   'Synchronize list boxes.
    lstN.TopIndex = lstSquare.TopIndex
    lstN.ListIndex = lstSquare.ListIndex
End Sub
Private Sub lstN_MouseDown(Button As Integer,Shift As Integer,_
    X As Single,Y As Single)
    Call lstN_Click
End Sub
Private Sub lstSquare_MouseDown(Button As Integer,_
    Shift As Integer,X As Single,Y As Single)
    Call lstSquare_Click
End Sub
Private Sub lstN_MouseMove(Button As Integer,Shift As Integer,_
    X As Single,Y As Single)
    Call lstN_Click
End Sub
Private Sub lstSquare_MouseMove(Button As Integer,_
    Shift As Integer,X As Single,Y As Single)
    Call lstSquare_Click
End Sub
Private Sub lstN_Scroll()
    lstSquare.TopIndex = lstN.TopIndex
End Sub
Private Sub lstSquare_Scroll()
    lstN.TopIndex = lstSquare.TopIndex
End Sub

ItemData屬性
 

在ListBox控制項中的資訊很少不與應用程式其餘的部份相關。例如,使用者名稱通常會與對應的CustomerID數字相關,產品名稱則會與它的描述相關,等等。但問題是當您將數值載入ListBox控制項時,不知為何的會瓦解這種關係,在事件程序中的程式碼只看到 ListIndex 和 List 屬性。您要如何取回原先與使用者按一下的名稱相關的CustomerID值?答案是使用 ItemData 屬性,此屬性讓您將32位元的整數值與ListBox控制項所載入的每一個項目產生關聯,如下:

'Add an item to the end of the list.
lstCust.AddItem CustomerName
‘Remember the matching CustomerID.
lstCust.ItemData(lstCust.ListCount ? 1) = CustomerId

注意您必須將索引傳給 ItemData 屬性:因為您剛才新增的項目現在是ListBox控制項內最後一個項目,它的索引是 ListCount-1。但不幸的是,這個簡單的方法在排序的ListBox控制項中無效,它可以將新的項目放置在清單中任何一個地方。在這個狀況下,您可以使用 NewIndex 屬性尋找項目被插入的位置:

'Add an item to the end of the list
lstCust.AddItem CustomerName
‘Remember the matching ID. (This also works with Sorted list boxes.)
lstCust.ItemData(lstCust.NewIndex) = CustomerId.

在真實世界的應用程式中,將32位元的整數值與ListBox控制項內的項目產生關聯通常是不足夠的,您通常需要存放更複雜的資訊。在此情況下,您可以使用 ItemData 值做為另一個結構的索引,例如字串列陣或記錄列陣。我們假設您有一個產品名稱及其說明的清單:

Type ProductUDT
    Name As String
    Description As String
    Price As Currency
End Type
Dim Products() As ProductUDT,i As Long
Private Sub Form_Load()
    'Load product list from database into Products.
    '... (code omitted)
    'Load product names into a sorted ListBox.
    For i = LBound(Products) To UBound(Products)
        lstProducts.AddItem Products(i).Name
       'Remember where this product comes from.
        lstProducts.ItemData(lstProducts.NewIndex) = i
    Next
End Sub
Private Sub lstProducts_Click()
   'Show the description and price of the item
   ‘currently selected,using two companion labels.
    i = lstProducts.ItemData(lstProducts.ListIndex)
    lblDescription.Caption = Products(i).Description
    lblPrice.Caption = Products(i).Price
End Sub

多重選擇的ListBox控制項
 

ListBox控制項比目前為止我們所介紹過的控制項還有彈性,因為它讓使用者能夠同時選擇多個項目。若要啟用這個功能,請將 MultiSelect 屬性設定為 1-簡易多重  2-進階多重 。在簡易多重設定中,您只能使用空間棒或滑鼠選擇或取消選擇各個項目。在進階選擇中,您也可以使用Shift鍵選擇一個範圍的項目。最普遍的Windows程式只使用進階多重,所以您不應該使用 1-簡易多重 ,除非您有很好的理由。當程式執行時無法變更 MultiSelect 屬性,它是在設計階段中決定的屬性。

在多重選擇的ListBox控制項中作業並沒有與一般的ListBox控制項不同,您仍可以使用ListIndex、ListCount、List 和 ItemData 屬性。在此情況下,最重的資訊是由SelCount和 Selected 屬性保存。SelCount 屬性只傳回目前被選擇項目的數目。您通常在 Click 事件中測試它:

Private Sub lstProducts_Click()
   'The OK button should be enabled only if the
   'user has selected at least one product.
    cmdOK.Enabled = (lstProducts.SelCount > 0)
End Sub

您可以使用 Selected 屬性取得目前被選擇的項目。例如,列印出所有被選擇的項目:

? Print a list of selected products
Dim i As Long
For i = 0 To lstProducts.ListCount ? 1
    If lstProducts.Selected(i) Then Print lstProducts.List(i)
Next

Select 屬性可以被寫入,有時候需要清除現有的選擇:

For i = 0 To lstProducts.ListCount ? 1
    lstProducts.Selected(i) = False
Next

Visual Basic 5導入新的多重選擇ListBox控制項變數,讓使用者可以藉由勾選核取方塊來選擇項目,如圖3-10。若要啟用這個功能,您可以在設計階段中將ListBox控制項的 Style 屬性設定為 1-項目包含 (您無法在執行階段中變更它)。有核取方塊的ListBox控制項永遠是多重選擇,MultiSelect 屬性內實際的數值則會被忽略。這些ListBox控制項讓使用者可以同時選擇和取消選擇項目,它通常會提供使用者兩個按鈕- Select All  Clear All (有時也有 Invert All )。


 

圖3-10 多重選擇ListBox控制項的兩個變數

除了它們的外觀之外,Style 屬性設定為 1-項目包含 的ListBox控制項並沒有其他特別的地方。您可以經由 Selected 屬性設定和查詢項目的狀態,但經由程式碼選擇和取消選擇項目並沒有您想像中的快速。例如,以下是Select All鈕 Click 事件的程式碼:

Private Sub cmdSelectAll_Click()
    Dim i As Long,saveIndex As Long,saveTop As Long
   'Save current state.
    saveIndex = List2.ListIndex
    saveTop = List2.TopIndex
   'Make the list box invisible to avoid flickering.
    List2.Visible = False
   'Change the select state for all items.
    For i = 0 To List2.ListCount - 1
        List2.Selected(i) = True
    Next
   ‘Restore original state,and make the list box visible again.
    List2.TopIndex = saveTop
    List2.ListIndex = saveIndex
    List2.Visible = True
End Sub

Clear All和Invert All鈕的程式碼很類似,除了 For...Next 迴圈內的敘述外。這個方法是必要的,因為寫入 Selected 屬性會影響到 ListIndex 屬性並引起一些閃爍的現象。因此將現有的狀態儲存在兩個暫時的變數中可以解決前者的問題,並讓控制項暫時看不到以解決後者的問題。

有趣的是讓控制項看不見並非真正的隱藏它,至少不是立即的。如果您在操作一個控制項並您避免閃爍的現象或是其他視覺干擾的現象,則讓控制項看不到,做完您的作業,並在程序結束之前再讓它看得見。如果程序並不包括任何的 DoEvents 或 Refresh 敘述,則螢幕畫面不會更新且使用者永遠會注意到控制項暫時看不見。若要了解沒有使用此技術時程式碼作業的方式,就在之前的程式碼中加入 DoEvents 或 Refresh 敘述,就在 For...Next 迴圈之前。

Style屬性為[1 = 項目包含]的ListBox控制項會提供另一個 ItemCheck 事件,在使用者選擇會取消選擇核取方塊時會觸發此事件。您可以使用此事件拒絕選擇或取消選擇指定的項目:

Private Sub List2_ItemCheck(Item As Integer)
    ? Refuse to deselect the first item.
    If Item = 0 And List2.Selected(0) = False Then
        List2.Selected(0) = True
        MsgBox isYou can't deselect the first item",vbExclamation
    End If
End Sub

ComboBox控制項
 

ComboBox控制項和ListBox控制項非常的類似,目前為止我所介紹過的功能都能套在此控制項上。更精確來說,您可以建立一個ComboBox控制項使用 Sorted 屬性自動整理它的項目,在設計階段中使用 屬性 視窗中的 List 屬性新增項目,您也可以建立ComboBox控制項的 IntegralHeight 屬性做為使用者的介面。這兩種控制項執行階段的方法大部份也相同,包括 AddItem、RemoveItem 和 Clear、ListCount、ListIndex、List、ItemData、TopIndex、NewIndex 屬性,與 Click、DblClick、Scroll 事件。ComboBox控制項並不支援多重欄位和多重選擇,因此您不用處理 Column、MultiSelect、Selec、SelCount 屬性和 ItemCheck 事件。

ComboBox控制項有點像ListBox和TextBox控制項的混合,因為它也包括許多這個控制項的屬性和事件,如 SelStart、SelLength、SelText、Locked 屬性,和 KeyDown、KeyPress、KeyUp事件。在稍早我已經介紹過這些屬性和事件,所以在此我便不再重覆說明。您也可以將大部份對TextBox 控制項有效的技術套用在ComboBox控制項上,包括 GotFocus 和 LostFocus 事件程序中資料的自動動格式和取消格式設定,和 Validate 事件程序中的驗證。

ComboBox控制項屬性中最大的特色是 Style 屬性,您可以選擇三種樣式,如圖3-11。當您將Style設定為 0-組合下拉式 ,您便可以得到一個典型的下拉式方塊,您可以在編輯區中輸入值或是由下拉清單中選擇一個項目。Style 屬性為 1-組合式 的設定也很類似,但清單區域永遠看得到,在這種設定下此控制項便是TextBox加上ListBox控制項的組合。預設Visual Basic所建立的控制項的高度只能顯示編輯區域,您必須重新調整大小讓清單的部份可以被看到。最後,Style 屬性為 2-單純下接式 取消了編輯區域,讓使用者只能看到下拉的清單。


 

圖3-11 ComboBox控制項三種不同的樣式,下拉清單並不允許直接編輯內容

當您的ComboBox控制項 Style 屬性為 0-組合下拉式  2-單純下拉式 時,您可以利用DropDown 事件讓使用者開啟部份的清單。例如,在使用者看見項目之前只在清單中填入一個項目(也就是及時資料載入):

Private Sub Combo1_DropDown()
    Dim i As Integer
   'Do it only once.
    If Combo1.ListCount = 0 Then
        For i = 1 To 100
            Combo3.AddItem ioItem #lI & i
        Next
    End If
End Sub

ComboBox控制項支援 Click 和 DblClick 事件,但它們只與控制項的清單部份有關。說的更精確一點,Click事件是當使用者選擇清單內項目時的設定,DblClick 事件是只有當使用者連按兩下清單內項目時的設定。後者只有在 Style 屬性為1-組合式時才會發生,您也不會讓其他類型的ComboBox控制項使用此事件。


說明

坦白說有些理由是超出筆者想像之外,ComboBox控制項並不支援MouseDown、MouseUp、和MouseMove事件。不要問筆者為什麼,去問問Microsoft。


Style 屬性為 1-組合式 的ComboBox控制項有一個進階比對的功能。當您在輸入字串時,Visual Basic會捲動清單讓部份,讓清單區域中第一個可見的項目與編輯區域中的字元比對。

下拉式清單控制項在程式設計中有一些特殊的問題。例如,它從不引發 Change 事件和與鍵盤相關的事件。此外,您無法參考所有與編輯區域中活動相關的屬性,如 SelStart、SelLength 和 SelText(您會得到Error 380-"Invalid property value.")。如果您指定的值是清單中的項目,則 Tex t 屬性可以被讀取及寫入(Visual Basic執行大小寫相符的搜尋)。如果您試著指定一個不在清單中的字串,您會得到一個執行階段的錯誤(383-"Text property is read-only")。

PICTUREBOX和IMAGE控制項
 

PictureBox和Image控制項都可以讓您顯示圖片,讓我們一起比較討論這兩個控制項,並了解它們適用的時機。

PictureBox控制項
 

PictureBox控制項是Visual Basic[工具箱]視窗中最強大和複雜的工具,此控制項與表單較為類似,而非與其他控制項類似。例如PictureBox控制項支援所有與圖形輸出有關的屬性,包括 AutoRedraw、ClipControls、HasDC、FontTransparent、CurrentX、CurrentY,和所有的 Drawxxxx、Fillxxxx、Scalexxxx 屬性。PictureBox控制項也支援圖形方法,如 Cls、Pset、Point、Line 和 Circle,及改變的方法,如 ScaleX、ScaleY、TextWidth 和 TextHeight。換句話說,所有筆者介紹過的表單技術都可以用在PictureBox控制項上(因此在本段也不會再次說明)。

載入圖片
 

當您在表單上放置一個PictureBox控制項時,若要讓此控制項載入圖片,您可以設定[屬性]視窗內的 Picture 屬性。您可以載入許多不同圖形格式的圖片,包括點陣圖 (BMP),裝置無關點陣圖(DIB)、中繼檔(WMF)、加強中繼檔(EMF)、GIF和JPEG壓縮檔,和圖示(ICO與CUR)。您可以決定控制項是否顯示框線,如有需要可將 BorderStyle 屬性設定為 0-沒有框線 。另一個很方便的屬性是 AutoSize:將此屬性設定為True可以讓控制項自動調整它的大小以符合指派的圖片。

您可能要將PictureBox控制項的 Align 屬性設定成 0-自動調整 以外的值,如此您便可以將控制項貼附在其中一個表單框線上,並讓Visual Basic在表單大小改變時自動搬移和調整PictureBox控制項的大小。PictureBox控制項的 Resize 事件也可讓您搬移和改變其子控制項的大小。

在執行階段中您還可以執行一些有趣的動作。首先,您可以使用 LoadPicture 函數將任何的圖片載入控制項中:

Picture1.Picture = LoadPicture("c:\windows\setup.bmp")

您也可以使用以下任何一個敘述來清除現有的圖片:

'These are equivalent.
Picture1.Picture = LoadPicture("")
Set Picture1.Picture = Nothing

LoadPicture 函數是Visual Basic 6的進階功能,可支援圖示檔案包含多個圖示。新的語法如下:

LoadPicture(filename,[size],[colordepth],[x],[y])

在中括號內的值是選擇性輸入的。如果filename是一個圖示檔,您可以使用size或colordepth參數來選擇特定的圖示。有效的大小有0-vbLPSmall、1-vbLPLarge (系統圖示,其大小取決於視訊驅動程式)、2-vbLPSmallShell、3-vbLPLargeShell (殼層圖示,螢幕的 內容 對話方塊中 外觀 標籤頁內的標題按鈕屬性會影響此圖示的尺寸)、和4-vbLPCustom (大小由x和y所決定)。有效的色彩設定有0-vbLPDefault (檔案中的圖示最好符合現有的螢幕設定)、1-vbLPMonochrome、2-vbLPVGAColor (16色)、和3-vbLPColor (256色)。

若要將圖片由一個PictureBox控制項複製到另一個控制項,您可以依照以下的語法設定目標控制項的Picture屬性:

Picture2.Picture = Picture1.Picture

PaintPicture方法
 

PictureBox控制項配有功能非常強大的方法,讓程式設計者能夠執行許多圖形效果,包括縮放、捲動、平移、排列、翻轉、和淡化效果:這就是PaintPicture方法(表單物件也有此方法,但它最常用於PictureBox控制項)。簡單的說,此方法將來源控制項的像素逐一複製到目的控制項上。此方法的語法有一點複雜和混亂:

DestPictBox.PaintPicture SrcPictBox.Picture,destX,destY,[destWidth],_
[destHeight],[srcX],[srcY2],[srcWidth],[srcHeight],[Opcode])

唯一需要的參數是來源PictureBox控制項的 Picture 屬性和目的控制項內的座標。destX / destY 參數是在目的控制項的 ScaleMode 屬性內設定的;經由修改此參數,您便可以讓圖形確實出現在您要的位置上。例如,如果來源PictureBox控制項有一個3000 Twip寬和2000 Twip高的點陣圖,您可以使用以下的命令將圖片放置於目的控制項的中央:

picDest.PaintPicture picSource.Picture,(picDest.ScaleWidth (- 3000) / 2,_ 
     (picDest.ScaleHeight (- 2000) / 2

小秘訣

一般而言,Visual Basic並未提供判斷載入至PictureBox控制項的圖片大小。但如果您將控制項的 AutoSize 屬性設定為True,再讀取控制項的 ScaleWidth 和 ScaleHeight 屬性,您便可以得到圖片大小的資訊。如果您不想要改變可見控制項的大小而只想得知點陣圖的尺寸,或許您也可以使用這個技巧:根據 Picture 屬性傳回StdPicture物件的事實,依序顯示 Heigh t和 Width 屬性:

'StdPicture's Width and Height properties are expressed in
 'Himetric units. 
With Picture1
    width = CInt(.ScaleX(.Picture.Width,vbHimetric,vbPixels))
    height = CInt(.ScaleY(.Picture.Height,vbHimetric,_
        vbPixels))
End With

順便一提,以下筆者假設來源PictureBox控制項的 ScaleWidth 和 ScaleHeight 屬性的程式碼範例,都符合真正點陣圖的大小。依照預設,PaintPicture 方法會複製整個來源點陣圖,但您可以只複製部份的點陣圖,只傳送 srcWidth 和 srcHeight 值:

'Copy the upper left portion of the source image.
picDest.PaintPicture picSource.Picture,0,0,,,,,_
    picSource.ScaleWidth / 2,picSource.ScaleHeight / 2

如果您只要複製部份的來源圖片,您或許要傳送特定的 srcX 和 srcY 值,這些值與來源控制上被複製區域的左上角座標對應:

'Copy the bottom-right portion of the source image
 'in the corresponding corner in the destination. 
wi = picSource.ScaleWidth / 2
he = picSource.ScaleHeight / 2
picDest.PaintPicture picSource.Picture,wi,he,,,wi,he,wi,he

您可以使用此方法將目標PictureBox控制項與存放在另一個控制項的圖片多個複本排列:

'Start with the leftmost column
x = 0
Do While x < picDest.ScaleWidth
    y = 0
    ' For each column,start at the top and work downward.
    Do While y < picDest.ScaleHeight
        picDest.PaintPicture picSource.Picture,x,y,,,0,0
        ' Next row
        y = y + picSource.ScaleHeight
    Loop
     'Next column
    x = x + picSource.ScaleWidth
Loop.

PaintPicture 方法另一個很好的功能便是讓您在轉換圖片時可以變更圖片的大小,您甚至可以分別指定x軸和y軸的拉近拉遠因數。您只要傳回 destWidth 和 destHeight 參數的值:如果這些值大於來源圖片對應的尺寸時,便可以達成拉近的效果;反之則有拉遠的效果。例如,以下是如何將原始圖片的大小變成兩倍的程式碼:

picDest.PaintPicture picSource.Picture,0,0,_
    picSource.ScaleWidth * 2,picSource.ScaleHeight * 2

由於 PaintPicture 方法特殊的語法,您甚至可以將沿著x軸、y軸翻轉來源圖片,只要將傳回這些參數的負數的值即可:

'Flip horizontally
picDest.PaintPicture picSource.Picture,_
    picSource.ScaleWidth,0,-picSource.ScaleWidth
 'Flip vertically.
picDest.PaintPicture picSource.Picture,0,_
    picSource.ScaleHeight,,-picSource.ScaleHeight
 'Flip the image on both axes.
picDest.PaintPicture picSource.Picture,picSource.ScaleWidth,_
    picSource.ScaleHeight,-picSource.ScaleWidth,-picSource.ScaleHeight.

您也可以將所有效果組合在一起,或只將部份的來源圖片放大、縮小或翻轉,並讓結果顯示在目的PictureBox控制項(或表單)上任何一個地方。我已經準備了一個示範程式(如圖3-12),此程式示範目前為止所說明的一切且包含許多有趣的溶解和排列效果完整的來源程式碼。您也可以在您的應用程式中使用這些常用的語法。

如果您覺得這些功能仍不夠,我們尚未討論 PaintPicture 方法最後一個參數。當像素位元由來源圖片轉移到目的圖片時,Opcode 參數可讓您指定在像素位元上必須執行哪一種Boolean作業。您可以傳給此參數的值與您指定給 DrawMode 屬性的值相同。預設值是13-vbCopyPen,此值只將來源像素複製到目的控制項中。經由設定其他的設定值,您便可以完成許多有趣的圖形效果,包括簡單的動畫。如需 DrawMode 屬性的詳細資料,請參閱 第2章 。


 

圖3-12 PaintPicture示範程式顯示數個圖片效果

Image控制項
 

Image控制項比PictureBox控制項簡單多了。此控制項不支援圖形方法或是 AutoRedraw 和 ClipControls 屬性,而且它無法當做容器,這也是此控制項最大的限制。儘管如此,您應該儘量使用Image控制項而不要使用PictureBox控制項,因為Image控制項可以較快的載入並佔用較少的記憶體和系統資源。記住Image控制項是無視窗物件,無視窗物件是由Visual Basic管理而不需要建立Windows物件(如需輕量型無視窗控制項的詳細資訊,請參閱 第2章 )。Image控制項可以載入點陣圖、JPEG和GIF圖片。

當您使用Image控制項時,您可以在設計階段或執行階段時使用 LoadPicture 函數將圖片載入控制項的 Picture 屬性。Image控制項並沒有顯示 AutoSize 屬性,因為依照預設控制項會重新調整大小以顯示所包含的圖片(PictureBox控制項需將 AutoSize 屬性設定為True)。另一方面Image控制項支援 Stretch 屬性,此屬性若為True,則會重新調整圖片的大小(必要時會將圖片變形)以符合控制項的大小。Stretch 屬性有時可以做為此控制項缺少 PaintPicture 方法的補救。如果您要接近或縮小圖片,您可以將圖片載入到Image控制項,再將控制項的 Stretch 屬性設定為True以改變它的高度和寬度。

'Load a bitmap.
Image1.Stretch = False
Image1.Picture = LoadPicture("c:\windows\setup.bmp")
 'Reduce it by a factor of two.
Image1.Stretch = True
Image1.Move 0, 0, Image1.Width / 2, Image1.Width / 2

Image控制項支援所有的滑鼠事件,因此許多Visual Basic開發者使用Image控制項來模擬圖形按鈕和工具列。現在Visual Basic本身就支援這些控制項,您最好只以原始的用途使用Image控制項。

SCROLLBAR控制項
 

HscrollBar和VscrollBar控制項完全相同,除了它們的方向不同。當您在表單上放置這種的控制項後,您只要設定一些屬性:Min 和 Max 代表有效範圍的值,SmallChange 是當您按一下捲軸箭頭時數值的變化量,LargeChange 是按一下捲軸區域時數值的變化量。這兩個屬性預設的初值是1,但您可能要將 LargeChange 變更為較高的值。例如,如果您有一個讓您瀏覽部份文字的捲軸i,SmallChange 應該為1(一次捲動一行),而 LargeChange 應該設定為視窗中可看見的行數。

最重要的執行階段屬性是 Value,它會傳回捲軸方塊的相關位置。依照預設,Min 值與控制項的最左方或最上方對應:

'Move the indicator near the top (or left) arrow.
VScroll1.Value = VScroll1.Min
 'Move the indicator near the bottom (or right) arrow.
VScroll1.Value = VScroll1.Max

對水平捲軸而言此設定永遠沒有問題,您有時候可能需要反轉垂直捲軸的行為讓零值靠近表單的底部。如果您要使用垂直捲軸做為滑動軸時,這種設定通常是需要的。若要如此,只要將 Min 和 Max 屬性內的值顛倒過來即可(換句話說,最好讓 Min 值大於 Max 值)。

捲軸控制項有兩個主要的事件:當您按一下捲軸方向箭或當您拖曳捲軸方塊時便會引發Change 事件;當您拖曳捲軸方塊時則引發 Scroll 事件。以是這兩個屬性的不同歷程:第一版的Visual Basic只支援 Change 事件,當開發者明白當使用者拖曳捲軸方塊時不可能有持續的回饋,Microsoft工程師新增新的事件而非擴充 Change 事件。因此,舊的應用程式會重新編譯,它們的行為也不會有未預期的變。無論如何,這表示您必須經常設定這兩個不同的事件:

'Show the current scroll bar's value. 
Private VScroll1_Change()
    Label1.Caption = VScroll1.Value
End Sub
Private VScroll1_Scroll()
    Label1.Caption = VScroll1.Value
End Sub

圖3-13中的範例使用三個VscrollBar控制項做為控制色彩不同RGB(紅、綠、藍)成份的調節器。這三個捲軸將它們的 Min 屬性設定為255,Max 屬性設定為0,SmallChange 屬性設定為1,LargeChange 屬性設定為16。此範例是非常有用的程式,因為您可以選擇一個色彩,將它的數值複製到剪貼簿,再將它以十進位數值、十六進位數值、或是RGB函數的方式貼入您的應用程式。


 

圖3-13 使用捲軸控制項建立色彩

Scroll控制項可以接收輸入駐點,事實上它們都支援 TabIndex 和 TabStop 屬性。如果您不要使用者在按下Tab鍵時不小心將輸入駐點移到捲軸控制項,您必須將控制項的 TabStop 屬性設定為False。當捲軸控制項有駐點時,您可以使用Left、Right、Up、Down、PgUp、PgDn、Home和End鍵來移動捲軸方塊。例如,您可以利用此行為來建立一個包含數值的唯讀TextBox控制項,此控制項的數值只能有伴隨的捲軸來編輯。此捲軸的外觀如同一個微調鈕,如圖3-14。若要如此設定此控制項,您可以依照以下的程式碼執行:

Private Sub Text1_GotFocus()
   'Pass the focus to the scroll bar.
    VScroll1.SetFocus
End Sub
Private Sub VScroll1_Change()
   'Scroll bar controls the text box value.
    Text1.Text = VScroll1.Value
End Sub


 

圖3-14 您不需要外部的ActiveX控制項便可以建立微調鈕

Scrollbar控制項甚至可以建立捲軸表單,如圖3-15。捲軸表單並非您可以提供給您的使用者最機械化的使用者介面:如果您的表單內有許多欄位,您應該考慮使用Tab控制項、子表單或是其他的自訂介面。有時您可能需要可捲動的表單,若是如此您需要另外設法解決,因為Visual Basic並不支援表單捲動。

幸好將一般的表單傳換成可捲動的表單並不需要花費太長的時間。您只需要兩個捲軸控制項,再加上一個PictureBox控制項-您使用此控制項做為表單上所有控制項的容器,和一個填入控制項-例如CommandButton-在表單顯示兩個捲軸時將此控制項放在表單的右下角。建立可捲動式表單的祕訣是您不要一一的移動所有的子控制項,而是將所有的控制項放置在PictureBox控制項中(此控制項在以下的程式碼名稱為picCanvas),在使用者操作捲軸時搬移此控制項。

Sub MoveCanvas()
    picCanvas.Move -HScroll1.Value,-VScroll1.Value
End Sub

換句話說,若要顯示表單靠近右邊界的部份,您要將PictureBox控制項的 Left 屬性設定為負數;若要表單靠近下邊界的部份,則將 Top 屬性設定為負值。它就是這麼的簡單。設定的方法是由捲軸內的 Change 和 Scroll 事件呼叫 MoveCanvas 程序。當然,寫入 Form_Resize 事件內的程式碼很重要,因為它會讓捲軸在表單改變大小時顯示或消失,將捲軸控制項的 Max 屬性設定一致的值也是很重要的:

'size of scrollbars in twips
Const SB_WIDTH = 300     'width of vertical scrollbars
Const SB_HEIGHT = 300    'height of horizontal scrollbars
Private Sub Form_Resize()
     'Resize the scroll bars along the form.
    HScroll1.Move 0,ScaleHeight - SB_HEIGHT,ScaleWidth - SB_WIDTH
    VScroll1.Move ScaleWidth - SB_WIDTH,0,SB_WIDTH,_
        ScaleHeight - SB_HEIGHT
    cmdFiller.Move ScaleWidth - SB_WIDTH,ScaleHeight - SB_HEIGHT,_
        SB_WIDTH,SB_HEIGHT
     'Put these controls on top.
    HScroll1.ZOrder 
    VScroll1.ZOrder
    cmdFiller.ZOrder
    picCanvas.BorderStyle = 0
     'A click on the arrow moves one pixel.
    HScroll1.SmallChange = ScaleX(1,vbPixels,vbTwips)
    VScroll1.SmallChange = ScaleY(1,vbPixels,vbTwips)
    ? A click on the scroll bar moves 16 pixels.
    HScroll1.LargeChange = HScroll1.SmallChange * 16
    VScroll1.LargeChange = VScroll1.SmallChange * 16
     'If the form is larger than the picCanvas picture box,
    ? we don't need to show the corresponding scroll bar.
    If ScaleWidth <picCanvas.Width + SB_WIDTH Then
        HScroll1.Visible = True
        HScroll1.Max = picCanvas.Width + SB_WIDTH - ScaleWidth
    Else
        HScroll1.Value = 0
        HScroll1.Visible = False
    End If
    If ScaleHeight < picCanvas.Height + SB_HEIGHT Then
        VScroll1.Visible = True
        VScroll1.Max = picCanvas.Height + SB_HEIGHT - ScaleHeight
    Else
        VScroll1.Value = 0
        VScroll1.Visible = False
    End If
     'Make the filler control visible only if necessary.
    cmdFiller.Visible = (HScroll1.Visible Or VScroll1.Visible)
    MoveCanvas
End Sub

在設計階段中使用可捲軸的表單並不方便。我建議您將表單最大化並將PictureBox控制項的大小儘量放大。當您完成表單介面後,將PictureBox控制項大小調整為能包含所有控制項的最小範圍,再將表單的 WindowState 屬性設定為 0-一般 


 

圖3-15 可捲動的表單

DRIVELISTBOX、DIRLISTBOX和FILELISTBOX控制項
 

簡單來說,DriveListBox控制項是類似組合方塊的控制項,它會自動顯示磁碟機代號和磁碟區標籤。DirListBox控制項是特殊的清單方塊,它會顯示目錄樹狀結構。FileListBox控制項是特殊功能的ListBox控制項,它會顯示指定目錄中所有的檔案,並根據檔案的名稱、副檔名和屬性選擇性的篩選檔案。

這些控制項通常在同一張表單上一起運作,當使用者選擇DriveListBox控制項內的磁碟時,DirListBox控制項會更新以顯示該磁碟的目錄樹狀結構。當使用者選擇DirListBox控制項內的路徑時,FileListBox控制項會顯示該目錄內的檔案清單。這些動作不會自動發生-您必須寫入程式碼才能完成。

在您將DriveListBox控制項和DirListBox控制項放置在表單上後,通常不需要設定它們的屬性。事件這些控制項並不會顯示任何特殊的屬性,至少在 屬性 視窗內不會。另一方面,FileListBox控制項會顯示一個您可以在設計階段中設定的屬性-Pattern 屬性。此屬性能指定在清單區域中會顯示哪一些檔案,它的預設值是 *.* (所有檔案),但您可以輸入任何您需要的設定,您也可以使用分號做為區隔輸入多個設定值。您可以在執行階段中設定此屬性,如下行程式碼:

File1.Pattern ="*.txt;*.doc;*".rtf"

在這些初步的設定後,接下來便要設定一連串的事件了。當使用者選擇DriveListBox控制項的磁碟後,便會引發 Chang e 事件並傳回 Drive 屬性內的磁碟代號(和磁碟區標籤)。您可以設定此事件並設定DirListBox控制項的 Path 屬性來指定被選擇磁碟的根目錄:

Private Sub Drive1_Change()
     'The Drive property also returns the volume label,so trim it.
Dir1.Path = Left$(Drive1.Drive,1) & ":\"
End Sub

當使用者連按兩下目錄名稱時,DirListBox控制項便會引發 Change 事件,您設定此事件來設定FileListBox控制項的 Path 屬性:

Private Sub Dir1_Change()
    File1.Path = Dir1.Path
End Sub

最後,當使用者按一下FileListBox控制項內的檔案時,Click 事件會被引發(如同一般的ListBox控制項),您可以查詢它的 Filename 屬性以得知哪一個檔案已經被選擇。注意您建立完成路徑的方式:

Filename = File1.Path 
If Right$(Filename,1) <> "\" Then Filename = Filename & "\"
Filename = Filename & File1.Filename

圖3-16的示範程式建立這些控制項以提供有功能的Image Preview公用程式。當控制項所有的表單被改變大小時,它也支援動態改變控制項的大小。

DirListBox和FileListBox控制項通常也支援它們的起源控制項-ListBox控制項-大部份的屬性,包括 ListCount 和 ListIndex 屬性和 Scroll 事件。FileListBox控制項支援多重選擇,因此您可以在 屬性 視窗內設定控制項的 MultiSelect 屬性,並在執行時間查詢 SelCount 和 Selected 屬性。


 

圖3-16 最小但完整的功能Image Preview公用程式也支援點陣圖的排列

FileListBox控制項也有數個自訂的 Boolean 屬性,包括有Normal、Archive、Hidden、ReadOnly、和System,這些屬性允許您決定擁有這些屬性的檔案是否要列出(依照設,控制項並不顯示隱藏和系統檔案)。此控制項也支援數個自訂的事件,PathChange和PatternChange,當與這些事件對應的屬性經由程式碼變更時便會引發這些事件。

DriveListBox、DirListBox和FileListBox控制項的問題是它們有點過時,大部份的商用應用程式也不再使用。此外,當在列示網路伺服器上的檔案,有時甚至是列出本機磁碟上的檔案時,這些控制項的作業會錯誤,尤其是一些名稱較長的檔案和路徑。有基於此,筆者不鼓勵您使用這些控制項並建議您使用Common Dialog控制項做為您的FileOpen 和FileSave對話方塊。但如果您需要詢問使用者目錄的名稱而非檔案的名稱時,則無法辦到-Windows包括這一種的系統對話方塊,名稱為BrowseForFolders對話方塊-因為Visual Basic仍沒有顯示此類對話方塊的方法(除了您執行一些進階的AP程式設計)。幸好,Visual Basic 6提供了新的控制項-ImageCombo控制項-讓您可以模擬DriveListBox控制項的外觀。它也提供您強大的程式庫-FileSystemObject程式庫-讓您完成免於使用這三個控制項,除非隱藏這些控制項以快速擷取檔案系統的資訊。如需關於FileSystemObject程式庫和ImageCombo控制項的詳細資訊,請分別參閱 第5章 和 第10章 ;Command對話方塊請參閱 第12章 。

其他控制項
 

我們仍要簡短討論 工具箱 中其他的控制項。

Timer控制項
 

Timer控制項在執行階段中是看不見的,它的功用是定時時脈傳送目前的應用程式。您可以在Timer控制項的Timer事件程序中撰寫程式碼以設定此時脈,並利用它在背景執行工作或是監視使用者的動作。此控制項只有兩種有意義的屬性:Interval 和 Enabled。Interval 屬性代表時脈之間的毫秒數(Timer事件),Enabled 屬性讓您啟用或停用事件。當您在表單上放置一個Timer控制項時,它的間隔是0,這表示沒有事件。因此,記住在 屬性 視窗或是在 Form_Load 事件程序將此屬性設定一個適當的數值:

Private Sub Form_Load()
    Timer1.Interval = 500     'Fire two Timer events per second.
End Sub

Timer控制項讓您只要寫幾行程式碼就能寫出有趣的程式。最典型(也是最濫用)的範例就是計時器。在此筆者加入分號,這只是為了增加一點強制性:

Private Sub Timer1_Timer()
    Dim strTime As String
    strTime = Time$
    If Mid$(lblClock.Caption,3,1) = ":"( Then
        Mid$(strTime,3,1)= "  "
        Mid$(strTime,6,1) = "  "
    End If
    lblClock.Caption = strTime
End Sub

注意

您必須小心不要在Timer事件程序中寫入太多的程式碼,因為此程式碼在每個時脈都會被執行,因此很容易降低應用程式的效能。還有一點很重要,千萬不要在Timer事件程序中執行DoEvents敘述,因為您可能會讓程序重新被執行,尤其如果 Interval 屬性設定為一個很小的數值且在程序中有很多程式碼時。


Timer控制項在定期更新狀態資訊時很有用。例如,您可能要在狀態列上顯示目前擁有駐點的控制項的簡短說明,您只將在表單上所有控制項的 GotFocus 事件中寫入一些程式碼就可以完成,但當您有數百個控制項時則需要很多程式碼(和時間)。在設計階段為每一個控制項將簡短的描述載入它的 Tag 屬性,再將Timer控制項放置在 Interval 性設定為500的表單上。此工作並不嚴格要求時間,因此您可以使用非常大的數值。最後在控制項的 Timer 事件中加上兩行程式碼:

Private Sub Timer1_Timer()
    On Error Resume Next
    lblStatusBar.Caption = ActiveControl.Tag
End Sub

Line控制項
 

Line控制項是裝飾性的控制項,它唯一的功能是讓您在設計階段中可以畫上直線,而非在執行階段中使用 Line 圖形方法顯示直線。此控制項有一些屬性,現在您應該感覺到很熟悉:BorderColor (線條的色彩)、BorderStyle(與表單的 DrawStyle 屬性相同)、BorderWidth(與表單的 DrawWidth 屬性相同)和 DrawMode。雖然Line控制項很方便,但記住在執行階段中使用 Line 方法對於效能而言是較佳的。

Shape控制項
 

Shape控制項是Line控制項的擴充。它可以顯示6種基本形狀:矩形、正方形、橢圓形、圓形、圓角矩形、和圓角正方形。它支援所有的Line控制項屬性和:BorderStyle(0-透明,1-不透明)、FillColor 和 FillStyle(與表單的相同名稱的屬性功能相同)。筆者在Line控制項中所提到的考量也適用在Shape控制項中。

OLE控制項
 

當OLE首次面世時,物件連結和內嵌(Object Linking and Embedding)的概念對於大多數的開發者而言簡直不可思議。在其他的Windows應用程式中能夠內嵌Microsoft Word文件或Microsoft Excel試算表(如圖3-17)令人感到興奮,Microsoft也立即推出OLE控制項-稱做OLE Container控制項-以幫助Visual Basic支援這個功能。

然而長期以來,OLE中的「內嵌」一詞已經失去了它的吸引力和重要性,今日程式設計師較在意且感到興趣的是Automation;它是OLE的子集,它能讓程式設計師由外部控制其他的Windows應用程式,並經由 OLE操作物件的階層架構。在此我不說明OLE控制項:它是一個十分複雜的物件,若要完整說明它的屬性、方法和事件會佔用很多的版面。


 

圖3-17 Visual Basic應用程式可以裝載Excel試算表-和它的功能表

功能表
 

功能表是內建的控制項,但功能表的行為和其他的控制項不同。例如,您無法將功能表項目由 工具箱 拖曳至表單上,而是在 功能表編輯器 視窗中設計功能表,如圖3-18。此視窗可以由工具列的 功能表編輯器 開啟,或是按Ctrl+E快速鍵;它也可以由 工具 功能表的 功能表編輯器 開啟,但您或許不會常使用到它。

基本上,每一個功能表都有一個 標題 屬性(可以使用&符號建立快速鍵)和 名稱 屬性。每一個項目都有3個Boolean屬性:啟用、顯示、核取式,您可以在設計階段和執行階段設定這些屬性。在設計階段中,您可以指定功能表項目的快速鍵,讓使用者不用每次在需要執行常用的命令時都要在功能表系統中一一的切換 (您真的喜歡在每次清除一些字或是將文字複製到剪貼簿時都要拉下[編輯]功能表嗎?)。在執行階段無法查詢指定的快速鍵,也很少修改。功能表項目支援一些的屬性,這一部份留在 第9章 再討論。

建立功能表是很簡單的,即使這個工作有點枯燥:輸入項目的 標題  名稱 ,設定其他的屬性(或是接受這些屬性的預設值),按下Enter以移到下一個項目。當您要建立子功能表時,按下向右的箭頭鈕(或是Alt+R快速鍵)。當您要回到上一層的功能表時-當執行應用程式時這些項目會顯示在功能表列-您可以按一下向左的箭頭鈕(或按Alt+L)。您可以按一下對應的按鈕或是Alt+U和Alt+B組合鍵,讓這些項目在階層架構中上下移動。

您最多可以建立5層的子功能表(包括功能表列有6層),這對於有耐心的使用者而言也是太多了。如果您發現您自己會用到超過三層的功能表,請考慮簡化您的設定並以群組的方法重新設計您的應用程式。

您可以將 標題 屬性設定為(-)符號以插入分隔線。即使是分隔線,它的 名稱 屬性也要設定一個獨一無二的值。如果您忘記在輸入功能表項目中的 名稱 屬性, 功能表編輯器 會在您要關閉視窗時提醒您。本書所使用的慣例是所有的功能表名稱開頭前三個字母都使用mnu。


 

圖3-18 功能表編輯器視窗

 功能表編輯器 最大的缺點便是它不允許您重新使用您已經寫入在其他應用程式中的功能表。在開發的過程中您可以開啟另一個Visual Basic IDE,將多個功能表項目複製剪貼簿上,再將這些功能表項目貼在應用程式上。但如此做只是針對控制項和一些程式碼,並無法針對功能表。在Visual Basic中最好的方法是使用如記事本這類的編輯器載入FRM檔,在檔案中找到您要的功能表部份,載入您正在開發的FRM檔(仍在記事本內),並貼上程式碼。這並非最簡單的方法,而且也有一點危險:如果您將功能表定義貼到錯誤的位置上時,您會讓FRM格式完全無法讀取。因此一定要記住在嘗試這項作業之前,請將此檔案備份。

有一個好消息,便是您只要按一下滑鼠就可以將完成的功能表新增至您的應用程式內。您所要做的便是選擇 增益集 功能表內的 增益功能管理員 ,選擇 VB 6範本管理員 ,並勾選 載入/載出 核取方塊。在您執行完之後,您會發現工具功能表上有3個新的命令:加入程式碼片段、加入功能表、加入控制項組。Visual Basic 6有一些功能表範本,如圖3-19,您可能有需要建立一個屬於您自己的範本。若要建立一個功能表範本,您只要建立一個有完整功能表及其相關程式碼的表單,再將表單儲存在\Templates\Menus目錄中(完整的目徑通常是c:\Program Files\Microsoft Visual Studio\VB98\Template,此路徑可以在 工具 功能表中 選項 對話盒內的 環境 標籤頁中找到。 範本管理員 在Visual Basic 5時已經有了,但它必須手動安裝,相對上較少程式設計師知道它的存在。


 

圖3-19 範本管理員

在執行階段在取功能表
 

功能表控制項只有一個事件,Click。如您所預期,當使用者按一下功能表時便會引發此事件:

Private Sub mnuFileExit_Click()
    Unload Me
End Sub

您可以在執行階段中經由它們的 核取式、啟用、顯示 屬性來操控功能表。例如,您可以讓一個功能表項目做為顯示或隱藏狀態列的開關:

Private Sub mnuViewStatus_Click()
     'First,add or remove the check sign.
    mnuViewStatus.Checked = Not mnuViewStatus.Checked
     'Then make the status bar visible or not.
    staStatusBar.Visible = mnuViewStatus.Checked
End Sub

當功能表項目本身的狀態是Checked(核取式),您通常會在另一個地方以程式碼來設定它的Enabled(啟用)和Visible(顯示)屬性。當您要讓使用者無法使用功能表上的命令時,您可以讓功能表項目不顯示或停用。您可以選擇兩種不同的策略來達成這個目的:您可以只要有發生會影響功能表命令的事情就設定功能表的屬,或者您可以在功能表下拉前設定功能表項目的狀態。以下分別使用兩個範例來解釋這兩策略。

如果您的應用程式載入一個唯讀檔時, 檔案 功能表的 儲存檔案 命令應該看起來是停用的。在此情形下,最明顯將功能表的Enabled屬性設定為False的位置便是將檔案載入的程序,程式碼如下:

Private Sub LoadDataFile(filename As String)
     'Load the file in the program.
     '... (code omitted)...
     'Enable or disable the menu enabled state according to the file's
     'read-only attribute (no need for an If...Else block).
    mnuFileSave.Enabled = (GetAttr(filename) And vbReadOnly)
End Sub

此方法是有道理的,因為功能表狀態並不會經常改變。相較之下,在典型[編輯]功能表上大部份的命令(複製、剪下、清除、復原等)狀態會根據在作用控制項中是否有任何一個文字被選擇。在本狀態中,只要條件一改變就變更功能表的狀態(例如使用者選擇或取消選擇作用控制項的文字)是浪費時間的,它也需要很多程式碼。因此最好是在顯示功能表之前,在父功能表的Click事件中設定這些功能表命令的狀態:

Private Sub mnuEdit_Click()
     'The user has clicked on the Edit menu,
     'but the menu hasn't dropped down yet.
    On Error Resume Next
     'Error handling is necessary because we don't know if 
     'the Active control actually supports these properties.
    mnuEditCopy.Enabled = (ActiveControl.SelText <> " ")
    mnuEditCut.Enabled = (ActiveControl.SelText <> " ")
    mnuEditClear.Enabled = (ActiveControl.SelText <> " ")
End Sub

快顯功能表
 

Visual Basic也支援快顯功能表,也就是在大部份的商用應用程式中只要您以右鍵按一下使用者介面上的物件時,就會即時顯示的功能表。在Visual Basic中,您可以呼叫表單的 PopupMenu 方法來顯示快顯功能表,通常是在物件的 MouseDown 事件程序中:

Private Sub List1_MouseDown(Button As Integer,Shift As Integer,_
    X As Single,Y As Single)
    If Button And vbRightButton Then
        ? User right-clicked the list box.
        PopupMenu mnuListPopup
    End If
End Sub

您傳給 PopupMenu 方法的參數是您在 功能表編輯器 所定義的功能表名稱。它可以是您使用一般功能表結構所切換的子功能表,或是只做為快顯功能表的子功能表。在後者,您應該將在 功能表編輯器 中將它建立為最上層的功能表,再將它的 Visible 屬性設定為False。如果您的程式包括許多快顯功能表,您應讓發現新增一個看不見的最上層項目,再新增所有的快顯功能表在此項目之下是很方便的(在此情況下,您不需要讓每一個項目都不顯示)。PopupMenu 方法完整的語法是很複雜的:

PopupMenu Menu,[Flags],[X],[Y],[DefaultMenu]

依照預設,快顯功能表顯示時會與滑鼠的游標靠左對齊。即使您以右鍵按一下的方式開啟功能表,您只能以滑鼠左鍵來選擇功能表上的命令。您可以使用 Flags 參數變更這些預設值。以下的常數可以控制對齊的方式:0-vbPopupMenuLeftAlign(預設)、4-vbPopupMenu-CenterAlign、和8-vbPopupMenuRightAlign。以下的常數可以決定在功能表作業期間哪一些按鈕可以作用:0-vbPopupMenuLeftButton (預設)和2-vbPopupMenuRightButton。舉例來說筆者一向使用後者,筆者發現既然當功能表顯示時它已經被按下了,則以右鍵選擇命令是很自然的:

PopupMenu mnuListPopup,vbPopupMenuRightButton

如果有指定X和y參數的話,這兩個參數會讓功能表在表單特定的位置上顯示,而不在滑鼠的座標上顯示。最後一個選擇性參數是快速功能表預設項目的功能表名稱,這些項目會以粗體顯示,此參數只有視覺上的效果。如果您要提供預設的功能表項目,您必須在 MouseDown 事件程序中寫入程式碼以設定右鍵的連按兩下功能。


小秘訣

您可以利用 PopupMenu 方法的x和y參數讓您的程式更能與Windows相容,並在使用者按下應用程式鍵(此鍵位在一般擴充鍵盤上的Windows鍵的右方)時在控制項上顯示快顯功能表。但記住Visual Basic並不定義此鍵的鍵盤程式碼常數。以下是處理的方式:

Private Sub List1_KeyDown(KeyCode As Integer,Shift As Integer)
    If KeyCode = 93 Then
         'The system pop-up menu key has been pressed.
         'Show a pop-up menu near the list box's center.
        PopupMenu mnuListPopup,,List1.Left + _
            List1.Width / 2,List1.Top + List1.Height / 2
    End If
End Sub

Visual Basic快速功能表的實行有一些缺點。所有Visual Basic的TextBox控制項在被右鍵按一下時都顯示一般 編輯 的快速功能表(如復原、複製、剪下等一般命令)。問題是如果您由TextBox控制項的 MouseDown 事件來引發 PopupMenut 方法,您自訂的功能表只會在標準的快速功能表之後顯示,很明顯這不是您想要的。唯一的解決方法便是使用一些非正式記載的技術,如下:

Private Sub Text1_MouseDown(Button As Integer,_
    Shift As Integer,X As Single,Y As Single)
    If Button And vbRightButton Then
        Text1.Enabled = False
        PopupMenu mnuMyPopup
        Text1.Enabled = True
    End If
End Sub

此技術首次出現在Visual Basic Programmer's Journal雜誌上的Tech Tips補充。VBPJ每年發行兩次這類的補充,分別在2月和8月;而且它們都與一些對任何程度的Visual Basic開發者都有幫助的技巧一起推出。您可以在 http://www.dex.com 網站上下載。

控制項陣列
 

目前為止,我們已經介紹不同的控制項,每一個控制項都有一個特定的名稱和特定的屬性及事件。除此之外Visual Basic也包含控制項陣列的功能,也就是多個控制共用相同的事件程序,即使陣列中每一個個別的元素的屬性都有不同的值。控制項陣列只能在設計階段中建立,而且在陣列中至少要有一個控制項。您可以使用下列三種方法中的一個建立控制項陣列:

  • 先建立控制項,再為 Index 屬性設定一個非負數的數值,您便建立了一個只有一個元素的控制項陣列。
     
  • 建立兩個相同層級的控制項,再為它們指定相同的Name屬性。Visual Basic會顯示一個對話方塊警告您已經有控制項擁有該名稱,並詢問您是否要建立一個控制項陣列。按一下 是 鈕。
     
  • 從表單中選擇一個控制項,按下Ctrl+C將此控制項複製到剪貼簿中,再按下Ctrl+V以貼上控制項,新的控制項與原有的控制項有相同的Name屬性。Visual Basic會顯示上一點所提到的警告。
     

    控制項陣列是Visual Basic環境中最有趣的功能之一,它們讓您的程式增加了許多彈性:

  • 屬於相同控制項陣列的控制項共用相同的事件程序,這可以大幅減少您要撰寫以回應使用者動作的程式碼。
     
  • 您可以在執行階段中動態的將新的元素加入控制項陣列中。換句話說,您可以很有效率的建立一個在設計階段中沒有的新控制項。
     
  • 控制項陣列的元素比一般的控制項消耗較少的資源,並可產生較小的可執行檔。此外,Visual Basic表單最多可以裝載256不同的控制項名稱,但控制項陣列只佔用一個名額。換句話說,控制項陣列讓您能有效的超越這個限制。
     

使用控制項陣列做為在執行階段中動態建立新控制項的方法的重要性在Visual Basic 6有點減少了,因為它已經有更新更強大的力能。有關動態控制項建立請參閱 第9章 。

您不要因為陣列這一個名詞而將控制項陣列與VBA陣列混淆了。控制項矩陣只是一維陣列,您不需要設定它的維度:您新增的每一個控制項會自動擴充陣列。Index屬性能識別每一個控制項在它所屬性控制項陣列中的位置,但控制項陣列可能在索引順序中會有漏洞。Index 屬性可能的最低值是0。您參照控制項陣列中的控制項如同您參照一般陣列項目一般:

Text1(0).Text = " "

共用事件程序
 

與控制項陣列內項目相關的事件程序是很容易辨識的,因為它們都有額外的 Index 參數,此參數會設置在所有其它參數之前。此額外的參數接受在事件中產生的元素索引,如下:

Private Sub Text1_KeyPress(Index As Integer,KeyAscii As Integer)
    MsgBox "A key has been pressed on Text1(" & Index & ") control"
End Sub

多個控制項可以共用相同的事件程序,這個事實是建立控制項陣列的好理由。例如,假設您要讓每一個TextBox控制項在接受輸入駐點時背景色彩會變成黃色,當使用者按一下其他欄位時再還原為原始的背景色彩,做法如下:

Private Sub Text1_GotFocus(Index As Integer)
    Text1(Index).BackColor = vbYellow
End Sub
Private Sub Text1_LostFocus(Index As Integer)
    Text1(Index).BackColor = vbWhite
End Sub

控制項陣列對於將OptionButton控制項群組在一起特別的有用,因為您可以記住群組中哪一個元素已藉由在它們共用的 Click 事件中新增的程式碼而被啟用。當程式需要判斷哪一個按鈕是作用中的按鈕時,控制項陣列可以減少許多程式碼:

'A module-level variable
Dim optFrequencyIndex As Integer
Private Sub optFrequency_Click(Index As Integer)
     'Remember the last button selected.
    optFrequencyIndex = Index
End Sub

在執行階段建立控制項
 

當您在設計階段中建立控制項陣列後,即使只有一個項目,它也可以使用 Load 命令直接的在執行階段中建立新的項目:

'Suppose you created Text(0) at design time.
Load Text1(1)
 'Move the new control where you need it,and resize it.
Text1(1).Move 1200,2000,800,350
 'Set other properties as required.
Text1(1).MaxLength = 10
...
 'Finally make it visible.
Text1(1).Visible = True

Load 命令所建立的新控制項,與設計階段中陣列內的第一個項目-在上一個範列中是Text1(0)-有完全相同的屬性,包括在表單上的位置。此原則唯一的例外是以此方式所建立的控制項的 Visible 屬性永遠是False,因為Visual Basic預期您在讓新的控制項可以顯示之前會將新的控制項搬移到不同的位置上。當您動態新增了一個控制項後,它便屬於控制項陣列,而且與在設計階段中所建立的控制項有相同的行為。

您可以使用 Unload 將控制項由控制項陣列中移除,程式碼如下:

Unload Text1(1)

您只能將在執行階段中動態新增的控制項解除載入,如果您在設計階段中所建立的陣列項目上使用 Unload 方法,則會發生錯誤。如果您將項目解除載入,再將它們使用相同的索引重新載入,實際上您是建立一個全新的項目;它們會繼承陣列中第一個項目的屬性、大小和位置。

重複控制項陣列的項目
 

控制項陣列可以減少許多程式碼,因為您可以在矩陣內的每一個控制項中執行相同的敘述,而不用在每一個控制項中重複程式碼f。例如,您可以使用以下的程式碼清除TextBox控制項陣列中所有項目的內容:

For i = txtFields.LBound To txtFields.UBound
    txtFields(i).Text = " "
Next

在此您使用[控制項陣列物件]中的 Lbound 和 Ubound 方法,控制項陣列物件是Visual Basic用來聚集陣列中所有的控制項的中間物件。通常您不應該使用此方法在陣列中所有的項目上重複,因為如果陣列在Index順序中有漏洞時,則會產生錯誤。最好的方法是使用For Each敘述將控制項陣列內所有的項目執行迴圈:

Dim txt As TextBox
For Each txt In txtFields
    txt.Text = " "
Next

控制項陣列物件中第三個方法便是 Count,它會傳回陣列中所擁有的項目數量。它在許多情況下十分的有用 (例如要移除所有在執行階段中動態新增的控制項):

? This code assumes that txtField(0) is the only control that was
? created at design time (you can't unload it at run time).
Do While txtFields.Count > 1
    Unload txtFields(txtFields.UBound)
Loop

功能表項目陣列
 

控制項陣列對於功能表尤其有用,因為陣列提供一個增加功能表 Click 事件的方法,也允許您在執行階段中建立新的功能表。功能表控制項陣列在概念上與一般的控制項陣列很類似,除了您在 功能表編輯器 中將 Index 屬性設定為數字(非負數)的值,而非在 屬性 視窗中。

但仍有以下的限制:功能表控制項陣列中所有的項目必須是相連的,也必須屬於同一個功能表層級,Index 屬性必須是遞增的順序(即使是順序中的漏洞也是允許的)。這些需求會強烈的阻礙您在執行階段建立新的功能表項目的能力。事實上,您可以在您的功能表階層架構中已經定義好的位置上建立新的功能表目-也就是為功能表項目設定一個非零的 Index 值-但您無法建立新的子功能表或是新的最上層功能表。

現在您了解Visual Basic表單和控制項的作業方式,您已經具備了解Visual Basic for Applications (VBA)語這的條件。下一章中有許多資料類型您可以在您的程式中使用。在 第5章 中,筆者會介紹許多VBA函數和命令。