VB6.0的事件、回调函数等
何时使用事件或回调通知
主题“使用回调的异步通知”和“使用事件的异步通知”演示表明回调所要实现的工作比事件多。但不应只是根椐工作量来决定使用哪一种方式。回调和事件代表不同的通讯方式,应选择最适合需要的。 可将事件和回调之间的差别特征化:事件象匿名广播,而回调象一次握手。 由此可知,引发事件的部件对其客户端一无所知,而进行回调的部件却知之甚详。 对于开发人员意味着:
注意 事件与回调的另一差别在于,事件不能具有可选参数、命名参数或 ParamArray 参数。
如果上述命题中有一为假,则应做额外工作使用回调方法来提供通知 当执行任务性能极为关键时,也可使用回调方法来做额外工作。使用 Implements 命题将回调接口添加到客户程序的回调对象上,就可得到具有回调方法的vtable 绑定。事件是不会被 vtable 绑定的。(对提供事件或回调的进程内部件,这点尤为显著。) 将函数指针传递到 DLL和类型库
熟悉 C 语言的程序员一定会熟悉函数指针的概念。对于不熟悉 C语言的读者,有必要对此进行一番解释。函数指针是一种约定,程序员可以用它将一个自定义的函数的地址作为参数传递到另一个函数。后面一个函数可以不是自己编写的,但是已经进行了声明,所以可以在应用程序中使用。利用函数指针,可以调用EnumWindows 等函数列出系统中打开的窗口,利用 EnumFontFamilies 列出所有的当前字体。利用函数指针还可以访问 Win32 API中的其它许多函数,早期的 Visual Basic 没有提供对它们的支持。 在 Visual Basic 5.0 中,使用函数指针时仍然存在若干限制。详细信息,请参阅本帮助主题后面的“函数指针的局限与风险”。 有关函数指针的知识使用例子可以很好地说明函数指针的用法。首先,看一看 Win32 API 中的 EnumWindows 函数: Declare Function EnumWindows lib "user32" _ (ByVal lpEnumFunc as Long,_ ByVal lParam as Long ) As Long
EnumWindows 是一个枚举函数,它能够列出系统中每一个打开的窗口的句柄。EnumWindows的工作方式是重复地调用传递给它的第一个参数(lpEnumFunc,函数指针)。每当 EnumWindows 调用函数,EnumWindows都传递一个打开窗口的句柄。 在代码中调用 EnumWindows时,可以将一个自定义函数作为第一个参数传递给它,用来处理一系列的值。例如,可以编写一个函数将所有的值添加到一个列表框中,将 hWnd值转换为窗口的名字,以及其它任何操作! 为了表明传递的参数是一个自定义函数,在函数名称的前面要加上 AddressOf 关键字。第二个参数可以是合适的任何值。例如,如果要把 MyProc作为函数参数,可以按下面的方式调用 EnumWindows: x = EnumWindows(AddressOf MyProc,5)
在调用过程时指定的自定义函数被称为回调函数。回调函数(通常简称为“回调”)能够对过程提供的数据执行指定的操作。 回调函数的参数集必须具有规定的形式,这是由使用回调函数的 API 决定的。关于需要什么参数,如何调用它们,请参阅 API 文档。 使用 AddressOf 关键字如果代码要调用 Visual Basic 5.0 的函数指针,则必须将该代码放到标准的 .BAS模块中,不可以将其放到类模块中,也不能将其附加到窗体上。在使用 AddressOf 关键字声明函数时,必须注意下列事项:
注意 可创建用 Visual C++ (或类似的工具)编译的 DLL 中的回调函数原型。要使用AddressOf 时,原型必须使用 __stdcall 调用约定。不能将缺省调用约定与 AddressOf 并用 在变量中存储函数指针在某些情况下,在将函数指针传递到 DLL 之前需要将其存储在一个中间变量中。如果需要将函数指针从一个 Visual Basic传递到另一个,这种做法是很有用的。例如,在调用 RegisterClass 之类的函数时就需要用结构 (WndClass) 的成员来传递函数指针。 要将一个函数指针赋予结构中的一个成员,需要编写一个包装函数(wrapper)。例如,下面创建的 FnPtrToLong就是一个包装函数,使用它可以将函数指针放入任何结构中: Function FnPtrToLong (ByVal lngFnPtr As Long) As Long FnPtrToLong = lngFnPtr End Function
要使用该函数,首先需要声明类型,然后再调用 FnPtrToLong。AddressOf 加上回调函数的名字作为函数的参数。 Dim mt as MyType mt.MyPtr = FnPtrToLong(AddressOf MyCallBackFunction)
子类派生利用子类派生技术,可以截取发送到控件或窗体的消息。通过截取这些消息,可以编写自己的代码来改变或者扩展对象的行为。类的派生技术比较复杂,对它的全面讨论将超出本书的范围。下例只能提供该技术的大致轮廓。 重点 当 Visual Basic 处于中断模式时,不允许调用 vtable 方法或AddressOf 函数。为了保证安全,Visual Basic 仅仅将 0 返回到 AddressOf 函数的调用者。对于子类派生情况,这意味着WindowProc 将 0 返回到 Windows。Windows 要求它的许多消息返回非 0 值,因此返回的常数 0 将导致 Windows 与Visual Basic 之间的死锁,从而迫使进程终止。 在下例中,应用程序包括一个简单的窗体,其中只有两个命令按钮。代码的作用是截取发送到窗体的 Windows消息,并在“立即”窗口中打印出这些消息的值。 代码的第一部分是声明部分,包括 API 函数声明,常数声明和变量声明: 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 Declare Function SetWindowLong Lib "user32" Alias _ "SetWindowLongA" (ByVal hwnd As Long,_ ByVal nIndex As Long,ByVal dwNewLong As Long) As Long Public Const GWL_WNDPROC = -4 Global lpPrevWndProc As Long Global gHW As Long
下一步,使用两个例程“钩入”消息流。第一个过程 (Hook) 调用了 SetWindowLong 函数,它使用了 GWL_WNDPROC索引来创建窗口类的子类,窗口类是用来创建窗口的。然后它使用 AddressOf 关键字和回调函数 (WindowProc)来截取消息并在“立即”窗口中打印消息的值。第二个过程 (Unhook) 关闭了子类,重新使原来的 Windows 过程成为回调函数。 Public Sub Hook() lpPrevWndProc = SetWindowLong(gHW,GWL_WNDPROC,_ AddressOf WindowProc) End Sub Public Sub Unhook() Dim temp As Long temp = SetWindowLong(gHW,_ lpPrevWndProc) End Sub Function WindowProc(ByVal hw As Long,ByVal uMsg As _ Long,ByVal wParam As Long,ByVal lParam As Long) As _ Long Debug.Print "Message: "; hw,uMsg,wParam,lParam WindowProc = CallWindowProc(lpPrevWndProc,hw,_ uMsg,lParam) End Function
最后,窗体的代码设置了 hWnd 的初始值,按钮的代码仅仅调用了上面的两个例程: Private Sub Form_Load() gHW = Me.hwnd End Sub Private Sub Command1_Click() Hook End Sub Private Sub Command2_Click() Unhook End Sub
声明、引发以及处理事件的总结
为了将某个事件添加到一个类中,然后使用该事件,必须这样做:
详细信息 在“向类中添加事件”中提供了细节和代码示例。
向类中添加事件
现在,假定已经用 Stegosaur、Triceratops 和 Tyrannosaur 类创建了一个恐龙模拟。最后要完成的是,想让Tyrannosaur 咆哮,并且当它这样做的时候,想让这个模拟中的其它每只恐龙都突然警觉起来。 如果 Tyrannosaur 类有一个 Roar 事件,就可以在所有其它恐龙类中处理该事件。本节主题讨论的就是,在类模块中事件的声明和处理。 注意 不要在家里试验这些,至少不要用许多恐龙来作试验。若是用事件将每只恐龙跟其它每一只连接起来,可能会使得恐龙非常之慢,以至于哺乳类对象将统治这个模拟。 有人说属性和方法属于入端接口,因为它们是从对象外面调用的。相对而言,事件被叫做出端接口,因为它们是在对象里边产生,在其它地方处理。 下面讨论的主题,是用实例来描述声明、产生和处理对象的过程。
详细信息 随专业版和企业版一起提供的《部件工具指南》中的“创建 ActiveX部件”,讨论了设计自己的软件部件时事件的使用问题。 关于处理恐龙问题的更好办法的讨论,请参阅本章后面的“多态”。
使用函数指针的局限与风险使用函数指针是有风险的。每当调用 DLL 的时候,就失去了 Visual Basic开发环境的稳定性,使用函数指针的危险性就更大了,因为它很容易导致应用程序失败并因此而丢失已完成的工作。在工作的时候必须经常地保存和备份工作成果。下面列出了使用函数指针的一些注意事项:
详细信息 随专业版和企业版一起提供的《部件工具指南》中的“创建 ActiveX部件”,讨论了设计自己的软件部件时事件的使用问题。 关于处理恐龙问题的更好办法的讨论,请参阅本章后面的“多态”。
详细信息 在“向类中添加事件”中提供了细节和代码示例。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |