34.進階應用程式

本章將要介紹幾個較為進階的程式設計技巧,增加你發展Visual Basic專案的生產力。Messages應用程式告訴你如何把指令放在外部文字檔中,然後利用這些指令來控制應用程式的表現方式。Secret應用程式把本書介紹過的資料安全性技術整合在一起,成為一個檔案加密程式。BitPack將介紹如何用C語言的DLL增加程式的執行速度。最後,Dialogs應用程式介紹了幾種增強表單功能的技巧。

Messages應用程式
 

你可以發展出一套屬於自己專用的指令,幫助你完成一些特別的工作,Messages應用程式就是一個這樣的實例。在本例中,我們設計了兩個指令,這兩個指令可以被輸入到訊息方塊中,用以控制訊息方塊的外觀和行為。在設計指令集時,我們把指令集設計得很簡單,但是如果你需要其他更具創意的指令,你可以依據同樣的觀念基礎來擴展指令的數目或功能。

Messages應用程式包含了Message物件,它是由MSG.CLS物件類別模組和MSG.FRM表單所共同定義的。Messages應用程式將會利用這個Message物件來顯示一系列的訊息方塊。Message物件有一個FileName屬性,這個屬性的設定值是一個具有特殊格式的文字檔檔名。檔案的附屬檔名為MES (訊息檔),(我們稍後會解釋訊息檔的語法),訊息檔的內容由MSG.CLS和MSG.FRM共同合作將之顯示出來。

訊息檔的語法


每一個訊息檔都包含了一個或是多個預備被顯示的文字段落,在段落與段落之間,我們以三個連續的"~"字元來表示段落的開端。在訊息檔的訊息文字段落會被循序地顯示在訊息方塊中,你可以在段落開端字元"~~~~~~"的後面加上指令,以控制訊息文字的顯示方式。

我們用一個樣本訊息檔MESSAGE.MES來舉例說明訊息檔的運作方式。你可以在隨書光碟中找到這個檔案:

MESSAGE.MES
This message file provides a sampling of the
features demonstrated in the Messages application.

Note that all of these lines appearing before
the first text block header will be ignored.

This is the first text block in the MESSAGE.MES file.
Notice that the display window sizes automatically for
the dimensions of the message. 

Close this display window to proceed to the next text
block in this file. Click the Close button in the 
upper-right corner of this message.
~~~ P 10
This message should automatically disappear in 10 seconds.
You may close it manually before then if you want.
~~~ F 2
This message should be in a flashing window, with the 
flash rate set to 2 times per second.
~~~ F 5
This message should be in a flashing window, with the
flash rate set to 5 times per second.
~~~ P 20 F 1
This is the last message in this file. The flash rate is 1 
time per second, and the message will disappear automatically
in 20 seconds if you don't close it manually before then.

這個檔案定義了五個訊息。第一個訊息會被顯示在一個不閃爍的方塊中,訊息一直停留在這個方塊裡,直到使用者將方塊關閉。第一個訊息方塊關閉後,第二個訊息就會接著出現;由於第二個訊息段落的開端字元後面有一個P10指令,這個訊息方塊將會在暫停10秒後消失。第三個訊息由F指令控制,F指令的功用在於設定訊息方塊的閃爍頻率;在本例中,訊息方塊每隔兩秒就會閃爍一次。最後一個訊息則使用了P和F兩個指令──這個訊息方塊將會每1秒鐘閃爍一次,訊息顯示了20秒後消失。

在這個應用程式裡,我們只設計了兩個指令── P和F,但是你可以很容易地加上其他的指令。例如,你可以加入一個"C"指令來控制訊息文字的顏色。仔細研究P和F指令運作的方式之後,你應該可以加上更多屬於自己的指令。

圖34-1所顯示的是第一個訊息被顯示的情形,圖34-2則是第三個訊息。


 

 圖34-1 由Messages應用程式所顯示的訊息


 

 圖34-2 閃爍的訊息方塊

為什麼要用訊息檔?
 

一般說來,把訊息文字內嵌在程式中是一件很自然的事,然而使用外部的ASCII檔案來存放所有的訊息也是有其優點。例如,如果你要修改某段訊息文字時,你只要用文字編輯器來修改訊息檔即可,不用修改程式。另外,在Visual Basic程式中撰寫多行的訊息文字時,你必須用vbCrLf常數來連結字串,而且維護這樣的訊息文字會比較困難。

如果你的應用程式準備在國際間散佈,使用外部訊息檔的好處和使用資源檔的好處是一樣的──你只要把訊息檔的內容翻譯成外國文字即可,不需重新修改和編譯程式。

圖34-3顯示的是Messages應用程式的專案內容。


 

 圖34-3 Messages應用程式的專案內容

MESSAGES.FRM


MESSAGES.FRM是Messages應用程式的啟動表單,在這張表單的程式中,我們定義了一個Message物件msgOne。為了要能夠循序地顯示在MESSAGE.MES檔中的所有訊息,我們把這個訊息檔的路徑和檔名指定給msgOne的FileName屬性,然後呼叫msgOne的Display方法,而訊息顯示的方式則由訊息檔中的指令所控制。

我們在這張表單上放置了一個通用對話方塊控制項和指令按鈕控制項,以便你透過通用對話方塊控制項選擇其他的訊息檔。圖34-4顯示的是設計階段中的Messages表單。


 

 圖34-4 設計階段中的Messages表單

如果要建立這張表單,請按照以下這兩張表設定表單和控制項的屬性內容,然後加入以下的程式碼。

MESSAGES.FRM功能表項目
標題 名稱 內縮 啟用
&File mnuFile 0 True
&New mnuNew 1 False
&Open... mnuOpen 1 False
&Save mnuSave 1 False
Save &As... mnuSaveAs 1 False
- mnuFileDash1 1 True
E&xit mnuExit 1 True
&Help mnuHelp 0 True
&Contents mnuContents 1 True
&Search for Help on... mnuSearch 1 True
- mnuHelpDash1 1 True
&About... mnuAbout 1 True
MESSAGES.FRM物件與屬性設定
屬性
 Form 
Name

BorderStyle

Caption

frmMessages

3 - Fixed Dialog

Display a Message File

 CommandButton 
Name

Caption

cmdMessages

&Display Message File

 CommonDialog 
Name cdlOne

MESSAGES.FRM原始程式碼
 

Option Explicit

Private Sub cmdMessages_Click()
    `Declare new Message object
    Dim msgOne As New Message

    `Prompt user for message file (*.MES)
    cdlOne.DialogTitle = "Message Files"
    cdlOne.Flags = cdlOFNHideReadOnly
    cdlOne.Filter = "Messages(*.mes)|*.mes"
    cdlOne.CancelError = True
    On Error Resume Next
    cdlOne.ShowOpen
    `Quit if user canceled or closed dialog box
    If Err Then Exit Sub
    On Error GoTo 0
    `Display message file
    With msgOne
        .FileName = cdlOne.FileName
        .Display
    End With
End Sub

Private Sub Form_Load()
    `Center this form
    Me.Left = (Screen.Width - Me.Width) \ 2
    Me.Top = (Screen.Height - Me.Height) \ 2
End Sub

Private Sub mnuAbout_Click()
    `Set properties
    About.Application = "Messages"
    About.Heading = _
        "Microsoft Visual Basic 6.0 Developer's Workshop"
    About.Copyright = "1998 John Clark Craig and Jeff Webb"
    About.Display
End Sub

Private Sub mnuExit_Click()
    Unload Me

End Sub

Private Sub mnuContents_Click()
    cdlOne.HelpFile = App.Path & "\..\..\Help\Mvbdw.hlp"
    cdlOne.HelpCommand = cdlHelpContents
    cdlOne.ShowHelp
End Sub

Private Sub mnuSearch_Click()
    cdlOne.HelpFile = App.Path & "\..\..\Help\Mvbdw.hlp"
    cdlOne.HelpCommand = cdlHelpPartialKey
    cdlOne.ShowHelp
End Sub

MSG.CLS
 

MSG.CLS物件類別模組是建立Message物件的藍圖,這個特別的物件類別模組需要一個MSG.FRM檔,以提供Message物件的視覺界面元素。如果你要在你的應用程式中加入Message物件,請務必加入MSG.CLS和MSG.FRM兩個檔。

每一個Message物件都有一個公用屬性FileName,這是原呼叫程式必須設定的屬性。Message物件中唯一的方法是Display方法,Display方法會把訊息檔中的訊息文字按照順序一一顯示出來。

在Display方法中的程式碼相當的多,這些程式碼主要在和frmMsg的屬性和方法進行互動。在訊息檔中的指令會由Display方法來解譯,而frmMsg則更進一步將解譯後的指令表現出來。

MSG.CLS原始程式碼
 

Option Explicit

`Property that defines message file to be displayed
Public FileName As String


`Method to display message file
Public Sub Display()
    Dim strH As String
    Dim strJ As String
    Dim strA As String
    Dim strB As String
    Dim strC As String
    Dim intFilNum As Integer
    Dim lngNdx As Long
    Dim lngFlashRate As Long
    Dim lngPauseTime As Long
    Dim lngHeight As Long
    Dim lngWidth As Long
    Dim lngMaxTextWidth As Long
    `Get next available file I/O number
    intFilNum = FreeFile
    `Trap error if filename is invalid
    On Error Resume Next
    Open FileName For Input As #intFilNum
    If Err Then
        MsgBox "File not found: " & FileName
        Exit Sub
    End If
    On Error GoTo 0
    `Find start of first text block
    Do Until EOF(intFilNum)
        Line Input #intFilNum, strH
        `Skip lines until three tilde characters are found
        If InStr(strH, "~~~") = 1 Then
            strJ = UCase$(strH)
            Exit Do
        End If
    Loop
    `Loop through all text blocks

    Do Until EOF(intFilNum)
        strB = ""
        strH = strJ
        lngWidth = 0
        lngHeight = 0
        `Load all of current text block
        Do Until EOF(intFilNum)
            Line Input #intFilNum, strA
            `End of this block is at start of next block
            If InStr(strA, "~~~") = 1 Then
                strJ = UCase$(strA)
                Exit Do
            End If
            `Keep track of widest line of text
            lngMaxTextWidth = frmMsg.TextWidth(strA & "XX")
            If lngMaxTextWidth > lngWidth Then
                lngWidth = lngMaxTextWidth
            End If
            `Keep track of total height of all lines
            lngHeight = lngHeight + 1
            `Accumulate block of text lines
            If lngHeight > 1 Then
                strB = strB & vbCrLf & strA

            Else
                strB = strA
            End If
        Loop
        `Check for flash rate in block header
        lngNdx = InStr(strH, "F")
        If lngNdx Then
            lngFlashRate = Val(Mid$(strH, lngNdx + 1))
        Else
            lngFlashRate = 0

        End If
        `Check for pause time in block header
        lngNdx = InStr(strH, "P")
        If lngNdx Then
            lngPauseTime = Val(Mid$(strH, lngNdx + 1))
        Else
            lngPauseTime = 0
        End If
        `Prepare message form's text box
        With frmMsg.txtMsg
            .Text = strB
            .Left = 0
            .Top = 0
            .Width = lngWidth
            .Height = (lngHeight + 1) * frmMsg.TextHeight("X")
        End With
        `Prepare message form
        With frmMsg
            .Width = .txtMsg.Width + (.Width - .ScaleWidth)
            .Height = .txtMsg.Height + (.Height - .ScaleHeight)
            .Left = (Screen.Width - .Width) \ 2
            .Top = (Screen.Height - .Height) \ 2
            `Set flash and pause properties if given
            If lngPauseTime > 0 Then .Pause = lngPauseTime
            If lngFlashRate > 0 Then .Flash = lngFlashRate
        End With
        `Show message and wait until it closes
        frmMsg.Show vbModal
    Loop
End Sub

MSG.FRM
 

frmMsg表單是MSG.CLS物件類別模組的工作夥伴,它們在一起共同形成了Message物件。主要表單frmMessages並不直接設定frmMsg表單的屬性,不呼叫任何frmMsg表單的方法,也不以任何方式直接與frmMsg進行互動。MSG.FRM只和MSG.CLS物件類別模組進行互動,以這種方式,我們便把frmMsg表單變成了Message物件的一部分。

MSG.FRM中有四個控制項:兩個計時器控制項,一個用來顯示訊息的文字方塊控制項,以及一個虛指令按鈕(dummy button)(稍後會加以解釋)。圖34-5顯示的是設計階段中的MSG.FRM。


 

 圖34-5 設計階段中的MSG.FRM

如果要建立這張表單,請按照下表設定表單和控制項的屬性內容,然後在表單中加入後面的程式碼。

MSG.FRM物件與屬性設定
屬性
 Form 
Name

Caption

BorderStyle

frmMsg

Message

3 - Fixed Dialog

 TextBox 
Name

ForeColor

MultiLine

Locked

txtMsg

&H00FF0000&

True

True

 Timer 
Name tmrTerminate
 Timer 
Name tmrFlash
 CommandButton 
Name

Default

Caption

cmdDummy

True

Dummy

MSG.FRM原始程式碼
 

Option Explicit

Private Declare Function FlashWindow _
Lib "user32" ( _
    ByVal hwnd As Long, _
    ByVal bInvert As Long _
) As Long

Private Sub Form_Paint()
    `Remove focus from text box
    cmdDummy.Left = Screen.Width * 2
    cmdDummy.SetFocus
End Sub

Private Sub tmrTerminate_Timer()
    Unload Me
End Sub

Private Sub tmrFlash_Timer()
    `Toggle form flashing
    FlashWindow hwnd, CLng(True)
End Sub

Property Let Flash(PerSecond As Integer)
    `Set and activate form flashing rate

    tmrFlash.Interval = 1000 / PerSecond
    tmrFlash.Enabled = True
End Property

Property Let Pause(Seconds As Double)
    `Set and activate auto-unload timing
    tmrTerminate.Interval = 1000 * Seconds
    tmrTerminate.Enabled = True
End Property

在tmrFlash_Timer事件程序中,我們呼叫FlashWindows API函式來對frmMsg表單作閃爍視窗的動作,而frmMsg表單閃爍的頻率則由tmrFlash的Interval屬性來決定。

虛指令按鈕cmdDummy在這裡唯一的功用就是用來取得駐點 (Focus),使frmMsg表單在顯示訊息文字時,文字方塊中不會出現閃爍的提示游標。當表單的Paint事件發生時,我們把這個指令按鈕控制項的Left屬性設為螢幕寬的兩倍,使這個指令按鈕不會讓使用者看到。另外,我們把cmdDummy的Default屬性設為True以確使cmdDummy一定能取得駐點。

Flash屬性和Pause屬性並不是由原呼叫程式直接設定,而是透過Message物件來加以設定。frmMsg表單將會依據設定值的不同而有不同的表現方式。

Secret應用程式


資料隱密性和安全性在今天成了愈來愈多人談論的話題,尤其是對在於Internet上面傳輸財務資料或其他專屬資訊這方面,這個話題被討論得更多。Secret應用程式雖然不能符合高度的保密需求,但是它對你的電子郵件或其他個人檔案提供了一個中間等級的保密措施。


注意:

這個應用程式所提供的安全性程度並不是萬無一失的,有些功力高深又鍥而不捨的高手仍然可以破解由這個程式加密後的資訊。然而,實際上說來,你已經阻止了百分之九十九想偷看檔案的人了。


為了使這個應用程式儘量簡單而易於說明起見,我們決定不用精巧但略嫌雜亂的公共鍵值(Public Key)技術,而採用私用鍵值(Private Key)技巧。如果要用這個應用程式來對你的電子郵件加密,那麼收件人必須要和你使用同一個密碼才能對郵件解密。任何一個具有合理長度的密碼字串都可以被用來作為加密處理所需的私用鍵值。

密碼字串會被作雜湊處理(Hash),變成24位元的鍵值資料──低於政府保密機關所設的上限,40位元。Visual Basic的亂數產生器被用來產生擬隨機位元組(Pseudorandom Byte)。一般而言,它的隨機性可以被中等保密程度的密碼技術所採用。但即使有了這種層次的安全措施,仍然有人可以破解由Secret應用程式加密後的訊息和檔案。因此,如果你需要十分嚴格的加密處理,市面上有一些產品可以供你選擇,但是如果你只是要有一些一般性的保護,那麼Secret應用程式就夠用了!


注意:

字串的雜湊處理是一個單向計算,有一點像位元加總檢查(Check Sum),它可以重複但不容易還原。同一個密碼經雜湊處理後可以得到相同的結果,但對於某個經過雜湊處理的結果而言,你很難判斷原來的密碼是什麼。


Secret應用程式如何運作?
 

你可以選擇任何一種檔案,用Secret應用程式予以加密或解密。在加密後,檔案的開端處會被插入一行識別字串,以便讓Secret應用程式檢查該檔案是否已經經過加密處理。在按下「Encrypt」或「Decrypt」按鈕之前,你必須輸入密碼;如果是加密處理,必須在兩個文字方塊中輸入同樣的密碼;而如果是解密處理,只要輸入一個密碼即可。這是典型的要求密碼輸入方式,用以防止使用者打錯的密碼變成真的密碼。我們用一種可顯示、可列印、可用電子郵件傳送的格式將加密後的檔案儲存起來,即使加密前的原始檔案是二進位檔,加密後仍然可以顯示、列印或用電子郵件傳送。

對同一個檔案加密時,即使每次都使用相同的密碼,我們採用的程式技巧仍然可以使加密後的檔案每次列印或顯示的結果都不一樣。

我們把一個八字元長的隨機"加味"("Salt")字串放進檔案開頭的識別行中,作為識別行的前半段,然後以這個"加味"字串與密碼合併在一起作雜湊處理,經由雜湊處理得到的字串則作為識別行的後半段。這16個字元長的識別字串接下來被用來對檔案進行加密處理,因此,每次同一個檔案加密後的結果都不一樣。另外,在進行解密之前,識別字串可以被用來快速地查驗使用者密碼的正確性;我們把使用者輸入的密碼和"加味"字串做一次雜湊處理,然後把得到的結果與識別字串進行比對,如果密碼正確則對檔案行解密,而由於不正確的密碼無法產生正確的雜湊處理結果,因此錯誤的密碼將不會通過檢查。

從圖34-6中你可以看到,使用者選擇TEST.TXT並且在兩個文字方塊中輸入同一個密碼HARRP (你看不見密碼)。因為這個檔案未經過加密處理,所以「Encrypt」按鈕處於作用狀態而「Decrypt」按鈕在非作用狀態。圖34-7顯示「Encrypt」按鈕被按下之後的情形。由於檔案已經經過加密處理,因此「Decrypt」按鈕現在是在作用狀態,而「Encrypt」按鈕則是在非作用狀態。另外,第二個密碼欄也在非作用狀態,不讓使用者輸入密碼。


 

 圖34-6 Secret應用程式即將對TEST.TXT進行加密處理


 

 圖34-7 TEST.TXT已經經過了加密處理

圖34-8顯示的是未經過加密前TEST.TXT檔的內容,圖34-9顯示的則是加密後TEST.TXT檔的內容,你可以在加密前和加密後按下「View」按鈕看到以上兩個輸出的結果。我們把加密後的TEST.TXT予以解密,然後再使用相同的密碼做另一次的加密處理,從圖34-10中你可以看到,即使兩次加密處理的對象是同一個原始檔,兩次的結果也會完全不同。


 

 圖34-8 按下「View」按鈕查看TEST.TXT的原始內容


 

 圖34-9 按下「View」按鈕查看TEST.TXT加密後的內容


 

 圖34-10 用同一個密碼對TEST.TXT做第二次加密處理

圖34-11顯示Secret應用程式包含四個檔案。CIPHER.CLS物件類別模組和 第十八章"安全性" 中所介紹的CIPHER.CLS相同;Secret表單中的Hash函式可以把任何字串轉換為一個可重複但無法預測的八字元長字串,這個雜湊處理的結果會被用來驗證使用者在解密時輸入的密碼。View表單則是一個簡單的檔案內容瀏覽器,讓使用者在唯讀模式中可以看見加密前和加密後檔案的內容。


 

 圖34-11 Secret應用程式的專案視窗

SECRET.FRM
 

SECRET.FRM是Secret應用程式的啟始表單。如圖34-12所示,這張表單包含了一個用來輸入檔名的文字方塊、兩個用來輸入密碼的文字方塊,以及四個指令按鈕。表單上的通用對話方塊被用來協助使用者選擇檔案。


 

 圖34-12 設計階段中的Secret表單

如果要建立這張表單,請按照以下這兩張表來設定表單及控制項的屬性,然後在表單中加入後面的程式碼。

SECRET.FRM功能表項目
標題 名稱 內縮 啟用
&File mnuFile 0 True
&New mnuNew 1 False
&Open... mnuOpen 1 False
&Save mnuSave 1 False
Save &As... mnuSaveAs 1 False
- mnuFileDash1 1 True
E&xit mnuExit 1 True
&Help mnuHelp 0 True
&Contents mnuContents 1 True
&Search for Help on... mnuSearch 1 True
- mnuHelpDash1 1 True
&About... mnuAbout 1 True
SECRET.FRM物件與屬性設定
編號 *  屬性
 Form  Name

Caption

Icon

frmSecret

Secret

Secur03.ico

 Label 

1

Name

Caption

lblFile

File:

 TextBox 

2

Name txtFile
 CommonDialog 

3

Name cdlOne
 CommandButton 

4

Name

Caption

cmdBrowse

&Browse...

 CommandButton 

5

Name

Caption

cmdView

&View

 CommandButton 

6

Name

Caption

cmdEncrypt

&Encrypt

 CommandButton 

7

Name

Caption

cmdDecrypt

&Decrypt

 Label 

8

Name

Caption

lblPassword1

Enter password:

 Label 

9

Name

Caption

lblPassword2

Enter password again:

 TextBox 

10

Name

PasswordChar

Text

txtPassword1

*

(blank)

 TextBox 

11

Name

PasswordChar

Text

txtPassword2

*

(blank)

*"編號"欄中的號碼用來標示圖34-12中表單上物件的位置。

SECRET.FRM原始程式碼
 

Option Explicit

Private Sub cmdBrowse_Click()
    `Prompt user for filename
    cdlOne.DialogTitle = "Secret"
    cdlOne.Flags = cdlOFNHideReadOnly

    cdlOne.Filter = "All files (*.*)|*.*"
    cdlOne.CancelError = True
    On Error Resume Next
    cdlOne.ShowOpen
    `Grab filename
    If Err = 0 Then
        txtFile.Text = cdlOne.FileName
    End If
    On Error GoTo 0
End Sub

Private Sub cmdEncrypt_Click()
    `Make sure both passwords match exactly
    If txtPassword1.Text <> txtPassword2.Text Then
        MsgBox "The two passwords are not the same!", _
            vbExclamation, "Secret"
        Exit Sub
    End If
    `Encrypt file
    MousePointer = vbHourglass
    cmdEncrypt.Enabled = False
    cmdDecrypt.Enabled = False
    cmdView.Enabled = False
    cmdBrowse.Enabled = False
    Refresh
    Encrypt
    txtFile_Change
    MousePointer = vbDefault
End Sub

Private Sub cmdDecrypt_Click()
    MousePointer = vbHourglass
    cmdEncrypt.Enabled = False
    cmdDecrypt.Enabled = False

    cmdView.Enabled = False
    cmdBrowse.Enabled = False
    Refresh
    Decrypt
    txtFile_Change
    MousePointer = vbDefault
End Sub

Private Sub cmdView_Click()
    Dim strA As String
    Dim lngZndx As Long
    MousePointer = vbHourglass
    `Get file contents
    Open txtFile.Text For Binary As #1
    strA = Space$(LOF(1))
    Get #1, , strA
    Close #1
    Do
        lngZndx = InStr(strA, Chr$(0))
        If lngZndx = 0 Or lngZndx > 5000 Then Exit Do
        Mid$(strA, lngZndx, 1) = Chr$(1)
    Loop
    `Display file contents
    MousePointer = vbDefault
    frmView.rtfView.Text = strA
    frmView.Caption = "Secret - " & txtFile.Text
    frmView.Show vbModal
End Sub

Private Sub Form_Load()
    `Center this form
    Me.Left = (Screen.Width - Me.Width) \ 2
    Me.Top = (Screen.Height - Me.Height) \ 2
    `Disable most command buttons

    cmdEncrypt.Enabled = False
    cmdDecrypt.Enabled = False
    cmdView.Enabled = False
    `Initialize filename field
    txtFile.Text = ""
End Sub

Private Sub mnuAbout_Click()
    `Set properties
    About.Application = "Secret"
    About.Heading = _
        "Microsoft Visual Basic 6.0 Developer's Workshop"
    About.Copyright = "1998 John Clark Craig and Jeff Webb"
    About.Display
End Sub

Private Sub mnuExit_Click()
    Unload Me
End Sub

Private Sub mnuContents_Click()
    cdlOne.HelpFile = App.Path & "\..\..\Help\Mvbdw.hlp"
    cdlOne.HelpCommand = cdlHelpContents
    cdlOne.ShowHelp
End Sub

Private Sub mnuSearch_Click()
    cdlOne.HelpFile = App.Path & "\..\..\Help\Mvbdw.hlp"
    cdlOne.HelpCommand = cdlHelpPartialKey
    cdlOne.ShowHelp
End Sub

Private Sub txtFile_Change()
    Dim lngFileLen As Long

    Dim strHead As String
    `Check to see whether file exists
    On Error Resume Next
    lngFileLen = Len(Dir(txtFile.Text))
    `Disable buttons if filename isn't valid
    If Err <> 0 Or lngFileLen = 0 Or Len(txtFile.Text) = 0 Then
        cmdEncrypt.Enabled = False
        cmdDecrypt.Enabled = False
        cmdView.Enabled = False
        lblPassword1.Enabled = False
        txtPassword1.Enabled = False
        lblPassword2.Enabled = False
        txtPassword2.Enabled = False
        txtPassword2.Text = ""
        Exit Sub
    End If
    `Get first 8 bytes of selected file
    Open txtFile.Text For Binary As #1
    strHead = Space(8)
    Get #1, , strHead
    Close #1
    `Check to see whether file is already encrypted
    If strHead = "[Secret]" Then
        cmdEncrypt.Enabled = False
        cmdDecrypt.Enabled = True
        lblPassword2.Enabled = False
        txtPassword2.Enabled = False
        txtPassword2.Text = ""
    Else
        cmdEncrypt.Enabled = True
        cmdDecrypt.Enabled = False
        lblPassword2.Enabled = True
        txtPassword2.Enabled = True
    End If

    lblPassword1.Enabled = True
    txtPassword1.Enabled = True
    cmdBrowse.Enabled = True
    cmdView.Enabled = True
End Sub

Sub Encrypt()
    Dim strHead As String
    Dim strT As String
    Dim strA As String
    Dim cphX As New Cipher
    Dim lngN As Long
    Open txtFile.Text For Binary As #1
    `Load entire file into strA
    strA = Space$(LOF(1))
    Get #1, , strA
    Close #1
    `Prepare header string with salt characters
    strT = Hash(Date & Str(Timer))
    strHead = "[Secret]" & strT & Hash(strT & txtPassword1.Text)
    `Do the encryption
    cphX.KeyString = strHead
    cphX.Text = strA
    cphX.DoXor
    cphX.Stretch
    strA = cphX.Text
    `Write header
    Open txtFile.Text For Output As #1
    Print #1, strHead
    `Write encrypted data
    lngN = 1
    Do
        Print #1, Mid(strA, lngN, 70)
        lngN = lngN + 70

    Loop Until lngN > Len(strA)
    Close #1
End Sub

Sub Decrypt()
    Dim strHead As String
    Dim strA As String
    Dim strT As String
    Dim cphX As New Cipher
    Dim lngN As Long
    `Get header (first 18 bytes of encrypted file)
    Open txtFile.Text For Input As #1
    Line Input #1, strHead
    Close #1
    `Check for correct password
    strT = Mid(strHead, 9, 8)
    If InStr(strHead, Hash(strT & txtPassword1.Text)) <> 17 Then
        MsgBox "Sorry, this is not the correct password!", _
            vbExclamation, "Secret"
        Exit Sub
    End If
    `Get file contents
    Open txtFile.Text For Input As #1
    `Read past the header
    Line Input #1, strHead
    `Read and build the contents string
    Do Until EOF(1)
        Line Input #1, strT
        strA = strA & strT
    Loop
    Close #1
    `Decrypted file contents
    cphX.KeyString = strHead
    cphX.Text = strA

    cphX.Shrink
    cphX.DoXor
    strA = cphX.Text
    `Replace file with decrypted version
    Kill txtFile.Text
    Open txtFile.Text For Binary As #1
    Put #1, , strA
    Close #1
End Sub

Function Hash(strA As String) As String
    Dim cphHash As New Cipher
    cphHash.KeyString = strA & "123456"
    cphHash.Text = strA & "123456"
    cphHash.DoXor
    cphHash.Stretch
    cphHash.KeyString = cphHash.Text
    cphHash.Text = "123456"
    cphHash.DoXor
    cphHash.Stretch
    Hash = cphHash.Text
End Function

VIEW.FRM


這張表單很簡單,只含一個RichTextBox控制項,用來在唯讀模式下顯示檔案內容。圖34-13顯示的是設計階段中的View表單。


 

 圖34-13 設計階段中的View表單

如果要建立這張表單,請按照下表來設定表單和控制項的屬性,然後在表單中加入後面的程式碼。

VIEW.FRM物件與屬性設定
屬性
 Form 
Name

Caption

MaxButton

MinButton

frmView

Secret - View File

False

False

 RichTextBox 
Name

Scrollbars

Locked

rtfView

3 - Both

True

VIEW.FRM原始程式碼
 

Option Explicit

Dim mblnBeenHereDoneThis As Boolean

Private Sub Form_Load()

    mblnBeenHereDoneThis = False

Private Sub Form_Resize()
    `Center this form, but only the first time
    If mblnBeenHereDoneThis = False Then
        Me.Left = (Screen.Width - Me.Width) \ 2
        Me.Top = (Screen.Height - Me.Height) \ 2
        mblnBeenHereDoneThis = True
    End If
    `Size RichTextBox to fill form
    rtfView.Move 0, 0, Me.ScaleWidth, Me.ScaleHeight
End Sub

CIPHER.CLS
 

這個物件類別模組是Secret應用程式的核心,在介紹 第十八章"安全性" 時,我們曾經詳細地討論過這些Cipher物件,在這裡我們還是把它們列出來,以方便讀者閱讀。

`CIPHER.CLS
Option Explicit

Private mstrKey As String
Private mstrText As String

`~~~.KeyString
`A string (key) used in encryption and decryption
Public Property Let KeyString(strKey As String)
    mstrKey = strKey
    Initialize
End Property

`~~~.Text
`Write text to be encrypted or decrypted
Public Property Let Text(strText As String)
    mstrText = strText
End Property

`Read text that was encrypted or decrypted
Public Property Get Text() As String
    Text = mstrText
End Property

`~~~.DoXor
`Exclusive-or method to encrypt or decrypt
Public Sub DoXor()
    Dim lngC As Long
    Dim intB As Long
    Dim lngN As Long
    For lngN = 1 To Len(mstrText)
        lngC = Asc(Mid(mstrText, lngN, 1))
        intB = Int(Rnd * 256)
        Mid(mstrText, lngN, 1) = Chr(lngC Xor intB)
    Next lngN
End Sub

`~~~.Stretch
`Convert any string to a printable, displayable string
Public Sub Stretch()
    Dim lngC As Long
    Dim lngN As Long
    Dim lngJ As Long
    Dim lngK As Long
    Dim lngA As Long
    Dim strB As String
    lngA = Len(mstrText)
    strB = Space(lngA + (lngA + 2) \ 3)
    For lngN = 1 To lngA
        lngC = Asc(Mid(mstrText, lngN, 1))
        lngJ = lngJ + 1
        Mid(strB, lngJ, 1) = Chr((lngC And 63) + 59)
        Select Case lngN Mod 3
        Case 1
            lngK = lngK Or ((lngC \ 64) * 16)
        Case 2
            lngK = lngK Or ((lngC \ 64) * 4)
        Case 0
            lngK = lngK Or (lngC \ 64)
            lngJ = lngJ + 1
            Mid(strB, lngJ, 1) = Chr(lngK + 59)
            lngK = 0
        End Select
    Next lngN
    If lngA Mod 3 Then
        lngJ = lngJ + 1
        Mid(strB, lngJ, 1) = Chr(lngK + 59)
    End If
    mstrText = strB
End Sub

`~~~.Shrink
`Inverse of the Stretch method;
`result can contain any of the 256-byte values
Public Sub Shrink()
    Dim lngC As Long
    Dim lngD As Long
    Dim lngE As Long
    Dim lngA As Long
    Dim lngB As Long
    Dim lngN As Long
    Dim lngJ As Long
    Dim lngK As Long
    Dim strB As String
    lngA = Len(mstrText)
    lngB = lngA - 1 - (lngA - 1) \ 4
    strB = Space(lngB)
    For lngN = 1 To lngB
        lngJ = lngJ + 1
        lngC = Asc(Mid(mstrText, lngJ, 1)) - 59
        Select Case lngN Mod 3
        Case 1
            lngK = lngK + 4
            If lngK > lngA Then lngK = lngA
            lngE = Asc(Mid(mstrText, lngK, 1)) - 59
            lngD = ((lngE \ 16) And 3) * 64
        Case 2
            lngD = ((lngE \ 4) And 3) * 64
        Case 0
            lngD = (lngE And 3) * 64
            lngJ = lngJ + 1
        End Select
        Mid(strB, lngN, 1) = Chr(lngC Or lngD)
    Next lngN
    mstrText = strB
End Sub

`Initializes random numbers using the key string
Private Sub Initialize()
    Dim lngN As Long
    Randomize Rnd(-1)
    For lngN = 1 To Len(mstrKey)
        Randomize Rnd(-Rnd * Asc(Mid(mstrKey, lngN, 1)))
    Next lngN
End Sub

BitPack應用程式
 

雖然Visual Basic提供了機器碼編譯器,但是在面對某些種類的問題時,C語言的DLL在速度上還是比Visual Basic略勝一籌。BitPack應用程式呼叫由C語言建立的DLL,展示了這種DLL在Visual Basic應用程式中執行時驚人的處理速度。

BITPACK.DLL被設計來處理位元組陣列中的每一個單獨的位元,它包含了三個函式:BitGet傳回某個位元的狀態;BitSet把某個位置上的位元設為1;BitClr設定某個位元為0。只要把位元組陣列和位元的序號傳給這些函式,C程式碼會為你完成工作。例如,要取得某個位元組陣列中第542個位元的狀態,BitGet函式可以很快地找到第67位元組後面的第6個位元,取得該位元的值,如果其值為1則傳回1,其值為0則傳回0。在32位元的Visual Basic裡,Byte陣列可以宣告得很大,因此我們可以把近乎無限大的位元集合放在一個Byte陣列中,用上述這些函式加以存取處理。

建立一個質數表
 

以上這些函式最能發揮它們效用的地方在資料擷取和開關控制處理這兩方面。數以千計的控制開關可以用位元集合來代表,把這個位元集合放在一個位元組陣列中,我們可以用這些函式來加以維護。在本書的範例中,我們用一個小程式建立了一個質數表,用以展示這些函式的用法。

我們以一個位元陣列代表這個質數表,陣列中的每一個位元都代表奇數(所有質數皆為奇數);如果某個位元的內含值是0,這個位元所代表的奇數就是一個質數,如果是1則代表非質數。因為DLL速度的關係,我們可以在很短的時間內建立一個龐大的質數表,例如範圍從1到1,000,000的質數表。了解到我們呼叫了多少次BitSet函式和BitGet函式之後,你就可以感覺得到C語言DLL函式的威力了。

建立BitPack DLL專案檔


在發展BitPack應用程式之前,必須先建C立語言的DLL,才能用BitPack應用程式產生上述的質數表。


注意:

因為本書著重於使用Microsoft的工具來建立在Windows 95下執行的應用程式,所以我們簡化了下面這個範例DLL的程式碼,這樣應該可以幫你了解建立DLL的重點步驟。如果你用的C語言編譯器並不是Microsoft Visual C++ 6.0,你可能要對以下的發展步驟和檔案內容作一些修改。關於建立DLL的詳細資訊,請參考相關的技術文件。


以下這三個檔是你在建立Viasual C++ 專案時唯一需要用到的檔案。請在32位元版的C++ 編譯器中新增一個專案,選擇DLL作為專案的類別,輸入BITPACK做為專案名稱。當你被問到要建立哪一種DLL時,請選擇"A DLL That Exports Some Symbols"選項。接下來,在專案中建立一個DEF檔,把下面這幾行輸入到檔案中,然後把檔案存成BITPACK.DEF:

;BitPack.def
LIBRARY BitPack

EXPORTS
    BitGet
    BitSet
    BitClr

DEF把這個DLL專案所要提供的函式名稱向外界宣佈。也就是說,DEF檔所列出的函式就是你可以在Visual Basic應用程式中所呼叫的函式。

你必須修改兩個由Visual C++ 自動產生的檔案。BITPACK.CPP包含了三個只有一行程式碼的函式,這些函式執行所有必要的定址、遮罩以及其他的位元運算,以便能夠對位元組陣列中某個特定的位元作存取動作。請將下面的程式碼輸入到一個新檔案中,然後把這個檔案存成BITPACK.CPP,而且讓這個檔案包括在你的Visual C++ 專案中。

// BitPack.cpp : Defines the entry point for the DLL application
//

#include "stdafx.h"
#include "BitPack.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}
BITPACK_API int _stdcall BitGet(LPBYTE bytes, int bitpos)
{
    return( bytes[bitpos >> 3] & (1 << (bitpos % 8)) ? 1: 0);
}

BITPACK_API int _stdcall BitSet(LPBYTE bytes, int bitpos)
{
    return( bytes[bitpos >> 3] |= (1 << (bitpos % 8)));
}

BITPACK_API int _stdcall BitClr(LPBYTE bytes, int bitpos)
{
    return( bytes[bitpos >> 3] &= ~(1 << (bitpos % 8)));
}

另一個需要修改的是BITPACK.H檔,它用來宣告BitPack的所有函式。修改後的BITPACK.H內容如下:

// The following ifdef block is the standard way of creating 
// macros that make exporting from a DLL simpler. All files 
// within this DLL are compiled with the BITPACK_EXPORTS
// symbol defined on the command line. This symbol should not 
// be defined on any project that uses this DLL. In this way, any 
// other project whose source files include this file see 
// BITPACK_API functions as being imported from a DLL, whereas 
// this DLL sees symbols defined with this macro as being exported.
#ifdef BITPACK_EXPORTS
#define BITPACK_API __declspec(dllexport)
#else
#define BITPACK_API __declspec(dllimport)
#endif

BITPACK_API int _stdcall BitGet(LPBYTE bytes, int bitpos);
BITPACK_API int _stdcall BitSet(LPBYTE bytes, int bitpos);
BITPACK_API int _stdcall BitClr(LPBYTE bytes, int bitpos);

在Visual C++ 環境中按下「Build All」的按鈕,編譯器會把上面的兩個檔案加以連結編譯,然後產生一個BITPACK.DLL模組。現在,把BITPACK.DLL模組複製到Windows的SYSTEM目錄下,這樣,在Visual Basic中使用的Declare陳述式就可以自動地找到這個DLL檔,BitPack裡的三個函式才能在Visual Basic的應用程式中被宣告和使用。

BITPACK.FRM
 

這張表單的用途在於要求使用者輸入預期中最大質數的上限值,然後表單會呼叫BitPack DLL中的函式,產生一個以位元組陣列為代表的質數表,並且列出所有的質數。我們在表單上放置了一個進度指示器,這樣你可以觀察程式在處理質數計算時的速度。在筆者的電腦上,建立質數表的速度比列印結果的速度還快。圖34-14顯示的是執行中的BitPack表單,它正在計算1到1,000,000之間所有的質數。


 

 圖34-14 執行中的BitPack表單

程式的輸出結果被寫到C:\WINDOWS\DESKTOP\PRIMES.TXT這個檔案中,當然,你可以改變這個路徑或是檔名。我們在BITPACK.FRM程式碼的開頭不遠處定義了一個常數FileName,用這個常數來代表檔名和路徑。如果你建立的質數表是一個很大的質數表,這個輸出檔可能也會相當地大。如果要約略估算輸出檔的大小,你可以把上限值除以2,例如,你要找所有小於200,000的質數,PRIME.TXT的檔案大小約為100,000位元組。

圖34-15顯示的是PRIME.TXT的內容,這是BitPack應用程式在計算所有小於1,000,000的質數時所找到前面部分的質數。

BitPack表單提供了一個ProgressBar控制項的實作範例,在這張表單中,這個ProgressBar控制項被用來顯示質數表產生的進度,也用來顯示輸出檔PRIMES.TXT產生的進度。我們讓ProgressBar控制項的Visible屬性和指令按鈕控制項的Visible屬性相互切換,這樣你不是看到ProgressBar控制項,就是看到指令按鈕,但決不會同時看到兩個控制項。


 

 圖34-15 PRIMES.TXT的內容


 

 圖34-16 顯示的是設計中的BitPack表單

如果要建立BITPACK.FRM,請按照以下這兩張表來設定表單及控制項的屬性,然後在表單中加入後面的程式碼。

BITPACK.FRM功能表項目
標題 名稱 內縮 啟用
&File mnuFile 0 True
&New mnuNew 1 False
&Open... mnuOpen 1 False
&Save mnuSave 1 False
Save &As... mnuSaveAs 1 False
- mnuFileDash1 1 True
E&xit mnuExit 1 True
&Help mnuHelp 0 True
&Contents mnuContents 1 True
&Search for Help on... mnuSearch 1 True
- mnuHelpDash1 1 True
&About... mnuAbout 1 True
BITPACK.FRM物件與屬性設定
編號 *  屬性
 Form  Name

BorderStyle

Caption

frmBitPack

3 - Fixed Dialog

BitPack - Prime Numbers

 Label 

1

Name

Caption

lblPrompt

Enter prime number ceiling...

 TextBox 

2

Name txtMaxPrime
 CommonDialog 

3

Name cdlOne
 Label 

4

Name lblStatus
 CommandButton 

5

Name

Caption

cmdPrimes

Generate PRIMES.TXT

 ProgressBar 

6

Name prgOne

*"編號"欄中的號碼用來標示圖34-16中表單上物件的位置。

BITPACK.FRM原始程式碼
 

Option Explicit

Private Declare Function BitGet _
Lib "BitPack.dll" ( _
    ByRef bytB As Byte, _
    ByVal lngN As Long _
) As Long

Private Declare Function BitSet _
Lib "BitPack.dll" ( _
    ByRef bytB As Byte, _
    ByVal lngN As Long _
) As Long

Private Declare Function BitClr _
Lib "BitPack.dll" ( _
    ByRef bytB As Byte, _
    ByVal lngN As Long _
) As Long

`Change output path or filename here
Const FileName = "C:\Windows\Desktop\Primes.txt"

Private Sub cmdPrimes_Click()
    Dim lngN As Long
    Dim lngI As Long
    Dim lngJ As Long
    Dim lngK As Long

    Dim lngNext As Long
    Dim lngLast As Long
    Dim bytAry() As Byte
    Dim strP As String
    `Show hourglass while busy
    MousePointer = vbHourglass
    cmdPrimes.Visible = False
    prgOne.Visible = True
    prgOne.Value = 0
    `Get largest prime number specified
    lngN = Abs(Val(txtMaxPrime.Text))
    `Match only odd numbers to bits in byte array
    ReDim bytAry(lngN \ 16)
    `Keep user informed of progress
    lblStatus.Caption ="Generating prime numbers tableDear John, How Do I... "
    Refresh
    `Process byte array; 0 bits represent prime numbers
    lngK = (lngN - 3) \ 2
    For lngI = 0 To lngK
        `If next number is primeDear John, How Do I... 
        If BitGet(bytAry(0), lngI) = 0 Then
            `Dear John, How Do I... set bits that are multiples
            For lngJ = 3 * lngI + 3 To lngK Step 2 * lngI + 3
                BitSet bytAry(0), lngJ
            Next lngJ
            `Update progress bar, but not too often
            lngNext = Int(100 * lngI / lngK)
            If lngNext <> lngLast Then
                lngLast = lngNext
                prgOne.Value = lngNext
            End If
        End If
    Next lngI
    `Keep user informed

    lblStatus.Caption = "Writing prime numbers fileDear John, How Do I... "
    lngLast = 0
    prgOne.Value = 0
    Refresh
    `Write primes to file on desktop
    Open FileName For Output As #1
    `Bit table starts at 3, so output 2 as prime
    Print #1, "Prime numbers up to" & Str$(lngN) & vbCrLf
    strP = "2"
    For lngI = 0 To lngK
        `If prime numberDear John, How Do I... 
        If BitGet(bytAry(0), lngI) = 0 Then
            `Concatenate number to string for output
            strP = strP & Str$(lngI + lngI + 3)
            `If string is long enoughDear John, How Do I... 
            If Len(strP) > 65 Then
                `Output string to file
                Print #1, LTrim$(strP)
                `Prepare for next line of output
                strP = ""
                `Update progress bar, but not too often
                lngNext = Int(100 * lngI / lngK)
                If lngNext > lngLast Then
                    lngLast = lngNext
                    prgOne.Value = lngNext
                End If
            End If
        End If
    Next lngI
    `Print any last-line primes
    Print #1, LTrim$(strP)
    Close #1
    `Set form to original visible state
    lblStatus.Caption = ""

    cmdPrimes.Visible = True
    prgOne.Visible = False
    MousePointer = vbDefault
End Sub

Private Sub Form_Load()
    txtMaxPrime.Text = ""
    lblStatus.Caption = ""
    prgOne.Visible = False
End Sub

Private Sub mnuAbout_Click()
    `Set properties
    About.Application = "BitPack"
    About.Heading = _
        "Microsoft Visual Basic 6.0 Developer's Workshop"
    About.Copyright = "1998 John Clark Craig and Jeff Webb"
    About.Display
End Sub

Private Sub mnuExit_Click()
    Unload Me
End Sub

Private Sub mnuContents_Click()
    cdlOne.HelpFile = App.Path & "\..\..\Help\Mvbdw.hlp"
    cdlOne.HelpCommand = cdlHelpContents
    cdlOne.ShowHelp
End Sub

Private Sub mnuSearch_Click()
    cdlOne.HelpFile = App.Path & "\..\..\Help\Mvbdw.hlp"
    cdlOne.HelpCommand = cdlHelpPartialKey
    cdlOne.ShowHelp
End Sub

在BitPack表單中,我們把位元組陣列bytAry() 的第一個元素傳給BITPACK.DLL裡的函式,你也可以傳遞一個不在陣列中的單一Byte變數給這些函式,在這種情形下,BitPos參數應該在0到7的範圍之間。為了得到最快的速度,我們在DLL的函式中故意不做數值範圍檢查的動作。你可以在Visual Basic應用程式中做這個動作,防止BitPos超過範圍。我們知道,位元組陣列中的元素個數是100,那麼BitPos的合法範圍應該是從0到807。

為了要用質數表計算質數,我們把奇數3,5,7.... 以位元0,1,2..... 來代表。這樣,位元組陣列中的每8個位元就可以涵蓋16個整數的範圍。另一方面,由於Visual Basic本身支援巨大的Byte陣列,因此,理論上你可以用這個程式計算出相當大的質數。

Dialogs應用程式
 

通用對話方塊控制項提供了許多威力強大的選項,讓使用者與系統可以透過一些標準的界面交談。Dialogs應用程式舉例說明了如何運用通用對話方塊控制項,呼叫五種由它所提供的對話方塊。我們在一個工具列上設計了五個按鈕,分別可以啟動「開啟舊檔」、「另存新檔」、「色彩」、「字型」以及「列印」等五個對話方塊。使用者選擇了這些選項之後,對應的對話方塊會被顯示出來,但是不會影響任何檔案或設定。例如,你可以用Dialogs應用程式叫出「另存新檔」對話方塊,選擇系統中的任何一個檔案,但是實際上不會有任何檔案受到影響。

圖34-17顯示的是執行中的Dialogs應用程式,圖34-18到圖34-22顯示的是由Dialogs工具列上的五個按鈕所啟動的五個不同的標準對話方塊。


 

 圖34-17 執行中的Dialogs應用程式


 

 圖34-18 由通用對話方塊叫出的「開啟舊檔」對話方塊


 

 圖34-19 由通用對話方塊叫出的「另存新檔」對話方塊


 

 圖34-20 由通用對話方塊叫出的「色彩」對話方塊


 

 圖34-21 由通用對話方塊叫出的「字型」對話方塊


 

 圖34-22 由通用對話方塊叫出的「列印」對話方塊

筆者在發展其他的應用程式時,經常利用Dialogs應用程式。例如,如果需要在新的應用程式中加入選擇色彩的功能時,只要從Dialogs應用程式的程式碼中,複製相關的部分到新的應用程式中,在新的應用程式中加入一個通用對話方塊即可,若有特殊需要再修改程式。這種方式可以省下很多的時間,比查詢線上說明再一步步建立通用對話方塊來得快多了!

特殊功能
 

Dialogs應用程式運用許多靈活的技巧提供了一些特殊的功能。

About和About2
 

這個應用程式展示了兩種不同的「About」對話方塊,它的「Help」功能表中包含了「About」和「About2」兩個選項。第一種「About」對話方塊是我們在本書中使用得最多的「關於」對話方塊,而「About2」則是在 第三十一章"日期與時間" 中介紹的另一種「關於」對話方塊。雖然這兩種對話方塊在外觀上相似,但事實上它們背後的設計技巧卻全然不同。

日落景色的背景
 

我們在 第十四章"繪圖技巧" 中曾經介紹過製作表單背景的技巧,我們把這個技巧加以修改,使原來"藍黑漸層"的背景變為"紅黃漸層",就像日落景色一樣。你可以仿此方法,很容易地就可以改變背景的色調。

隱藏的訊息方塊
 

在 第十八章"安全性" 中,我們曾經介紹過"復活節彩蛋"訊息方塊,在這裡我們也把這項功能添加在Dialogs應用程式裡。隱藏式的訊息方塊可以用很多種方式啟動,在這個應用程式中,我們把四次滑鼠按鍵事件(Click)所發生的位置記錄下來,如果說這四次按鍵的位置和順序剛好符合程式所要求的按鍵位置和順序,隱藏的訊息方塊就會顯示出來。也就是說,如果你在表單中央圖片方塊的左上角、右上角、右下角和左下角這四個位置上,依序按下滑鼠左鍵,那麼隱藏的訊息方塊就會出現,並且停留五秒鐘後自動消失。

表單定位
 

在整本書的範例中,我們一直在表單的Load事件程序中把表單定位在螢幕中央,如果稍微修改這個表單定位技巧,你可以把表單定位在螢幕的任何一處。如果要知道這個修改後的螢幕定位技巧實際的運作情形,請在Dialogs應用程式主表單中央的圖形上隨處按下滑鼠左鍵;如果在這張圖形上按鍵的位置是圖形由左至右的四分之一、由頂到底的四分之三處,那麼表單的中心點會跳到整個螢幕上相同的相對位置上(在螢幕由左至右的四分之一、由頂到底的四分之三處),兩秒鐘之後,表單又會跳回原來螢幕中央的位置。這些表單重新定位的動作都在picScreen_Click事件程序中,你可以看看這裡的程式碼以了解這一切動作是如何運作的。

圖34-23顯示滑鼠游標在上述的位置上,圖34-24顯示表單被暫時移到了新的位置上。


 

 圖34-23 點選圖形的任何一處使表單暫時移到新的位置上


 

 圖34-24 整張表單會暫時移動到指定的位置上

應用程式的所有檔案


Dialogs應用程式中有四個檔案,除了主表單之外,還有兩張「About」表單和一張隱藏的訊息表單。圖34-25顯示Dialogs應用程式的專案內容。


 

 圖34-25 Dialogs應用程式的專案內容

DIALOGS.FRM
 

Dialogs應用程式把Windows 95的桌面圖片放在表單中央的圖形方塊中(前面提過,程式執行時,若在這張圖形的任何一處按下滑鼠左鍵,表單就會移動到真實桌面上相同的對應位置上)。圖34-26顯示的是設計中的Dialog表單。


 

 圖34-26 設計中的Dialog表單

如果要建立DIALOGS.FRM,請按照以下這兩張表來設定表單及控制項的屬性,然後在表單中加入後面的程式碼。

DIALOGS.FRM功能表項目
標題 名稱 內縮 啟用
&File mnuFile 0 True
&New mnuNew 1 False
&Open... mnuOpen 1 False
&Save mnuSave 1 False
Save &As... mnuSaveAs 1 False
- mnuFileDash1 1 True
E&xit mnuExit 1 True
&Help mnuHelp 0 True
&Contents mnuContents 1 True
&Search for Help on... mnuSearch 1 True
- mnuHelpDash1 1 True
&About... mnuAbout 1 True
About&2... mnuAbout2 1 True
DIALOGS.FRM物件與屬性設定
編號 *  屬性
 Form  Name

Caption

BorderStyle

Icon

frmDialogs

Dialogs

3 - Fixed Dialog

Pc01.ico

 PictureBox 

1

Name

Align

picTop

1 - Align Top

 CommandButton 

2

Name

Caption

cmdOpen

&Open

 CommandButton 

3

Name

Caption

cmdSave

&Save

 CommandButton 

4

Name

Caption

cmdColor

&Color

 CommandButton 

5

Name

Caption

cmdFont

&Font

 CommandButton 

6

Name

Caption

cmdPrint

&Print

 PictureBox 

7

Name

AutoSize

Picture

picScreen

True

DESKTOP.BMP

 CommonDialog 

8

Name cdlOne
 Timer 

9

Name

Interval

tmrClock

100

 Timer 

10

Name

Enabled

Interval

tmrRelocate

False

2000

 StatusBar 

11

Name

Align

Style

stbBottom

2 - vbAlignBottom

1 - sbrSimple

*"編號"欄中的號碼用來標示圖34-26中表單上物件的位置

DIALOGS.FRM原始程式碼
 

Option Explicit

Dim mvntX, mvntY
Dim mvntLastSec
Dim mvntEggX(1 To 4)
Dim mvntEggY(1 To 4)

Private Sub Form_Click()
    Dim intI As Integer
    `Keep track of last four clicks on form
    For intI = 1 To 3
        mvntEggX(intI) = mvntEggX(intI + 1)
        mvntEggY(intI) = mvntEggY(intI + 1)
    Next intI
    mvntEggX(4) = mvntX
    mvntEggY(4) = mvntY
    `Check for correct sequence and position
    If Abs(mvntEggX(1) - 70) < 30 And _
        Abs(mvntEggY(1) - 60) < 30 And _
        Abs(mvntEggX(2) - 360) < 30 And _
        Abs(mvntEggY(2) - 60) < 30 And _
        Abs(mvntEggX(3) - 360) < 30 And _

        Abs(mvntEggY(3) - 290) < 30 And _
        Abs(mvntEggX(4) - 70) < 30 And _
        Abs(mvntEggY(4) - 290) < 30 Then
        `Display hidden message
        dlgEgg.Show vbModal
    End If
End Sub

Private Sub Form_Load()
    `Center this form
    Me.Left = (Screen.Width - Me.Width) \ 2
    Me.Top = (Screen.Height - Me.Height) \ 2
    `Adjust button bar height
    picTop.Height = cmdOpen.Height + _
        (picTop.Height - picTop.ScaleHeight)
End Sub

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
    `Signal timer to update status bar
    mvntLastSec = -1
    `Keep track of mouse location
    mvntX = X

    mvntY = Y
End Sub

Private Sub Form_Paint()
    Dim lngN As Long
    With Me
        .ScaleMode = vbPixels
        .DrawStyle = 5 `Transparent
        .DrawWidth = 1
    End With

    `Draw sunset background (fade from red to yellow)
    For lngN = 0 To ScaleHeight Step ScaleHeight \ 16
        Line (-1, lngN - 1) -
(ScaleWidth, lngN + ScaleHeight \ 16), _
            RGB(255, lngN * 255 \ ScaleHeight, 0), BF
    Next lngN
End Sub

Private Sub mnuAbout_Click()
    `Set properties for the About dialog
    About.Application = "Dialogs"
    About.Heading = _
        "Microsoft Visual Basic 6.0 Developer's Workshop"
    About.Copyright = "1998 John Clark Craig and Jeff Webb"
    About.Display
End Sub

Private Sub mnuAbout2_Click()
    `Display the About2 dialog
    frmAbout2.Display
End Sub

Private Sub mnuExit_Click()
    Unload Me
End Sub

Private Sub mnuContents_Click()
    cdlOne.HelpFile = App.Path & "\..\..\Help\Mvbdw.hlp"
    cdlOne.HelpCommand = cdlHelpContents
    cdlOne.ShowHelp
End Sub

Private Sub mnuSearch_Click()
    cdlOne.HelpFile = App.Path & "\..\..\Help\Mvbdw.hlp"

    cdlOne.HelpCommand = cdlHelpPartialKey
    cdlOne.ShowHelp
End Sub

Private Sub picScreen_Click()
    Dim vntXpct
    Dim vntYpct
    `Determine mouse's relative position in picture
    vntXpct = 100 * mvntX \ picScreen.ScaleWidth
    vntYpct = 100 * mvntY \ picScreen.ScaleHeight
    `Move form's center to same relative position on screen
    Me.Left = Screen.Width * vntXpct \ 100 - Me.Width \ 2
    Me.Top = Screen.Height * vntYpct \ 100 - Me.Height \ 2
    `Set timer to move form back later
    tmrRelocate.Enabled = True
End Sub

Private Sub picScreen_MouseMove(Button As Integer, _
    Shift As Integer, X As Single, Y As Single)
    `Keep track of mouse location
    mvntX = X
    mvntY = Y
    `Update status message at bottom of form
    stbBottom.SimpleText = "Click to temporarily relocate " & _
        "center of this form on desktop"
    `Signal timer not to display date and time in status bar
    mvntLastSec = -2
End Sub

Private Sub cmdColor_Click()
    `Set flags for Color dialog box
    cdlOne.Flags = cdlCCRGBInit
    `Show Color dialog box
    cdlOne.ShowColor

    `Display selected color value
    MsgBox "&H" & Hex$(cdlOne.Color), , _
        "Selected colorDear John, How Do I... "
End Sub

Private Sub cmdFont_Click()
    Dim strTab2 As String
    strTab2 = vbTab & vbTab
    `Set flags for Font dialog box
    cdlOne.Flags = cdlCFWYSIWYG + cdlCFBoth + cdlCFScalableOnly
    `Show Font dialog box
    cdlOne.ShowFont
    `Display selected font values
    MsgBox _
        "Font Name:" & vbTab & cdlOne.FontName & vbCrLf & _
        "Font Size:" & strTab2 & cdlOne.FontSize & vbCrLf & _
        "Bold:" & strTab2 & cdlOne.FontBold & vbCrLf & _
        "Italic:" & strTab2 & cdlOne.FontItalic, , _
        "Selected fontDear John, How Do I... "
End Sub

Private Sub cmdOpen_Click()
    `Set up sample filter for Open dialog box
    Dim strBat As String
    Dim strTxt As String
    Dim strAll As String
    strBat = "Batch Files (*.bat)|*.bat"
    strTxt = "Text Files (*.txt)|*.txt"
    strAll = "All Files (*.*)|*.*"
    cdlOne.Filter = strBat & "|" & strTxt & "|" & strAll
    `Set default filter to third one listed
    cdlOne.FilterIndex = 3
    `Hide "ReadOnly" check box

    cdlOne.Flags = cdlOFNHideReadOnly
    `Deselect previously selected file, if any
    cdlOne.FileName = ""
    `Show Open dialog box
    cdlOne.ShowOpen
    `Display selected filename
    If cdlOne.FileName = "" Then Exit Sub
    MsgBox cdlOne.FileName, , "Selected fileDear John, How Do I... "
End Sub

Private Sub cmdPrint_Click()
    Dim strPrintToFile As String
    `Set flags for Print dialog box
    cdlOne.Flags = cdlPDAllPages + cdlPDNoSelection
    `Set imaginary page range
    cdlOne.Min = 1
    cdlOne.Max = 100
    cdlOne.FromPage = 1
    cdlOne.ToPage = 100
    `Show Print dialog box
    cdlOne.ShowPrinter
    `Extract some printer data
    If cdlOne.Flags And cdlPDPrintToFile Then
        strPrintToFile = "Yes"
    Else
        strPrintToFile = "No"
    End If
    `Display selected print values
    MsgBox _
        "Begin Page:" & vbTab & cdlOne.FromPage & vbCrLf & _
        "End Page:" & vbTab & cdlOne.ToPage & vbCrLf & _
        "No. Copies:" & vbTab & cdlOne.Copies & vbCrLf & _
        "Print to File:" & vbTab & strPrintToFile _

        , , "Selected print informationDear John, How Do I... "
End Sub

Private Sub cmdSave_Click()
    `Set up filter for Save As dialog box
    Dim strBat As String
    Dim strTxt As String
    Dim strAll As String
    strBat = "Batch Files (*.bat)|*.bat"
    strTxt = "Text Files (*.txt)|*.txt"
    strAll = "All Files (*.*)|*.*"
    cdlOne.Filter = strBat & "|" & strTxt & "|" & strAll
    `Set default filter to third one listed
    cdlOne.FilterIndex = 3
    `Hide ReadOnly check box
    cdlOne.Flags = cdlOFNHideReadOnly
    `Deselect previously selected file, if any
    cdlOne.FileName = ""
    `Show the Save As dialog box
    cdlOne.ShowSave
    `Display the selected file
    If cdlOne.FileName = "" Then Exit Sub
    MsgBox cdlOne.FileName, , "`Save As' fileDear John, How Do I... "
End Sub

Private Sub tmrRelocate_Timer()
    `Relocate form once per move
    tmrRelocate.Enabled = False
    `Center this form
    Me.Left = (Screen.Width - Me.Width) \ 2
    Me.Top = (Screen.Height - Me.Height) \ 2
End Sub

Private Sub tmrClock_Timer()

    Dim vntSec
    vntSec = Second(Now)
    If vntSec = mvntLastSec Then Exit Sub
    If mvntLastSec = -2 Then Exit Sub
    mvntLastSec = vntSec
    `Update date and time in status line
    stbBottom.SimpleText = Format(Date, "Long Date") & _
        Space$(5) & Format(Time, "hh:mm AMPM")
End Sub

DLGEGG.FRM
 

DLGEGG.FRM是一張很簡單的表單,用來顯示預先隱藏的訊息,我們用一個計時器控制項讓表單出現後五秒鐘自動關閉。你可以改變表單中的訊息以及表單關閉的方式。

圖34-27顯示的是設計階段中的dlgEgg表單。


 

 圖34-27 設計階段中的dlgEgg表單
DLGEGG.FRM物件與屬性設定
屬性
 Form 
Name

Caption

BackColor

BorderStyle

ControlBox

MaxButton

MinButton

WindowState

dlgEgg

dlgEgg

&H0000FFFF&

1 - Fixed Single

False

False

False

0 - Normal

 Label 
Name

Alignment

Caption

Font

BackColor

lblEgg

2 - Center

This "Easter egg" (hidden message) will disappear in 5 seconds.

Arial - Italic - 14

&H0000FFFF&

 Timer 
Name

Enabled

Interval

tmrQuit

True

5000

DLGEGG.FRM原始程式碼
 

Option Explicit

Private Sub tmrQuit_Timer()
    Unload Me
End Sub