VCL与Windows消息
VCL——Visual Component Library,是 Delphi 的基石。Delphi 的优秀,很大程度上得益于 VCL 的优秀。
VCL 是 Delphi 所提供的基本组件库,也就是所谓的 Application Framework,它对Windows API(应用程序接口)进行了全面封装,为桌面开发(不限于桌面开发)提供了整套的解决方案,使得程序员可以在不知晓 API 的情况下进行 Windows 编程。
不过,作为专业的程序员,不知晓 API 是不可能的。VCL 还是一个 Framework(应用程序框架),可以将 VCL 作为一个平台,程序员在其基础上构建应用程序,便可以忽略很多系统 API 的细节,而使得开发速度更快。
VCL 的组件也不同于 ActiveX 控件,VCL 组件通过源代码级连接到可执行文件中,因此其速度更快。而且,企业版的 Delphi 带有全部 VCL 库的源代码,这样程序员不单单可以知道如何使用 VCL 组件,更可以了解其运行机制与构架。
了解 VCL 的构架,无论对于编写自己的 Application,还是设计程序框架,或者创建自己的组件/类融入 VCL 构架中,都是必需和大有裨益的。这也符合某种规律:
在学习的时候,求甚解;
而在应用的时候,则寻找捷径。
Delphi和 VCL 都能满足这两种需求,因为使用它
- 可以不隐藏任何想知道的细节;
- 可以忽略不想知道的细节。
# VCL 类图的主要分支
TObject 封装了 Object Pascal 类/对象的最基本行为。
TPersistent 派生自 TObject,TPersistent 使得自身及其派生类对象具有自我保存、持久存在的能力。
TComponent 派生自 TPersistent,这条分支之下所有的类都可以被称为“组件”。组件的一般特性是:
- 可出现在开发环境的“组件板”上。
- 能够拥有和管理其他组件。
- 能够存取自身(这是因为 TComponent 派生自 TPersistent)。
TControl 派生自 TComponent,其分支之下所有的类,都是在运行时可见的组件。
TWinControl 派生自 TControl,这个分支封装了 Windows 系统的屏幕对象,也就是一个真正的 Windows 窗口(拥有窗口句柄)。
TCustomControl 派生自 TwinControl。从 TCustomControl 开始,组件拥有了 Canvas(画布)属性。
# TObject 与消息分发
在 TObject 类中,有一个 Dispatch()方法和一个 DefaultHandler()方法,它们都是与消息分发机制相关的。
Dispatch()负责将特定的消息分发给合适的消息处理函数。首先它会在对象本身类型的类中寻找该消息的处理函数,如果找到,则调用它;如果没有找到而该类覆盖了 TObject 的 DefaultHandler(),则调用该类的 DefaultHandler();如果两者都不存在,则继续在其基类中寻找,直至寻找到 TObject 这一层,而 TObject 已经提供了默认的 DefaultHandler() 方法。
示例:
unit MsgDispTest;
interface
uses
Dialogs, Messages;
type
TMyMsg = record
Msg: Cardinal;
MsgText: ShortString;
end;
TMsgAccepter = class // 消息接收器类
private
procedure AcceptMsg2000(var msg: TMyMsg); message 2000;
procedure AcceptMsg2002(var msg: TMyMsg); message 2002;
public
//默认处理函数
procedure DefaultHandler(var Message); override;
end;
implementation
{ TMsgAccepter }
procedure TMsgAccepter.AcceptMsg2000(var msg: TMyMsg);
begin
ShowMessage('嗨,我收到了编号为 2000 的消息,它的描述是:' + msg.MsgText);
end;
procedure TMsgAccepter.AcceptMsg2002(var msg: TMyMsg);
begin
ShowMessage('嗨,我收到了编号为 2002 的消息,它的描述是:' + msg.MsgText);
end;
procedure TMsgAccepter.DefaultHandler(var message);
begin
ShowMessage('嗨,这个消息我不认识,无法接收,它的描述是:' + TMyMsg(message).MsgText);
end;
end.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
界面程序单元代码
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, MsgDispTest;
type
TForm1 = class(TForm)
btnMsg2000: TButton;
btnMsg2001: TButton;
btnMsg2002: TButton;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnMsg2000Click(Sender: TObject);
procedure btnMsg2002Click(Sender: TObject);
procedure btnMsg2001Click(Sender: TObject);
end;
var
Form1: TForm1;
MsgAccept: TMsgAccepter; // 自定义的消息接收类
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
// 创建 TMsgAccepter 类的实例
MsgAccept := TMsgAccepter.Create();
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
// 析构 TMsgAccepter 类的实例
MsgAccept.Free();
MsgAccept := nil;
end;
procedure TForm1.btnMsg2000Click(Sender: TObject);
var
Msg: TMyMsg;
begin
// 将值为 2000 的消息分发给 MsgAccept 对象,观察其反应
Msg.Msg := 2000;
Msg.MsgText := 'Message 2000'; // 消息的文字描述
MsgAccept.Dispatch(Msg); // 分发消息
end;
procedure TForm1.btnMsg2002Click(Sender: TObject);
var
Msg: TMyMsg;
begin
// 将值为 2002 的消息分发给 MsgAccept 对象,观察其反应
Msg.Msg := 2002;
Msg.MsgText := 'Message 2002'; // 消息的文字描述
MsgAccept.Dispatch(Msg); // 分发消息
end;
procedure TForm1.btnMsg2001Click(Sender: TObject);
var
Msg: TMyMsg;
begin
// 将值为 2001 的消息分发给 MsgAccept 对象,观察其反应
Msg.Msg := 2001;
Msg.MsgText := 'Message 2001'; // 消息的文字描述
MsgAccept.Dispatch(Msg); // 分发消息
end;
end.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# TControl 与 Windows 消息的封装
TObject 提供了最基本的消息分发和处理的机制,而 VCL 真正对 Windows 系统消息的封装则是在 TControl 中完成的。
TControl 将消息转换成 VCL 的事件,以将系统消息融入 VCL 框架中。
调用关系
TControl 组件类的声明
TControl = class(TComponent)
Private
FOnMouseDown: TMouseEvent;
procedure DoMouseDown(var Message: TWMMouse; Button: TMouseButton; Shift: TShiftState);
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;X, Y: Integer); dynamic;
procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
procedure WMRButtonDown(var Message: TWMRButtonDown); message WM_RBUTTONDOWN;
procedure WMMButtonDown(var Message: TWMMButtonDown); message WM_MBUTTONDOWN;
//省略……
protected
//省略……
property OnMouseDown: TMouseEvent read FOnMouseDown write FOnMouseDown;
//省略……
end;
procedure TControl.WMLButtonDown(var Message: TWMLButtonDown);
begin
SendCancelMode(Self);
inherited;
if csCaptureMouse in ControlStyle then
MouseCapture := True;
if csClickEvents in ControlStyle then
Include(FControlState, csClicked);
DoMouseDown(Message, mbLeft, []);
end;
procedure TControl.WMRButtonDown(var Message: TWMRButtonDown);
begin
inherited;
DoMouseDown(Message, mbRight, []);
end;
procedure TControl.WMMButtonDown(var Message: TWMMButtonDown);
begin
inherited;
DoMouseDown(Message, mbMiddle, []);
end;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
当 TObject.Dispatch()将 WM_LBUTTONDOWN 消息、WM_RBUTTONDOWN 消息或WM_MBUTTONDOWN 消息分发给 TControl 的派生类的实例后,WMLButtonDown()、WMRButtonDown()或 WMMButtonDown()被执行,然后它们都有类似这样 DoMouseDown(Message, mbRight, []);
的代码来调用 DoMouseDown():
procedure TControl.DoMouseDown(var Message: TWMMouse; Button:
TMouseButton; Shift: TShiftState);
begin
if not (csNoStdEvents in ControlStyle) then
with Message do
if (Width > 32768) or (Height > 32768) then
with CalcCursorPos do
MouseDown(Button, KeysToShiftState(Keys) + Shift, X,Y)
else
MouseDown(Button,KeysToShiftState(Keys) + Shift, Message.XPos, Message.Ypos);
end;
2
3
4
5
6
7
8
9
10
11
在 DoMouseDown()中进行一些必要的处理工作后(特殊情况下重新获取鼠标位置),就会调用 MouseDown():
procedure TControl.MouseDown(Button: TMouseButton;Shift: TShiftState; X, Y: Integer);
begin
if Assigned(FOnMouseDown) then
FOnMouseDown(Self, Button, Shift, X, Y);
end;
2
3
4
5
在 MouseDown()中,才会通过 FOnMouseDown 事件指针真正去执行用户定义的OnMouseDown 事件的代码。
由此,完成了 Windows 系统消息到 VCL 事件的转换过程。
备注:以上内容来源于【Delphi高手突破】一书