圖檔的顯示設定都完成後,接者就進入tmrShow來執行Show圖的步驟了,tmrShow的程式有點長, 不過你可以把它分為三個步驟來看

1.檢查錯誤

If loopNo = 0 Then
   Call ShowMessage(CENTER_MIDDLE, 16, DefaultFontName, &HFFFFFF, "開啟圖檔發生問題!請確定檔案名稱及路徑是正確的,圖檔是否毀損")
   waitCount = 1
   tmrActive.Interval = 1000
   tmrActive.Enabled = True
   Exit Sub
End If

'Init Data
'略

當我們使用LoadImagePicture來載入圖檔時,如果發生任何錯誤,那麼loopNo就會傳回0, 這裡我們就是依據loopNo來檢查,如果有錯誤,我們就使用ShowMessage來秀出錯誤訊息, 跳出這一段程式,停止這一次的圖形顯式的動作。不過,說來真不好意思,這裡我又做錯了, 如果你照上面的程式執行的話,當預覽圖形發生錯誤時,會進到tmrActive這一段程序, 這一段程序是在做螢幕保護程式等待切換到下一張圖的動作,再繼讀取下一張的圖檔的資料, 但是你如果經由預覽圖片的程序進來時,其ActiveNo的值為0,當程式要讀取ProFile(ActiveNo) 裡的資料時就會發生錯誤了!所以我們要將上面的程式改為
If loopNo = 0 Then
   Call ShowMessage(CENTER_MIDDLE, 16, DefaultFontName, &HFFFFFF, "開啟圖檔發生問題!請確定檔案名稱及路徑是正確的,圖檔是否毀損")
   If ActiveType = Active_PreView Then
      Exit Sub
   Else
      waitCount = 1
      tmrActive.Interval = 1000
      tmrActive.Enabled = True
      Exit Sub
   End If
End If

這樣才不會有問題!接者Init Data這一段作的是當螢幕保護程式執行時才會作到的一些動作, 預覽的時候不會執行到,後面提到的時候再說明,接者我們要作的是

2.顯示圖片

Select Case EffectBuffer
Case LEFT_TO_RIGHT
     '略
End Select

我們再這一段所做的就是依EffectBuffer來決定顯示圖形的方式,在圖片還沒有顯示完畢以前, overFlag設為false,等到執行完畢後,再設為True繼續執行下面的動作,圖形顯示的方式和原理 比較繁雜,所以我們也等到以後再一一說明,最後在結束圖形的特效顯示後, 我們就要作

3.顯示文字訊息及播放音樂
不過這一段如果是在執行預覽圖片時也一樣不會執行的,所以我們也先跳過,待會再講。 到此為止,預覽圖檔的動作就算完成了,接者就是等待使用者移動滑鼠或按下鍵盤,就會跳回 設定視窗了。

接者我們再回到cmdStart_Click的程式看看當螢幕保護程式實際執行時的步驟

Private Sub cmdStart_Click()
If ActiveType = Active_PicPreview Then
   '略
ElseIf ActiveType = Active_Start Then
   Call LoadSPMData("")
   tmrActive.Interval = 250
   tmrActive.Enabled = True
   Exit Sub
Else.....'略
End If

當要執行螢幕保護程式時,第一件要作的事就是載入螢幕保護程式設定檔的各種相關內容, 這裡用LoadSPMData這段副程式來做,我們直接來看這段程式
Private Sub LoadSPMData(FileName As String)
Dim SPMFile$
Dim fNum%, rBuf$, i%, no%

'1.Get SPM FileName
'2.Check SPM Error(Error or Can't Find)
'3.Get SPM File Data
'4.If Error Then Set ActiveType = DefaultAction

If FileName$ = "" Then
   SPMFile$ = GetStringFromIni("START", "PROFILE", workPath & "System.SPM", workPath & "ADVSET.INI")
Else
   SPMFile$ = FileName$
End If


On Error GoTo LoadError_Handle

If Dir$(SPMFile$) <> "" And SPMFile$ <> "" Then
   'Check Head Data
   fNum% = FreeFile
   Open SPMFile For Input As #fNum%
   Line Input #fNum%, rBuf$
   If rBuf$ <> "'[Please Don't Change This File!]" Then
      ActiveType = DefaultAction
      Exit Sub
   End If
   Line Input #fNum%, rBuf$
   If rBuf$ <> "'[請勿更改此設定檔內容!否則系統可能無法正常運作!]" Then
      ActiveType = DefaultAction
      Exit Sub
   End If
   Line Input #fNum%, rBuf$
   If rBuf$ <> "'[Screen Saver Maker Profile Information]" Then
      'MsgBox "開啟設定檔發生錯誤!請檢查檔案是否正確或經過修改!"
      ActiveType = DefaultAction
      Exit Sub
   End If

   
   'Input ProFile Data
   
   no% = 0
   Do Until EOF(fNum%)
      no% = no% + 1
      '略  載入檔案的資料
   Loop
   Close #fNum%
Else
   ActiveType = DefaultAction
End If
   
ActiveNo = 1
ActiveSUM = no%

Exit Sub


LoadError_Handle:
   'MsgBox "開啟設定檔發生錯誤!請檢查檔案是否正確或經過修改!"
   ActiveType = DefaultAction
   Resume LoadErrorQuit
   
LoadErrorQuit:
   Close #fNum%
   Exit Sub
End Sub

首先我們要判斷要開啟的設定檔名,如果傳入的FileName是空值,我們就去找ADVSET.INI這個檔 案,取出PROFILE的值,接者再使用Dir來判斷User所指定的設定檔存不存在,因為ADVSET.INI內 的資料有可能因為User的修改導至程式取得空值,而Dir敘述在VB環境下執行時會發生一些很奇 怪的情形,如果你執行Dir("")的話,你所得到的傳回值不會是"",而會得到一些奇奇怪怪的檔 名(作者的情形是會傳回"README.TXT"、"CVPACK.EXE"等....),所以在這裡我們要下兩個判斷 If Dir$(SPMFile$) <> "" And SPMFile$ <> "" Then,這樣在VB執行時才可以正確的判斷檔案到 底存不存在,接者我們就可以執行載入的動作了,這裡做的大致上與設定視窗的載入檔案步驟相 同,檢查過檔頭無誤後,將檔案資料載入ProFile內即可,最後將ActivNo及ActiveSUM變數設定好 即可,在這裡如果發生任何錯誤的話,我就會將Active_Type設為DefaultAction,在後面的時候 就會Show出一些訊息給使用者,這一來前置作業就完成了,接者程式會啟動tmrActive,來看 一下程式
Private Sub tmrActive_Timer()
'1. Check waitCount if > 0 then Check ActiveType then Check Delay Time
'2. Check ActiveType  Start -> Load Picture File Get EffectTime Value Goto tmrShow
'         ActiveType  DefaultAction -> SPM File Error or Can't Find,Show Default Message
tmrActive.Enabled = False

If waitCount <> 0 Then
   '略
End If

If ActiveType = Active_Start Then
   picName = ProFile(ActiveNo).PicFile
   EffectBuffer = ProFile(ActiveNo).Effect
   Call LoadImagePicture(ProFile(ActiveNo).FullPicture)
   picPreView.AutoRedraw = True
   Call GetEffectTime

   tmrShow.Enabled = True
ElseIf ActiveType = DefaultAction Then
   lblMessage.Visible = False
   DefaultActNo = DefaultActNo Mod 3 + 1
   
   Select Case DefaultActNo
   Case 1
        Call ShowMessage(CENTER_MIDDLE, 16, DefaultFontName, &HFFFFFF, "如果你是第一次執行Screen Saver Maker的話,請先執行設定,決定播放的螢幕保護程式格式")
   Case 2
        '略
   End Select
   waitCount = 5
   tmrActive.Interval = 1000
   tmrActive.Enabled = True
   Exit Sub
End If
End Sub

第一段的程式我們就先跳過不看,現在還執行不到這一段,先往下看,下面這一段就是判斷程式 在先前載入設定檔的動作有沒有發生任何錯誤,如果發生錯誤,我們就在這裡對使用者Show出一 些錯誤的提示,下面是一段很簡單的Select Case敘述,使用Timer及一個變數來循環切換顯示的 訊息內容,趁者這個時候,我來說明一下ShowMessage的內容
Private Sub ShowMessage(Align As Integer, fSize As Integer, fName As String, fColor As Long, StrMes As String)
Dim xWidth As Single
Dim xHeight As Single
Dim xLeft%, xTop%

'1.Set lblMessage's Type ,Get TextWidth & TextHeight
'2.Get Align Type ,Get Top,Left,Width,Height Value

lblMessage.FontSize = fSize
lblMessage.FontName = fName
lblMessage.ForeColor = fColor
lblMessage.Caption = StrMes

Me.FontName = fName
Me.FontSize = lblMessage.FontSize

xWidth = TextWidth(lblMessage.Caption)
xHeight = TextHeight(lblMessage.Caption)

Select Case Align
Case RIGHT_TOP
     If xWidth > (Me.ScaleWidth / 2) Then
        xHeight = xHeight * (xWidth \ (Me.ScaleWidth / 2) + 1)
        xWidth = Me.ScaleWidth / 2
     End If
     xLeft = Me.ScaleWidth - xWidth - 10
     xTop = 10
     
Case LEFT_TOP
     '略
Case CENTER_MIDDLE
     '略
Case RIGHT_BOTTOM
     '略
Case LEFT_BOTTOM
     '略
End Select

lblMessage.Width = xWidth
lblMessage.Height = xHeight
lblMessage.Move xLeft, xTop
lblMessage.Visible = True

End Sub

這個程式傳入的參數有點煩,不過為了能讓使用者能多一點自主性,也只好辛苦一點了... ShowMessage傳入的參數分別是(Align - 訊息顯示位置,fSize - 字型大小,fName - 字型名稱 ,fColor - 字型顏色,StrMes - 顯示訊息),然後我們將這些傳入的值指定到lblMessage中, 但是我們必須順者顯示者輸入的訊息多寡和指定的顯示位置來調整lblMessage的大小,所以我們暫 時不指定Caption,接者我們要把fSize及fName指定給Form,因為VB所提供的TextWidth及 TextHeight這兩個計算文字所佔範圍的方法是用Form為基準計算的,所以我們要將其指定給Form 才能算出正確的長度,接下來程式呼叫這兩個方法後,就可得知訊息顯示後的長度及高度了,但 是這個結果仍然不是正確的答案,算出來的結果是以一行做基準的,程式不會自動為你算出換行後 的資料,我們仍然需要自己計算,這裡我們用右上位置的計算來解釋,首先我們先決定文字顯示的 基準位置,X位置基本上是依文字長度靠右計算出來,Y軸我們則設定在10的位置,顯示訊息的最大 寬度我則定為螢幕寬度的一半,那麼,假設解析度為800*600,求出來的文字長度為920Pixel, 高度為30Pixel,那麼lblMessage的寬度就是螢幕的一半400Pixel,高度則為30 * ((920 \ 400)+1) = 90Pixel,則為正確的值了。其它幾個位置的求法概念大致相同,位置有點不同而已,基準值你 可以自己再定,可以設窄一點、寬一點,隨你高興了。要非常注意的地方是這裡是用Label物件來 顯示訊息的,在某些情況下,計算出來的結果會與實際顯示的狀況有所不同,為什麼呢?你可以 試試看在Form怖置一個Label,然後在Caption打一段很長的英文字,注意!不要有空格!你會發 覺,Label物件會為了不把你的拼字打散,而很"聰明"?的不為你做換行的動作,可是那就事情大 條了,你就會發覺你的Message被Cut掉了!那該怎麼辦?換成使用Text物件?喔!不行,它沒有 透空的功能!你想看到一條白布條浮在你的螢幕保護程式上嗎?那麼自己為Message加上換行符 號?饒了我吧!還要去注意中英文的問題,不然也Cut不準的!所以?暫時沒有更好的辦法囉! 自己Show Message時多注意一下,不要造成這種結果就好了!不然就不要打英文,全打中文、 符號用全形也是可以的

O.K.!再回到tmrActive的部份,如果載入設定檔沒有錯誤,那麼我們就將ProFile.PicFile代 入picName,ProFile.Effect代入EffectBuffer,呼叫LoadImagePicture、GetEffectTime做好 顯示圖形的前置工作,再啟動tmrShow顯示圖片,再回頭看tmrShow的程式

If loopNo = 1 And ActiveType = Active_Start Then
   lblMessage.Visible = False
   
   If Trim$(ProFile(ActiveNo).MidiFile) <> "" Then
      If ProFile(ActiveNo).ReplayMidi = 1 Then
         ReplayMidi = True
      Else
         ReplayMidi = False
      End If
      If ActiveNo <> ReplayMidiFlag Then rc = PlayMusic(ProFile(ActiveNo).MidiFile, MIDI_FILE)
   End If
   
   If Trim$(ProFile(ActiveNo).WavFile) <> "" Then
      If ProFile(ActiveNo).ReplayWav = 1 Then
         ReplayWav = True
      Else
         ReplayWav = False
      End If

      If ActiveNo <> ReplayWavFlag Then rc = PlayMusic(ProFile(ActiveNo).WavFile, WAV_FILE)
   End If
End If

先看前面進入時的處理,這裡是要先作一些播放音樂的處理,音樂播放是在載入圖型前做的 (loopNo = 1),順便把上一次的Message清除,然後Check這個設定檔有沒有播放Midi或Wave ,並記錄下是否有Replay的功能,接者再呼叫PlayMusic播放音樂,PlayMusic大致與設定視窗 的PlayMusic相同,差別只在於Error Message的處理方式,既然螢幕保護程式已經在Run了, 我們就應該儘量將程式保持在不斷Runing的狀態,所以Show出Message Box這種會將流程Lock 住的方法就應該避免使用才對,所以這裡都改成用ShowMessage副程式來帶過,再來要注意的就 是如果假設你的螢幕保護程式只設定了一個循環播放的音樂檔,當螢幕保護程式Run過一輪後, 再度讀取到這個設定檔的資料時,音樂會被中斷,再重頭播起,這個可就一點都不美了, 所以我們在PlayMusic時,順便將目前設定檔編號記錄下來,當程式要播放音樂前,先比對目前 設定檔編號和前一次播放音樂檔的編號是否相同?如果相同的話,就不執行PlayMusic了, 而Replay的工作就交給tmrDetMusic來做了,這一段也和設定視窗大致相同,我就不說明了。 再往下看下去
If loopNo = 1 And picCut = True Then

   'If DestWidth < screenWidth Or DestHeight < screenHeight Then
   If DestWidth < preWidth Or DestHeight < preHeight Then
      Me.AutoRedraw = True
      Me.Picture = LoadResPicture("HPattern08", vbResBitmap)
      DoEvents

      Me.AutoRedraw = False
   End If
End If

這裡要Check目前圖型與上一張圖型的大小差別,如果picCut = False,圖型一定是填滿全螢幕的 ,所以不必擔心什麼,但是如果這一次顯示的圖型比上一張顯示的圖型範圍小,那麼就沒有辦法 完全遮蓋住上一張圖型,會變的很難看,所以我們就要將From的畫面清除掉,才不會有問題, 這裡用的LoadResPicture沒什麼意義,其實用LoadPicture()就可以了,要比對用的值在 LoadImagePicture就算出來了,有疑問的再自己回頭看一下。接下來顯示的部份一樣先跳過, 來看顯示完畢後的處理
If overFlag = True And ActiveType = Active_Start Then
   If Trim$(ProFile(ActiveNo).AddText) <> "" Then
      If lblMessage.Visible = False Then
          Call ShowMessage(ProFile(ActiveNo).TextSet.Alignment, ProFile(ActiveNo).TextSet.Size, ProFile(ActiveNo).TextSet.Name, ProFile(ActiveNo).TextSet.Color, ProFile(ActiveNo).AddText)
      Else
         lblMessage.Visible = True
      End If
   End If

   waitCount = 1
   tmrActive.Interval = 1000
   tmrActive.Enabled = True
End If

結束的時候,Check需不需要秀Message,如果需要的話,就Call ShowMessage顯示訊息,接者 將waitCount設回1,啟動tmrActive,程式裡有一段else的處理,當lblMessage在這時已設為 Visible = True,那麼就一定是有錯誤發生要顯示錯誤訊息的關係,但是如果經過顯示圖型 的處理後,Message會被遮住,需要再呼叫一次Visible = True來顯示Message。O.K.,再回 頭看tmrActive
If waitCount <> 0 Then
   If ActiveType = DefaultAction Then
      waitCount = waitCount - 1
      
      tmrActive.Interval = 1000
      tmrActive.Enabled = True
      Exit Sub
   End If
   
   If waitCount >= ProFile(ActiveNo).DelayTime Then
      ActiveNo = ActiveNo Mod ActiveSUM + 1
      waitCount = 0
   Else
      waitCount = waitCount + 1
      tmrActive.Interval = 1000
      tmrActive.Enabled = True
      Exit Sub
   End If
End If

這裡做的就很單純了,如果是DefaultAction就將waitCount遞減,減到零的時候,就到下面的程 式Show出下一個Message。如果是正常的情況下,就將waitCount遞增,等於Profile.DelayTime時 就算出下一個設定檔的編號,再跑到下面的程式LoadImagePicture、GetEffectTime...一直循環 直到使用者移動滑鼠或鍵盤再跳出程式。跳出時,一些關閉音樂等清除動作別忘了
Ver1.0 Update:
在ShowMessage的顯示方面,後來作者又作了一些修正,改為用API來繪出文字,倒不是這個方法 有什麼錯誤或不好的地方,而是為了這個功能去改變了某些設定,造成了速度上的差異, 記得前一陣子我在網頁上請大家幫忙我做一些關於Screen Saver Maker的調查嗎? 當初就是在顯示速度上出了問題,在同事的P!!!執行的效能竟然遠遜於一台P233的電腦, 在多方嘗試後,我終於發現問題所在
第一階段,也就是剛開始的時候(對不起,你最好先看過後面特效執行的解說,這邊才比較好了解我在說什麼), Form的AutoRedraw在執行特效時,是設為False的,然後,在特效執行完時,我再將AutoReDraw設為True並將 整張圖重繪一次,這有兩點用意,第一點是為了一開始的特效計算並不完善,要做一些計算補償用的, 第二點,也是很重要的一點,為了顯示文字Message。問題就在這裡,當你將Label設定好,並顯示出來時, Label會自動重繪它底下的區域,因為我們是把AutoReDraw設為False,因此Label一但重繪,出現的背景將 是一片黑色底色的Form,所以在這裡我將AutoReDraw設為True,將整張圖重貼一次, 因此,在Ver0.91時,雖然改變了一些計算式避開了整張圖重繪的需要,但也為了 能一樣正常顯示Message,讓我把Form的AutoReDraw設成了True。
接下來第二階段,在修改了上面所說的方式之後,我注意到,特效的執行速度變慢了, 一開始嘗試時,在執行特效的時候,我並沒有使用Timer來做特效間隔的時間控制,而是直接使用 For迴圈繪製。就如同後面所說的-太快了~~~快的看不清楚我在做什麼特效,因此,我才修改成以Timer 控制,第二次,我想,也許問題是出在Timer身上吧!於是我再把繪製方法改成單純使用For迴圈, 並以TimeGetTime來控制間隔,一試之下,果然速度大為改觀,我想,這樣的速度應該就沒問題了---直到 我在P!!!發生慘不忍睹的執行效果以前...
問題呢~~~就是出在AutoReDraw的設定上,AutoReDraw這項屬性到底會使用到什麼,主機版上的記憶體? 影像卡的RAM?我也不清楚,但是很明顯的是,它大大的降低了程式的效率,後來,我再度將AutoReDraw 設定為False來繪製特效,再用TimeGetTime來控制時間,一切總算恢復正常,就算在P133,也能順暢的執行 所有的特效了,但是...問題又繞回來了...那,當我要顯示Message時怎麼辦?哈哈哈哈~~~一塊黑布條就出現 了.........>_<,那怎麼行!!!用TEXT物件,它有hDC的屬性,把字型顯示出來在繪製背景!.....不行, 它連顯示的文字都蓋掉了....那怎麼辦....只有Call API了,我找到一個DrawText的API,可以在DC上繪 出文字,一試之下終於沒問題了,現在ShowMessage修改如下
Private Sub ShowMessage(Align As Integer, fSize As Integer, fName As String, fColor As Long, StrMes As String)
Dim xWidth As Single
Dim xHeight As Single
Dim xLeft%, xTop%
Dim txtRECT As RECT

'1.Set lblMessage's Type ,Get TextWidth & TextHeight
'2.Get Align Type ,Get Top,Left,Width,Height Value

lblMessage.FontSize = fSize
lblMessage.FontName = fName
lblMessage.ForeColor = fColor
lblMessage.Caption = StrMes

Me.FontName = fName
Me.FontSize = fSize
Me.ForeColor = fColor

xWidth = TextWidth(lblMessage.Caption)
xHeight = TextHeight(lblMessage.Caption)

'略

txtRECT.Left = xLeft
txtRECT.Top = xTop
txtRECT.Right = xLeft + xWidth
txtRECT.Bottom = xTop + xHeight

DrawText Me.hdc, StrMes, LenB(StrConv(StrMes, vbFromUnicode)), txtRECT, DT_WORDBREAK

End Sub
其實程式修改的並不多,因為使用DrawText並沒有什麼特別的地方,只需要指定顯示的範圍,方法和 訊息,並沒有什麼特別的地方,至於FontSize,ForeColor等,它會自動套用hDC擁有者的設定, 只要將設定套用Form上就行了,那可能有人覺得說,為什麼不用Print方法直接印在Form上呢? 別忘了!現在AutoReDraw是設為False的,你不能去呼叫Refresh方法,當然更別說是Cls了, 那這一張的文字訊息顯示出來了,下一張呢?所以我們不得不用API來做這件事
簡單說明一下DrawText的參數 DrawText (要將文字繪在那個DC裝置上?),(要顯示的訊息),(訊息的長度,別忘了我們可能使用中文 ,要使用StrConv算出正確的長度),(顯示的範圍,如果訊息超過這個範圍就看不到囉!),(對齊的方法等 ,在這裡我傳入的是DT_WORDBREAK,自動斷行的意思)
大概就是這樣子囉,好好想想吧!

後記: 到這裡為止,程式的大部份講解都算完成了,在下次預覽螢幕保護程式的部份講解完後, 接下來後面幾次的課程我會回過頭去開始講解我之前先跳過了程式,請多支持
再過約一個月左右,這個主題應該就可以Close了,再來大概就會講解大家來玩猜數字,再來嘛 ...還沒有計畫,電腦壞掉前,本來有一個正在寫的題目,完成30%左右了.....可是....... 最近抓了DirectX 7.0 SDK回來,有空打算研究一下,看能不能寫個範例出來....(00/10/23:哈哈~~ 隔了那麼久,還沒有動,太混了....)

回到VB教學教室