cyberfan's blog

正其谊不谋其利,明其道不计其功

  IT博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  15 随笔 :: 489 文章 :: 44 评论 :: 0 Trackbacks
子类处理,熟悉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下调试通过。
posted on 2005-08-12 14:36 cyberfan 阅读(194) 评论(0)  编辑 收藏 引用 所属分类: vb
只有注册用户登录后才能发表评论。