12. 其他的ActiveX控制項

Microsoft VB應用程式可以使用任何的ActiveX控制項,不管是產品本身提供或是向協入廠商購買。當然,您也可以像第十七章所說的,製造出自己的ActiveX控制項。在這一章節,筆者將介紹在VB套裝軟體中一些有趣的控制項。而這些控制項也包含在VB5版本之中。

格式編輯控制項MASKEDBOX控制項
 

MaskEdBox控制項類似TextBox控制項,但又多了一些有用的功能,必要時,可以建立完善安全的資料輸入程序。這個控制項是內嵌在MSMask32.ocx中,所以當您的應用程式中有使用到一或多個MaskEdBox控制項實體時,您必須連這個檔案一起散佈。

設定執行時期的屬性
 

MaskEdBox控制項所有的自訂屬性都可在自訂屬性頁對話方塊中的一般頁籤設定,如圖12-1。MaxLength屬性代表這個控制項所能接受的最多字元數目。假使AutoTab屬性為True,則當使用者輸入指定的字元數後,駐點(Focus)將移到定位順序中的下一控制項。PromptChar屬性設定提示字元,即是使用者輸入字元位置的符號。(預設字元值是底線字元。)

AllowPrompt布林型態函數決定提示字元是否為正確的輸入字元。(預設值為False。) PromptInclude屬性用來決定Text屬性值中是否包含提示字元。


 

圖12-1在設定時期的MaskEdBox控制項

Mask是MaskEdBox控制項的關鍵屬性其可以一字串資料來決定控制項?容在每一個位置上有哪些字元是被允許的。這個字串可包含一些特殊的字元以用來代表輸入的字元是否需為數字、字元、小數點、千分隔符號或是其他種類的字元。 (表12-1是所有特殊字元的列表。)例如下面的陳述式讓MaskEdBox控制項只接受電話號碼:

MaskEdBox1.Mask="(###)###-####"

您可以用適當的分隔符號來指定日期及時間格式,如:

MaskEdBox1.Mask = " ##/##/##"       ' A date value in mm/dd/yy format
MaskEdBox1.Mask = " ##:##"         ' A time value in hh:mm format

在這個情況下,MaskEdBox控制項只執行對個別字元的驗證,而您必須自已在Validate事件程序中檢查是否為一有效的日期。因為這個原因,您通常比較會使用DateTimePicker控制項來作日期及時間的輸入,因為這個控制項會自動地作複雜的驗證。若Mask屬性被設為空字串,則MaskEdBox控制項的行為就像一般的TextBox控制項一樣。

字元 描述
# 數字 (0至9,必須輸入)
9 數字 (0至9,可不輸入)
. 小數點分隔符號
, 千分位符號
: 時間分隔符號
/ 日期分隔符號
& 任何字元,但不含非列印字元,如Tab字元。
C 與&相同,是用來與Access相容。
A 字母或數字 (必須輸入)。
a 字母或數字 (可不輸入)。
? 字母 (A至Z,a至z)。
> 使所有字元被轉換成大寫。
< 使所有字元被轉換成小寫。
\ 跳脫符號:使接下來的字元以文字字元顯示 (例如,\A顯示出來只有A而已)。
其他字元 在遮罩中當作一個字元來顯示。
表12-1 MaskEdBox控制項Mask屬性中的特殊字元。實際上可接受的小數點、千分隔符號、日期及時間會依據您系統的地區設定不同而不一樣。

Format屬性決定當駐點(focus)離開MaskEdBox控制項時所顯示的外觀。例如,當使用者離開這個控制項時,您需要讓日期欄位以完整日期格式來顯示。您可以在設計時期或是執行時期給予Format屬性適當的字串來排列日期格式:

MaskEdBox1.Format="mmmm dd, yyyy"

在Format屬性中您可以傳入在VBA內Format函式的任何值,除了像scientific或是long date的命名格式。您也傳入四個子字串參數分別代表正數、負數、零、或是Null值的格式字串,參數之間用分號隔開,如:

' Show two decimal digits and the thousand separator, enclose
' negative numbers within parentheses, and show "Zero" when "0"
' has been entered.

MaskEdBox1.Format = "#,##0.00;(#,##0.00);Zero"

第四個格式子字串,會在當控制項與資料庫欄位連結,而資料為Null時使用到。

另一個您可在設計時期設定的自訂屬性為ClipMode,這會決定使用者執行 剪下  複製 指令到Clipboard時,會產生什麼動作。假使設為0-mskIncludeLiterals,則執行剪下或複製指令時包含文字字元。若設為1-mskExcludeLiterals則會在 剪下  複製 指令時排除文字字元。如果 Mask屬性被設定為空字串 (""),則ClipMode屬性沒有任何作用。

執行時期作業
 

基本上,MaskEdBox控制項是TextBox控制項再加上一些強大的功能。所以也具有很多與TextBox相同的屬性,方法及事件。然而,仍有些許的不同是您必須考慮到的。

使用Text屬性
 

MaskEdBox控制項提供Text屬性以及與Text屬性相關的SelStart,SelLength及SelText屬性。Text屬性會傳回MaskEdBox現在的內容,包括所有文字字元、分隔字元和底線,即使它們是輸入遮罩的一部份。要排除上述多餘的字元,您可以使用ClipText唯讀屬性回傳值:

' Work with a date control.
MaskEdBox1.Mask = "##/##/####"
' Assign a date using the Text property.
MaskEdBox1.Text = "12/31/1998"
' Read it back with the ClipText property.
Print MaskEdBox1.ClipText          ' Displays "1231998"

請不要忘記,當您試圖為Text屬性指定新的值,也是同樣受到在MaskEdBox輸入字串所會碰到的限制,也就是當您輸入一個無效的字串時,會產生錯誤。

很幸運地,要取得MaskEdBox控制項的內容,您不需要自己來排除分隔字元。當ClipMode屬性為True時,從SelText屬性傳回的字串就不包含任何的文字字元及分隔字元。甚至更有趣地,當您為這個屬性指定一個值,這個效果就好比從Clipboard貼上資料或是在MaskEdBox上直接打字一樣。這代表您不需要在指定值的陳述式中包含任何的文字字元及分隔字元:

' Read the contents of the control without any separator.
MaskEdBox1.ClipMode = mskExcludeLiterals
MaskEdBox1.SelStart = 0: MaskEdBox1.SelLength = 9999
MsgBox "The control's value is " & MaskEdBox1.SelText
' Assign a new date value. (Don't worry about date separators.)
MaskEdBox1.SelText = "12311998"

另一個存取MaskEdBox控制項內容的方法是利用FormattedText屬性,控制項失去駐點(focus)時,FormattedText屬性會傳回控制項中所顯示的字串。假使Mask屬性是空字串,這個屬性與Text屬性一樣,除了FormattedText屬性是唯讀外。請注意,如果將HideSelection屬性設定為False,那麼控制項在失去駐點時不會顯示已設定格式的文字,但您仍然可以透過此屬性存取設定格式的文字。

驗證使用者輸入
 

MaskEdBox在使用者按下無效的按鍵或是試圖貼上不合法的字串時,會產生ValidationError 事件。這個事件接收兩個參數:InvalidText代表在MaskEdBox 的Text屬性中所輸入的不合法字串,而StartPosition是第一個無效字元出現的位置。

Private Sub MaskEdBox1_ValidationError(InvalidText As String, _
    StartPosition As Integer)
    ' StartPosition is zero-based.
    LblStatus.Caption = "'" & Mid$(InvalidText, StartPosition + 1, 1) _
        &  "' is an invalid character"
End Sub

上面的程式片斷有一個缺點,當無效字元出現在字串的最後面時,就無法正常運作。在這種情況,Mid$ 函數將會傳回一個空字串,但沒有辦法取得無效字元,因為這個原因,您可能會較喜歡顯示一個"包含無效字元"的一般性錯誤訊息。

ValidationError事件有一個缺點,它似乎無法顯示訊息方塊。假使您試圖顯示一個訊息方塊,將會陷入無窮的迴圈,且重複地呼叫ValidationError事件,除非您按下Ctrl+Break鍵。(假使是執行編譯過的應用程式,只有被強迫按下Ctrl+Alt+Del。)

從VB6開始,MaskEdBox控制項提供標準的Validate事件,使得資料驗證工作變得更簡單。

COMMONDIALOG控制項
 

CommonDialog控制項提供簡單及方便的方法來呼叫Color、Font、Printer、FileOpen及FileSave的Windows標準對話方塊,且允許您透過執行Windows說明引擎,來顯示「說明」。這個控制項只有屬性及方法,沒有事件。通常您不會在設計時期設定它的屬性,因為這些屬性我們比較會在執行時期才來作設定,特別當您要使用相同的控制項來顯示不同的對話方塊時。

在執行時期,這個控制項是隱形的,因此它並不提供類似Left、Visible或TabIndex的屬性。這個控制項是內嵌在ComDlg32.ocx檔案中,同樣地,任何用到它的VB應用程式都必須包含這個檔案。

雖然缺少了可見的介面及缺少了事件,並不代表這個控制項很容易使用。反而,使用CommonDialog控制項變得是一種複雜的工作,因為CommonDialog提供很多選項,而其中的一些並不是使用起來那麼直覺化。甚至有一些屬性代表的意義隨著您顯示對話方塊種類不同而有所不同。舉例來說,Flags屬性是一個bit-field的型態,而每一個bit所代表的意義隨不同類的對話方塊而不同。

有一些屬性,不論您顯示哪一種對話方塊,所代表的意義都相同,其中之一就是CancelError屬性。假使這個屬性被設定為True,當使用者是利用Cancel按鍵來關閉對話方塊時,會在呼叫對話方塊的程式中產生錯誤代碼32755的錯誤,(等於cdllCancel常數)。CommonDialog控制項包含內建的常數,來對應所有會在執行時期產生的錯誤。

所有種類的對話方塊也共用少許關於支援「說明」的屬性。您可以在對話方塊內顯示 說明 的按鈕,並且告訴CommonDialog控制項當使用者按下「說明」>按鈕時,要顯示哪個說明檔案中的哪一個說明主題。HelpFile是說明檔案的完整檔名,HelpContext是所要求的「說明主題」的內容代碼而HelpCommand適當按鈕被按下時, 說明 的線上輔助型態(通常是1-cdlHelpContext)。別忘了要顯示 說明 按鈕,要先設定Flag屬性中的位元。這個位元的位置會隨特定的對話方塊而不同,如:

' Show a Help button. 
CommonDialog1.HelpFile = "F:\vbprogs\DlgMaste\Tdm.hlp"
CommonDialog1.HelpContext = 12
CommonDialog1.HelpCommand = cdlHelpContext
' The value for the Flags property depends on the dialog.
If ShowColorDialog Then
    CommonDialog1.Flags = cdlCCHelpButton
    CommonDialog1.ShowColor
ElseIf ShowFontDialog Then
    CommonDialog1.Flags = cdlCFHelpButton
    CommonDialog1.ShowFont
Else
    ' And so on
End If

有關更多「說明」屬性的資訊,請參考下一節的 〈「說明」視窗〉 。

CommonDialog控制項提供六個方法:ShowColor、ShowFont、ShowPrinter、ShowOpen、ShowSave及ShowHelp。每一個方法都顯示不同的對話方塊,筆者將在接下來的幾節來講解。在隨書光碟中,可以找到完整的示範程式 (圖12-2是其中一個畫面),這個程式會展示所有將在下面幾節說明的對話方塊。

「色彩」對話方塊
 

「色彩」標準對話方塊可以讓使用者選擇顏色。它也可以讓使用者自訂顏色,您可以將Flag屬性設為4-cdlCCPreventFullOpen來關閉這項功能。或者,您可以設定2-cdlCCFullOpen位元,來讓對話方塊在顯示時一併顯示定義自訂顏色部份 (圖12-2在對話方塊右半邊的區域)。將Color屬性指定一個RGB值,並且設定1-cdlCCRGBInit旗標位元,您可預設對話方塊顏色,如:

' Let the user change the ForeColor of the Text1 control.
With CommonDialog1
    ' Prevent display of the custom color section
    ' of the dialog.
    .Flags = cdlCCPreventFullOpen Or cdlCCRGBInit 
    .Color = Text1.ForeColor
        .CancelError = False
        .ShowColor
        Text1.ForeColor = .Color
End With

當您提供預設的顏色,就不需要設定CancelError屬性為True;假使使用者按下Cancel按鍵,Color屬性的值不會改變。


 

圖12-2這是一個已開啟自訂顏色部分的「色彩」對話方塊,如同程式示範

「字型」對話方塊
 

「字型」對話方塊讓使用者選擇字型的名稱及屬性。您可以預設在對話方塊中的值,也可以決定有哪些屬性可以被更改。當然,您也必須將這些新選擇的字型屬性運用在您的控制項及物件中。一個所有選項都可使用的「字型」對話方塊如圖12-3。


 

圖12-3 「字型」對話方塊

Font屬性可透過一組屬性來設定(而且當使用者關閉對話方塊時用來取得這些值):FontName、FontSize、FontBold、FontItaltic、FontUnderLine、FontstrikeThru及Color,從名稱就可以解釋它們所代表的意義。

當使用「字型」對話方塊時,Flag所有可接受的選項如表12-2。您可以用這些旗標來決定有哪些字型可在對話方塊中顯示,並限制使用者的選擇。有一個位元您通常會用到:cdlCFForceFontExist。除此之外,在表12-2中的前四個值必須選擇一個。否則,對話方塊控制項將產生一個錯誤代號24574 "No fonts exist"的錯誤。

常數 描述
cdlCFScreenFonts 使對話方塊只列出系統支援的螢幕畫面字型。
cdlCFPrinterFonts 使對話方塊只列出印表機所支援的字型。
cdlCFBoth 使對話方塊列出所有可用的印表機和螢幕字型。(這是cdlCFScreenFonts與cdlCFPrinterFonts的和)
cdlCFWYSIWYG 指定對話方塊只允許選擇在印表機和螢幕上均可用的字型。
cdlCFANSIOnly 要求對話方塊只允許選擇Windows字元集中的字型。
cdlCFFixedPitchOnly 讓對話方塊只能選擇固定間距的字型。
cdlCFNoVectorFonts 讓對話方塊不允許選擇向量字型。
cdlCFScalableOnly 指定對話方塊只允許選擇可縮放的字型。
cdlCFTTOnly 讓對話方塊只允許選擇TrueType字型。
cdlCFNoSimulations 對話方塊不允許圖形裝置介面 (GDI)字型模擬。
cdlCFLimitSize 指定對話方塊只能在由Min和Max屬性規定的範圍內選擇字型大小。
cdlCFForceFontExist 如果使用者想選擇不存在的字型或樣式,則顯示錯誤訊息方塊。
cdlCFEffects 讓對話方塊允許刪除線、底線以及顏色效果。
cdlCFNoFaceSel 表示沒有選擇字型名稱。
cdlCFNoSizeSel1 沒有選擇字型大小。
cdlCFNoStyleSel 沒有選擇樣式。
cdlCFHelpButton 使對話方塊顯示「說明」按鈕。
表12-2 「字型」對話方塊中Flags屬性的值。

下面的程式讓使用者更改在TextBox控制項中的字型屬性。它限制使用者只能選擇螢幕畫面字型,而且限制字型大小在8到80點間:

With CommonDialog1
    .Flags = cdlCFScreenFonts Or cdlCFForceFontExist Or cdlCFEffects _
        Or cdlCFLimitSize
    .Min = 8
    .Max = 80
    .FontName = Text1.FontName
    .FontSize = Text1.FontSize
    .FontBold = Text1.FontBold
    .FontItalic = Text1.FontItalic
    .FontUnderline = Text1.FontUnderline
    .FontStrikethru = Text1.FontStrikethru
    .CancelError = False
    .ShowFont
    Text1.FontName = .FontName
    Text1.FontBold = .FontBold
    Text1.FontItalic = .FontItalic
    Text1.FontSize = .FontSize
    Text1.FontUnderline = .FontUnderline
    Text1.FontStrikethru = .FontStrikethru
End With

在這個特殊案例中,您不用去設定CancelError為True,因為當使用者按下Cancel按鈕時,這個控制項不會改變任何FontXXXX的屬性,而當FontXXXX屬性被指定回控制項時,不會有任何不可預期的後果發生。

當您不想預設一個定義明確的值給欄位,將會有更複雜的問題。假設這種情況: 您正撰寫一個文書處理之類的應用程式,而希望使用者透過顯示的「字型」對話方塊,來對所選取的文字部分設定字型名稱、大小及屬性。若所選取的文字區域中,所有的文字屬性皆相同,您可以(應該)在對話方塊中預設相對應的屬性值。另一方面,假使所選擇的文字包含不同的字型、大小或是屬性,您應該將這些欄位設為空白,而這個動作可以藉由設定Flags屬性中的cdlCFNoFaceSel,cdlCFNoStyleSel及cdlCFNoStyleSel位元來達成。下面的程式讓使用者更改RichTextBox控制項的屬性。 (筆者將在下一章中詳細探討這個控制項。)

On Error Resume Next
With CommonDialog1
    .Flags = cdlCFBoth Or cdlCFForceFontExist Or cdlCFEffects
    If IsNull(RichTextBox1.SelFontName) Then
        .Flags = .Flags Or cdlCFNoFaceSel
    Else
        .FontName = RichTextBox1.SelFontName
    End If
    If IsNull(RichTextBox1.SelFontSize) Then
        .Flags = .Flags Or cdlCFNoSizeSel
    Else
        .FontSize = RichTextBox1.SelFontSize
    End If
    If IsNull(RichTextBox1.SelBold) Or IsNull(RichTextBox1.SelItalic) Then
        .Flags = .Flags Or cdlCFNoStyleSel
    Else
        .FontBold = RichTextBox1.SelBold
        .FontItalic = RichTextBox1.SelItalic
    End If
    .CancelError = True
    .ShowFont
    If Err = 0 Then
        RichTextBox1.SelFontName = .FontName
        RichTextBox1.SelBold = .FontBold
        RichTextBox1.SelItalic = .FontItalic
        If (.Flags And cdlCFNoSizeSel) = 0 Then
            RichTextBox1.SelFontSize = .FontSize
        End If
        RichTextBox1.SelUnderline = .FontUnderline
        RichTextBox1.SelStrikeThru = .FontStrikethru
    End If
End With

「列印」對話方塊
 

CommonDialog控制項中可以顯示兩種不同的對話方塊:Print Setup對話方塊允許使用者選擇印表機的屬性,而標準的Print對話方塊提供很多關於列印工作的選項給使用者,像是列印文件的範圍(全部、頁數或所選擇範圍),列印份數等。請看圖12-4及圖12-5的範例。


 

圖12-4 「列印」對話方塊


 

圖12-5 「列印設定」對話方塊

您可以設定Flags的cdlPDPrintSetup位元來決定要顯示哪一個對話方塊。表12-3是Flags可設定的所有屬性。

常數 描述
cdlPDPrintSetup 使系統顯示「列印設定」對話方塊而不是「列印」對話方塊。
cdlPDNoWarning 避免沒有預設印表機時顯示警告訊息。
cdlPDHidePrintToFile 隱藏「列印到檔案」核取方塊。
cdlPDDisablePrintToFile 使「列印到檔案」核取方塊無效。
cdlPDNoPageNums 使「頁數」選項按鈕和相關的編輯控制項無效。
cdlPDNoSelection 使「選擇的範圍」選項按鈕無效。
cdlPDPrintToFile 傳回或設定「列印至檔案」核取方塊的狀態。
cdlPDAllPages 傳回或設定「全部」選項按鈕的狀態。
cdlPDPageNums 傳回或設定「頁數」選項按鈕的狀態。
cdlPDSelection 傳回或設定「選擇的範圍」選項按鈕的狀態。
cdlPDCollate 傳回或設定「自動分頁」核取方塊的狀態。
cdlPDReturnDC 為該對話方塊中選擇的印表機傳回一個裝置內容。
cdlPDReturnIC 為該對話方塊中選擇的列印至印表機傳回一個資訊內容。
cdlPDReturnDefault 傳回預設印表機名稱。
CdlPDUseDevModeCopies 設定支援多份數列印。
cdlPDHelpButton 要求對話方塊顯示「說明」按鈕。
表12-3 「列印」對話方塊Flags屬性的值,當您使用「列印設定」對話方塊(Flags=cdlPDPrintSetup)時,大多數的屬性是沒有意義的。

當顯示「列印」對話方塊時,Min及Max屬性代表允許列印範圍的最大和最小值,而FromPage及ToPage屬性則設定「列印」對話方塊中「從」和「到」的文字方塊。若設定了cdlPDPageNums位元,通常會在進入時設定後兩者的值,而離開時再去讀取這兩者的值。Copies傳回使用者需要列印的份數。

PrinterDefalut屬性用來決定使用者的選擇是否要用來改變系統預設的印表機設定。筆者建議設定這個位元,因為它大大的簡化了列印操作程序。假使不設定這個位元,您要取得所選擇印表機的資訊,只有透過標準對話方塊的hDC屬性,這意味著您必須藉由呼叫API來執行列印的工作(這不並容易)。

當您顯示 列印設定 對話方塊時,Orientation屬性傳回或設定在列印工作中選擇的列印文件方向。(可設為1-cdlPortrait直向 或是2-cdlLandscape橫向。)然而,在Window NT下,對Orientation或是Copies設定都不會正確。

假使您顯示一個一般的 列印 對話方塊,您必須決定是否要使用 全部 ,或 選擇的範圍 。例如,若使用者要列印TextBox控制項的內容,而 選擇的範圍 選項按鈕應該只有在使用者實際上有選取文字時才有效:

On Error Resume Next
With CommonDialog1
    ' Prepare to print using the Printer object.
    .PrinterDefault = True
    ' Disable printing to file and individual page printing.
    .Flags = cdlPDDisablePrintToFile Or cdlPDNoPageNums
    If Text1.SelLength = 0 Then
         ' Hide Selection button if there is no selected text.
        .Flags = .Flags Or cdlPDNoSelection
    Else 
        ' Else enable the Selection button and make it the default
        ' choice.
        .Flags = .Flags Or cdlPDSelection
    End If
    ' We need to know whether the user decided to print.
    .CancelError = True
    .ShowPrinter
    If Err = 0 Then
        If .Flags And cdlPDSelection Then
            Printer.Print Text1.SelText
        Else
            Printer.Print Text1.Text
        End If
    End If
End With

「開啟舊檔」或「另存新檔」對話方塊
 

 開啟舊檔  另存新檔 對話方塊非常相似,事實上,在VB說明文件中兩者被擺在一起解釋。雖然這是合理的做法,但筆者發現在兩者間共同隱藏了很多巧妙的不同處。因此筆者決定先描述兩者通用屬性,而後再分別針對兩個對話方塊來作介紹。

通用的屬性
 

有數種方式可以讓我們自訂出令人深刻印象的 開啟舊檔  另存新檔 對話方塊外觀及特性。例如,DialogTitle屬性決定對話方塊標題列所顯示的字串。而InitDir設定當對話方塊顯示時的初始檔案目錄。當呼叫對話方塊,FileName屬性傳回提示檔案的檔名。當對話方塊關閉時,FileName傳回使用者所選取的檔案名稱。DefaultExt屬性設定預設檔案的副檔名,當使用者沒有鍵入副檔名時,對話方塊會在FileName屬性中自動給該檔案所指定的副檔名。或者,您可以使用FileTitle屬性來設定或傳回基本的檔名(不包含副檔名的檔名)。

您可以定義在瀏覽目錄時,對話方塊內 檔案 清單方塊中的檔案類型。將一個包含使用篩管 ( | )符號將filter與description的值隔開的字串來指定給Filter屬性。例如,當使用圖形檔時,您可以用下列的方法定義三種檔案類型:

' You can specify multiple filters by using the semicolon as a delimiter.
CommonDialog1.Filter = " All Files|*.*|Bitmaps|*.bmp|Metafiles|*.wmf;*.emf"

您可以使用FilterIndex屬性以決定哪一個作為預設顯示的檔案類型。

' Display the Bitmaps filter. (Filters are one-based.)
CommonDialog1.FilterIndex = 2

使用 開啟舊檔  另存新檔對 話方塊真正困難的地方在於它們提供很多的旗標(flags),而大多數的旗標在VB手冊中並沒有足夠的說明文件。有的情況下,筆者必須依靠Windows SDK文件來瞭解某些旗標所真正代表的意義。表12-4列出 開啟舊檔  另存新檔 對話方塊所支援的所有旗標。

常數 描述
cdlOFNReadOnly 讓Read Only核取方塊在建立對話方塊時,便初始化為核取。(只適用 開啟舊檔 )
cdlOFNOverwritePrompt 產生一個訊息方塊,確認是否要覆蓋掉該檔案。(只適用 另存新檔 )
cdlOFNHideReadOnly 隱藏唯讀核取方塊。 另存新檔 對話方塊應該要設定。
cdlOFNNoChangeDir 不要改變現在目錄(預設值為將對話方塊開啟時的目錄設成目前目錄)
cdlOFNNoValidate 讓通用對話方塊可以在傳回的檔名中含有非法字元。(不建議使用)
cdlOFNAllowMultiselect 允許在 檔案名稱 清單方塊作多重選擇。(只適用 開啟舊檔 )
CdlOFNExtensionDifferent 判斷傳回的檔案副檔名與DefaultExt屬性指定的副檔名是否不一致。(在關閉時檢查)
cdlOFNPathMustExist 要求使用者只能輸入正確路徑。(強烈建議使用)
cdlOFNFileMustExist 拒絕不存在的檔名(只適用 開啟舊檔 )
cdlOFNCreatePrompt 要求對話方塊在檔案不存在時,要提示使用者建立檔案。該旗標會自動設定cdlOFNPathMustExist和cdlOFNFileMustExist旗標。(只適用 開啟舊檔 )
cdlOFNShareAware 忽略網路資源分享衝突的錯誤。(不建議使用,除非在試圖利用程式來解決分享資源的衝突時)
cdlOFNNoReadOnlyReturn 傳回的檔案將不會有Read Only屬性,也不會位於防寫目錄下。
cdlOFNExplorer 使用類似 檔案總管  開啟舊檔 對話方塊範本。(只適用於可多重選擇的 開啟舊檔 對話方塊)
cdlOFNLongNames 在類似 檔案總管 的可作多重選擇對話方塊中使用長檔名。但結果是對話方塊都支援長檔名,因此這個特性似乎沒有用 (只適用於可多重選擇的 開啟舊檔 對話方塊)
CdlOFNNoDereferenceLinks 傳回使用者選擇的檔名及路徑,而不要還原LNK檔(也就是所謂的捷徑)。若省略設定此旗標,會引起它還原出原來所參照的檔案。
cdlOFNHelpButton 使對話方塊顯示 說明 按鈕。
cdlOFNNoLongNames 不使用長檔名。
表12-4 開啟舊檔或另存新檔對話方塊Flags屬性的值,請注意某些位元只有在某個對話方塊中有意義。

當使用 開啟舊檔  另存新檔 對話方塊時,通常必須把CancelError屬性設為True,因為您需要知道使用者是否取消了檔案作業。

「另存新檔」對話方塊
 

因為 另存新檔 對話方塊是兩者中較簡單的一個,因此筆者先介紹它,現在您應該知道顯示 另存新檔 對話方塊就像圖12-6一樣。接下來的常式會接受TextBox控制項及CommonDialog控制項的引用:這常式在將TextBox控制項的內容存檔前,使用後者的控制項來詢問檔案名稱。第三個引數則傳回檔案名稱:

' Returns False if the Save command has been canceled,
' True otherwise.
Function SaveTextControl(TB As Control, CD As CommonDialog, _
    Filename As String) As Boolean
    Dim filenum As Integer
    On Error GoTo ExitNow
    
    CD.Filter = " All files (*.*)|*.*|Text files|*.txt"
    CD.FilterIndex = 2
    CD.DefaultExt = "txt"
    CD.Flags = cdlOFNHideReadOnly Or cdlOFNPathMustExist Or _
        cdlOFNOverwritePrompt Or cdlOFNNoReadOnlyReturn
    CD.DialogTitle = " Select the destination file "
    CD.Filename = Filename
    ' Exit if user presses Cancel.
    CD.CancelError = True
    CD.ShowSave
    Filename = CD.Filename
    
    ' Write the control's contents.
    filenum = FreeFile()
    Open Filename For Output As #filenum
    Print #filenum, TB.Text;
    Close #filenum
    ' Signal success.
    SaveTextControl = True
ExitNow:
End Function

您可以如下使用SaveTextControl常式:

Dim Filename As String
If SaveTextControl (RichTextBox1, CommonDialog1, Filename) Then
    MsgBox "Text has been saved to file " & Filename
End If

當離開 另存新檔 對話方塊( 開啟舊檔 對話方塊亦同)時,假使您要知道所選擇的檔案副檔名是否與指定給DefaultExt屬性的相同,您可以檢查Flags屬性的cdlOFNExtensionDifferent位元:

If CD.Flags And cdlOFNExtensionDifferent Then
        ' Process nonstandard extensions here.
    End If


 

圖12-6「另存新檔」對話方塊。

請不要忘記設定 另存新檔 對話方塊的cdlOFNHideReadOnly位元。若您忘記的話,那麼就會一併顯示唯讀核取方塊,這可能會困擾使用者,因為他們是要儲存檔案而不是要開啟檔案。另外一個方便的旗標是cdlOFNNoReadOnlyReturn ,它可確定檔案不是唯讀屬性,這樣可避免覆蓋此檔時,會發生錯誤的情況。

只可單選的「開啟舊檔」對話方塊
 

CommonDialog控制項提供兩種 開啟舊檔 對話方塊,單選及可多重選擇的。前者實際上與 另存新檔 對話方塊沒什麼不同, 雖然您必須在Flags屬性中設定不同的位元。底下是一個可重複使用的常式,它可從載入文字檔的內容到TextBox控制項中:

' Returns False if the command has been canceled, True otherwise.
Function LoadTextControl(TB As Control, CD As CommonDialog, _
    Filename As String) As Boolean
    Dim filenum As Integer
    On Error GoTo ExitNow
    
    CD.Filter = " All files (*.*)|*.*|Text files|*.txt"
    CD.FilterIndex = 2
    CD.DefaultExt = "txt"
    CD.Flags = cdlOFNHideReadOnly Or cdlOFNFileMustExist Or _
        cdlOFNNoReadOnlyReturn
    CD.DialogTitle = " Select the source file "
    CD.Filename = Filename

' Exit if user presses Cancel.
    CD.CancelError = True
    CD.ShowOpen
    Filename = CD.Filename
    
    ' Read the file's contents into the control.
    filenum = FreeFile()
    Open Filename For Input As #filenum
    TB.Text = Input$(LOF(filenum), filenum)
    Close #filenum
    ' Signal success.
    LoadTextControl = True
ExitNow:
End Function

若您沒有設定Flags屬性中的cdlOFNHideReadOnly位元,對話方塊會顯示一個唯讀核取方塊。要知道使用者是否點選這個核取方塊,必須如下面的方式檢查Flags屬性:

If CD.Flags And cdlOFNReadOnly Then
        ' The file has been opened in read-only mode.
        ' (For example, you should disable the File-Save command.)
    End If

可多重選擇的「開啟舊檔」對話方塊
 

使用可多重選擇的 開啟舊檔 對話方塊比單選的 開啟舊檔 對話方塊要有一點複雜。設定Flags屬性中的cdlOFNAllowMultiselect位元來允許在「檔案名稱」清單方塊作多重選擇:所有使用者所選擇的檔案,會連成一串字串,在FileName屬性中傳回。

這個字串可能會非常長,因為使用者有可能選擇甚至多達一百個的檔案。然而,在 開啟舊檔 對話方塊中,預設值只能傳回256個字元: 若使用者所選擇的檔案名稱,合計起來超過這個長度,會傳回錯誤代碼20476 "Buffer too small"的錯誤。要避免這個錯誤,您可以指定給MaxFileSize更大的值。例如,10KB應該足以應付大部分的要求:

CommonDialog1.MaxFileSize=10240

為了要保持與16-bit程式的相容性,可多重選擇的 開啟舊檔 對話方塊傳回的檔名是利用Space來當作分隔。不幸的是,在長檔名中Space是一個有效字元,因此可能會造成潛在的困擾,在圖12-7所有的檔名以舊的8.3 MS-DOS格式來顯示,而對話方塊本身使用舊式的顯示方式。要解決這個問題,您必須設定Flags屬性中的cdlOFNExplorer旗標,來使用類似現代 檔案總管 的使用者介面,讓所傳回的長檔名間,利用null字元來作分隔。請注意說明文件是不正確的,cdlOFNLongNames可以放心的省略,因為類似 檔案總管 的對話方塊本身就自動的接受長檔名。


 

圖12-7一個沒有設定cdlOFNExplorer旗標的可多重選擇「開啟舊檔」對話方塊,使用舊式的Windows 3.x外觀。

底下是一個可重複使用的常式,它可向使用者要求多個的檔案,並且把檔名存放到一個字串陣列中:

' Returns False if the command has been canceled, True otherwise.
Function SelectMultipleFiles(CD As CommonDialog, Filter As String, _
    Filenames() As String) As Boolean
    On Error GoTo ExitNow
    
    CD.Filter = " All files (*.*)|*.*|" & Filter
    CD.FilterIndex = 1
    CD.Flags = cdlOFNAllowMultiselect Or cdlOFNFileMustExist Or _
        cdlOFNExplorer
    CD.DialogTitle = " Select one or more files"
    CD.MaxFileSize = 10240
    CD.Filename = ""
    ' Exit if user presses Cancel.
    CD.CancelError = True
    CD.ShowOpen

    ' Parse the result to get filenames.
    Filenames() = Split(CD.Filename, vbNullChar)
    ' Signal success.
    SelectMultipleFiles = True
ExitNow:
End Function

當使用者關閉對話方塊,根據所選擇的檔案數目,Filename屬性可能會有不同格式的資料,底下兩種情況:

接下的程式碼使用之前的SelectMultipleFiles常式,並能夠分辨上述兩種情況:

Dim Filenames() As String, i As Integer
If SelectMultipleFiles(CommonDialog1, "", Filenames()) Then
    If UBound(Filenames) = 0 Then
        ' The Filename property contained only one element.
        Print irSelected file: " & Filenames(0)
    Else
        ' The Filename property contained multiple elements.
        Print "Directory name: " & Filenames(0)
        For i = 1 To UBound(Filenames)
            Print "File #" & i & " : " & Filenames(i)
        Next
    End If
End If

「說明」視窗
 

您可以使用CommonDialog控制項來顯示說明檔的資訊。在這種情況,不會顯示任何的對話方塊,並且只會用到一些屬性。您必須指定檔案名稱及路徑到HelpFile屬性中,並且在HelpCommand屬性中來設定「說明」的線上輔助型態。依據所要執行的動作,您可能需要設定HelpKey或是HelpContext屬性的值。表12-5列出所有提供的指令。

常數 描述
cdlHelpContents 顯示 說明 內容主題
cdlHelpContext 當contextID等於HelpContext屬性時,為特定的內容顯示「說明」。
cdlHelpContextPopup 與cdlHelpContext相同,但以彈出式視窗顯示一個特定的說明主題,
cdlHelpKey 根據所傳入的HelpKey屬性,為特定的關鍵字顯示「說明」。
cdlHelpPartialKey 與cdlHelpKey相同,但會搜尋部分相同的字串
cdlHelpCommandHelp 執行名稱為HelpKey屬性的「說明」巨集。
cdlHelpSetContents 判斷當使用者按F1鍵時顯示哪個內容的主題。
cdlHelpForceFile 保證 說明 視窗是可見的 (visible)
cdlHelpHelpOnHelp 為使用說明應用程式本身顯示 說明 
cdlHelpQuit 關閉 說明 視窗
cdlHelpIndex 顯示 說明 內容主題(同cdlHelpContents)
cdlHelpSetIndex 在多個索引的說明檔,指定的說明檔(同cdlHelpSetContents)
表12-5表列出在顯示「說明」時,所有可以指定給HelpCommand屬性的值。

底下的程式片斷說明如何顯示說明檔中的內容主題:

' Show the contents page of DAO 3.5 help file.
With CommonDialog1
    ' Note: The path of this file may be different on your system.
    .HelpFile = "C:\WINNT\Help\Dao35.hlp"
    .HelpCommand = cdlHelpContents
    .ShowHelp
End With

您也可以為特定的關鍵字顯示「說明」。如果沒有與HelpKey屬性相符合的,則顯示「說明」的索引。

With CommonDialog1
    .HelpFile = "C:\WINNT\Help\Dao35.hlp"
    .HelpCommand = cdlHelpKey
    .HelpKey = " BOF property"
    .ShowHelp
End With

同樣地,也可使用context ID來為特定的內容顯示「說明」。此時需要將cdlHelpContext常數指定給HelpCommand屬性,而將context ID指定給HelpContext屬性。當然,您必須先知道所要顯示「說明」的context ID,但假使您是「說明」的製作者,這應該不是問題。關於「說明」的context ID更多資訊,請參閱第五章中的 〈顯示「說明」〉 小節。

RICHTEXTBOX控制項
 

RichTextBox控制項是在所有VB提供的控制項中,最功能強大的之一。概括地說,它是一個可顯示Rich Text Format (RTF)格式的文字方塊,幾乎所有的文書處理器都支援RTF,包含了Microsoft WordPad (並不令人驚訝,因為WordPad本身就是使用RichTextBox控制項)。這個控制項支援多種的字型及色彩、左右邊界、項目符號等。

您可能需要時間去瞭解RichTextBox控制項所提供的眾多功能。好消息是,RichTextBox控制項與多行文字的TextBox控制項是程式碼相容的(code-compatible),所以您可以重複使用那些您原本為TextBox控制項所寫的程式。

但是,不像標準的TextBox控制項,RichTextBox控制項並沒有對可容納的文字行數作特別限制。這個控制項內嵌在RichTx32.ocx檔案中,任何使用到它的VB應用程式都必須包含這個檔案。

設定設計時期的屬性
 

您可以直接利用在屬性頁的 一般 頁籤來設定一些有用的屬性,如圖12-8

例如您可以在Filename屬性中輸入TXT或RTF格式的檔案名稱,讓表單載入時去開啟。

RightMargin屬性代表從控制項左邊框到文字的右邊邊界的距離(單位為twips)。當SetBullet屬性設為True時,BulletIndent代表使用的縮排數(twips)。AutoVerbMenu決定當使用者按下滑鼠右鍵選取控制項時,不要顯示標準Edit快顯功能表(pop-up menu)。假使您要顯示自訂的快顯功能表,請將此屬性設為False。其他的在 一般 頁籤中的屬性與TextBox控制項皆相同,筆者將不在此講解。

在對話方塊中的 外觀 頁籤,可以找到其他的屬性,如BorderStyle及ScrollBars,您應該已經瞭解它們的意義。DisableNoScroll屬性是一個例外:若DisableNoScroll設為True,而ScrollBars屬性是0-rtfNone外的其他值時,RichTextBox控制項會一直顯示捲軸,甚至當文字太少而不需要捲軸時。這項特性與大多數的文書處理器一樣。

RichTextBox屬於資料連結的控制項,因此提供一般的Dataxxxx屬性,讓您與資料庫作連結。換句話說,您可以將TXT或RTF文件存放到資料庫的某一欄位中。


 

圖12-8 RichTextBox屬性頁的「一般」頁籤

執行時期作業
 

RichTextBox提供很多的屬性及方法,因此將它們根據您要執行的動作來分類會比較容易瞭解。

載入及儲存檔案
 

您可以利用LoadFile方法來載入文字檔到控制項中,它需要指定檔案名稱及一個可選擇的參數,代表要開啟的檔案是RTF(0-rtfRTF預設值)或是文字檔(1-rtfText):

' Load an RTF file into the control.
RichTextBox1.LoadFile "c:\Docs\TryMe.Rtf", rtfRTF

載入的檔案名稱可以由FileName屬性中取得。您也可以間接的由FileName屬性來載入檔案,但在這個情況下,就沒有辦法指定是哪一種格式。用相似的語法,您可以使用SaveFile方法來儲存控制項的內容:

' Save the text back into the RTF file.
RichTextBox1.SaveFile RichTextBox1.FileName, rtfRTF

使用LoadFile及SaveFile方法,是載入及儲存內容的好方法。然而,有時候您需要將控制項的內容,增加到原檔案的後面,或是將多個部分的文字儲存到同一個檔案中。此時您可使用TextRTF屬性配合VB一般檔案指令與函數來達成:

' Store the RTF text from two RichtextBox controls in the same file.
Dim tmp As Variant
Open "c:\tryme.rtf" For Binary As #1


' Use an intermediate Variant variable to ease the process.
' (Don't need to store the length of each piece of data.)
tmp = RichTextBox1.TextRTF: Put #1, , tmp
tmp = RichTextBox2.Text RTF: Put #1, , tmp
Close #1

' Read the data back in the two controls.
Open "c:\tryme.rtf" For Binary As #1
Get #1, , tmp: RichTextBox1.TextRTF = tmp
Get #1, , tmp: RichTextBox2.TextRTF = tmp
Close #1

您可以使用這個技巧來儲存或是重載控制項現在的格式為文字或是RTF的內容 (使用Text或是TextRTF屬性),甚至可以只儲存或重載現在所選取的文字 (利用SelText及SelRTF屬性)。

更改字元屬性
 

RichTextBox控制項提供多組的屬性來更改所選擇文字的屬性:它們是SelFontName、SelFontSize、SelFontSize、SelColor、SelBold、SelItalic、SelUnderline及SelStrikeThru。從名稱上便可看出它們代表的意義,因此筆者不打算一一的解釋它們。您可以發現,這些屬性跟在一般的文書處理器中相同。若有文字被選取,則它們設定或傳回相對應的屬性。若沒有文字被選取,則它們設定或傳回插入點(游標)之前的屬性。

RichTextBox同樣提供Font屬性,及各種的Fontxxxx屬性,但只有當RichTextBox被載入時,這些屬性才有作用。若您需要更改目前文件的屬性,必須先把整個文件選取起來:

' Change font name and size of entire contents.
RichTextBox1.SelStart = 0 
RichTextBox1.SelLength = Len(RichTextBox1.Text)
RichTextBox1.SelFontName = " Times New Roman"
RichTextBox1.SelFontSize = 12
' Cancel the selection.
RichTextBox1.SelLength = 0

當您讀取Selxxxx屬性的直時,可能是所選取文字的屬性,也有可能是Null,當所選取文字的屬性不同時,就會傳回Null。這代表在您使用選取文字的屬性時,必須先作一些事先準備:

Private Sub cmdToggleBold_Click()
    If IsNull(RichTextBox1.SelBold) Then
        ' Test for Null values first to avoid errors later.
        RichTextBox1.SelBold = True
    Else
        ' If not Null, we can toggle the Boolean value using
        ' the Not operator.


        RichTextBox1.SelBold = Not RichTextBox1.SelBold
    End If
End Sub

在應用程式中使用工具列時,工具列中的按鈕可以選取文字的Bold、Italic、UnderLine及其他屬性,也會發生和前面相同的問題。在這種情況必須使用工具列Button物件的MixedState屬性。當使用者選取或是取消選取文字時,RichTextBox控制項會發生SelChange事件,而您必須在這個事件中設定MixedState屬性:

Private Sub RichTextBox1_SelChange()
    ' Keep the toolbar's button in sync with current selection.
    If IsNull(RichTextBox1.SelBold) Then
        ToolBar1.Buttons(i1Boldlo).MixedState = True
    Else
        ToolBar1.Buttons("Bold").MixedState = False
        ToolBar1.Buttons("Bold").Value = IIf(rtfText.SelBold, _
            tbrPressed, tbrUnpressed)
    End If
    ' Add similar code that deals with Italic, Underline, and so on.
    ....
End Sub

在圖12-9的示範程式中使用了這個技巧。筆者先以應用程式精靈建立了程式大綱,而後手動更改精靈所產生的程式碼來處理大多數Selxxxx屬性可能傳回Null值的情況。


 

圖12-9一個多重文件介面的mini文書處理器示範程式。

SelProtected是一個有趣的屬性,可以讓您保護所選取的文字不被可編輯。您可用它來避免使用者不小心刪除或是更改到重要的文字。然而,若整份文件都不想被更改,最好將Locked屬性設為True。

更改段落屬性
 

您可以控制目前選取的段落格式。SelIndent及SelHangingIndent屬性可以一起使用來定義第一行文字與同一段落的下一行文字左邊邊界之間的距離。這些屬性的功用與一般文書處理器所定義的有幾分不同:SelIndent屬性是段落的第一行與左邊框的距離(twips),而SelHangingIndent屬性代表接下幾行縮排相對於第一行的縮排的值。例如要有一個縮排距離為400twips的段落,而第一行要再縮排200twips,就必須執行底下的程式:

RichTextBox1.SelIndent = 600   ' Left indentation + 1st line indentation
RichTextBox1.SelHangingIndent = -200  ' A negative value

SelRightIndent屬性是段落與文件右邊邊界距離(依據RightMargin屬性)。下面的程式將右邊邊界移到與右邊邊框300twips的距離,並將目前所選取的段落向右縮排100twips:

' RightMargin is measured from the left border.
RichTextBox1.RightMargin = RichTextBox1.Width - 300
RichTextBox1.SelRightIndent = 100

您可以使用SelAlignment屬性來控制段落對齊的調整值。SelAlignment可指定0-rtfLeft、1-rtfRight或2-rtfCenter (RichTextBox不支援段落分散對齊。) 。您可以藉由這個屬性來取得所有選取段落的對齊方式,若有不同對齊方式的段落,則會傳回Null。

SelCharOffset屬性可建立在基線之上或之下的文字,換句話說,將文字稍微地移到文字基線的上或下。正數值會建立在基線之上的文字,而負數值會建立在基線之下的文字,零則會還原到一般文字的位置。儘管如此,您不應該傳入大數目的正值或負值給這個屬性,因為這會使文字變得不易閱讀(甚至看不見)─RichTextBox不會自動調整具有在基線之上或之下的文字的行距:

' Make the selection superscript text.
RichTextBox1.SelCharOffset = 40


' Don't forget to reduce the characters' size.
RichTextBox1.SelFontSize = RichTextBox1.SelFontSize \ 2

將SelBullet屬性設為True可以把一個正常的段落轉變為具有項目符號型態得段落。它會傳回目前段落的屬性,若段落具有不同的屬性時,會傳回Null:

' Toggle the bullet attribute of the selected paragraphs.
Private Sub cmdToggleBullet_Click()
    If IsNull(RichTextBox1.SelBullet) Then
        RichTextBox1.SelBullet = True
    Else
        RichTextBox1.SelBullet = Not RichTextBox1.SelBullet
    End If
End Sub

您可以利用BulletIndent屬性來控制項目符號縮排,此屬性作用範圍為整份文件。

管理Tab(定位)鍵
 

如同一般的文書處理器,RichTextBox具有段落間管理Tab絕對位置的能力。要達到這個功能必須依賴SelTabCount及SelTabs兩個屬性﹔前者用來決定在被選取的段落中Tab位置數目的整數,後者用來指定已指派Tab位置的整數。這裡有一個簡單的範例告訴您如何使用這幾個屬性:

' Add three tabs, at 300, 600, and 1200 twips from left margin.
RichTextBox1.SelTabCount = 3
' The SelTabs property is zero-based.
' Tabs must be specified in increasing order, otherwise they are ignored.
RichTextBox1.SelTabs(0) = 300
RichTextBox1.SelTabs(1) = 600
RichTextBox1.SelTabs(2) = 1200

您也可以利用這些屬性來找到被選取的段落中Tab位置。請記得要對可能所傳回的Null值作處理。

在使用Tab方面還有一點必須考慮的;Tab鍵只有在表單上所有控制項的TabStop屬性設為False時,才會插入Tab字元,否則,要在文件中插入Tab字元只有使用CTRL+TAB組合鍵。

一個簡單的解決方法是當RichTextBox控制項取得駐點時,可將Form物件中所有控制項的TabStop屬性,暫時切換為False,而當失去駐點時,再還原成True。(在使用者按下其它控制項的hot key或是在其他控制項上按下滑鼠按鍵時,RichTetBox控制項會失去駐點)。底下有一個可重複使用的函式來完成上述動作。

' In a BAS module
Sub SetTabStops(frm As Form, value As Boolean)
    Dim ctrl As Control
    On Error Resume Next
    For Each ctrl In frm.Controls
        ctrl.TabStop = value
    Next
End Sub
' In the form module that contains the RichTextBox control
Private Sub RichTextBox1_GotFocus()
    SetTabStops Me, False
End Sub
Private Sub RichTextBox1_LostFocus()
    SetTabStops Me, True
End Sub

尋找及取代文字
 

要在RichTextBox控制項中尋找文字,您可以使用InStr函數來搜尋Text屬性傳回來的值。這個控制項也提供Find方法來讓尋找過程更容易及快速,Find方法的語法如下:

pos = Find(Search,[Start],[End],[Options])

其中Search是您要在控制項中尋找的字串。Start用來決定從什麼地方開始搜尋的整數字元索引。(第一個字元索引為0)。End決定在什麼地方結束搜尋的整數字元索引。Options可以是底下一或多個常數:2-rtfWholeWord,4-rtfMatchCase或8-rtfNoHighlight。若成功找到要搜尋的文字,會以亮度(反白)顯示所指定的文字,並傳回索引;若搜尋失敗,則傳回-1。即使HideSelection屬性為True,仍將以亮度顯示所找到的文字,RichTextBox直到您指定rtfNoHighlight旗標才會取得駐點。若省略了Start參數,將會從目前游標位置搜尋到End參數所指定結束的位置。若省略了End參數,將會從Start參數所指定開始的位置搜尋到文件的結尾部分。若同時省略Start及End參數,若您有選取文字,則搜尋選取文字;若您未選取文字,則搜尋整個內容。

要實作「尋找」、「取代」是很容易的。因為Find方法會以亮度顯示所找到的文字,您只要做的是將之以SelText屬性來指定新的值。您也可很簡單的寫一個函式來取代所有相同的字串,並且傳回被取代字串的總數:

Function RTFBoxReplace(rtb As RichTextBox, search As String, _
    replace As String, Optional options As FindConstants) As Long
    Dim count As Long, pos As Long
    Do


        ' Search the next occurrence.
        ' (Ensure that the rtfNoHighlight bit is off.)
        pos = rtb.Find(search, pos, , options And Not rtfNoHighlight)
        If pos = -1 Then Exit Do
        count = count + 1
        ' Replace the found substring.
        rtb.SelText = replace
        pos = pos + Len(replace)
    Loop
    ' Return the number of occurrences that have been replaced.
    RTFBoxReplace = count
End Function

RTFBoxReplace函式會比純VBA Replace函數來得慢,但它會保存被取代字串原有的屬性。

移動游標及所選取的文字
 

Span方法會將文件中文字選取範圍向前或向後延伸,直到找到指定的字元

語法如下:

Span CharTable. [Forward]. [Negate]

CharTable是一個包含一或多個字元的字串。Forward是移動的方向 (True是向前,False向後)。Negate指出移動的終點:假使為False(預設值),將在第一個字元不出現在CharTable時結束 (因此所選取的範圍只包含出現在CharTable中的字元)。假使為True時,在遇到任何出現在CharTable中的字元時結束(此時所選取的字串都不包含出現在CharTable中的字元)。Span方法在以程式來選取單字或是一個句子時很有用:

' Select from the caret to the end of the sentence.
' You need the CRLF to account for the paragraph's end.
RichTextBox1.Span " .,;:!?" & vbCrLf, True, True

要不選取文字而移動插入點,可使用UpTo方法,語法跟Span一樣:

' Move the caret to the end of the current sentence.
RichTextBox1.UpTo " .,;:!?" & vbCrLf, True, True

另外一個可能您會覺得有用的方法是GetLineFromChar,它回傳回指定字元位置的行數。例如使用下面的方法可以顯示目前游標所在位置的行數:

Private Sub RichTextBox1_SelChange()
    ' The return value from GetLineFromChar is zero-based.

    lblStatus.Caption = " Line "  & (1 + RichTextBox1.GetLineFromChar _
        (RichTextBox1.SelStart))
End Sub

您可使用下面的程式碼取得文件的總列數:

Msgbox (1 + RichTextBox1.GetLineFromChar(Len(RichTextBox1.Text))) _
	& " Lines"

列印目前文件
 

RichTextBox控制項提供SelPrint方法來支援直接列印目前選擇的文字或是整份文件(沒有選取文字時),它的語法如下:

SelPrint hDC,[StartDoc]

hDC是目標印表機的周邊裝置內容,StartDoc是布林值,用來決定是否要把StartDoc及EndDoc命令送到印表機。EndDoc在VB6已經介紹過,當某些印表機並不像其他一般印表機作用時,此參數會很有用。只要兩個陳述式就可列印出目前文件內容:

RichTextBox1.SelLength = 0         ' Clear selection, if any.
RichTextBox1.SelPrint Printer.hDC    ' Send to the current printer.

SelPrint方法有一個缺點,那就是您沒有辦法來控制列印的邊界。隨書光碟中的示範程式利用呼叫Windows API方法來克服上面的限制。

內建的物件
 

RichTextBox控制項具有一個複雜的功能,那就是具有內嵌OLE物件的能力,就像在內建的OLE控制項中一樣 (OLE控制項在 第三章 中有簡短的描述)。要使用這項功能必須藉由OLEObjects集合,它會包含0或多個OLEObject項目。每個OLEObject都對應到文件中內嵌的OLE物件。您可以利用程式透過OLEObject集合的Add方法來新增OLE物件,語法如下:

Add ([Index],[Key],[SourceDoc],[ClassName]) As OLEObject

Index代表物件在集合中被插入的位置。Key代表用來識別集合中物件的唯一字串。SourceDoc代表將被複製到RichTextBox控制項的內嵌文件檔案名稱。(可省略來插入空白文件)。ClassName是嵌入物件OLE類別名稱,您可以執行這段程式來在目前游標位置嵌入空白的Microsoft Excel試算表:

' This new object is associated to the "Statistics" key.
Dim statObj As RichTextLib.OLEObject
Set statObj = RichTextBox1.OLEObjects.Add(, "Statistics", _
    , "Excel.Sheet")

一旦您新增一個OLEObject,它就變成就地啟動,讓您可以馬上對它新增資料。OLEObject項目提供少許的屬性及方法讓您(部分地)可用程式來控制它們。例如,DisplayType屬性可指定物件顯示內容(0-rtfDisplayContent )或是圖示(1-rtfDisplayIcon):

' Show the object just added as an icon.
statObj.DisplayType = rtfDisplayIcon

內嵌的物件提供一組叫做verbs的動作。您可以使用FetchVerbs來取得嵌入物件支援的動作,並以ObjectVerbs和ObjectVerbsCount屬性來查詢:

' Print the list of supported verbs to the Debug window.
statObj.FetchVerbs
For i = 0 To statObj.ObjectVerbsCount - 1
    ' These strings are printed as they might appear in a pop-up
    ' menu and can include an & character.
    Debug.Print Replace(statObj.ObjectVerbs(i), "&" , "")
Next

動詞清單支援典型的動作像Edit或是Open。您可以使用DoVerb方法來執行這些動作,它接受動詞名稱、ObjectVerbs屬性的索引、或是代表一般動詞的負數值 (-1-vbOLEShow,-2-vbOLEOpen,-3-vbOLEHide,-4-vbOLEUIActivate,-5-vbOLEInPlaceActivate,-6-vbOLEDiscardUndoState)。您可使用ObjectVerbsFlags來測試物件是否支援某個動詞。例如您可以利用下面的程式列印嵌入物件的內容:

Dim i As Integer
For i = 0 To statObj.ObjectVerbsCount ? 1
    ' Filter out "&" characters.
    If Replace(statObj.ObjectVerbs(i), "&" , "") = " Print" Then
        ' A " Print" verb has been found, check its current state.
        If statObj.ObjectVerbFlags(i) = vbOLEFlagEnabled Then
            ' If the verb is enabled, start the print job.
            statObj.DoVerb i
        End If
        Exit For
    End If
Next

要獲得更多關於這項功能的資訊,請參考VB說明文件。

SSTAB控制項
 

SSTab控制項可以讓您如同使用TabStrip一般控制項的方法,來新增頁籤式對話方塊。兩者間最大的不同在於SSTab控制項是真正的收納器,所以您可在它表面直接放上子控制項。甚至您可在設計時期時切換頁籤,讓準備工作比TabStrip控制項更簡單、更快速。

很多程式設計人員發現使用SSTab控制項比較容易,因為它不需附屬的控制項,而且屬性或是事件的語法更簡單。這個控制項是內嵌在TabCtl32.ocx檔案中,任何使用到它的VB應用程式都必須包含這個檔案。

設定設計時期的屬性
 

當您把SSTab控制項放到表單後的第一個動作,就是把它的Style屬性從0-ssStyleTabbedDialog換成1-ssStylePropertyPage,如圖12-10。通常頁籤會顯示在控制項外框的上方,但您也可以使用TabOrientation屬性來更換這個預設值。


 

圖12-10 SSTab控制項中屬性頁對話窗中的「一般」頁籤。

您可以在TabCount欄位(等於Tabs屬性) 中改變值,來增加或刪除頁籤,也可在TabsPerRow屬性中設定適合的值來建立多列的頁籤。當建立完足夠的頁籤後,可以使用spin按鈕來在頁籤間切換,並且更改每個頁籤的TabCaption屬性 (這是在對話方塊中唯一會依賴目前頁籤的屬性)。頁籤標題可以包含&字元定義熱鍵,讓使用者可作快速的切換。

TabHeight屬性設定SSTab控制項上的所有頁籤的高度(twips)。TabMaxWidth屬性設定SSTab控制項上的每個頁籤的最大寬度 (為0時,會自動調整頁籤大小,使它們適合於控制項)。WordWarp屬性必須設為True來讓太長文字自動換行。ShowFocusRect屬性為True時,可顯示頁籤上的駐點框線。

每個頁籤都可顯示小圖片。要在設計時期設定,必須先在屬性頁對話方塊中的 一般 頁籤選擇要設定的頁籤,再切換到 圖片 頁籤,點選在左邊清單方塊中的Picture屬性,然後選擇要指定給目前頁籤的bitmap或是icon。Bitmap可以在程式中使用TabPicture屬性來引用。

當您建立所有需要的頁籤後,便可在每個上面放上控制項。因為您可在設計時期選擇頁籤,這項動作並不困難。但您必須關心一些細節,在VB的看法,在每個不同頁籤上所放的控制項,都是收納在SSTab控制項中,換句話說,它們的收納器是SSTab控制項,而不是每個頁籤頁。這代表很多的涵義。例如,若有兩組的OptionButton控制項在SSTab控制項上不同的頁籤頁,您應該將每一組使用Frame或是其他的收納器來分隔,否則VB會把它們當作成一組。

執行時期作業
 

SSTab控制項主要的屬性是Tab,它將傳回使用者目前所選擇頁籤的索引。您也可在程式中利用這個屬性來切換到其他的頁籤。

更改頁籤的屬性
 

在更改頁籤屬性時並不需要將它變成目前的頁籤,因為大多數的屬性都需要索引。例如您可使用TabCaption屬性來傳回或設定頁籤標題,利用TabPicture屬性為頁籤加入圖片, 利用TabEnabled決定頁籤是否有效,以及利用TabVisible屬性來決定頁籤是否為可見的:

' Change caption and bitmap of the second tab. (Tabs' indexes are 0-based.)
SSTab1.TabCaption(1) = "Information"
' Note: The actual path of this file might be different on your system.
filename = "c:\VisStudio\Common\Graphics\Bitmaps\Assorted\balloon.bmp"
SSTab1.TabPicture(1) = LoadPicture(filename)
' Make the first tab invisible.
SSTab1.TabVisible(0) = False

Tabs屬性傳回存在的頁籤數:

' Disable all the tabs except the current one.
For i = 0 To SSTab1.Tabs ? 1
    SSTab1.TabEnabled(i) = (i = SSTab1.Tab)
Next

建立新的頁籤
 

在執行時期,您可以藉由增加Tabs屬性的值來建立新的頁籤。您只能把頁籤加在所有已存在頁籤的後面。

SSTab1.Tabs=SSTab1.Tabs +1
SSTab1.TabCaption(SSTab1.Tabs -1)="Summary"

在新增完頁籤後,接下來您也許會想在上面加入新的控制項。此時您可以動態地建立新控制項,並且更改它們的Container屬性。它們就會變成目前選擇頁籤的子控制項:

' Create a TextBox control.
Dim txt As TextBox
Set txt = Controls.Add("VB.TextBox", "txt")
' Move it on the new tab. (You must select it first.)
SSTab1.Tab = SSTab1.Tabs - 1
Set txt.Container = SSTab1
txt.Move 400, 800, 1200, 350
txt.Visible = True

對頁籤的選擇作出反應
 

SSTab並不提供任何自訂的事件。然而Click事件會接收前一次所使用頁籤的索引。您可以利用這個參數來對在失去駐點頁籤上的控制項作資料驗證,並且將Tab屬性重設為取消駐點:

Private Sub SSTab1_Click(PreviousTab As Integer)
    Static Active As Boolean
    If Active Then Exit Sub
    ' Prevent recursive calls.
    Active = True
    Select Case PreviousTab
        Case 0
            ' Validate controls on first tab.
            If Text1 = "" Then SSTab1.Tab = 0
        Case 1
            ' Validate controls on the second tab.
            ' ...
    End Select
    Active = False
End Sub

在程式中設定Tab屬性,也會發生Click事件,所以您必須使用一個靜態旗標來避免程式在事件程序中產生遞迴 (上面程式中的Active變數)。

管理輸入駐點
 

在SSTab控制項中管理輸入駐點,您必須考慮到幾個問題:

  • 當使用者按下在頁籤(不管是不是目前的頁籤)上控制項的熱鍵時,輸入駐點會轉移到該控制項上,但是SSTab控制項不會自動切換到該控制項所屬的頁籤,因此該控制項可能是看不見的。
     
  • 當使用者切換到其他的頁籤時,在該頁籤上第一個控制項不會自動取得輸入駐點。
     

針對第一個問題,最簡單的解決方法是將所有不在目前頁籤上的控制項都失效 (disable),當使用者按下它們的熱鍵時,不讓它們取得輸入駐點。並沒有文件說明如何辨識哪一個控制項在哪一頁上面的方法。但是很容易可以證明,SSTab控制項讓所有不在目前頁籤上的控制項,不在螢幕上顯示的方法是將每個子控制項的Left屬性設為一負數值。您可以使用下面的方法來暫時將所有控制項失效:

' This routine can be reused for any SSTab controls in the application.
Sub ChangeTab(SSTab As SSTab)
    Dim ctrl As Control, TabIndex As Long
    TabIndex = 99999          ' A very high value.
    On Error Resume Next

    For Each ctrl In SSTab.Parent.Controls
        If ctrl.Container Is SSTab Then
            If ctrl.Left < -10000 Then
                ctrl.Enabled = False
            Else
                ctrl.Enabled = True
                If ctrl.TabIndex >= TabIndex Then
                    ' This control comes after our best candidate or
                    ' it doesn't support the TabIndex property.
                Else
                    ' This is the best candidate so far to get the focus.
                    TabIndex = ctrl.TabIndex
                    ctrl.SetFocus
                End If
            End If
        End If
    Next
End Sub
' Call from within the Click event procedure.
Private Sub SSTab1_Click(PreviousTab As Integer)
    ChangeTab SSTab1
End Sub

ChangeTab函式也一併解決了前面所提到的,將駐點移到目前頁籤的第二個問題。它將駐點移到目前頁籤上所有子控制項中,具有最小TabIndex屬性的控制項上。您唯一要作的事,就是對於在SSTab控制項上的所有子控制項,將它們的TabIndex屬性指定一個遞增的值。想要知道更詳細的細節,請參閱隨書光碟的範例程式。

SYSINFO控制項
 

SysInfo控制項可以讓VB程式員發展符合Windows logo的應用程式。要成為這種應用程式的前提之一,就是可回應系統事件的能力,例如當螢幕解析度改變時或是當plug-and-play裝置與系統連接或斷線時。

這個控制項是內嵌在Sys檔案中,任何使用到它的VB應用程式都必須包含這個檔案。

屬性
 

Sysinfo控制項非常容易使用:它並沒有任何設計時期的屬性,也不提供任何方法。要使用Sysinfo控制項只有透過查詢它的執行時期屬性,以及撰寫事件的程式碼。Sysinfo控制項的屬性大概可以分作三組:傳回作業系統資訊的屬性、傳回螢幕設定的屬性、傳回電源狀態資訊的屬性。這個控制項所有的屬性都是唯讀的(read-only)。

第一組屬性包含OSPlatform、OSVersion及OSBuild。若應用程式是在Windows 95或是Windows 98下執行,OSPlatform會傳回1,若在Windows NT下,則回傳回2。OSVersion會傳回Windows的版本(Single值)。OSBuild讓您區別同一版本的Builds。

第二組屬性包含:WorkAreaLeft、WorkAreaTop、WorkAreaWidth、WorkAreaHeight及ScrollBarSize。前四個屬性傳回工作區的位置及大小(twips),也就是桌面上不含工作列的區域。您可以使用這些資訊適當將表單搬移或是改變大小。ScrollBarSize會傳回系統捲軸的寬度值,您可以使用這個值將程式中的捲軸與螢幕解析度作配合。

第三組屬性包括:ACStatus (0代表電池,1代表AC電源)、BatteryFullTime (預估的電池壽命)、BatteryLifePercent (預估電池所剩能量的百分比)以及BatteryStatus(1-充足、2-不足、4-處於臨界狀態、8-正在充電)。當所需要的資訊無法獲得時,這些屬性會傳回一個特定值(ACStatus、BatteryStatus傳回-1,其他屬性傳回255)。要更進一步瞭解,請參考隨書CD中的示範程式(圖12-11)。


 

圖12-11示範程式將展示如何利用Sysinfo控制項的屬性及事件。

事件
 

Sysinfo控制項提供18種事件,可將它們分作四組來討論:

  • 當plug-and-play週邊設備連接或是移除時所發生的事件:DeviceArrival、DeviceOtherEvent、DeviceQueryRemove、DeviceQueryRemoveFailed、DeviceRemoveComplete及DeviceRemovePending。
     
  • 當硬體組態改變時所發生的事件:QueryChangeConfig、ConfigChanged及ConfigChangeCancelled。在QueryChangeConfig事件中,您可傳回Cancel參數為True,來取消這些改變。
     
  • 當電源狀態改變時所發生的事件:PowerQuerySuspend、PowerResume、PowerStatusChanged及PowerSuspend。例如您可以在PowerSuspend事件發生時,做出反應,將重要的資料寫回磁碟中。
     
  • 當系統設定改變時所發生的事件:DisplayChanged、SysColorsChanged、TimeChanged、SettingChanged及DevModeChanged。最後一個事件發生在當使用者或另一個應用程式改變周邊設備的組態時。
     

其中最簡單及最常用的屬性是DisplayChanged、SysColorChanged及TimeChanged,這些屬性由它們的名稱就可解釋。另外一個有趣的屬性是SettingChanged,它會傳回一個代表哪個系統設定被改變的整數值。例如,您可以用下面的方法來偵測使用者移動或是改變Windows工具列的大小:

Private Sub SysInfo1_SettingChanged(ByVal Item As Integer)
    Const SPI_SETWORKAREA = 47
    If Item = SPI_SETWORKAREA Then Call Resize_Forms
End Sub

然而,除了這些簡單的情況,要利用Sysinfo事件,需要對Windows作業系統有十分仔細的認識。

MSCHART控制項
 

MSChart控制項是一個外部的ActiveX控制項,它可以讓您的應用程式具有製作圖表的能力。您可以建立不同樣式(如直條圖、折線圖、圓形圖)的2D或是3D圖表。您必須自己完全控制圖表的項目,如標題、說明、註腳、座標軸、資料點數列等。甚至可以旋轉圖片,虛擬地在圖表上的每個元素加上背景圖,設定您自己的光源,並且放在想要的地方。在執行時期,您可以提供讓使用者選擇圖表上某一部份,移動或是更改大小的能力。

MSChart控制項毫無疑問地是所有VB提供的ActiveX控制項中,最複雜的一個。提供一點讓您瞭解它的複雜度,細想型態程式庫中47個不同的物件,其中大多數具有很多的屬性、方法及事件。要詳細的描述這個物件,大約需要100頁甚至更多的文字。因為這個原因,筆者將只針對它主要的特性作一些描述,提供些許的範例程式。若您想大膽去深入瞭解物件彼此間複雜的關係,會發現圖12-12將會幫助您避免迷失在這個迷宮中。

在階層中最上層的物件是MSChart,它可讓您設定圖表的一般特性,並提供幾個自訂的事件。在階層中其他的物件都依賴MSChart物件。

DataGrid讓您存放那些想要以圖表顯示的資料。Plot物件是一個混合(compound)物件(指物件與其他的子物件),包含所有關於資料數列圖表化的資訊(色彩、標記、背景圖樣、位置及光線來源的屬性等)。Title、Legend及Footnote是具有相似結構的混合物件,用來控制圖表上有關元素的特性(文字、色彩、位置等)。


 

圖12-12 MSChart階層中的上層。

設定設計時期屬性
 

在VB套裝提供的所有控制項中,MSChart控制項擁有最多功能的屬性頁對話方塊 (請看圖12-13),它提供了八個頁籤。

 圖表 頁籤讓您決定顯示哪種圖表類型,是否需要堆疊數列,是否顯示圖列來說明資料數列的意義。這些設定相對應於MSChart物件中的ChartType、Chart3d、Stacking及ShowLegend屬性。

 座標軸 頁籤用來選擇圖表上座標軸的屬性:線條寬度、線條色彩、是否顯示刻度,以及是否讓控制項自動決定刻度(建議值),或是由程式設計人員手動設定。在手動的情況,您必須設定最小刻度及最大刻度,及刻度間格數。

2D的圖表有三個座標軸(X軸、Y軸、副Y軸),而3D圖表額外多出第四個座標軸(Z軸)。程式中可使用Plot物件的子物件-Axis物件,來更改這些屬性。


 

圖12-13 MSChart控制項的屬性頁對話方塊,「圖表」頁籤。

 座標軸格線 頁籤讓您更改座標軸格線的樣式;這些設定與AxisGrid物件(Axis物件的子物件)的屬性相對應。

 數列 頁籤中,您可定義如何顯示數列。您可以隱藏數列(但會在圖表上保留空間)、排除數列(也在圖表上重新使用空間)、顯示標記,及恢復Y軸。若您畫出2D折線圖,您也可以顯示統計資料,例如最大最小值、平均值、標準差及回歸分析。您可以在程式中透過SeriesCollection及Series物件來更改這些特性。

 數列色彩 頁籤中,您可以設定數列邊線與內部的樣式及色彩(後者折線圖及XY散佈圖不提供),而讓每一數列的外觀更優雅。程式中可使用DataPoint物件的設定這些屬性。

在這個控制項中所有的主要物件-MsChart、Plot、Title、Legend及Footnote-都可以有背景圖樣。您可在屬性頁 背景 頁籤中設定每個背景的色彩及樣式。標題,圖例及圖表上座標軸的標題,都可在 文字、字型 頁籤中來設定。

執行時期作業
 

除非您想提供讓使用者更改圖表關鍵屬性的功能,您可以在設計時期在屬性頁對話方塊中就將所有關鍵屬性定義好,

這樣在執行時期時只需提供要在MSChart控顯示的資料即可,DataGrid物件能幫助您達成這個目的。您可以把DataGrid物件想成一個多為陣列,用來存放資料及與資料結合的標籤。

利用DataGrid的RowCount及ColumnCount屬性來更改陣列的大小,而使用RowLabelCount及ColumnLabelCount屬性來定義標籤數。例如您可以在12列的資料中,為每三個資料點加上一個標籤:

' 12 rows of data, with a label every third row
MSChart1.DataGrid.RowCount = 12
MSChart1.DataGrid.RowLabelCount = 4
' 10 columns of data, with a label on the 1st and 6th column
MSChart1.DataGrid.ColumnCount = 10
MSChart1.DataGrid.ColumnLabelCount = 2

或者,可將上述四個屬性,利用SetSize方法一次設定完成:

' Syntax is: SetSize RowLabelCount, ColLabelCount, RowCount, ColCount
MSChart1.DataGrid.SetSize 4, 2, 12, 10

您可利用RowLabel及ColumnLabel屬性來決定標籤的文字,它們需傳入兩個參數,您要指定標籤的資料列或是資料行數,以及該標籤的索引。

' Set a label every three years.
MSChart1.DataGrid.RowLabel(1, 1) = "1988"
MSChart1.DataGrid.RowLabel(4, 2) = "1991"
MSChart1.DataGrid.RowLabel(7, 3) = "1994"
' And so on.

您可利用SetData方法來為各資料點指定值,語法如下:

MSChart.DataGrid.SetData Row,Column,Value,NullFlag

Value是一個雙精確度數值。而當資料為空值時,NullFlag為True。您可以透過DataGrid物件的一組方法來很容易地(快速地)插入或是刪除資料列、資料行。它們是InsertRows、DeleteRows、InsertColumns、DeleteColumns、InsertRowLabels、DaleteRowLabels、InsertColumnLabels及DeleteColumnLabels。您也可以利用RandomDataFill方法來將亂數值填入資料方格(當沒有資料,而必須要顯示畫面給使用者看時會很有用)。

研習VB6所附的Chrtsamp.vbp示範專案,可以學習到更多如圖12-14所示關於MSChart控制項的技巧。


 

圖12-14 Microsoft Chart範例程式

VB套裝軟體中也包含了其他的控制項,可能在您的應用程式中會很有用。很可惜,筆者並沒有足夠的頁數來一一作深入的介紹。然而,在 第十章 、 十一章 所介紹過的控制項應該能足夠讓您建立精緻且具有強大功能使用者介面的Windows應用程式。

這一章專注於探討建立應用程式的使用者介面。建立一個好看的及具有邏輯的使用者介面,是一個成功的Windows應用程式所必備的。但外觀並不能代表全部,事實上,應用程式真正的價值在於處理資料的能力,所以在您所撰寫的VB程式中,大部分的程式必須對資料庫中的資訊作讀、寫及運算。在本書的第三部份,筆者將告訴您如何以最具效益的方式來完成這些工作。