📄 delphi消息机制.txt
字号:
从名字也可以看出该函数的大概目的:最终的消息处理函数。在 TObject 的定义中 DefaultHandler 并没有代码,DefaultHandler 是在需要处理消息的类(TControl)之后被重载的。
从上面的讨论中已经知道 DefaultHandler 是由 TObject.Dispatch 调用的,所以 DefaultHandler 和 Dispatch 的参数类型一样都是无类型的 var Message。
由于 DefaultHandler 是个虚方法,所以执行流程是从子类到父类。在 TWinControl 和 TControl 的 DefaultHandler 中,仍然遵从 WndProc 的执行规则,也就是 TWinControl 没处理的消息,再使用 inherited 调用 TControl.DefaultHandler 来处理。
在 TWinControl.DefaultHandler 中先是处理了一些不太重要的Windows 消息,如WM_CONTEXTMENU、WM_CTLCOLORMSGBOX等。然后做了两件比较重要的工作:1、处理 RM_GetObjectInstance 消息;2、对所有未处理的窗口消息调用 TWinControl.FDefWndProc。
下面分别讨论。
RM_GetObjectInstance 是应用程序启动时自动使用 RegisterWindowMessage API 注册的 Windows 系统级消息ID,也就是说这个消息到达 Dispatch 后会无条件地传递给 DefaultHandler(见 Dispatch 的分析)。TWinControl.DefaultHandler 发现这个消息就把 Self 指针设置为返回值。在 Controls.pas 中有个函数 ObjectFromHWnd 使用窗口句柄获得 TWinControl 的句柄,就是使用这个消息实现的。不过这个消息是由 Delphi 内部使用,不能被应用程序使用。(思考:每次应用程序启动都会调用 RegisterWindowMessage,如果电脑长期不停机,那么 0xC000 - 0xFFFF 之间的消息 ID 是否会被耗尽?)
另外,TWinControl.DefaultHandler 在 TWinControl.FHandle 不为 0 的情况下,使用 CallWindowProc API 调用 TWndControl.FDefWndProc 窗口过程。FDefWndProc 是个指针,它是从哪里初始化的呢?跟踪一下,发现它是在 TWinControl.CreateWnd 中被设置为如下值:
FDefWndProc := Params.WindowClass.lpfnWndProc;
还记得前面讨论的窗口创建过程吗?TWinControl.CreateWnd 函数首先调用 TWinControl.CreateParams 获得待创建的窗口类的参数。CreateParams 把 WndClass.lpfnWndProc 设置为 Windows 的默认回调函数 DefWindowProc API。但 CreateParams 是个虚函数,可以被 TWinControl 的继承类重载,因此程序员可以指定一个自己设计的窗口过程。
所以 TWinControl.DefaultHandler 中调用 FDefWndProc 的意图很明显,就是可以在 Win32 API 的层次上支持消息的处理(比如可以从 C 语言写的 DLL 中导入窗口过程给 VCL 控件),给程序员提供充足的弹性空间。
TWinControl.DefaultHandler 最后一行调用了 inherited,把消息传递给 TControl 来处理。
TControl.DefaultHandler 只处理了三个消息 WM_GETTEXT、WM_GETTEXTLENGTH、WM_SETTEXT。为什么要处理这个几个看似不重要的消息呢?原因是:Windows 系统中每个窗口都有一个 WindowText 属性,而 VCL 的 TControl 为了模拟成窗口也存储了一份保存在 FText 成员中,所以 TControl 在此接管这几个消息。
TControl.DefaultHandler 并没有调用 inherited,其实也没有必要调用,因为 TControl 的祖先类都没有实现 DefaultHandler 函数。可以认为 DefaultHandler 的执行到此为止。
VCL 的消息流程至此为止。
===============================================================================
⊙ TControl.Perform 和 TWinControl.Broadcast
===============================================================================
现在介绍 VCL 消息系统中两个十分简单但调用频率很高的函数。
TControl.Perform 用于直接把消息送往控件的消息处理函数 WndProc。Perform 方法不是虚方法,它把参数重新组装成一个 TMessage 类型,然后调用 WindowProc(还记得 WindowProc 的作用吗?),并返回 Message.Result 给用户。它的调用格式如下:
function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;
Perform 经常用于通知控件某些事件发生,或得到消息处理的结果,如下例:
Perform(CM_ENABLEDCHANGED, 0, 0);
Text := Perform(WM_GETTEXTLENGTH, 0, 0);
TWinControl.Broadcast 用于把消息广播给每一个子控件。它调用 TWinControl.Controls[] 数组中的所有对象的 WindowsProc 过程。
procedure TWinControl.Broadcast(var Message);
注意 Broadcast 的参数是无类型的。虽然如此,在 Broadcast 函数体中会把消息转换为 TMessage 类型,也就是说 Broadcast 的参数必须是 TMessage 类型。那么为什么要设计为无类型的消息呢?原因是 TMessage 有很多变体(Msg 和 Result 字段不会变,WParam 和 LParam 可设计为其它数据类型),将 Broadcast 设计为无类型参数可以使程序员不用在调用前强制转换参数,但调用时必须知道这一点。比如以下字符消息的变体,是和 TMessage 兼容的:
TWMKey = packed record
Msg: Cardinal;
CharCode: Word;
Unused: Word;
KeyData: Longint;
Result: Longint;
end;
===============================================================================
⊙ TWinControl.WMPaint
===============================================================================
上面在讨论 TWinControl.WndProc 时提到,TControl 类控件的鼠标和重绘消息是从 Parent TWinControl 中产生的。但我们只发现了鼠标消息的产生,那么重绘消息是从哪里产生出来的呢?答案是TWinControl.WMPaint:
procedure TWinControl.WMPaint(var Message: TWMPaint); message WM_PAINT;
在 TWinControl.WMPaint 中建立了双缓冲重绘机制,但我们目前不关心这个,只看最关键的代码:
if not (csCustomPaint in ControlState) and (ControlCount = 0) then
inherited // 注意 inherited 的实现
else
PaintHandler(Message);
这段代码的意思是,如果控件不支持自绘制并且不包含 TControl 就调用 inherited。
inherited 是什么呢?由于 TWinControl.WMPaint 的父类 TControl 没有实现这个消息句柄,Delphi 生成的汇编代码竟然是:call Self.DefaultHandler。(TWinControl.DefaultHandler 只是简单地调用 TWinControl.FDefWndProc。)
如果条件为否,那么将调用 TWinControl.PaintHandler(不是虚函数)。PaintHandler 调用 BeginPaint API 获得窗口设备环境,再使用该设备环境句柄为参数调用 TWinControl.PaintWindow。在 TWinControl 中 PaintWindow 只是简单地把消息传递给 DefaultHandler。PaintWindow 是个虚函数,可以在继承类中被改写,以实现自己需要的绘制内容。PaintHandler 还调用了 TWinControl.PaintControls 方法。PaintControls 使用 Perform 发送 WM_PAINT 消息给 TWinControl 控件包含的所有 TControl 控件。
这样,TControl 控件才获得了重绘的消息。
让我们设计一个 TWinControl 的继承类作为练习:
TMyWinControl = class(TWinControl)
protected
procedure PaintWindow(DC: HDC); override;
public
constructor Create(AOwner: TComponent); override;
end;
constructor TMyWinControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlState := ControlState + [csCustomPaint];
// 必须通知 WMPaint 需要画自己
end;
procedure TMyWinControl.PaintWindow(DC: HDC);
var
Rect: TRect;
begin
Windows.GetClientRect(Handle, Rect);
FillRect(DC, Rect, COLOR_BTNSHADOW + 1);
SetBkMode(DC, TRANSPARENT);
DrawText(DC, ‘Hello, TMyWinControl’, -1, Rect, DT_SINGLELINE or DT_VCENTER
or DT_CENTER);
end;
上面实现的 TMyWinControl 简单地重载 PaintWindow 消息,它可以包含 TControl 对象,并能正确地把它们画出来。如果你确定该控件不需要包含 TControl 对象,你也可以直接重载 WMPaint 消息,这就像用 C 语言写普通的 WM_PAINT 处理函数一样。
===============================================================================
⊙ 以 TWinControl 为例描述消息传递的路径
===============================================================================
下图描述一条消息到达后消息处理函数的调用路径,每一层表示函数被上层函数调用。
TWinControl.FObjectInstance
|-TWinControl.MainWndProc
|-TWinControl.WindowProc
|-TWinControl.WndProc
|-TControl.WndProc
|-TObject.Dispatch
|-Call DMT messages
|-TWinControl.DefaultHandler
|-TControl.DefaultHandler
注:
如前文所述,上图中的 WindowProc 是个指针,所以它在编译器级实际上等于 WndProc,而不是调用 WndProc,图中为了防止与消息分枝混淆特意区分成两层。
TObject.Dispatch 有两条通路,如果当前控件以 message 关键字实现了消息处理函数,则呼叫该函数,否则调用 DefaultHandler。
有些消息处理函数可能在中途就已经返回了,有些消息处理函数可能会被递归调用。
===============================================================================
结束语
VCL 的消息机制就讨论到这里。希望我们通过本文的讨论理清了 VCL 处理消息的框架,今后我们将使用这些最基础的知识开始探索 Delphi 程序设计的旅程。
===============================================================================
Post in Delphi
--------------------------------------------------------------------------------
0 Responses to “Delphi 的消息机制浅探”
Feed for this Entry Trackback Address
没有评论 Leave a Reply
名称
Mail ( will not be published )
网址
XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>
提示:如果你刚刚提交过评论,但是还没有被显示出来,请点击这里刷新一下: 刷新评论 。
--------------------------------------------------------------------------------
Robot5’s Blog is proudly powered by WordPress and Blue Memories by iqwolf. AK47 | ITech Upload.
Entries (RSS) and Comments (RSS).
0
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -