用户登录  |  用户注册
首 页商业源码原创产品编程论坛
当前位置:PB创新网文章中心编程技巧编程其他

深入VCL 理解BCB的消息机制(二)

减小字体 增大字体 作者:佚名  来源:本站整理  发布时间:2009-03-16 19:38:26

深入VCL 理解BCB的消息机制

方法2

Written by
CKER

方法2。重载TControl的WndProc方法

还是先谈谈VCL的继承策略。VCL中的继承链的顶部是TObject基类。一切的VCL组件和对象都继承自TObject。

打开BCB帮助查看TControl的继承关系:

TObject->TPersistent->TComponent->TControl

呵呵,原来TControl是从TPersistent类的子类TComponent类继承而来的。TPersistent抽象基类具有使用流stream来存取类的属性的能力。

TComponent类则是所有VCL组件的父类。

这就是所有的VCL组件包括您的自定义组件可以使用dfm文件存取属性的原因『当然要是TPersistent的子类,我想您很少需要直接从TObject类来派生您的自定义组件吧』。

TControl类的重要性并不亚于它的父类们。在BCB的继承关系中,TControl类的是所有VCL可视化组件的父类。实际上就是控件的意思吧。所谓可视化是指您可以在运行期间看到和操纵的控件。这类控件所具有的一些基本属性和方法都在TControl类中进行定义。

TControl的实现在BorlandCBuilder5SourceVclcontrol.pas中可以找到。『可能会有朋友问你怎么知道在那里?使用BCB提供的Search -> Find in files很容易找到。或者使用第三方插件的grep功能。』

好了,进入VCL的源码吧。说到这里免不了要抱怨一下Borland。哎,为什么要用pascal实现这一切.....:-(

TControl继承但并没有重写TObject的Dispatch()方法。反而提供了一个新的方法就是xycleo提到的WndProc()。一起来看看Borland的工程师们是怎么写的吧。

procedure TControl.WndProc(var Message: TMessage);var  Form: TCustomForm;begin//由拥有control的窗体来处理设计期间的消息  if (csDesigning in ComponentState) then  begin    Form := GetParentForm(Self);    if (Form <> nil) and (Form.Designer <> nil) and      Form.Designer.IsDesignMsg(Self, Message) then Exit;  end//如果需要,键盘消息交由拥有control的窗体来处理  else if (Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST) then  begin    Form := GetParentForm(Self);    if (Form <> nil) and Form.WantChildKey(Self, Message) then Exit;  end//处理鼠标消息  else if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then  begin    if not (csDoubleClicks in ControlStyle) then      case Message.Msg of        WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK:          Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);      end;    case Message.Msg of      WM_MOUSEMOVE: Application.HintMouseMessage(Self, Message);      WM_LBUTTONDOWN, WM_LBUTTONDBLCLK:        begin          if FDragMode = dmAutomatic then          begin            BeginAutoDrag;            Exit;          end;          Include(FControlState, csLButtonDown);        end;      WM_LBUTTONUP:        Exclude(FControlState, csLButtonDown);    end;  end// 下面一行有点特别。如果您仔细的话会看到这个消息是CM_VISIBLECHANGED.// 而不是我们熟悉的WM_开头的标准Windows消息.// 尽管Borland没有在它的帮助中提到有这一类的CM消息存在。但很显然这是BCB的// 自定义消息。呵呵,如果您对此有兴趣可以在VCL源码中查找相关的内容。一定会有不小的收获。  else if Message.Msg = CM_VISIBLECHANGED then    with Message do      SendDockNotification(Msg, WParam, LParam);// 最后调用dispatch方法。  Dispatch(Message);end;

看完这段代码,你会发现TControl类实际上只处理了鼠标消息,没有处理的消息最后都转入Dispatch()来处理。

但这里需要强调指出的是TControl自己并没有获得焦点Focus的能力。TControl的子类TWinControl才具有这样的能力。我凭什么这样讲?呵呵,还是打开BCB的帮助。很多朋友抱怨BCB的帮助实在不如VC的MSDN。毋庸讳言,的确差远了。而且这个帮助还经常有问题。但有总比没有好啊。

言归正传,在帮助的The TWinControl Branch 分支下,您可以看到关于TWinControl类的简介。指出TWinControl类是所有窗体类控件的基类。所谓窗体类控件指的是这样一类控件:

1. 可以在程序运行时取得焦点的控件。

2. 其他的控件可以显示数据,但只有窗体类控件才能和用户发生键盘交互。

3. 窗体类控件能够包含其他控件(容器)。

4. 包含其他控件的控件又称做父控件。只有窗体类控件才能够作为其他控件的父控件。

5. 窗体类控件拥有句柄。

除了能够接受焦点之外,TWinControl的一切都跟TControl没什么分别。这一点意味着TwinControl可以对许多的标准事件作出响应,Windows也必须为它分配一个句柄。并且与这个主题相关的最重要的是,这里提到是由BCB负责来对控件进行重画以及消息处理。这就是说,TwinControl封装了这一切。

似乎扯的太远了。但我要提出来的问题是TControl类的WndProc方法中处理了鼠标消息。但这个消息只有它的子类TwinControl才能够得到啊!?

这怎么可以呢... Borland是如何实现这一切的呢?这个问题实在很奥妙。为了看个究竟,再次深入VCL吧。

还是在control.pas中,TWinControl继承了TControl的WndProc方法。源码如下:

procedure TWinControl.WndProc(var Message: TMessage);var  Form: TCustomForm;  KeyState: TKeyboardState;  WheelMsg: TCMMouseWheel;begin  case Message.Msg of    WM_SETFOCUS:      begin        Form := GetParentForm(Self);        if (Form <> nil) and not Form.SetFocusedControl(Self) then Exit;      end;    WM_KILLFOCUS:      if csFocusing in ControlState then Exit;    WM_NCHITTEST:      begin        inherited WndProc(Message);        if (Message.Result = HTTRANSPARENT) and (ControlAtPos(ScreenToClient(          SmallPointToPoint(TWMNCHitTest(Message).Pos)), False) <> nil) then          Message.Result := HTCLIENT;        Exit;      end;    WM_MOUSEFIRST..WM_MOUSELAST:      //下面这一句话指出,鼠标消息实际上转入IsControlMouseMsg方法来处理了。      if IsControlMouseMsg(TWMMouse(Message)) then      begin        if Message.Result = 0 then          DefWindowProc(Handle, Message.Msg, Message.wParam, Message.lParam);        Exit;      end;    WM_KEYFIRST..WM_KEYLAST:      if Dragging then Exit;    WM_CANCELMODE:      if (GetCapture = Handle) and (CaptureControl <> nil) and        (CaptureControl.Parent = Self) then        CaptureControl.Perform(WM_CANCELMODE, 0, 0);  else    with Mouse do      if WheelPresent and (RegWheelMessage <> 0) and        (Message.Msg = RegWheelMessage) then      begin        GetKeyboardState(KeyState);        with WheelMsg do        begin          Msg := Message.Msg;          ShiftState := KeyboardStateToShiftState(KeyState);          WheelDelta := Message.WParam;          Pos := TSmallPoint(Message.LParam);        end;        MouseWheelHandler(TMessage(WheelMsg));        Exit;      end;  end;  inherited WndProc(Message);end;

鼠标消息是由IsControlMouseMsg方法来处理的。只有再跟到IsControlMouseMsg去看看啦。源码如下:

function TWinControl.IsControlMouseMsg(var Message: TWMMouse): Boolean;var  //TControl出现啦  Control: TControl;  P: TPoint;begin  if GetCapture = Handle then  begin    Control := nil;    if (CaptureControl <> nil) and (CaptureControl.Parent = Self) then      Control := CaptureControl;  end else    Control := ControlAtPos(SmallPointToPoint(Message.Pos), False);  Result := False;  if Control <> nil then  begin    P.X := Message.XPos - Control.Left;    P.Y := Message.YPos - Control.Top;    file://TControl的Perform方法将消息交由WndProc处理。    Message.Result := Control.Perform(Message.Msg, Message.Keys, Longint(PointToSmallPoint(P)));    Result := True;  end;end;

原来如此,TWinControl最后还是将鼠标消息交给TControl的WndProc来处理了。这里出现的Perform方法在BCB的帮助里可以查到是TControl类中开始出现的方法。它的作用就是将指定的消息传递给TControl的WndProc过程。

结论就是TControl类的WndProc方法的消息是由TwinControl类在其重载的WndProc方法中调用IsControlMouseMsg方法后使用Peform方法传递得到的。

由于这个原因,BCB和Delphi中的TControl类及其所有的派生类都有一个先天的而且是必须的限制。那就是所有的TControl类及其派生类的Owner必须是TwinControl类或者TWinControl的派生类。Owner属性最早可以在TComponent中找到,一个组件或者控件是由它的Owner拥有并负责释放其内存的。这就是说,当Owner从内存中释放的时候,它所拥有的所有控件占用的内存也都被释放了。Owner最好的例子就是Form。Owner同时也负责消息的分派,当Owner接收到消息的时候,它负责将应该传递给其所拥有的控件的消息传递给它们。这样这些控件就能够取得处理消息的能力。TImage就是个例子:你可以发现Borland并没有让TImage重载TControl的WndProc方法,所以TImage也只有处理鼠标消息的能力,而这种能力正是来自TControl的。

唧唧崴崴的说了一大堆。终于可以说处理消息的第二种方法就是重载TControl的WndProc方法了。例程如下:

void __fastcall TForm1::WndProc(TMessage &Message){     switch (Message.Msg)     {            case WM_CLOSE:                 OnCLOSE(Message);  // 处理WM_CLOSE消息的方法            break;     }     TForm::WndProc(Message);}

乍看起来,这和上次讲的重载Dispatch方法好象差不多。但实际上还是有差别的。差别就在先后次序上,从前面TControl的WndProc可以看到,消息是先交给WndProc来处理,最后才调用Dispatch方法的啦。

这样,重载WndProc方法可以比重载Dispatch方法更早一点点得到消息并处理消息。

好了,这次就说到这里。在您的应用程序里还有没有比这更早得到消息的办法呢?有,下次再说。

 

版权说明:
您可以随意复制、分发、下载此文档。但未经本人同意,您不可以截取、改动本文片断,或用本文谋取任何形式的利益。


Tags:

作者:佚名

文章评论评论内容只代表网友观点,与本站立场无关!

   评论摘要(共 0 条,得分 0 分,平均 0 分) 查看完整评论
PB创新网ourmis.com】Copyright © 2000-2009 . All Rights Reserved .
页面执行时间:14,312.50000 毫秒
Email:ourmis@126.com QQ:2322888 蜀ICP备05006790号