VB的子類處理應用兩例
子類處理,熟悉API函數的VB愛好者們一定不會感到陌生,它又稱為“子類化”或“子類派生”,是一種功能強大的技術。在應用它之前,我們需要先對之原理進行簡單的了解:在WINDOWS中,每一個窗口都有一個默認的窗口函數,它的作用是對發送到窗口的消息進行處理。在VB中,這個默認的窗口函數不是直接公開的,它提供了對VB中的事件進行處理的代碼,當接受到一條WINDOWS消息時,這個窗口函數就會響應并產生一個VB事件,換言之,這個窗口函數隱藏了消息處理的細節,用一個VB事件來響應一條WINDOWS消息。然而,VB沒有提供對所有WINDOWS消息的支持,許多WINDOWS消息都不會生成一個VB事件,但這不能說是VB的缺點,恰是VB的優點,放棄對那些程序員并不常用的消息的支持,在功能強大和性能穩定之間做了很好的平衡。而且,幸運的是,盡管這個幕后主宰是默認的,但它不是唯一的,我們完全可以用自己定制的一個窗口函數替代它,并保留指向默認窗口函數的指針,當一個消息到達窗口時,自制的窗口函數會攔截它并進行識別處理,對不能識別或不需進行特別處理的消息,就通過指向默認窗口函數的指針傳遞給默認的窗口函數進行處理,這樣便擴充了默認窗口函數的功能。這種用定制的窗口函數代替默認的窗口函數,攔截并處理到達窗口的消息的技術,我們就稱之為“子類處理”,定制的函數我們稱之為“回調函數”。子類處理的方法主要有三種:忽略消息并傳遞給默認的窗口函數;截獲消息,執行特定操作后,傳遞給默認的窗口函數或傳遞給默認的窗口函數處理后,對返回值進行控制;截獲消息,執行特定操作并禁止默認的窗口函數對之進行處理。我們將通過下面兩個實例對之進行簡要說明。
一、實現無標題欄窗口的拖動
大家都知道,按住窗口的標題欄可以拖動窗口。可如果窗口沒有標題欄,怎樣拖動它呢?那就按在窗口的客戶區上吧,只要讓窗口覺得是按在了標題欄上就可以了。
首先需要在一個模塊文件Modulel內輸入以下代碼,(我們自制的回調函數必須在模塊文件中聲明,不可將其放到類模塊中,也不能附加到窗體中):
下面聲明的是子類處理中最重要的三個函數。
SetWindowLong函數使用GWL_WNDPROC索引將默認的窗口函數替換成我們自制的回調函數,回調函數的地址由AddressOf操作符得到。SetWindowsLong函數返回值為默認的窗口函數的地址。
Declare Function SetWindowLong Lib“user32"Alias“SetWindowLingA"(ByValhwnd As Long,ByVal nIndex As Long,By Val dwNewLong As Long)As Long
DefWindowProc函數調用默認的窗口函數對消息進行處理,并返回消息處理的指定返回值。
Declare Function DefWindowProc Lib“user32"Alias “DefWindowProcA"(ByValhwnd As Long,ByVal wMsg As Long,ByVal wParam As Interger,ByVal lParam As Long)As Long
CallWindowProc函數傳遞消息到指定的窗口函數(窗口函數地址由lpPrevWndFunc參數給定),并返回消息處理的指定返回值。
Public 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 proroc As Long
Public Const WM_NCHITTEST=&H84
Public Const HTCAPTION=2
Public Const HTCLIENT=1
Public Const GWL_WNDPROC=(-4)
回調函數WindowProc結構如下,共有四個參數,第一個參數是窗口句柄,第二個參數是消息編號,第三、四個參數是32位整數,它們根據不同的消息而不同。
本例中,當鼠標在窗口內進行了一個按下或松開的操作時,WINDOWS會向窗口發出一條WM_NCHITTEST消息(該消息用于判斷窗口的非客戶區域的什么部分包含了鼠標指針),回調函數在收到WM_NCHITTEST消息后,首先調用窗口的默認函數進行處理,然后判斷返回值,如果是HTCLIENT(表示鼠標指針在客戶區內),就改變它,使之返回HTCAPTION(表示鼠標指針在標題欄內),這樣,當我們在窗口的客戶區內按住鼠標移動時,窗口就會傻呼呼地以為按在了標題框內,當然就跟著我們的鼠標動了。
Function WindowProc(ByVal hwnd As Long, ByVal msg As Long,ByVal wParam As Long,ByVal lParam As Long)As Long
Dim rv As Long
If msg=WM_NCHITTEST Then
rv=DefWindowProc(hwnd,msg,wParam,lParam)
If rv=HTCLIENT Then
WindowProc=HTCAPTION
Else
WindowProc=rv
End If
將其他的消息傳遞給默認的窗口函數進行處理。
Else
WindowProc=CallWindowProc(proroc,hwnd,msg,wParam,lParam)
End If
在Forml內輸入如下代碼:
Private Sub Form_load()
proroc=SetWindowLong(hwnd,GWL_WNDPROC,AddressOf WindowProc)
End Sub
一定要記得在窗口卸載之前恢復默認的窗口函數,否則……您試一試就知道了。
Private Sub Form_Unload(Cancel As Integer)
Dim rv As Long
rv=SetWindowLong(hwnd,GWL_WNDPROC,proroc)End Sub
二、規模文本框的關聯菜單
大家都知道,文本框控件有自己的關聯菜單(上有剪切、復制、全選之類的命令),這無疑為我們在編程時提供了便利。可是,有利必有弊,當我們需要給文本框提供一些定制命令,并把它們加到一個自定義的關聯菜單中時,默認的關聯菜單給我們帶來了不便(在文本框內單擊右鍵時,會先后彈出兩個關聯菜單)。如何防止默認的關聯菜單彈出呢?
程序如下:
需要在一個模塊文件Modulel內輸入以下代碼:
首先加入SetWindowLong函數和CallWindowProc函數的聲明
Declare Function GetMenu Lib“user32"(ByVal hwnd As Long)As Long
Declare Function GetSubMenu Lib“user32"(Byual hMenu As Long,ByVal nPOS As Long)As Long
Declare Function TrackPopupMenuBynum Lib“user32"Alias“TrackPopupMenu"(ByVal hMenu As Long,ByVal wFlags As Long,ByVal x As Long,ByVal y As Long,ByVal nReserved As Long,ByVal hwnd As Long,ByVal lprc As Long)As Long
Public Const WM_CONTEXTMENU=&H7B
Public Const GWL_WNDPROC=(-4)
Public Const TPM_LEFTALIGN=&H0&
Public Const TPM_LEFTBUTTON=*H0&
Public proroc As Long
Public hMenu As Long
Public pMenu As Long
當我們在一個窗口內單擊右鍵時(確切地說應是按下的右鍵抬起時),會向窗口發出一條WM_CONTEXTMENU的消息,其LParam參數為鼠標指針的屏幕座標(低字為X座標,高字為Y座標),其WParam參數為鼠標指針的窗口句柄,如果該窗口有默認的關聯菜單,則其彈出。在文本框內單擊右鍵時,我們必須先攔截這條消息,然后在鼠標指針所在位置彈出自制的關聯菜單。
Function WindowProc(ByVal hwnd As Long,ByVal msg As Long,ByVal wParam As Long,ByVal lParam As Long)As Long
Dim xl As Long
Dim yl As Long
If msg=WM_CONTEXTMENU Then
xl=lParam Mod&H10000
y1=lParam/&H10000
TrackPopupMenuBynum pmenu,TPM_LEFTALIGN Or TPM_LEFTBUTTON,xl,yl,0,hwnd,0
Else
WindowProc=CallWindowProc(proroc,hwnd,msg,wParam,lParam)
End If
End Function
在Forml內輸入如下代碼:Private Sub Form_Load()
本例假定把菜單欄在頂級菜單的第1個條目作為關聯菜單。
hMenu=GetMenu(Me.hwnd)
pmenu=GetSubMenu(hMenu,0)
proroc=SetWindowLong(Textl.hwnd,GWL_WNDPROC,AddressOf WindowProc)
End Sub
Private Sub Form_Unload(Cancel As Integer)
Dim rv As Long
rv=SetWindowLong(Textl.hwnd,GWL_WNDPROC,proroc)
End Sub
利用SetWindowLong函數和AddressOf操作符進行子類處理必須注意:
1、不可以在程序中設置斷點對代碼進行調試(這就是說你在回調函數中的一點小語法錯誤都會導致應用程序崩潰,所以在執行程序前一定要謹慎再謹慎),可使用debug.print語句獲得消息處理的信息。千萬不要按VB工具欄上的停止按鈕,它甚至會使VB.EXE崩潰。
2、只能對進程內的窗口進行子類處理,對進程外的窗口,可以使用一些廠商提供的子類處理控件(如Desaware公司的SpyWorks軟件包中的dwsbc32d.ocx控件)。此外,對于線程窗口,不能使用SetWindowLong函數,應使用SetWindowHookEx函數進行子類處理。
上述如有不對之處,歡迎各位批評指正。
以上程序在中文WIN95,VB5.0下調試通過。
一、實現無標題欄窗口的拖動
大家都知道,按住窗口的標題欄可以拖動窗口。可如果窗口沒有標題欄,怎樣拖動它呢?那就按在窗口的客戶區上吧,只要讓窗口覺得是按在了標題欄上就可以了。
首先需要在一個模塊文件Modulel內輸入以下代碼,(我們自制的回調函數必須在模塊文件中聲明,不可將其放到類模塊中,也不能附加到窗體中):
下面聲明的是子類處理中最重要的三個函數。
SetWindowLong函數使用GWL_WNDPROC索引將默認的窗口函數替換成我們自制的回調函數,回調函數的地址由AddressOf操作符得到。SetWindowsLong函數返回值為默認的窗口函數的地址。
Declare Function SetWindowLong Lib“user32"Alias“SetWindowLingA"(ByValhwnd As Long,ByVal nIndex As Long,By Val dwNewLong As Long)As Long
DefWindowProc函數調用默認的窗口函數對消息進行處理,并返回消息處理的指定返回值。
Declare Function DefWindowProc Lib“user32"Alias “DefWindowProcA"(ByValhwnd As Long,ByVal wMsg As Long,ByVal wParam As Interger,ByVal lParam As Long)As Long
CallWindowProc函數傳遞消息到指定的窗口函數(窗口函數地址由lpPrevWndFunc參數給定),并返回消息處理的指定返回值。
Public 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 proroc As Long
Public Const WM_NCHITTEST=&H84
Public Const HTCAPTION=2
Public Const HTCLIENT=1
Public Const GWL_WNDPROC=(-4)
回調函數WindowProc結構如下,共有四個參數,第一個參數是窗口句柄,第二個參數是消息編號,第三、四個參數是32位整數,它們根據不同的消息而不同。
本例中,當鼠標在窗口內進行了一個按下或松開的操作時,WINDOWS會向窗口發出一條WM_NCHITTEST消息(該消息用于判斷窗口的非客戶區域的什么部分包含了鼠標指針),回調函數在收到WM_NCHITTEST消息后,首先調用窗口的默認函數進行處理,然后判斷返回值,如果是HTCLIENT(表示鼠標指針在客戶區內),就改變它,使之返回HTCAPTION(表示鼠標指針在標題欄內),這樣,當我們在窗口的客戶區內按住鼠標移動時,窗口就會傻呼呼地以為按在了標題框內,當然就跟著我們的鼠標動了。
Function WindowProc(ByVal hwnd As Long, ByVal msg As Long,ByVal wParam As Long,ByVal lParam As Long)As Long
Dim rv As Long
If msg=WM_NCHITTEST Then
rv=DefWindowProc(hwnd,msg,wParam,lParam)
If rv=HTCLIENT Then
WindowProc=HTCAPTION
Else
WindowProc=rv
End If
將其他的消息傳遞給默認的窗口函數進行處理。
Else
WindowProc=CallWindowProc(proroc,hwnd,msg,wParam,lParam)
End If
在Forml內輸入如下代碼:
Private Sub Form_load()
proroc=SetWindowLong(hwnd,GWL_WNDPROC,AddressOf WindowProc)
End Sub
一定要記得在窗口卸載之前恢復默認的窗口函數,否則……您試一試就知道了。
Private Sub Form_Unload(Cancel As Integer)
Dim rv As Long
rv=SetWindowLong(hwnd,GWL_WNDPROC,proroc)End Sub
二、規模文本框的關聯菜單
大家都知道,文本框控件有自己的關聯菜單(上有剪切、復制、全選之類的命令),這無疑為我們在編程時提供了便利。可是,有利必有弊,當我們需要給文本框提供一些定制命令,并把它們加到一個自定義的關聯菜單中時,默認的關聯菜單給我們帶來了不便(在文本框內單擊右鍵時,會先后彈出兩個關聯菜單)。如何防止默認的關聯菜單彈出呢?
程序如下:
需要在一個模塊文件Modulel內輸入以下代碼:
首先加入SetWindowLong函數和CallWindowProc函數的聲明
Declare Function GetMenu Lib“user32"(ByVal hwnd As Long)As Long
Declare Function GetSubMenu Lib“user32"(Byual hMenu As Long,ByVal nPOS As Long)As Long
Declare Function TrackPopupMenuBynum Lib“user32"Alias“TrackPopupMenu"(ByVal hMenu As Long,ByVal wFlags As Long,ByVal x As Long,ByVal y As Long,ByVal nReserved As Long,ByVal hwnd As Long,ByVal lprc As Long)As Long
Public Const WM_CONTEXTMENU=&H7B
Public Const GWL_WNDPROC=(-4)
Public Const TPM_LEFTALIGN=&H0&
Public Const TPM_LEFTBUTTON=*H0&
Public proroc As Long
Public hMenu As Long
Public pMenu As Long
當我們在一個窗口內單擊右鍵時(確切地說應是按下的右鍵抬起時),會向窗口發出一條WM_CONTEXTMENU的消息,其LParam參數為鼠標指針的屏幕座標(低字為X座標,高字為Y座標),其WParam參數為鼠標指針的窗口句柄,如果該窗口有默認的關聯菜單,則其彈出。在文本框內單擊右鍵時,我們必須先攔截這條消息,然后在鼠標指針所在位置彈出自制的關聯菜單。
Function WindowProc(ByVal hwnd As Long,ByVal msg As Long,ByVal wParam As Long,ByVal lParam As Long)As Long
Dim xl As Long
Dim yl As Long
If msg=WM_CONTEXTMENU Then
xl=lParam Mod&H10000
y1=lParam/&H10000
TrackPopupMenuBynum pmenu,TPM_LEFTALIGN Or TPM_LEFTBUTTON,xl,yl,0,hwnd,0
Else
WindowProc=CallWindowProc(proroc,hwnd,msg,wParam,lParam)
End If
End Function
在Forml內輸入如下代碼:Private Sub Form_Load()
本例假定把菜單欄在頂級菜單的第1個條目作為關聯菜單。
hMenu=GetMenu(Me.hwnd)
pmenu=GetSubMenu(hMenu,0)
proroc=SetWindowLong(Textl.hwnd,GWL_WNDPROC,AddressOf WindowProc)
End Sub
Private Sub Form_Unload(Cancel As Integer)
Dim rv As Long
rv=SetWindowLong(Textl.hwnd,GWL_WNDPROC,proroc)
End Sub
利用SetWindowLong函數和AddressOf操作符進行子類處理必須注意:
1、不可以在程序中設置斷點對代碼進行調試(這就是說你在回調函數中的一點小語法錯誤都會導致應用程序崩潰,所以在執行程序前一定要謹慎再謹慎),可使用debug.print語句獲得消息處理的信息。千萬不要按VB工具欄上的停止按鈕,它甚至會使VB.EXE崩潰。
2、只能對進程內的窗口進行子類處理,對進程外的窗口,可以使用一些廠商提供的子類處理控件(如Desaware公司的SpyWorks軟件包中的dwsbc32d.ocx控件)。此外,對于線程窗口,不能使用SetWindowLong函數,應使用SetWindowHookEx函數進行子類處理。
上述如有不對之處,歡迎各位批評指正。
以上程序在中文WIN95,VB5.0下調試通過。