25. 螢幕保護程式

如何建立螢幕保護程式?
 

螢幕保護程式的用途在於保護螢幕,避免一些不常變化的像素對螢幕造成"burn-in";現在的螢幕保護程式雖然已愈來愈著重娛樂性,但其基本的設計理念仍然沒有改變──讓所有螢幕上的像素都有機會呈現不同的顏色。因此,螢幕保護程式基本上應該顯示不斷移動與變化的圖形。

我們可以用Visual Basic來建立一個基本的螢幕保護程式:一張蓋住整個螢幕的表單,使圖形不斷移動及改變的程式碼,以及反映使用者動作、使程式停止的程序。這些就是一個螢幕保護程式的基本要件。在往後的幾節中,我們會介紹一些加強的功能。

如果要使你的Visual Basic應用程式成為Windows環境中的螢幕保護程式,必須把程式編譯成螢幕保護程式。請在「檔案」功能表中選擇「製成」,在對話方塊中點選「選項」。在「專案屬性」對話方塊的「製成」頁籤裡,在「名稱」一欄中填入 "scrnsave:" 及螢幕保護程式之名稱,例如,SCRNSAVE:MySaver。接下來,按下「確定」離開「專案屬性」對話方塊。把執行檔的副檔名改為.SCR,再按下「確定」,開始進行編譯。你必須把這個執行檔移到Windows目錄下,這樣「顯示器內容」對話方塊的「螢幕保護裝置」頁籤中才會出現這個螢幕保護程式。


注意:

如果在為螢幕保護程式命名時,你用兩個大寫的S作為檔名最前面的兩個字母,如"SSaver1.scr",那麼在「顯示器內容」對話方塊中只會列出"aver1",這是因為受到早期版本Windows的螢幕保護程式所影響。


一個基本的螢幕保護程式應符合幾個要求:當使用者移動滑鼠、按下滑鼠鍵或按下字鍵時,螢幕保護程式應該停止執行;這個應用程式不能有多個執行實體同時執行;另外,在Windows 95的環境下,它應該能處理「顯示器內容」 - 「螢幕保護裝置」的「設定」、「預覽」以及「密碼」等設定項目。

下面這個螢幕保護程式範例並未針對「顯示器內容」 - 「螢幕保護裝置」中所有的設定項目設計相對的處理程序(但是「等候」除外,因為系統會自己處理「等候」)。另外,它不會對滑鼠的移動事件有所反應。除這兩點之外,其餘的基本功能都在範例程式中。

現在請新增一個標準執行檔專案MySaver1,然後再加入一個程式模組。把程式模組命名為modMySaver1,表單命名為frmMySaver1,分別將它們存成MySaver1.frm和MySaver1.bas。在frmMySaver1中加入一個計時器控制項tmrExitNotify,把計時器控制項的Interval屬性設為1,Enabled屬性設為False;接著把表單的BorderStyle屬性設為"0 - 沒有框線",WindowsState屬性設為"2 - 最大化",最後加入下列的程式:

`MySaver1.frm
Option Explicit

`Module-level variables
Dim mblnQuit As Boolean

Private Sub Form_Paint()
    Dim lngX As Long
    `Display different graphics every time
    Randomize
    `Initialize graphics parameters
    Scale (0, 0)-(1, 1)
    BackColor = vbBlack
    `Do main processing as a loop
    Do
        `Update display
        DoGraphics
        `Yield execution
        DoEvents
    Loop Until mblnQuit = True
    `Can't quit in this context; let timer do it
    tmrExitNotify.Enabled = True
End Sub

Private Sub Form_Click()
    mblnQuit = True
End Sub

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
    mblnQuit = True
End Sub

Private Sub tmrExitNotify_Timer()
    End
End Sub

`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`This is where the real graphics drawing takes place
`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sub DoGraphics()
    `Occasionally change line color and width
    If Rnd < 0.03 Then
        ForeColor = QBColor(Int(Rnd * 16))
        DrawWidth = Int(Rnd * 9 + 1)
    End If
    `Draw circle
    Circle (Rnd, Rnd), Rnd
End Sub

把以下這段程式加入到MySaver1.bas模組中,然後把專案的啟動物件設為Sub Main。

`MySaver1.bas
Option Explicit
    
`Starting pointDear John, How Do I... .
Public Sub Main()
    Dim strCmd As String
    Dim strTwo As String
    `Process the command line
    strCmd = UCase(Trim(Command))
    strTwo = Left(strCmd, 2)
    Select Case strTwo
    `Show screen saver in normal full screen mode
    Case "/S"
        Load frmMySaver1
        frmMySaver1.Show
        Exit Sub
    Case Else
        Exit Sub
    End Select
End Sub

接著按照前面介紹過的編譯程序,把應用程式製成SCR檔,然後把它移到Windows目錄中。現在讓我們來執行這個應用程式。在「桌面」上按下滑鼠右鍵,選擇「內容」,在「顯示器內容」對話方塊中點選「螢幕保護裝置」頁籤,在下拉式清單中你會看到MySaver1,點選MySaver1然後按下「預覽」。

當程式啟動後,螢幕上會不斷地出現許多不同顏色、線條粗細不一的圓圈,如圖25-1。


 

 圖25-1 MySaver1螢幕保護程式執行的情形

參考資料:

請參閱 第二十九章"圖形" 中的MySaver應用程式,這個螢幕保護程式較為完整。


如何避免同時執行兩個螢幕保護程式?
 

要避免同時執行兩個螢幕保護程式有兩種方法。

如果應用程式已經在執行狀態,Visual Basic中App物件的PrevInstance屬性會被設為True;你可以利用這個屬性,在Form_Load事件程序中加入程式碼,以避免兩個以上的應用程式執行實體同時在系統中執行。以下這段程式碼告訴你如何在螢幕保護程式中使用App.PrevInstance:

`Don't allow multiple instances of program
If App.PrevInstance = True Then
    Unload Me
    Exit Sub
End If

第二種防止多個螢幕保護程式同時執行的方法是呼叫一個API函式,以這個函式告訴系統目前螢幕保護程式已經啟動。這個方法讓作業系統完全控管啟動螢幕保護程式的所有細節。這種方法就是後面幾個範例和 第二十六章 所採用的方法。

現在讓我們看看如何使用這個API函式。首先建立一個與MySaver1相同的專案,把其中的檔案改名為frmMySaver2.frm和modMySaver.bas,將modMySaver2的內容改為以下這段程式,然後將專案編譯成MySaver2.scr:

`MySaver2.bas
Option Explicit
    
`Constants for some API functions
Private Const SPI_SETSCREENSAVEACTIVE = 17

Private Declare Function SystemParametersInfo _
Lib "user32" Alias "SystemParametersInfoA" ( _
    ByVal uAction As Long, _
    ByVal uParam As Long, _
    ByRef lpvParam As Any, _
    ByVal fuWinIni As Long _
) As Long
    
`Starting pointDear John, How Do I... 
Public Sub Main()
    Dim lngRet As Long
    Dim lngParam As Long
    Dim strCmd As String
    Dim strTwo As String
    `Process the command line
    strCmd = UCase(Trim(Command))
    strTwo = Left(strCmd, 2)
    `Tell system screen saver is active now
    lngRet = SystemParametersInfo( _
        SPI_SETSCREENSAVEACTIVE, 1, lngParam, 0)
    Select Case strTwo
    `Show screen saver in normal full screen mode
    Case "/S"
        Load frmMySaver2
        frmMySaver2.Show
        Exit Sub
    Case Else
        Exit Sub
    End Select
End Sub

以預覽螢幕保護程式的方式觀察MySaver2,並且和MySaver1加以比較,看看兩者的差異。如果把等候的時間設為一分鐘,你會發現MySaver1在第一分鐘結束時會突然再啟動,而MySaver2則會繼續執行。

如何在螢幕保護程式執行時隱藏滑鼠游標?
 

Windows API函式ShowCursor可以隱藏或是顯示滑鼠游標;如果要隱藏游標,傳入False給ShowCursor,如果要顯示游標,則傳入True。我們用這個函式在螢幕保護程式中隱藏游標。


注意:

在Visual Basic中滑鼠游標的原文為"pointer",但在C++ 裡卻稱為"cursor",這就是為什麼這個函式被命名為ShowCursor的原因。


以下是API函式ShowCursor的宣告:

Private Declare Function ShowCursor _
Lib "user32" ( _
    ByVal bShow As Long _
) As Long

這裡有兩個例子使用ShowCursor函式,一個用來隱藏游標,另一個顯示游標。

`Hide mouse pointer
x = ShowCursor(False)

`Show mouse pointer
x = ShowCursor(True)

請務必要讓這些函式成對地執行,這樣才能確保程式結束執行時,滑鼠游標可以回復原狀。如果要能在螢幕保護程式中隱藏游標,請把MySaver2存成MySaver3專案,把MySaver3.bas裡的 "MySaver2.frm" 全部改為 "frmMySaver3.frm",然後把MySaver3.frm改成以下這段程式:

`MySaver3.frm
Option Explicit

`API function to hide or show the mouse pointer
Private Declare Function ShowCursor _
Lib "user32" ( _
    ByVal bShow As Long _
) As Long

`Module-level variables
Dim mblnQuit As Boolean

Private Sub Form_Paint()
    Dim lngRet As Long
    `Display different graphics every time
    Randomize
    `Initialize graphics parameters
    Scale (0, 0)-(1, 1)
    BackColor = vbBlack
    `Hide mouse pointer
    lngRet = ShowCursor(False)
    `Do main processing as a loop
    Do
        `Update display
        DoGraphics
        `Yield execution
        DoEvents
    Loop Until mblnQuit = True
    `Show mouse pointer
    lngRet = ShowCursor(True)
    `Can't quit in this context; let timer do it
    tmrExitNotify.Enabled = True
End Sub

Private Sub Form_Click()
    mblnQuit = True
End Sub

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
    mblnQuit = True
End Sub

Private Sub tmrExitNotify_Timer()
    End
End Sub

`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`This is where the real graphics drawing takes place
`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sub DoGraphics()
    `Occasionally change line color and width
    If Rnd < 0.03 Then
        ForeColor = QBColor(Int(Rnd * 16))
        DrawWidth = Int(Rnd * 9 + 1)
    End If
    `Draw circle
    Circle (Rnd, Rnd), Rnd
End Sub

當你預覽MySaver3時不會看到滑鼠游標,但是仍然可以按下滑鼠鍵以停止螢幕保護程式。

如何偵測滑鼠事件以結束螢幕保護程式?
 

一講到偵測滑鼠移動事件,我們第一個想到的就是MouseMove事件程序,但如果用它來結束程式的執行卻會有些問題。在程式剛啟動時,不管滑鼠是否實際上被移動過,MouseMove事件都會被驅動,如果把結束程式執行的程式碼放在這裡,那麼程式一開始就結束了。為了克服這個問題,我們必須在MouseMove事件程序中確定滑鼠游標的位置確實被移動過,然後再決定結束程式。請看下列這段程式:

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
    `Keep track of last known mouse position
    Static sngX As Single
    Static sngY As Single
    `On first move, simply record position
    If sngX = 0 And sngY = 0 Then
        sngX = X
        sngY = Y
    End If
    `Quit only if mouse actually changes position
    If X <> sngX Or Y <> sngY Then
        mblnQuit = True
    End If
End Sub

靜態變數sngX和sngY記錄著滑鼠游標的起始位置,只有在MouseMove事件被驅動而且滑鼠游標離開起始位置雙重條件成立下,程式才會結束執行。

我們假定滑鼠游標的起始位置是在非零的座標位置,但即使在程式啟動之初,游標位置是 (0,0),一旦滑鼠移動時仍然會將程式停下來。

現在請把所有MySaver3相關檔案複製到新專案MySaver4裡,將MySaver4.bas裡所有的"frmMySaver3"改為"frmMySaver4",把上面的MouseMove程式碼加入到MySaver4.frm裡,然後將專案編譯成MySaver4.scr。當你預覽MySaver4時,你會發現滑鼠被移動或滑鼠鍵被按下,程式就會立刻停止。

如何偵測鍵盤事件以結束螢幕保護程式?
 

表單的Keypress事件可以用來偵測鍵盤上的事件,但Keypress事件不能完全偵測到所有的鍵盤事件。例如按下Shift並不會引發Keypress事件,因此,我們決定用KeyDown事件來偵測所有的鍵盤事件,包括各個變換鍵(Shift、Ctrl、Alt)被按下的時候:

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
    mblnQuit = True
End Sub

如何用螢幕上的畫面作為螢幕保護程式的圖形?
 

你也許看過有一種較奇特的螢幕保護程式,這種螢幕保護程式會讓螢幕上的畫面"融解"或是像漩渦水流一樣地流走。其實,只要用幾個Windows API函式,你也可以建立一個具有相同效果的螢幕保護程式。下面這個範例就是使用這種特效的螢幕保護程式。這個程式與本章前面幾節中的MySaverX很相似,兩者的不同在於本例多使用了幾個API函式,而且本例多了許多增強的功能。我們把這個範例命名為MySaver5。

程式一開始宣告了好幾個API函式,包括BitBlt、GetDesktopWindow、GetDC和ReleaseDC,這些函式被用來複製螢幕上的畫面到表單上,這些函式在Form_Load事件程序中被呼叫。

請按照第一節中介紹過的編譯程序,將本範例編譯成SCR檔,然後將這個檔案移到Windows目錄中。另外,請務必設定以下幾個表單的屬性:Auto Redraw設為True,BorderStyle設為"0-沒有框線",KeyPreview設為True,WindowState設為"2-最大化"。最後,在表單中加入一個計時器控制項,把Interval屬性設為1微秒,Enabled屬性設為False。以下是程式內容:

Here's the code for MySaver5.bas: 


`MySaver5.bas
Option Explicit
    
`Constants for some API functions
Private Const SPI_SETSCREENSAVEACTIVE = 17

Private Declare Function SystemParametersInfo _
Lib "user32" Alias "SystemParametersInfoA" ( _
    ByVal uAction As Long, _
    ByVal uParam As Long, _
    ByRef lpvParam As Any, _
    ByVal fuWinIni As Long _
) As Long
    
`Starting pointDear John, How Do I... .
Public Sub Main()
    Dim lngRet As Long
    Dim lngParam As Long
    Dim strCmd As String
    Dim strTwo As String
    `Process the command line
    strCmd = UCase(Trim(Command))
    strTwo = Left(strCmd, 2)
    `Tell system screen saver is active now
    lngRet = SystemParametersInfo( _
        SPI_SETSCREENSAVEACTIVE, 1, lngParam, 0)
    Select Case strTwo
    `Show screen saver in normal full screen mode
    Case "/S"
        Load frmMySaver5
        frmMySaver5.Show
        Exit Sub
    Case Else
        Exit Sub
    End Select
End Sub
Here's the code for MySaver5.frm: 

`MySaver5.frm
Option Explicit

`API function to hide or show the mouse pointer
Private Declare Function ShowCursor _
Lib "user32" ( _
    ByVal bShow As Long _
) As Long

`Declare API to get a copy of entire screen
Private Declare Function BitBlt _
Lib "gdi32" ( _
    ByVal hDestDC As Long, _
    ByVal lngX As Long, ByVal lngY As Long, _
    ByVal nWidth As Long, _
    ByVal nHeight As Long, _
    ByVal hSrcDC As Long, _
    ByVal lngXSrc As Long, ByVal lngYSrc As Long, _
    ByVal dwRop As Long _
) As Long

`Declare API to get handle to screen
Private Declare Function GetDesktopWindow _
Lib "user32" () As Long

`Declare API to convert handle to device context
Private Declare Function GetDC _
Lib "user32" ( _
    ByVal hwnd As Long _
) As Long

`Declare API to release device context
Private Declare Function ReleaseDC _
Lib "user32" ( _
    ByVal hwnd As Long, _
    ByVal hdc As Long _
) As Long

`Module-level variables
Dim mblnQuit As Boolean

Private Sub Form_Load()
    Dim lngX As Long
    Dim lngY As Long
    Dim lngXSrc As Long
    Dim lngYSrc As Long
    Dim dwRop As Long
    Dim hwndSrc As Long
    Dim hSrcDC As Long
    Dim lngRes As Long
    Dim lngM1 As Long
    Dim lngM2 As Long
    Dim lngN1 As Long
    Dim lngN2 As Long
    Dim lngPixelColor As Long
    Dim lngPixelCount As Long
    Dim lngRet As Long
    Dim intPowerOfTwo As Integer    
    `Display different graphics every time
    Randomize
    `Copy entire desktop screen into picture box
    ScaleMode = vbPixels
    Move 0, 0, Screen.Width + 1, Screen.Height + 1
    dwRop = &HCC0020
    hwndSrc = GetDesktopWindow()
    hSrcDC = GetDC(hwndSrc)
    lngRes = BitBlt(hdc, 0, 0, ScaleWidth, _
        ScaleHeight, hSrcDC, 0, 0, dwRop)
    lngRes = ReleaseDC(hwndSrc, hSrcDC)
    `Display full size
    Show
    `First time use high power of 2
    intPowerOfTwo = 128
    `Hide mouse pointer
    lngRet = ShowCursor(False)
    `Do main processing as a loop
    Do
        `Map screen into rectangular blocks
        Scale (0, 0)-(intPowerOfTwo, intPowerOfTwo)
        `Set a random solid color
        lngPixelColor = (lngPixelColor * 9 + 7) Mod 16
        lngPixelCount = 0
        `Algorithm to hit each location on screen
        lngM1 = Int(Rnd * (intPowerOfTwo \ 4)) * 4 + 1
        lngM2 = Int(Rnd * (intPowerOfTwo \ 4)) * 4 + 1
        lngN1 = Int(Rnd * (intPowerOfTwo \ 2)) * 2 + 1
        lngN2 = Int(Rnd * (intPowerOfTwo \ 2)) * 2 + 1
        Do
            `Jump to next coordinate
            lngX = (lngX * lngM1 + lngN1) Mod intPowerOfTwo

            If lngX <> 0 Then
                lngY = (lngY * lngM2 + lngN2) Mod intPowerOfTwo
            Else
                `Let system do its thing
                DoEvents
            End If
            `Fill rectangular block with solid color
            Line (lngX, lngY)-
(lngX + 1, lngY + 1), QBColor(lngPixelColor), BF
            lngPixelCount = lngPixelCount + 1
            `Exit this loop only to quit screen saver
            If mblnQuit = True Then Exit Do
        Loop Until lngPixelCount = intPowerOfTwo * intPowerOfTwo
        intPowerOfTwo = 2 ^ (Int(Rnd * 5) + 2)
        `Yield execution
        DoEvents
    Loop Until mblnQuit = True
    `Show mouse pointer
    lngRet = ShowCursor(True)
    `Can't quit in this context; let timer do it
    tmrExitNotify.Enabled = True
End Sub

Private Sub Form_MouseMove(Button As Integer, Shift As Integer, _
    X As Single, Y As Single)
    Dim sngXnow As Single
    Dim sngYnow As Single
    sngXnow = Round(X)
    sngYnow = Round(Y)
    `Keep track of last known mouse position
    Static sngX As Single
    Static sngY As Single
    `On first move, simply record position
    If sngX = 0 And sngY = 0 Then
        sngX = sngXnow
        sngY = sngYnow
    End If
    `Quit only if mouse actually changes position
    If sngXnow <> sngX Or sngYnow <> sngY Then
        mblnQuit = True
    End If
End Sub

Private Sub Form_Click()
    mblnQuit = True
End Sub

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
    mblnQuit = True
End Sub

Private Sub tmrExitNotify_Timer()
    End
End Sub

在Form_Load中的繪圖迴圈會在表單上畫出顏色方塊,使螢幕畫面逐漸融解。圖25-2所顯示的是逐漸融解的螢幕畫面。


 

 圖25-2 螢幕保護程式會讓螢幕上的畫面"融解"

參考資料:

請參閱 第二十九章"圖形" 中的MySaver應用程式。


如何在螢幕保護程式中加入密碼和設定選項的功能?
 

當螢幕保護程式啟動時,Windows會自動傳遞命令列參數給螢幕保護程式,根據這些參數,螢幕保護程式會有不同的執行結果。以下即是對這些參數的描述:

命令列參數 描述
/a 「顯示器內容」對話方塊中的「更改密碼」按鈕已被按下。
/p nnnn 只要當「螢幕保護裝置」頁籤被點選,「顯示器內容」對話方塊就會讓使用者預覽螢幕保護程式。命令列中的數字是 hWnd 的值,代表小張的桌面畫面圖形。
/c 「顯示器內容」對話方塊中的「設定」按鈕已被按下。
/s 「顯示器內容」對話方塊中的「預覽」按鈕已被按下,或是程式由系統正常啟動。

我們在Form_Load事件程序中檢查這些參數,並根據參數的不同而採取不同的回應措施。在前面兩個範例中,我們只檢查/s參數,並且在收到其他參數時,一律停止程式的執行。如果我們在收到/s以外其他的參數時,給使用者一個回應的訊息,會使得整個程式的界面更為友善。請看以下程式:

`Configuration command
Case "/C"
    `Temporarily show mouse pointer
    x = ShowCursor(True)
    `Perform any user interaction
    MsgBox "No setup options for this screen saver"
    `Hide mouse pointer
    x = ShowCursor(False)
    `Configuration is completed
    Unload Me
    Exit Sub
`Password setting command
Case "/A"
    `Temporarily show mouse pointer
    x = ShowCursor(True)
    `Get and record new password here
    MsgBox "No password for this screen saver"
    `Hide mouse pointer
    x = ShowCursor(False)
    `Setting of new password is completed
    Unload Me
    Exit Sub

上面這段程式提供了一個範例,告訴你如何處理命令列參數。如果你想看看使用所有參數(/a參數除外)的完整範例,請跳到 第二十九章 " 圖形 " ,參考MySaver應用程式。MySaver應用程式使用了本章所介紹的程式碼,包括預覽縮小的螢幕畫面。