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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章