• delphi VCL研究之消息分发机制-delphi高手突破读书笔记


    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这个“万物之源”究竟长得何等模样。它的声明如下:

    [delphi] view plain copy
     
    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代替:

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


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

    [delphi] view plain copy
     
    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关键字与消息值,以此来通知编译器。正如上面类定义中的

    [delphi] view plain copy
     
    1. procedure AcceptMsg2000(var msg : TMyMsg); message 2000;  

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

    [delphi] view plain copy
     
    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类的实例对象。完整的界面程序单元代码如下:

    [delphi] view plain copy
     
    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域)。

  • 相关阅读:
    hexo 建站参考
    如何在element-UI 组件的change事件中传递自定义参数
    vue 项目中当访问路由不存在的时候默认访问404页面
    百度地图引用时 报出A Parser-blocking, cross site (i.e. different eTLD+1) script
    echarts之legend-改变图例的图标为自定义图片
    大数据浪潮下的前端工程师
    为什么要使用TypeScript开发Web应用程序
    【转】简单理解Vue中的nextTick
    vue项目如何刷新当前页面
    安装Genymotion android模拟器
  • 原文地址:https://www.cnblogs.com/h2zZhou/p/9204778.html
Copyright © 2020-2023  润新知