星期一, 九月 03, 2007

C Builder中如何应用消息

标准的BCB程序使用Application->Run()进入消息循环,在Application的ProcessMessage方法中,使用PeekMessage方法从消息队列中提取消息,并将此消息从消息队列中移除。然后ProcessMessage方法检查是否存在Application->OnMessage方法。存在则转入此方法处理消息。之后再将处理过的消息分发给程序中的各个对象。至此,WndProc方法收到消息,并进行处理。如果有无法处理的交给重载的Dispatch方法来处理。要是还不能处理的话,再交给父类的Dispatch方法处理。最后Dispatch方法实际上将消息转入DefaultHandler方法来处理。(嘿嘿,实际上,你一样可以重载DefaultHandler方法来处理消息,但是太晚了一点,我想没有人愿意最后一个处理消息吧)。

1.TApplication的OnMessage事件的应用
在C++Builder开发的应用程序中,任何窗体接收到一个Windows消息都会触发一次OnMessage事件,所以,可以通过相应TApplication对象的OnMessage事件来捕获任何发送给本程序的Windows消息。


OnMessage的事件的处理函数原型如下:
typedef void __fastcall (__closure *TMessageEvent ) (tagMsg &Msg,bool &Handled );
这个处理函数有两个参数,其中参数Msg表示的是被截获的消息,而参数Handled则用来指示本消息是否已经处理完成。在程序中可以通过设置参数Handled为true,以避免后续的过程处理这个消息,反之把Handled设为false则允许后继过程继续处理这个消息。
需要注意的是,OnMessage事件仅仅接受发送到消息队列的消息,而直接通过API函数SendMessage()发送给窗口函数的消息将不会被截获。另外,当程序运行的时候,OnMessage事件被触发的频率有可能非常高,所以这个事件的处理函数代码执行时间将直接影响到整个程序的运行效率。

2.利用消息映射截获消息
C++Builder的VCL提供了对大多数Windows消息的处理机制,对于一般的应用程序是足够了。但是,VCL也不是无所不包的。有的情况下,程序需要处理那些VCL处理没有处理的Windows消息,或者程序需要屏蔽某些特定的消息时,则就需要程序员自己捕获Windows消息。
为此C++Builder提供了一种消息映射机制,通过消息映射程序能将特定的Windows消息与对应的处理函数联系起来,当窗口捕获到这个消息时就会自动调用对应的处理函数。
使用消息映射有一下几个步骤:
(1) 消息映射表,把某些消息的处理权交给自定义的消息处理函数。
这样的消息映射列表应该位于一个组件类的定义中,它以一个没有参数的BEGIN_MESSAGE_MAP 宏开始,以END_MESSAGE_MAP宏结束。END_MESSAGE_MAP宏的唯一参数应该是组件的父类的名字。通常情况下,这个所谓的父类指的就是TForm。在宏BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间插入一个或者是多个MESSAGE_HANDLER 宏。
MESSAGE_HANDLER宏将一个消息句柄和一个消息处理函数联系在一起。
MESSAGE_HANDLER宏有三个参数:Windows消息名、消息结构体名和对应的消息处理函数名。其中,消息结构体名既可以是通用的消息结构体TMessage,也可以是特定的消息结构体,比如TWMMouse。
在使用消息映射的时候要注意以下两点:
a.一个窗口类定义中只能有一个消息映射表。
b.消息映射必须位于它所引用的所有消息处理函数声明的后面。
(2) 在窗口类中声明消息处理函数
这里的消息处理函数名和参数都必须和对应的MESSAGE_HANDLER宏一致。
一个典型的消息处理函数的声明如下:
void __fastcall 消息处理函数名(消息结构体名 &Message);
例如:
void __fastcall WMNchitTest(TMessage &message);
(3) 实现消息处理函数
消息处理函数的编制和普通的函数没什么太大的差异,唯一不同的是,通常在此函数的最后要加上一条语句 TForm::Dispatch(&Message),以完成VCL对于消息的默认处理。如果没有这一句,消息将会被完全拦截;在某些情况下,VCL可能会因为得不到消息而无法工作。

3.重载WndProc()函数
在某些情况下,程序需要捕获或者屏蔽某些特定的消息,这时可以用前面介绍的消息映射的方法。当然,这种方法也不是唯一的,也可以通过重载窗口函数WndProc()来实现。因为系统将在调用函数Dispatch()派发消息之前调用窗口函数WndProc(),所以,可以通过重载函数WndProc()得到一个在分派消息之前过滤消息的机会。
这个消息处理的窗口函数的原型如下:
virtual void __fastcall WndProc(TMessage &Message);
例如:(详细请看NowCan的例子)
void __fastcall TForm1::WndProc(TMessage &Message)
{
PCOPYDATASTRUCT pMyCDS;
if(Message.Msg==g_MyMsg)
{
ShowMessage("收到注册消息,wParam="+IntToStr(Message.WParam)+" lParam="+IntToStr(Message.LParam));
Message.Result=0;//消息处理的结果,当然在本例中没有意义。
}
else if(Message.Msg==g_MyMsg1)
{
Application->MessageBoxA((char *)Message.LParam,"收到发送方的字符串",MB_OK);
}
else if(Message.Msg==WM_COPYDATA)
{
pMyCDS = (PCOPYDATASTRUCT)Message.LParam;
Application->MessageBoxA((char *)pMyCDS->lpData,"收到发送方的字符串",MB_OK);
}

TForm::WndProc(Message);//其他的消息继续传递下去
}
乍看起来,这和重载Dispatch方法好象差不多。但实际上还是有差别的。差别就在先后次序上,消息是先交给WndProc来处理,最后才调用Dispatch方法的。这样,重载WndProc方法可以比重载Dispatch方法更早一点点得到消息并处理消息。

4.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 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);
}
//---------------------------------------------------------------------------

5.自己发送消息
应用程序也可以像Windows系统一样在窗口或者是组件之间发送消息。C++Builder为此提供了几种途径:使用函数TControl::Perform()或者API函数SendMessage()和PostMessage()向特定的窗体发送消息,或者是使用函数TWinControl::Broadcast()和API函数BroadcastSystemMessage()广播消息。


Perform()函数的作用就是将指定的消息传递给TControl的WndProc过程,适用于所有由TControl类派生的对象,Perform()原型如下:
int __fastcall Perform(unsigned Msg, int WParam, int LParam);
要等到消息处理之后才返回。


在同一个应用程序的不同窗体和控件之间使用函数Perform()是非常便捷的。但是这个函数是TControl类的成员函数。也就是说,使用它时,程序必须知道这个接受消息的控件的实例。而在许多情况下程序并不知道这个接受消息的窗体的实例而只是知道这个窗体的句柄,比如,在不同应用程序的窗体之间发送消息就属于这种情况。这时,函数Perform()显然无法使用,取而代之的应该是函数SendMessage()和PostMessage()。


函数SendMessage()和PostMessage()的功能基本上一样,它们都可以用来向一个特定的窗口句柄发送消息。主要的区别是,函数SendMessage()直接把一个消息发送给窗口函数,等消息被处理之后才返回;而函数PostMessage()则只是把消息发送到消息队列,然后就立刻返回。
这两个函数的原型声明分别如下:
LRESULT SendMessage(HWND hWnd,UINRT Msg,WPARAM,wParam,LPARAM,lParam);
BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM,wParam,LPARAM,lParam);
可以看到,这两个函数参数同函数Perform()十分类似,只是增加了一个hWnd参数用以表示目标窗口的句柄。


Broadcast()和BroadcastSystemMessage()
函数Broadcast()适用于所有由TWinControl类派生的对象,它可以向窗体上的所有子控件广播消息。其函数原型如下:
void __fastcall Broadcast(void *Message);
可以看到,这个函数只有一个Message参数,它指向被广播的TMessage类型的消息结构体。
函数Broadcast()只能向C++Builder应用程序中的指定窗体上的所有子控件广播消息,如果要向系统中其他应用程序或者窗体广播消息,函数Broadcast()就无能为力了。这时可以使用API函数BroadcastSystemMessage(),这个函数可以向任意的应用程序或者组件广播消息。其函数原型如下:
long BroadcastSystemMessage(
DWORD dwFlags,
LPWORD lpdwRecipients,
UINT uiMessage,
WPAREM wParam,
LPARAM lParam
);

没有评论: