娃娃鸭深入核心VCL架构剖析(李维)笔记

 

40VCL组件和窗口控件的结合

constructor TWinControl.Create(AOwner: TComponent);

begin

FObjectInstance := Classes.MakeObjectInstance(MainWndProc);

end;

function MakeObjectInstance(Method: TWndMethod): Pointer;

const

  BlockCode: array[1..2] of Byte = (

    $59,       { POP ECX }

    $E9);      { JMP StdWndProc }

  PageSize = 4096;

var

  Block: PInstanceBlock;

  Instance: PObjectInstance;

begin

  if InstFreeList = nil then

  begin

    Block := VirtualAlloc(nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    Block^.Next := InstBlockList;

    Move(BlockCode, Block^.Code, SizeOf(BlockCode));

    Block^.WndProcPtr := Pointer(CalcJmpOffset(@Block^.Code[2], @StdWndProc));

    Instance := @Block^.Instances;

    repeat

      Instance^.Code := $E8;  { CALL NEAR PTR Offset }

      Instance^.Offset := CalcJmpOffset(Instance, @Block^.Code);

      Instance^.Next := InstFreeList;

      InstFreeList := Instance;

      Inc(Longint(Instance), SizeOf(TObjectInstance));

    until Longint(Instance) - Longint(Block) >= SizeOf(TInstanceBlock);

    InstBlockList := Block;

  end;

  Result := InstFreeList;

  Instance := InstFreeList;

  InstFreeList := Instance^.Next;

  Instance^.Method := Method;

end;

 

type

  PInstanceBlock = ^TInstanceBlock;

  TInstanceBlock = packed record

    Next: PInstanceBlock;

    Code: array[1..2] of Byte;

    WndProcPtr: Pointer;

    Instances: array[0..InstanceCount] of TObjectInstance;

  end;

 

41VCL Framework的自定义消息

VCL Framework之中的自定义消息主要分成两类:

·VCL控件消息

·VCL控件通知消息

VCL控件消息主要是映对相关的窗口消息,而VCL控件通知消息则是用来在VCL类中相互通知发生的事件的。

VCL控件消息:

const

  CM_BASE                   = $B000;

  CM_ACTIVATE               = CM_BASE + 0;

  CM_DEACTIVATE             = CM_BASE + 1;

  CM_GOTFOCUS               = CM_BASE + 2;

  CM_LOSTFOCUS              = CM_BASE + 3;

  CM_CANCELMODE             = CM_BASE + 4;

  CM_DIALOGKEY              = CM_BASE + 5;

  CM_DIALOGCHAR             = CM_BASE + 6;

  CM_FOCUSCHANGED           = CM_BASE + 7;

  CM_PARENTFONTCHANGED      = CM_BASE + 8;

  CM_PARENTCOLORCHANGED     = CM_BASE + 9;

  CM_HITTEST                = CM_BASE + 10;

  CM_VISIBLECHANGED         = CM_BASE + 11;

  CM_ENABLEDCHANGED         = CM_BASE + 12;

  CM_COLORCHANGED           = CM_BASE + 13;

  CM_FONTCHANGED            = CM_BASE + 14;

  CM_CURSORCHANGED          = CM_BASE + 15;

  CM_CTL3DCHANGED           = CM_BASE + 16;

  CM_PARENTCTL3DCHANGED     = CM_BASE + 17;

  CM_TEXTCHANGED            = CM_BASE + 18;

  CM_MOUSEENTER             = CM_BASE + 19;

  CM_MOUSELEAVE             = CM_BASE + 20;

  CM_MENUCHANGED            = CM_BASE + 21;

  CM_APPKEYDOWN             = CM_BASE + 22;

  CM_APPSYSCOMMAND          = CM_BASE + 23;

  CM_BUTTONPRESSED          = CM_BASE + 24;

  CM_SHOWINGCHANGED         = CM_BASE + 25;

  CM_ENTER                  = CM_BASE + 26;

  CM_EXIT                   = CM_BASE + 27;

  CM_DESIGNHITTEST          = CM_BASE + 28;

  CM_ICONCHANGED            = CM_BASE + 29;

  CM_WANTSPECIALKEY         = CM_BASE + 30;

  CM_INVOKEHELP             = CM_BASE + 31;

  CM_WINDOWHOOK             = CM_BASE + 32;

  CM_RELEASE                = CM_BASE + 33;

  CM_SHOWHINTCHANGED        = CM_BASE + 34;

  CM_PARENTSHOWHINTCHANGED  = CM_BASE + 35;

  CM_SYSCOLORCHANGE         = CM_BASE + 36;

  CM_WININICHANGE           = CM_BASE + 37;

  CM_FONTCHANGE             = CM_BASE + 38;

  CM_TIMECHANGE             = CM_BASE + 39;

  CM_TABSTOPCHANGED         = CM_BASE + 40;

  CM_UIACTIVATE             = CM_BASE + 41;

  CM_UIDEACTIVATE           = CM_BASE + 42;

  CM_DOCWINDOWACTIVATE      = CM_BASE + 43;

  CM_CONTROLLISTCHANGE      = CM_BASE + 44;

  CM_GETDATALINK            = CM_BASE + 45;

  CM_CHILDKEY               = CM_BASE + 46;

  CM_DRAG                   = CM_BASE + 47;

  CM_HINTSHOW               = CM_BASE + 48;

  CM_DIALOGHANDLE           = CM_BASE + 49;

  CM_ISTOOLCONTROL          = CM_BASE + 50;

  CM_RECREATEWND            = CM_BASE + 51;

  CM_INVALIDATE             = CM_BASE + 52;

  CM_SYSFONTCHANGED         = CM_BASE + 53;

  CM_CONTROLCHANGE          = CM_BASE + 54;

  CM_CHANGED                = CM_BASE + 55;

  CM_DOCKCLIENT             = CM_BASE + 56;

  CM_UNDOCKCLIENT           = CM_BASE + 57;

  CM_FLOAT                  = CM_BASE + 58;

  CM_BORDERCHANGED          = CM_BASE + 59;

  CM_BIDIMODECHANGED        = CM_BASE + 60;

  CM_PARENTBIDIMODECHANGED  = CM_BASE + 61;

  CM_ALLCHILDRENFLIPPED     = CM_BASE + 62;

  CM_ACTIONUPDATE           = CM_BASE + 63;

  CM_ACTIONEXECUTE          = CM_BASE + 64;

  CM_HINTSHOWPAUSE          = CM_BASE + 65;

  CM_DOCKNOTIFICATION       = CM_BASE + 66;

  CM_MOUSEWHEEL             = CM_BASE + 67;

  CM_ISSHORTCUT             = CM_BASE + 68;

{$IFDEF LINUX}

  CM_RAWX11EVENT            = CM_BASE + 69;

{$ENDIF}

VCL控件通知消息:

{ VCL control notification IDs }

 

const

  CN_BASE              = $BC00;

  CN_CHARTOITEM        = CN_BASE + WM_CHARTOITEM;

  CN_COMMAND           = CN_BASE + WM_COMMAND;

  CN_COMPAREITEM       = CN_BASE + WM_COMPAREITEM;

  CN_CTLCOLORBTN       = CN_BASE + WM_CTLCOLORBTN;

  CN_CTLCOLORDLG       = CN_BASE + WM_CTLCOLORDLG;

  CN_CTLCOLOREDIT      = CN_BASE + WM_CTLCOLOREDIT;

  CN_CTLCOLORLISTBOX   = CN_BASE + WM_CTLCOLORLISTBOX;

  CN_CTLCOLORMSGBOX    = CN_BASE + WM_CTLCOLORMSGBOX;

  CN_CTLCOLORSCROLLBAR = CN_BASE + WM_CTLCOLORSCROLLBAR;

  CN_CTLCOLORSTATIC    = CN_BASE + WM_CTLCOLORSTATIC;

  CN_DELETEITEM        = CN_BASE + WM_DELETEITEM;

  CN_DRAWITEM          = CN_BASE + WM_DRAWITEM;

  CN_HSCROLL           = CN_BASE + WM_HSCROLL;

  CN_MEASUREITEM       = CN_BASE + WM_MEASUREITEM;

  CN_PARENTNOTIFY      = CN_BASE + WM_PARENTNOTIFY;

  CN_VKEYTOITEM        = CN_BASE + WM_VKEYTOITEM;

  CN_VSCROLL           = CN_BASE + WM_VSCROLL;

  CN_KEYDOWN           = CN_BASE + WM_KEYDOWN;

  CN_KEYUP             = CN_BASE + WM_KEYUP;

  CN_CHAR              = CN_BASE + WM_CHAR;

  CN_SYSKEYDOWN        = CN_BASE + WM_SYSKEYDOWN;

  CN_SYSCHAR           = CN_BASE + WM_SYSCHAR;

  CN_NOTIFY            = CN_BASE + WM_NOTIFY;

 

 

42TButton

TButtonControl = class(TWinControl)

  private

FClicksDisabled: Boolean;

  protected

   procedure WndProc(var Message: TMessage); override;

end;

procedure TButtonControl.WndProc(var Message: TMessage);

begin

  case Message.Msg of

    WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:

      if not (csDesigning in ComponentState) and not Focused then

      begin

        FClicksDisabled := True;

        Windows.SetFocus(Handle);

        FClicksDisabled := False;

        if not Focused then Exit;

      end;

    CN_COMMAND:

      if FClicksDisabled then Exit;

  end;

  inherited WndProc(Message);//不处理的消息提交给父类处理

end;

TButton = class(TButtonControl)

  private

protected

procedure CreateParams(var Params: TCreateParams); override;

end;

每个不同的VCL组件类都必须重载CreateParams方法。

procedure TButton.CreateParams(var Params: TCreateParams);

const

  ButtonStyles: array[Boolean] of DWORD = (BS_PUSHBUTTON, BS_DEFPUSHBUTTON);

begin

  inherited CreateParams(Params);

  CreateSubClass(Params, 'BUTTON');

  Params.Style := Params.Style or ButtonStyles[FDefault];

end;

由于TButton是属于主窗体的组件,因此窗口触发的WM_COMMAND窗口消息会调用主窗体的MainWndProc函数来处理,这就是TWinCtorl.MainWndProcTWinCtorl.MainWndProc会调用TForm.WndProc虚拟方法来处理窗口消息,由于TForm.WndProc没有处理WM_COMMAND窗口消息,因此TForm.WndProc又会调用TCustomForm.WndProc来处理,而TCustom.WndProc调用了TControl.WndProc

由于到了TControl.WndProc仍然没有处理WM_COMMAND消息,因此TControl便调用TObject的消息分派服务虚拟方法Dispatch来处理此消息:

 procedure TControl.WndProc(var Message: TMessage);

var

  Form: TCustomForm;

  KeyState: TKeyboardState; 

  WheelMsg: TCMMouseWheel;

begin

 Dispatch(Message);

end;

TObject.Dispatch会在TForm1的动态方法窗体中搜寻是否有方法可以处理WM_COMMAND消息,由于TForm1没有定义处理WM_COMMAND消息的动态方法,因此TObject.Dispatch会继续到TForm1的父类的动态方法表格中搜寻,一直到寻找到能够处理WM_COMMAND窗口消息的动态方法为止。

TCustomForm = class(TScrollingWinControl)

  private

   procedure WMCommand(var Message: TWMCommand); message WM_COMMAND;

 

end;

 

procedure TCustomForm.WMCommand(var Message: TWMCommand);

begin

  with Message do

    if (Ctl <> 0) or (Menu = nil) or not Menu.DispatchCommand(ItemID) then

      inherited;

end;

TCustomForm.WMCommand最终会调用到TWinControlWMCommand来处理WM_COMMAND窗口消息:

TWinControl = class(TControl)

  private

procedure WMCommand(var Message: TWMCommand); message WM_COMMAND;

 

end;

TWinControl.WMCommand就是解开VCL Framework处理窗口消息并且连接到程序员在Delphi程序单元中撰写的处理函数的秘密的地方。TWinControl.WMCommand的实现程序代码首先调用DoControlMsg函数来处理触发的窗口消息,并且传入触发事件的窗口控件Handle值以及窗口消息。当然如果DoControlMsg无法处理此窗口消息,那么TWinControl.WMCommand便再调用TWinControl的父类来处理。

procedure TWinControl.WMCommand(var Message: TWMCommand);

begin

  if not DoControlMsg(Message.Ctl, Message) then inherited;

end;

DoControlMsg的函数的工作是什么?DoControlMsg接受的参数是窗口控件Handle值,因此DoControlMsg必须先经由此Handle值找到对应的VCL组件,然后再调用找到的VCL组件的处理函数来处理此窗口消息

function DoControlMsg(ControlHandle: HWnd; var Message): Boolean;

var

  Control: TWinControl;

begin

  DoControlMsg := False;

  Control := FindControl(ControlHandle);// 由此Handle值找到对应的VCL组件

  if Control <> nil then

    with TMessage(Message) do

    begin

      Result := Control.Perform(Msg + CN_BASE, WParam, LParam);

      DoControlMsg := True;

    end;

end;

DoControlMsg先调用FindControl函数,经由窗口控件的Handle值来找到封装的VCL组件。如果找到之后就调用找到的VCL组件的Perform方法,并且把窗口消息加上CN_Base转换成VCL Framework的自定义消息。这是为了能够在VCL组件的VMT中的动态方法表格中找到处理此消息的动态方法。例如在这个范例中触发的消息是WM_COMMAND,但是在VCL FrameworkVCL组件处理的此消息的对应自定义消息却是CN_COMMAND详情见:CN_COMMAND           = CN_BASE + WM_COMMAND;Perform(Msg+CN_BASE)):

procedure CNCommand(var Message: TWMCommand); message CN_COMMAND;

 

function FindControl(Handle: HWnd): TWinControl;

var

  OwningProcess: DWORD;

begin

  Result := nil;

  if (Handle <> 0) and (GetWindowThreadProcessID(Handle, OwningProcess) <> 0) and

     (OwningProcess = GetCurrentProcessId) then

  begin

    if GlobalFindAtom(PChar(ControlAtomString)) = ControlAtom then

      Result := Pointer(GetProp(Handle, MakeIntAtom(ControlAtom)))

    else

      Result := ObjectFromHWnd(Handle);

  end;

end;

 

function ObjectFromHWnd(Handle: HWnd): TWinControl;

var

  OwningProcess: DWORD;

begin

  if (GetWindowThreadProcessID(Handle, OwningProcess) <> 0) and

     (OwningProcess = GetCurrentProcessID) then

    Result := Pointer(SendMessage(Handle, RM_GetObjectInstance, 0, 0))

  else

    Result := nil;

end;

ObjectFromHWnd用来搜寻相对应的VCL组件的方法就是调用Window APISendMessage向此窗口控件发出询问消息RM_GetObjectInstance,然后就可以经由VCL组件自己回传符合的VCL组件了。

为什么使用SendMessage发出RM_GetObjectInstance消息就可以自动找到相对应的封装的VCL控件?

前面我们说明VCL Framework如何封装窗口时,已经说明了VCL组件使用虚拟方法WndProc来处理窗口消息,并且经由定义各种处理特定窗口消息的动态方法来处理特定窗口消息。不过对于VCL组件不处理的窗口消息,就会由VCL Framework中的DefaultHandler函数以及窗口的内定窗口消息处理函数DefWindowProc来处理。而每一个VCL组件在DefaultHandler函数中便会处理ObjectFromHwnd函数中发出的RM_GetObjectInstance消息。

procedure TWinControl.DefaultHandler(var Message);

begin

  if FHandle <> 0 then

  begin

    with TMessage(Message) do

    begin

      if (Msg = WM_CONTEXTMENU) and (Parent <> nil) then

      begin

        Result := Parent.Perform(Msg, WParam, LParam);

        if Result <> 0 then Exit;

      end;

      case Msg of

        WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC:

          Result := SendMessage(LParam, CN_BASE + Msg, WParam, LParam);

        CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:

          begin

            SetTextColor(WParam, ColorToRGB(FFont.Color));

            SetBkColor(WParam, ColorToRGB(FBrush.Color));

            Result := FBrush.Handle;

          end;

      else

        if Msg = RM_GetObjectInstance then

          Result := Integer(Self)

        else

          Result := CallWindowProc(FDefWndProc, FHandle, Msg, WParam, LParam);

      end;

      if Msg = WM_SETTEXT then

        SendDockNotification(Msg, WParam, LParam);

    end;

  end

  else

    inherited DefaultHandler(Message);

end;

因此当ObjectFromHWnd调用SendMessage API之后,由于VCL组件没有处理RM_GetObjectInstance消息的动态方法,因此最后TObject的消息分派服务会把未处理的消息分派级DefaultHandler,而DefaultHandler在得到执行权并且检查触发的消息是RM_GetObjectInstance之后自然就加以处理并且回传VCL组件自己了。ObjectFromHwnd使用了非常聪明的方法来搜寻目标VCL组件,比使用一个For循环直接在Delphi应用程序中一一地搜寻所有的VCL组件巧妙得多,绝对值得我们学习并善加使用这个巧妙的设计和技巧。

DoControlMsg找到目标VCL组件后,就调用TControlPerform方法把窗口消息分派至最终的VCL组件和它的消息处理函数。在TControl.perform中,Perform先创建一个TMessage数据结构内容,最后调用VCL组件重载的WindowProc函数来处理窗口消息:

function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;

var

  Message: TMessage;

begin

  Message.Msg := Msg;

  Message.WParam := WParam;

  Message.LParam := LParam;

  Message.Result := 0;

  if Self <> nil then WindowProc(Message);

  Result := Message.Result;

end;

TControl的构造函数中,已经把虚拟方法WndProc指定给了Perform函数中的WindowProc特性值了,所以Perform是调用VCL组件的虚拟方法WndProc来处理窗口消息。

constructor TControl.Create(AOwner: TComponent);

begin

  inherited Create(AOwner);

  FWindowProc := WndProc;

 

end;

VCL组件的虚拟方法WndProc则会再经由TObjectDispatchVCL组件中搜寻能够处理此消息的处理函数,我们再到TButton类中果然就可以发现TButton定义了CNCommand方法来处理DoControlMsg发出的CN_COMMAND消息:

TButton = class(TButtonControl)

  private

procedure CNCommand(var Message: TWMCommand); message CN_COMMAND;

end;

TButtonCNCommand先确定触发的消息是否点击消息,如果是的话就调用动态方法Click来处理TButton组件被点击的事件:

procedure TButton.CNCommand(var Message: TWMCommand);

begin

  if Message.NotifyCode = BN_CLICKED then Click;

end;

Click方法是一个动态方法,它是定义在TControl类中,并且由TButton重载的:

TButton = class(TButtonControl)

 

 public

  procedure Click; override;

end;

TControl = class(TComponent)

protected

procedure Click; dynamic;

end;

Click方法使用动态方法声明当然也是为了减少VCL派生类VMT的大小,TButtonClick方法先把包含它的TForm组件的ModalResult特性值设定成ModalResult,接着调用父类TControl.click准备连接程序撰写的事件处理函数:

procedure TButton.Click;

var

  Form: TCustomForm;

begin

  Form := GetParentForm(Self);

  if Form <> nil then Form.ModalResult := ModalResult;

  inherited Click;

end;

procedure TControl.Click;

begin

  { Call OnClick if assigned and not equal to associated action's OnExecute.

    If associated action's OnExecute assigned then call it, otherwise, call    OnClick. }

  if Assigned(FOnClick) and (Action <> nil) and (@FOnClick <> @Action.OnExecute) then

    FOnClick(Self)

  else if not (csDesigning in ComponentState) and (ActionLink <> nil) then

    ActionLink.Execute(Self)

  else if Assigned(FOnClick) then

    FOnClick(Self);

end;//关联到自定义的事件中。

 

43、动态消息和VCL事件处理函数的结合

procedure TControl.Click;

begin

  { Call OnClick if assigned and not equal to associated action's OnExecute.

    If associated action's OnExecute assigned then call it, otherwise, call    OnClick. }

  if Assigned(FOnClick) and (Action <> nil) and (@FOnClick <> @Action.OnExecute) then

    FOnClick(Self)

  else if not (csDesigning in ComponentState) and (ActionLink <> nil) then

    ActionLink.Execute(Self)

  else if Assigned(FOnClick) then

    FOnClick(Self);

end;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
深入核心——VCL架构剖析》光盘说明-、光盘用途 本光盘为《深入核心——VCL架构剖析》一书的配套光盘,供读者阅读图书时参考和学习。二、光盘内容 光盘“源代码”目录中包含了书中所有源代码,文件目录和图书的目录相对应。如“Chap01”表示书中第1章的范例源代码。 光盘包含了全部的pas、dfm和dpr文件。 我们已经对所有文件进行了简体化工作。如果您在使用中发现有界面乱码问题,请将窗体Font改为“宋体”,Charset改为gb2312即可,并请即时告知我们,让更多读者受益。三、运行环境 多数代码可以直接在Delphi6和Delphi7环境下运行。部分涉及.NET技术内容的代码,需要在Delphi 7上安装Borland .NET Complier for Delphi编译器方可编译执行。Borland已经正式推出Delphi 8 for .NET,所以本书范例中部分内容可能与D8最终版本不符。四、使用方法 直接将范例文件拷贝至硬盘适当目录即可。 多数完整应用程序代码已经编译为.exe可执行文件,读者可直接运行之。五、防病毒 本光盘所有文件都已经过Norton Antivirus扫描,未发现有任何已知病毒。六、风险 读者须对使用光盘所附代码、文件所造成的一切后果负责。 七、如果对代码有任何疑问、建议或者发现有遗漏、错误之处请与 liwei@csdn.net联系。六、所有源代码可以在学习和工作中直接使用,但请不要用于商业目的。
VCL(Visual Component Library)是Delphi编程语言中的一个重要特性,它是一个层次化的组件库,用于构建用户界面和应用程序的交互逻辑。VCL架构核心是一系列基础组件,这些组件包括窗体、按钮、标签等常用控件,以及数据访问、图形绘制等功能模块。 VCL架构设计十分灵活,能够满足各种应用程序开发的需求。其核心思想是面向对象编程(OOP),通过组合和继承来构建复杂的控件和功能模块。 VCL框架可以分为两个主要层次:可视化层和非可视化层。可视化层负责处理用户界面的显示和交互,而非可视化层则处理底层的逻辑和数据操作。 在可视化层中,VCL使用窗体作为最基础的容器,并提供了一系列的控件用于用户界面的构建和展示。这些控件通过事件机制实现与用户的交互,并通过属性设置来调整外观和行为。 在非可视化层中,VCL提供了许多功能模块,例如数据访问模块(如数据库连接和查询),图形绘制模块(如图表和图像处理),以及网络通信模块等。这些模块通过封装底层API和算法,为开发者提供了便利的功能接口。 在VCL架构中,还有一个重要的特性是可视化设计器(IDE),它提供了开发者友好的图形界面,用于快速创建和编辑界面和逻辑。开发者可以通过拖放和属性设置等方式来构建复杂的界面和控制流程,从而快速实现应用程序的开发和调试。 总的来说,VCL架构是一种灵活而强大的开发框架,能够帮助开发者快速构建高效、易用的应用程序。通过深入核心vcl架构剖析,开发者可以更好地理解和应用VCL框架,提高开发效率和程序质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值