BCB消息机制

方法1。使用消息映射(Message Map)重载TObject的Dispatch虚成员函数
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER( … …)
END_MESSAGE_MAP( …)
熟悉MFC都知道,上面其实是定义了一段消息处理的宏
e.g
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:
    Form1::Dispatch(Message);
     break;
    }
}

virtual void __fastcall Dispatch(void *Message) 这个虚方法的定义最早可以在TObject的定义中找到。打开BCB的帮助,查找TForm的Method(方法),你会发现这里很清楚的写着Dispatch方法继承自TObject。如果您关心VCL的继承机制的话,您会发现TObject是所有VCL对象的基类。

方法二
重载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类的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);
}
从前面TControl的WndProc可以看到,消息是先交给WndProc来处理,最后才调用Dispatch方法的啦。
重载WndProc方法可以比重载Dispatch方法更早一点点得到消息并处理消息。

方法三

TApplication、TScreen和TForm构成了所有BCB风格的Win32 GUI程序的脊梁,他们控制着您程序的行为。TApplication类提供的属性和方法封装了标准Windows程序的行为。TApplication表现了在Windows操作系统中创建、运行、支持和销毁应用程序的基本原理。因此,TApplication大大简化了开发者和Windows环境之间的接口。这正是BCB的RAD特性。
TApplication封装的标准Windows行为大致包括如下几部分:
1> Windows 消息处理
2> 上下文关联的在线帮助
3> 菜单的快捷键和键盘事件处理
4> 异常处理
5> 管理由操作系统定义的程序基础部分,如:MainWindow 主窗口、WindowClass 窗口类等。
一般情况下,BCB会为每个程序自动生成一个TApplication类的实例。这部分源码可以在yourproject.cpp文件中见到(这里假定您的工程名称就叫yourproject.bpr)。
当然TApplication是不可见的,他总是在您的Form背后默默的控制着您的程序的行为。但也不是找不到蛛丝马迹。如果您新建一个程序(New Application),然后不作任何改动,编译运行的话,你会发现程序窗体的Caption是Form1,但在Windows的状态条上的Caption确写着project1的字样。这就是TApplication存在的证据。当然,这只是一种臆测,实战的方法应该打开BCB附带的WinSight来查看系统的进程。您可以清楚的看到TApplication类的存在,他的大小是0(隐藏的嘛),然后才是TForm1类。
好了,既然TApplication封装了消息处理的内容。我们就研究一下TApplication的实际动作吧。实际上消息到达BCB程序时,最先得到它们的就是TApplication对象。经由TApplication之后,才传递给Form的。以前的方法都是重载TForm的方法,显然要比本文所提到的方法要晚一些收到消息。对您来说,是不是希望在第一时间收到消息并处理它们呢?
要清楚的知道TApplication的处理机制还是深入VCL源码。首先看一看最最普通的一段代码吧。
#include <vcl.h> #pragma hdrstop USERES("Project1.res"); USEFORM("Unit1.cpp", Form1);
//--------------------------------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{ try { // 初始化Application Application->Initialize(); // 创建主窗口,并显示
Application->CreateForm(__classid(TForm1), &Form1); // 进入消息循环,直到程序退出Application->Run();
}
catch (Exception &exception)
{ Application->ShowException(&exception);
}
return 0;
}
短短的几行代码就可以让您的BCB程序自如运行。因为一切都已经被VCL在后台封装好了。Application->Run()方法进入程序的消息循环,直到程序退出。一起跟进VCL源码看个究竟吧。TApplication的定义在forms.pas中。procedure TApplication.Run;
begin FRunning := True;
try AddExitProc(DoneApplication);
if FMainForm <> nil then begin // 设置主窗口的显示属性
case CmdShow of
SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized; SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if FMainForm.FWindowState = wsMinimized then
Minimize else
FMainForm.Visible := True;
// 看见了吧,这里有个循环,直到Terminated属性为真退出。Terminated什么意思,就是取消,结束
repeat
HandleMessage
until Terminated;
end;
finally
FRunning := False;
end;
end;
消息处理的具体实现不在Run方法中,很显然关键在HandleMessage方法,看看这函数名字-消息处理。只有跟进HandleMessage瞧瞧喽。
procedure TApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then Idle(Msg);
end;
咳,这里也不是案发现场。程序先将消息交给ProcessMessage方法处理。如果没什么要处理的,就转入Application.Idle方法“程序在空闲时调用的方法”。
呼呼,再跟进ProcessMessage方法吧。
function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end
else
FTerminate := True;
end;
end;
哎呀呀,终于有眉目了。ProcessMessage采用了一套标准的Windows API 函数PeekMessage .... TranslateMessage;DispatchMessage。
有人说:Application->OnMessage = MyOnMessage; //不能响应SendMessage的消息,但是可以响应PostMessage发送的消息,也就是消息队列里的消息
SendMessage和PostMessage最主要的区别在于发送的消息有没有通过消息队列。
原因就在这里。ProcessMessage使用了PeekMessage(Msg, 0, 0, 0, PM_REMOVE) 从消息队列中提取消息。然后先检查是不是退出消息。不是的话,检查是否存在OnMessage方法。如果存在就转入OnMessage处理消息。最后才将消息分发出去。
这样重载Application的OnMessage方法要比前两种方法更早得到消息,可以说是最快速的方法了吧。举个例子:
void __fastcall TForm1::MyOnMessage(tagMSG &Msg, bool &Handled)
{
TMessage Message;
switch (Msg.message)
{
case WM_KEYDOWN:
Message.Msg = Msg.message;
Message.WParam = Msg.wParam;
Message.LParam = Msg.lParam;
MessageDlg("You Pressed Key!", mtWarning, TMsgDlgButtons() << mbOK, 0);
Handled = true;
break;
}
}
void __fastcall TForm1::FormCreate(TObject *Sender)
Application->OnMessage = MyOnMessage;
}
现在可以简短的总结一下VCL的消息机制了。
标准的BCB程序使用Application->Run()进入消息循环,在Application的ProcessMessage方法中,使用PeekMessage方法从消息队列中提取消息,并将此消息从消息队列中移除。然后ProcessMessage 方法检查是否存在Application->OnMessage方法。存在则转入此方法处理消息。之后再将处理过的消息分发给程序中的各个对象。至此,WndProc方法收到消息,并进行处理。如果有无法处理的交给重载的Dispatch方法来处理。要是还不能处理的话,再交给父类的Dispatch方法处理。最后Dispatch方法实际上将消息转入DefaultHandler方法来处理。
“嘿嘿,实际上,你一样可以重载DefaultHandler方法来处理消息。但是太晚了一点。我想没有人愿意最后一个处理消息吧...:-)”
写到这里似乎可以结束了。但如果您看过上一篇的话,一定会注意到Application->HookMainWindow方法。这又是怎么一回事呢?
如果您打算使用Application->OnMessage来捕获所有发送至您的应用程序的消息的话,您大概要失望了。原因已经讲过,它无法捕获使用SendMessage直接发送给窗口的消息,因为这不通过消息队列。您也许会说我可以直接重载TApplication的WndProc方法。呵呵,不可以。因为TApplication的WndProc方法被Borland申明为静态的,从而无法重载。显而易见,这么做的原因很可能是Borland担心其所带来的副作用。那该如何是好呢?
查看TApplication的WndProc的pascal源码可以看到:
procedure TApplication.WndProc(var Message: TMessage);
... // 节约篇幅,此处与主题无关代码略去
begin
try
Message.Result := 0;
for I := 0 to FWindowHooks.Count - 1 do
if TWindowHook(FWindowHooks[I]^)(Message) then Exit;
... // 节约篇幅,此处与主题无关代码略去
WndProc方法一开始先调用HookMainWindow挂钩的自定义消息处理方法,然后再调用缺省过程处理消息。这样使用HookMainWindow就可以在WndProc中间接加入自己的消息处理方法。使用这个方法响应SendMessage发送来的消息很管用。最后提醒一下,使用HookMainWindow挂钩之后一定要对应的调用UnhookMainWindow卸载钩子程序。给个例子:
void __fastcall TForm1::FormCreate(TObject *Sender)
{
Application->HookMainWindow(AppHookFunc);
}
bool __fastcall TForm1::AppHookFunc(TMessage &Message)
{
bool Handled ;
switch (Message.Msg)
{
case WM_CLOSE:
mrYes==MessageDlg("Really Close??", mtWarning, TMsgDlgButtons() << mbYes <<mbNo, 0)? Handled = false : Handled = true ;
break;
}
return Handled;
}
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
Application->UnhookMainWindow(AppHookFunc);
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{
SendMessage(Application->Handle,WM_CLOSE,0,0);
}
这样,将本文中的两种方法相结合,您就可以自如的处理到达您的应用程序的各种消息了|

 

http://showmealone.i.sohu.com/blog/view/71816314.htm


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值