#E6E6E6 【VB 控件】FlatButton 演示平面按鈕與影像處理 作者:吳文成

  一項新控件的開發,往往是因應某個應用程式內部的需要。尤其是當
[[img src=computer/FlatButton2.gif height=39 width=234 align=left]]這種需要又是經常性出現的時候,撰寫與封裝新的控制項,以簡化應用程式的[[img src=computer/FlatButton.gif height=99 width=234 align=left]]規劃流程與結構化應用程式的模組編碼
,這就顯得格外地重要。左圖顯示的是由我所開發的影音播放軟體 ExtraPlayer 的兩個不同部份的畫面,你會發現我使用了二十幾個按鈕,除了一般狀態的樣式之外,還有滑鼠經過(周圍隆起)的樣式,有執行狀態(壓陷下去)的樣式,以及無效狀態(灰階)的樣式。

  這種按鈕,我們稱為「平面按鈕」[[img src=computer/FlatButton1.gif height=79 width=250 align=right]],如圖所示,在外觀上,它可以顯示出剛剛提到的四種樣式。可惜的是
, 在 Visual Basic 並不提供這樣的平面按鈕,它只提供圖示右邊的那種預設按鈕。也就是說,我們必須自己來寫平面按鈕的控件,此控件除了能夠顯示四種外觀樣式之外,它還需要能適當地回應滑鼠事件:包括滑鼠經過、滑鼠按下、滑鼠按起等等,別忘了
,它也需要有圖像處理的能力(例如有 AutoSize 與影像加工的功能)

  讓我們一步一步地釐清我們的思慮,才能夠明晰地界定這個控件所需要的外觀、屬性、方法與功能,然後我們才知道如何針對特定問題,來尋求測試方案與解決方案。實體控件的開發往往可以從它的外觀樣式作為思考的起點,根據實際的需求,我們已經知道它要有四種顯示樣式(我依序定義為 NormalState、MouseOverState、MouseDownState 與 InvalidState ),但是在撰寫 ExtraPlayer 的過程中,我發現平面按鈕還應該有兩種模式,這兩種模式相同的部分是:滑鼠經過需要呈現周圍隆起,無效狀態需要呈現灰階圖像。但是不同的是,第一種模式(DisplayMode=CommonButton)在滑鼠按下(Click)之後 , 壓陷狀態的按鈕會「自動彈起」; 第二種模式(DisplayMode=SelectedButton)就不會自動彈起,而一直保持壓陷狀態(
或者說是執行狀態)。

  前者如按下「儲存檔案」的平面按鈕,按下之後按鈕就恢復原狀,後者如按下「播放檔案」的平面按鈕,按下之後就一直維持播放中的按鈕樣貌。這樣我們就越來越清楚它所需要具備的屬性,除了四種 ButtonState, 還有兩種 DisplayMode( 這屬性的表現,其實就是在回應滑鼠事件時,四種 ButtonState 的不同搭配)。記得,開發一項新的控件,要界定它所需要的外觀、屬性、方法與功能,必須要回到它的「可能」實際運用之中,才能夠清楚,天馬行空地揣想只是在浪費時間,也只有在實際的運用與撰寫之中,我們才會遭遇真正的編碼問題與程序問題

  會遭遇什麼問題呢 ? 如何偵測滑鼠移入( MouseHover)與滑鼠移出(MouseLeave)事件就是一個大問題,因為 Visual Basic 與 Window API 都沒有明確地提供這些事件程序,如果我們要求準確而靈敏的事件偵測,那麼我們將碰到相當複雜且迂迴的程式碼(這就是平面按鈕控件為什麼如此特別與寶貴的原因 )。 較簡單的做法是使用 SetCapture 與 ReleaseCapture 兩個 API 函數,但是代價是控件的 ToolTipText 屬性將會失效 , 如果你不想放棄這個重要的屬性 ,要不就藉由 SubClass 技巧註冊新視窗 , 要不只好另尋他法來偵測滑鼠移入與移出事件,有兩種方法可以選擇:一種是使用 Mouse Hook( SetWindowsHookEx 與 CallNextHookEx)捕捉全域的滑鼠事件與位置,然後再以 WM_MOUSEMOVE 的事件座標來區分滑鼠移入與移出,另一種方法是結合 TrackMouseEvent 與 CallWindowProc 兩種技巧。這些方法都牽涉到 SubClass 與低階的窗口訊息事件。

  以上談的是如果你要求準確而靈敏的滑鼠移入與移出事件,如果你要求不高,那麼你可以使用 GetCursorPos 與 WindowFromPoint 兩個 API 函數 與 Timer 控件,在每個間隔時間,偵測滑鼠所在的位置與滑鼠所在的物件
,以判斷滑鼠是否經過或離開指定的物件,但是其精確度依賴於 Timer 所設定的 Interval 屬性。大概輪廓的程式碼像是以下這樣:

Private Type POINTAPI
 X As Long
 Y As Long
End Type

' 取得滑鼠位置與其所在物件
Private Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long

Private Function isInSelf() As Boolean
 Dim mp As POINTAPI
 
 GetCursorPos mp
 If UserControl.hWnd = WindowFromPoint(mp.X, mp.Y) Then isInSelf = True
End Function

Private Sub tmrCheck_Timer()
 If isInSelf Then
  ' Debug.Print "滑鼠進入"
 Else
  ' Debug.Print "滑鼠離開"
 End If
End Sub


  然而解決了偵測滑鼠移入與移出事件的問題,還有下個問題。

(接下文)
2004/11/14