BCB消息消息机制

来源:互联网 发布:复杂网络同步 编辑:程序博客网 时间:2024/05/16 18:49
方法1:使用消息映射(Message Map)重载TObject的Dispatch虚成员函数
  形式如下:
  BEGIN_MESSAGE_MAP  
  VCL_MESSAGE_HANDLER(   …   …   )  
  END_MESSAGE_MAP(   …   )
在\Borland\CBuilder5\Include\Vcl找到sysmac.h,其中有如下的预编译宏定义:
#define BEGIN_MESSAGE_MAP virtual void __fastcallDispatch(void *Message)\   
  \   
switch (((PMessage)Message)->Msg)   \   
   
  #define VCL_MESSAGE_HANDLER(msg,type,meth)   \  
  case   msg:   \  
  meth(*((type   *)Message));   \  
  break;     
//NOTE:ATL defines MESSAGE_HANDLER macro which conflicts  
//with VCL's macro. The VCL macro has been renamed to  VCL_MESSAGE_H//ANDLER. If you are not using ATL, MESSAGE_HANDLER is defined as
//in previous versions of BCB.  
   
  #if !defined(USING_ATL)&&!defined(USING_ATLVCL)&& !defined  (INC_ATL_HEADERS)  
  #define MESSAGE_HANDLER   VCL_MESSAGE_HANDLER  
  #endif //   ATL_COMPAT  
   
  #define END_MESSAGE_MAP(base)  
  default:   \  
  base:   ispatch(Message);   \  
  break;   \  
  }   \  
  }
 
  这样对如下的例子:  
  BEGIN_MESSAGE_MAP  
  VCL_MESSAGE_HANDLER(WM_PAINT,TMessage,OnPaint)  
  END_MESSAGE_MAP(TForm1)  
   
  在预编译时,就被展开成如下的代码  
  virtual   void   __fastcall   Dispatch(void   *Message)  
  {  
  switch   (((PMessage)Message)->Msg)  
  {  
  case   WM_PAINT:  
  OnPaint(*((TMessage   *)Message));  
  //消息响应句柄,也就是响应消息的成员函数,在Form1中定义  
  break;  
  default:  
  TForm1:   dispatch(Message);  
  break;  
  }  
   
  }   
    
  对这种方法有两点要解释一下:  
  1.virtual   void   __fastcall   Dispatch(void   *Message)  
  这个虚方法的定义最早可以在TObject的定义中找到。打开BCB的帮助,查找TForm的方法(Method),你会发现这里很清楚的写着Dispatch方法继承自TObject。如果您关心VCL的继承机制的话,您会发现TObject是所有VCL对象的基类。TObject的抽象凝聚了Borland的工程师们的心血。如果有兴趣。您应该好好查看一下TObject的定义,很显然,所有TObject的子类都可以重载基类的Dispatch方法,来实现自己的消息调用。如果Dispatch方法找不到此消息的定义,会将此消息交由TObject:DefaultHandler方法来处理。抽象基类TObject的DefaultHandler方法实际上是空的。同样要由继承子类重载实现它们自己的消息处理过程。   
       
  2.很多时候,我见到的第二行是这样写的:  
  MESSAGE_HANDLER(WM_PAINT,TMessage,OnPaint)  
  在这里,您可以很清楚地看到几行注解,意思是ATL中同样包含了一个MESSAGE_HANDLER  
  的宏定义,这与VCL发生了冲突。为了解决这个问题,Borland改用  
  VCL_MESSAGE_HANDLER。当您没有使用ATL的时候,MESSAGE_HANDLER将转换成  
  VCL_MESSAGE_HANDLER。但如果用了ATL就会有问题。所以我建议您始终使用  
  VCL_MESSAGE_HANDLER的写法,以免出现问题。
 
方法2:重载TControl的WndProc方法   
  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的实现在\Borland\CBuilder5\Source\Vcl\control.pas中可以找到。(可能会有朋友问你怎么知道在那里?使用BCB提供的Search   ->   Find   in   files很容易找到,
或者使用第三方插件的grep功能。)  
   
  好了,进入VCL的源码吧。说到这里免不了要抱怨一下Borland。哎,为什么要用pascal实现这一切……:-(     
  TControl继承但并没有重写TObject的Dispatch方法。反而提供了一个新的方法  
  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方法更早一点点得到消息并处理消息。
0 0
原创粉丝点击