C++ 代理和 Borland C++ Builder 事件处理 - 第二部分






4.67/5 (7投票s)
描述了 Borland 如何巧妙地使用其 __closure 关键字来处理事件
引言
欢迎阅读我们 CPP 委托 文章的第二部分。
在第二部分中,我们将通过 Borland C++ Builder 使用其专有关键字“__closure
”和委托模型来演示它如何处理 Windows 事件,而不是像 MFC 库和 Win32 API 那样使用继承。
背景
我假设读者已阅读本文的第一部分。此外,我假设他/她对 Borland C++ Builder IDE 具有中高级知识。
第二部分 - 使用闭包处理事件
什么是委托?
当你希望另一个人替你完成一项工作时,这就称为委托给这个人。我们可能会看到一位公司经理在自己不在的时候,将一位经验丰富的员工委托给别人来暂时替代他。
在编程世界中,这通过让某个类中的某个函数来执行我们类无法完成的某些任务来实现。这种模式最敏感的例子是“事件处理”。当你点击一个按钮时,一个消息会被发送到操作系统(即 Microsoft Windows),声明你已单击鼠标左键。这就是事件。你的软件作为对该消息或事件的响应所执行的任务称为事件处理。负责处理该事件的函数(即实际在你按下按钮时执行的代码)称为事件处理程序。
Borland 处理 C++ 中事件的独特方式
Borland C++ Builder 使用闭包来实现委托模型,从而实现“事件处理故事”。这是通过声明一个可以指向窗体中的某种方法的闭包来实现的,如下所示:
-
__property OnClick; // declared as __published in class TButton in stdctrls.hpp
-
__property Classes::TNotifyEvent OnClick = {read=FOnClick, write=FOnClick, stored=IsOnClickStored}; // declared as protected in //class TControl in controls.hpp
-
Classes::TNotifyEvent FOnClick; // declared as private in TControl in controls.hpp
-
typedef void __fastcall (__closure *TNotifyEvent) (System::TObject* Sender); // declared as global in namespace // Classes in classes.hpp
要理解这是什么,让我们从下往上解释
- 首先,我们有一个闭包类型,它可以指向一个接受
TObject*
类型参数且不返回任何值的函数,这应该是某个程序员在某个类中编写的“事件处理程序”。这个闭包类型使用typedef
关键字被赋予了一个别名TNotifyEvent
。 - 这个别名用于在
TControl
类中声明一个private
闭包来处理OnClick
事件。 - 由于它是
private
的,我们无法调用它或为其赋值,因此 Borland 使用其自己的方法来更改private
数据,就好像它们是public
一样。这通过__property
关键字来实现。直观地说,这个属性应该声明为protected
/public
/__published
,而不是private
。 - 以上所有这些点都是你期望使用的类(即
TButton
类)的基础。因此,我们应该将属性(OnClick
闭包)的访问级别提高到public
,但这只会允许我们在代码中为其赋值。如果我们想通过我们的 RAD 环境(IDE)赋值怎么办?这里 Borland 在语言中添加了另一个新关键字。它是__published
关键字。简而言之,它为你提供了与public
关键字相同的访问级别,但它允许你通过在 Borland IDE 上单击几次鼠标来赋值,而无需添加任何一行代码。
就这样。要查看单击按钮时会发生什么,让我们回顾一下我们所说的。
假设你已经在一个函数中编写了一些代码,它完成了一项特定的工作,并将你的函数命名为 OnClickHandler
。
使用 IDE,我们可以将此函数地址分配给我们的闭包(OnClick
),它是 TButton
类的成员。
但是,等等,OnClick
本身并不是一个变量。它只是一个通道,允许你通过赋值运算符(l-value
/r-value
)而不是使用老式的 Set
/Get
方法来为实际成员(通常是 private
)赋值或从中检索值。我们将要被赋值为这个地址的实际闭包是 FOnClick
闭包。
这个赋值称为委托。
TButton
对象被编程为捕获操作系统发送的单击消息(事件),然后在响应此消息(事件)时调用 FOnClick
闭包,但是 TButton
类处理了该事件吗?不,真正的处理程序是你编写的位于某个类中的函数。这是 TButton
将你的函数委托给它来处理此类事件。通过这种方式,每个 TButton
类型的对象都有自己的处理程序。如果我们将在 TButton
类中定义处理程序,情况就不是这样了,因为它将是所有 TButton
对象通用的。这意味着所有按钮都会有相同的工作。
你可能会想,如果我们使用继承和虚拟函数来实现每个 TButton
对象的特定处理程序,会怎么样。你是对的。这是解决方案之一,即声明一个没有主体的虚拟函数作为处理程序,然后每次你想在窗体中添加按钮时都必须继承此类,而且你必须重新定义你的虚拟函数以适应你期望的工作。
假设你需要编写一个具有 30 多个不同按钮和截然不同功能的科学计算器。使用 Windows API、Borland 的 OWL 或 Microsoft 的 MFC,你必须进行 30 多次继承。但使用 Borland C++ Builder,你只需要进行 30 多次赋值。甚至可以使用 IDE 中的鼠标单击。
重新定义你的虚拟函数等于定义你的处理程序,并且无法避免这种情况。Borland 的产品不够智能,无法预测你的按钮功能。
结束语
想象一下你的源代码文件包含 30 个继承,而另一个文件包含 30 个赋值(如果有的话)。
如果你理解了这篇文章,你也许就能理解 Charlie Calvert 在他的《Borland C++ Builder Unleashed》一书中的陈述。
“委托是继承的替代方案。它是一种技巧,可以让你获得与继承相同的益处,但工作量却减少了。”
感谢 Borland。
关于代码
第一部分的代码和第二部分的类跟踪已在 Borland C++ Builder 6.0 Enterprise Edition 上进行测试。
Windows XP Service Pack 3.0 平台。
历史
- 2009 年 11 月 26 日:首次发布