這回來談談WINDOW PROCEDURE,談這個主題前,我們先看看Window系統的運作,
詳細細節請參閱相關的書,在此僅簡介。
我們知道Window系統是以Message-Driven的程式,而它的主程式架構如下:
Type WNDCLASSEX
cbSize As Long
style As Long
lpfnWndProc As Long
cbClsExtra As Long
cbWndExtra As Long
hInstance As Long
hIcon As Long
hCursor As Long
hbrBackground As Long
lpszMenuName As String
lpszClassName As String
hIconSm As Long
End Type
Type MSG
hwnd As Long
message As Long
wParam As Long
lParam As Long
time As Long
pt As POINTAPI
End Type
Function WinMain(....) as Long
Dim WndClass as WNDCLASSEX
Dim Message as MSG
'設定wndClass內容
RegisterClassEx(WndClass)
hwnd = CreateWindow(...)
.
.
Do while (GetMessage(Message, null,0,0)) = 1
TranslateMessage(Message)
DispatchMessage(Message)
Loop
End Function
1.註冊(Register) window class ( RegisterClassEx() )
.設Window Icon、Window class name、Cursor type、BackGround
、Window Procedure名稱等。
.配置足夠的記憶體以存Window Class的內容
.只有視窗被註冊了,才能去Create Window,然而Window Class
為System Global者(如EditBox, ListBox, ComboBox, Lable等)
使用時不用註冊,因為這是OS會自動註冊。
2.Create Window (CreateWindow() )
依上述的Window Class Name 當作參數來Create Window,於是
可以知道是要產生哪一種類別的window。(Window Class Name 是
唯一的,所以,可以用它當參數代表是那一種類型的window)
該function將傳回取得hwnd。
3.進入一個GetMessage()的迴圈
這個GetMessage()作用在於取得Thread的Message Queue裡頭的東西(這些訊
息主要來自鍵盤輸入與Mouse的動作),這個迴圈一直到收到WM_QUIT之後
才會結束(就好比unload form,form會結束)。
而TranslateMessage()做的是鍵盤訊息的轉換(試想:非英語系的OS按了"A"
鍵,出來的不一定就是"A"字母)。
至於DispatchMessage()是將訊息傳給某個Window的Window Procedure,這
Window Procedure與一般的Procedure沒有太大的不同,它長作下的樣子:
Function WndProc(ByVal hwnd As Long, ByVal Msg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case Msg
case WM_CREATE
'處理window create時的initial動作
WndProc = 0
case WM_MOUSEMOVE
'處理mouse move的訊息
WndProc = 0
case WM_PAINT
'處理paint的訊息
WndProc = 0
.
.
case else
WndProc = DefWindowProc(hwnd, Msg, wParam, lParam)
End Select
End Function
可見,它是針對想處理的訊息加以處理,否則,就將訊息傳給DefWindowProc()
做預設的訊息處理。每個Window都有其Window Procedure,這個Window Procedure
的名稱是定在Window Class中所設定,即WndClass.lpfnWndProc指向WndProc的
Address(所以相同的window Class所造出來的Window,其Window Procedure名
稱都相同,而Window Procedure名稱可自訂)。這Window Procedure是由程式設計
去寫,但是是給Window OS呼叫,所以它是一個CallBack Function。我們看一看
這Window Procedure的樣子,應可以想像,像Form中有Paint Event這便是對應
上面所寫的WM_PAINT這個部份,而MouseMove Event便是對應於WM_MOUSEMOVE
的部份,以此類推。我猜,VB包裝了Window Procedure,而將之變成我們熟知的
Event Driven的程式設計(這是本人猜測,或許有誤)。
Select Case Msg
case WM_CREATE
'vb做了一些事
RaiseEvent Form_Load '這是我們所能控制的部份
'vb做了一些事
case WM_MOUSEMOVE
'vb做了一些事
RaiseEvent Form_MouseMove
'vb做了一些事
case WM_PAINT
'vb做了一些事
RaiseEvent Form_Paint
'vb做了一些事
case else
WndProc = DefWindowProc(hwnd, Msg, wParam, lParam)
End Select
而在每個RaiseEvent 的之前或之後,VB可能另外加入了一些控制,所以,有
時候會覺得許多事似乎怪怪的,明明已設定了某個功能,怎麼忽然又消失了,例如
我們設定Caret的長像,可是才輸完一個字,就又回復成Default值,這就有可能是
VB暗地裡於某個地方做了某些事,而我們無法預知。
另外有一點要注意,一個Window為一個Thread所執行,一個thread可以有多個
windows,那變成許多的window共用同一個thread的Message Queue,所幸MSG的
結構中有hwnd,使得OS可以知道那是傳給誰的Message。
相同的Window Class所註冊出來的Window,其外觀、行為都相同,因為他們
共用一個Window Procedure,當然,我們可以針對個別的Window做控制,使之行為
與原來的有所不同,這就是SubClassing的動作。
這SubClass的動作才是我們vb中常用的技巧,例如,Combo沒有MouseMove的Event
那如何來解決呢?先來介紹SubClassing 的原理:要先取得原先Window Procedure
所在的位址,將之記錄起來,接著設定所有的Message都先轉到我們所寫的訊息處理
程式來處理特定的訊息,而不處理的訊息將之送往原來的Window Procedure。等到
我們不需要再處理這些特定的訊息時,便取消Message的截取,而使之又只送往原來
的Window Procedure。
'以下程式在module1.bas
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex As Long) As Long
Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
(ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
Public Const GWL_WNDPROC = (-4)
Public Const WM_MOUSEMOVE = &H200
Public Const WM_RBUTTONDOWN = &H204
Public preWinProc As Long
Public Function wndproc(ByVal hwnd As Long, ByVal Msg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
'以下程式會截取mouse move,處理完後,再將之送往原來的Window Procedure
If Msg = WM_MOUSEMOVE Then
'請處理Mouse Move的動作
Debug.print "Combol Mouse Move "
End if
'將之送往原來的Window Procedure
wndproc = CallWindowProc(preWinProc, hwnd, Msg, wParam, lParam)
End Function
'以下程式在Form1, form1中有一Combo1
Sub Form_Load()
Dim ret As Long
'記錄原本的Window Procedure的位址
preWinProc = GetWindowLong(Combo1.hwnd, GWL_WNDPROC)
'設定Combo1的window Procedure到wndproc
ret = SetWindowLong(Combo1.hwnd, GWL_WNDPROC, AddressOf wndproc)
End Sub
Private Sub Form_Unload(Cancel As Integer)
Dim ret As Long
'取消Message的截取,而使之又只送往原來的Window Procedure
ret = SetWindowLong(Combo1.hwnd, GWL_WNDPROC, preWinProc)
End Sub
|