VB的子类处理应用两例

来源:岁月联盟 编辑:zhu 时间:2007-01-31
   

  子类处理,熟悉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

[责任编辑:editor]

   

  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

[责任编辑:editor]

   

  在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参数为鼠标指针的窗口句柄,如果该窗口有默认的关联菜单,则其弹出。在文本框内单击右键时,我们必须先拦截这条消息,然后在鼠标指针所在位置弹出自制的关联菜单。

[责任编辑:editor]

   

  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下调试通过。

[责任编辑:editor]