1.VCL 概貌

先看一下VCL类图的主要分支,如图4.1所示。
在图中可以看到,TObject是VCL的祖先类,这也是Object Pascal语言所规定的。但实际上,TObject以及TObject声明所在的system.pas整个单元,包括在“编译器魔法”话题中提到的_ClassCreate等函数,都是编译器内置支持的。因此,无法修改、删除system.pas中的任何东西,也无法将system.pas加入你的project,否则会得到“Identifier redeclared ‘system’”的错误提示,因project中已经被编译器自动包含了system单元。
意思是,TObject是Object Pascal语言/编译器本身的一个性质!

TObject封装了Object Pascal类/对象的最基本行为。
TPersistent派生自TObject,TPersistent使得自身及其派生类对象具有自我保存、持久存在的能力。
TComponent派生自TPersistent,这条分支之下所有的类都可以被称为“组件”。组件的一般特性是:
(1)可出现在开发环境的“组件板”上。

(2)能够拥有和管理其他组件。

(3)能够存取自身(这是因为TComponent派生自TPersistent)。
TControl派生自TComponent,其分支之下所有的类,都是在运行时可见的组件。
TWinControl派生自TControl,这个分支封装了Windows系统的屏幕对象,也就是一个真正的Windows窗口(拥有窗口句柄)。
TCustomControl派生自TwinControl。从TCustomControl开始,组件拥有了Canvas(画布)属性。

图4.1 VCL类图主要分支(深色表示核心分支)

2.TObject与消息分发

首先来看一下TObject这个“万物之源”究竟长得何等模样。它的声明如下:

  1. TObject = class
  2. constructor Create;
  3. procedure Free;
  4. class function InitInstance(Instance: Pointer): TObject;
  5. procedure CleanupInstance;
  6. function ClassType: TClass;
  7. class function ClassName: ShortString;
  8. class function ClassNameIs(const Name: string): Boolean;
  9. class function ClassParent: TClass;
  10. class function ClassInfo: Pointer;
  11. class function InstanceSize: Longint;
  12. class function InheritsFrom(AClass: TClass): Boolean;
  13. class function MethodAddress(const Name: ShortString): Pointer;
  14. class function MethodName(Address: Pointer): ShortString;
  15. function FieldAddress(const Name: ShortString): Pointer;
  16. function GetInterface(const IID: TGUID; out Obj): Boolean;
  17. class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
  18. class function GetInterfaceTable: PInterfaceTable;
  19. function SafeCallException(ExceptObject: TObject;
  20. ExceptAddr: Pointer): HResult; virtual;
  21. procedure AfterConstruction; virtual;
  22. procedure BeforeDestruction; virtual;
  23. procedure Dispatch(var Message); virtual;
  24. procedure DefaultHandler(var Message); virtual;
  25. class function NewInstance: TObject; virtual;
  26. procedure FreeInstance; virtual;
  27. destructor Destroy; virtual;
  28. end;

从TObject的声明中可以看到,TObject包含了诸如实例初始化、实例析构、RTTI、消息分发等相关实现的方法。现在就来研究一下TObject与消息分发,这也是VCL对Windows消息封装的模型基础。
  在TObject类中,有一个Dispatch()方法和一个DefaultHandler()方法,它们都是与消息分发机制相关的。
Dispatch()负责将特定的消息分发给合适的消息处理函数。首先它会在对象本身类型的类中寻找该消息的处理函数,如果找到,则调用它;如果没有找到而该类覆盖了TObject的DefaultHandler(),则调用该类的DefaultHandler();如果两者都不存在,则继续在其基类中寻找,直至寻找到TObject这一层,而TObject已经提供了默认的DefaultHandler()方法。
  先来看一个示例程序,它演示了消息分发及处理的过程。
首先自定义一个消息结构TMyMsg,它是我们自定义的消息记录类型。对于自定义的消息类型,VCL只规定它的首4字节必须是消息编号,其后的数据类型任意。同时,VCL也提供了一个TMessage类型用于传递消息。在此程序中,不使用TMessage,而用TMyMsg代替:

  1. type
  2. TMyMsg = record // 自定义消息结构
  3. Msg : Cardinal; // 首4字节必须是消息编号
  4. MsgText : ShortString; // 消息的文字描述
  5. end;

TMyMsg记录类型的第2个域我们定义为MsgText,由该域的字符串来给出对这个消息的具体描述信息。当然,这些信息都是由消息分发者给出的。
然后,定义一个类,由它接受外界发送给它的消息。这个类可以说明这个演示程序的核心问题。

  1. TMsgAccepter = class // 消息接收器类
  2. private
  3. // 编号为2000的消息处理函数
  4. procedure AcceptMsg2000(var msg : TMyMsg); message 2000;
  5. // 编号为2002的消息处理函数
  6. procedure AcceptMsg2002(var msg : TMyMsg); message 2002;
  7. public
  8. procedure DefaultHandler(var Message); override; //默认处理方法
  9. end;

在Object Pascal中,指明类的某个方法为某一特定消息的处理函数,则在其后面添加message关键字与消息值,以此来通知编译器。正如上面类定义中的

  1. procedure AcceptMsg2000(var msg : TMyMsg); message 2000;

指明AcceptMsg2000()方法用来处理值为2000的消息,该消息以及参数将通过msg参数传递给处理函数。
TMsgAccepter类除提供了值为2000和2002的两个消息的处理函数外,还提供了一个默认的消息处理方法DefaultHandler()。该方法是在TObject中定义的虚方法,而在TMsgAccepter类中覆盖(override)了该方法,重新给出了新的实现。
TMyMsg结构声明与TMsgAccepter类的声明与实现都被定义在MsgDispTest单元中。完整的单元代码如下,请参看其中的TMsgAccepter类的各方法的实现:

  1. unit MsgDispTest;
  2. interface
  3. uses Dialogs, Messages;
  4. type
  5. TMyMsg = record
  6. Msg : Cardinal;
  7. MsgText : ShortString;
  8. end;
  9. TMsgAccepter = class // 消息接收器类
  10. private
  11. procedure AcceptMsg2000(var msg : TMyMsg); message 2000;
  12. procedure AcceptMsg2002(var msg : TMyMsg); message 2002;
  13. public
  14. procedure DefaultHandler(var Message); override; //默认处理函数
  15. end;
  16. implementation
  17. { TMsgAccepter }
  18. procedure TMsgAccepter.AcceptMsg2000(var msg: TMyMsg);
  19. begin
  20. ShowMessage('嗨,我收到了编号为 2000 的消息,它的描述是:' + msg.MsgText);
  21. end;
  22. procedure TMsgAccepter.AcceptMsg2002(var msg: TMyMsg);
  23. begin
  24. ShowMessage('嗨,我收到了编号为2002的消息,它的描述是:' + msg.MsgText);
  25. end;
  26. procedure TMsgAccepter.DefaultHandler(var message);
  27. begin
  28. ShowMessage('嗨,这个消息我不认识,无法接收,它的描述是:' +
  29. TMyMsg(message).MsgText);
  30. end;
  31. end.

接着就是界面代码,我们在Application的主Form(Form1)上放入3个按钮,程序界面如图4.2所示。
界面上的3个按钮的名字分别是:btnMsg2000、btnMsg2001、btnMsg2002。该3个按钮用来分发3个消息,将3个消息的值分别定义为2000、2001和2002。
在Form的OnCreate事件中,创建一个TMsgAccepter类的实例。然后,在3个按钮的OnClick事件中分别加上代码,将3个不同的消息分发给TMsgAccepter类的实例对象,以观察TMsgAccepter作出的反应。最后,在Form的OnDestroy事件中,析构TMsgAccepter类的实例对象。完整的界面程序单元代码如下:

  1. unit Unit1;
  2. interface
  3. uses
  4. Windows, Messages, SysUtils, Variants, Classes, Graphics,
  5. Controls,Forms, Dialogs, StdCtrls, MsgDispTest;
  6. type
  7. TForm1 = class(TForm)
  8. btnMsg2000: TButton;
  9. btnMsg2001: TButton;
  10. btnMsg2002: TButton;
  11. Label1: TLabel;
  12. procedure FormCreate(Sender: TObject);
  13. procedure FormDestroy(Sender: TObject);
  14. procedure btnMsg2000Click(Sender: TObject);
  15. procedure btnMsg2002Click(Sender: TObject);
  16. procedure btnMsg2001Click(Sender: TObject);
  17. end;
  18. var
  19. Form1: TForm1;
  20. MsgAccept : TMsgAccepter; // 自定义的消息接收类
  21. implementation
  22. {$R *.dfm}
  23. procedure TForm1.FormCreate(Sender: TObject);
  24. begin
  25. // 创建TMsgAccepter类的实例
  26. MsgAccept := TMsgAccepter.Create();
  27. end;
  28. procedure TForm1.FormDestroy(Sender: TObject);
  29. begin
  30. // 析构TMsgAccepter类的实例
  31. MsgAccept.Free();
  32. MsgAccept := nil;
  33. end;
  34. procedure TForm1.btnMsg2000Click(Sender: TObject);
  35. var
  36. Msg : TMyMsg;
  37. begin
  38. // 将值为2000的消息分发给MsgAccept对象,观察其反应
  39. Msg.Msg := 2000;
  40. Msg.MsgText := 'Message 2000'; // 消息的文字描述
  41. MsgAccept.Dispatch(Msg); // 分发消息
  42. end;
  43. procedure TForm1.btnMsg2002Click(Sender: TObject);
  44. var
  45. Msg : TMyMsg;
  46. begin
  47. // 将值为2002的消息分发给MsgAccept对象,观察其反应
  48. Msg.Msg := 2002;
  49. Msg.MsgText := 'Message 2002'; // 消息的文字描述
  50. MsgAccept.Dispatch(Msg); // 分发消息
  51. end;
  52. procedure TForm1.btnMsg2001Click(Sender: TObject);
  53. var
  54. Msg : TMyMsg;
  55. begin
  56. // 将值为2001的消息分发给MsgAccept对象,观察其反应
  57. Msg.Msg := 2001;
  58. Msg.MsgText := 'Message 2001'; // 消息的文字描述
  59. MsgAccept.Dispatch(Msg); // 分发消息
  60. end;
  61. end.

在TMsgAccepter类的代码中可以看到,它只能处理编号为2000和2002的消息,而没有编号为2001的消息的处理函数,但它覆盖了TObject的DefaultHandler(),于是就提供了默认的消息处理函数。
运行程序,分别单击3个按钮,得到了3句不同的回答。对于消息2000和2002,TMsgAccepter照单全收,正确识别出所接收到的消息。而只有在接收消息2001时,由于没有提供专门的消息处理函数,导致了对DefaultHandler()的调用。幸运的是,在DefaultHandler中,还可以使用message参数给出的附加信息(TMyMsg记录类型中的MsgText域)。

delphi VCL研究之消息分发机制-delphi高手突破读书笔记的更多相关文章

  1. Cocos2d-x 3.0 屏幕触摸及消息分发机制

    ***************************************转载请注明出处:http://blog.csdn.net/lttree************************** ...

  2. Android 消息分发机制

    Android 中针对耗时的操作,放在主线程操作,轻者会造成 UI 卡顿,重则会直接无响应,造成 Force Close.同时在 Android 3.0 以后,禁止在主线程进行网络请求. 针对耗时或者 ...

  3. 轻松搞定RabbitMQ(二)——工作队列之消息分发机制

    转自 http://blog.csdn.net/xiaoxian8023/article/details/48681987 上一篇博文中简单介绍了一下RabbitMQ的基础知识,并写了一个经典语言入门 ...

  4. Android正在使用Handler实现消息分发机制(零)

    演讲前,AsyncTask文章.我们在最后谈到.AsyncTask它是利用Handler异步消息处理机制,操作结果.使用Message回到主线程,从而执行UI更新线程. 而在我们的日常开发工作,Han ...

  5. Android正在使用Handler实现消息分发机制(两)

    在开始这篇文章之前,.首先,我们在总结前两篇文章Handler, Looper和MessageQueue像一些关键点: 0)在创建线程Handler之前,你必须调用Looper.prepare(), ...

  6. RabbitMQ中交换机的消息分发机制

    RabbitMQ是一个消息代理,它接受和转发消息,是一个由 Erlang 语言开发的遵循AMQP协议的开源实现.在RabbitMQ中生产者不会将消息直接发送到队列当中,而是将消息直接发送到交换机(ex ...

  7. Android中View的事件分发机制——Android开发艺术探索笔记

    原文链接 http://sparkyuan.me/ 转载请注明出处 介绍 点击事件的事件分发就是对MotionEvent事件的分发过程.当一个MotionEvent产生了以后,系统须要把这个事件传递给 ...

  8. delphi高手突破学习笔记之面向对象类和对象的本质

    知识点1:堆和栈 每个应用程序可以获得的内存空间分为两种:堆(heap)和栈(stack). 堆又称为“自由存储区”,其中的内存空间的分配与释放是必须由程序员来控制的.例如,用GetMem函数获取了一 ...

  9. delphi高手突破学习笔记之面向对象类和对象的本质(有汇编解释 good)

    知识点1:堆和栈 每个应用程序可以获得的内存空间分为两种:堆(heap)和栈(stack). 堆又称为“自由存储区”,其中的内存空间的分配与释放是必须由程序员来控制的.例如,用GetMem函数获取了一 ...

随机推荐

  1. django使用haystack对接Elasticsearch实现商品搜索

    # 原创,转载请留言联系 前言: 在做一个商城项目的时候,需要实现商品搜索功能. 说到搜索,第一时间想到的是数据库的 select * from tb_sku where name like %苹果手 ...

  2. POJ 2387 Til the Cows Come Home(dijkstra裸题)

    题目链接:http://poj.org/problem?id=2387 题目大意:给你t条边(无向图),n个顶点,让你求点1到点n的最短距离. 解题思路:裸的dijsktra,注意判重边. 代码: # ...

  3. UVA - 315

    B - Network Time Limit:3000MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Description A ...

  4. Linux:hping高级主机扫描

    https://www.aliyun.com/jiaocheng/167107.html https://blog.csdn.net/weixin_39762926/article/details/7 ...

  5. [水煮 ASP.NET Web API2 方法论](1-2)在 WebForm 应用程序中添加 ASP.NET Web API

    问题 怎么样将 Asp.Net Web Api 加入到 Asp.Net Web From 应用程序中 解决方案 在 Visual Studio 2013 中,创建新的 Web From,可以直接在&q ...

  6. 解决CentOS7.4KDE桌面或者gnome桌面安装VLC及声音问题

    一.安装VLC 1.下载源 https://mirrors.tuna.tsinghua.edu.cn/epel/7/x86_64/e/epel-release-7-11.noarch.rpm http ...

  7. Delphi 设计模式:《HeadFirst设计模式》Delphi7代码---工厂模式之简单工厂

    简单工厂:工厂依据传进的参数创建相应的产品. http://www.cnblogs.com/DelphiDesignPatterns/archive/2009/07/24/1530536.html { ...

  8. 阿里云轻量级学生机搭建FTP最新教程

    碰了几次壁,我整理一下分析自己在阿里云上成功安装FTP的教程. 1.使用root用户进入云服务器. 2.rpm  -qa|grep vsftpd 查看是否安装了ftp,一般阿里云服务器你以前没安装过, ...

  9. Python中sorted函数的用法(转)

    [Python] sorted函数 我们需要对List.Dict进行排序,Python提供了两个方法 对给定的List L进行排序, 方法1.用List的成员函数sort进行排序,在本地进行排序,不返 ...

  10. TIANKENG’s restaurant HDU - 4883 (暴力)

    TIANKENG manages a restaurant after graduating from ZCMU, and tens of thousands of customers come to ...