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