本文絕大部分內(nèi)容來源于《C++ Builder 高級(jí)應(yīng)用開發(fā)指南》,清華大學(xué)出版社出版,李幼儀、甘志 編著,版權(quán)歸他們所有。作者僅僅對(duì)其進(jìn)行了拷貝,不享有除學(xué)習(xí)之外的任何權(quán)利。
本文以及作者其他系列文章中出現(xiàn)的所有程序代碼均經(jīng)過嚴(yán)格的驗(yàn)證,如果沒有特別說明即是在Win XP SP2 + C++ Builder 6.0的環(huán)境下測(cè)試通過。
一、消息的基本概念
消 息(Message)指的就是Windows操作系統(tǒng)發(fā)給應(yīng)用程序的一個(gè)通告,它告訴應(yīng)用程序某個(gè)特定的事件發(fā)生了。比如,用戶單擊鼠標(biāo)或按鍵都會(huì)引發(fā) Windows系統(tǒng)發(fā)送相應(yīng)的消息。最終處理消息的是應(yīng)用程序的窗口函數(shù),如果程序不負(fù)責(zé)處理的話系統(tǒng)將會(huì)作出默認(rèn)處理。
從數(shù)據(jù)結(jié)構(gòu)的角度來說,消息是一個(gè)結(jié)構(gòu)體,它包含了消息的類型標(biāo)識(shí)符以及其他的一些附加信息。比如對(duì)于鼠標(biāo)單擊產(chǎn)生的消息而言,它就包含了窗口句柄、此消息的常量標(biāo)識(shí)符、鼠標(biāo)的位置坐標(biāo)等相關(guān)信息。
Windows 系統(tǒng)定義了許多消息常量,包括標(biāo)準(zhǔn)的Windows消息、通知消息、命令消息等等。這些消息常量通常具有XX_YYYY的形式,其他XX通常代表消息的類 型,而后面的YYYY通常是這個(gè)消息所對(duì)應(yīng)事件的英文縮寫。比如WM_LBUTTONDOWN代表的事件就是按下了鼠標(biāo)左鍵。
二、Windows的消息系統(tǒng)
Windows的消息系統(tǒng)由3個(gè)部分組成:
消息隊(duì)列。Windows操作系統(tǒng)本身維護(hù)了一個(gè)系統(tǒng)消息隊(duì)列,而對(duì)于每一個(gè)正在執(zhí)行的Windows應(yīng)用程序,系統(tǒng)會(huì)為其建立一個(gè)應(yīng)用程序消息隊(duì)列。應(yīng)用程序可以從這個(gè)消息隊(duì)列中獲取消息,然后分派給對(duì)應(yīng)的窗口。
消息循環(huán)。Windows應(yīng)用程序中都包含了一段稱作“消息循環(huán)(也稱消息泵)”的代碼,用來從消息隊(duì)列中檢索消息并把他們分發(fā)到相應(yīng)的窗口函數(shù)中。正是這個(gè)消息循環(huán)使得一個(gè)應(yīng)用程序能夠響應(yīng)外部的各種事件,所以消息循環(huán)往往是一個(gè)Windows應(yīng)用程序的核心部分。
窗 口函數(shù)。最終為了處理各種消息,Windows應(yīng)用程序所創(chuàng)建的每個(gè)窗口(廣義,包括實(shí)際窗口、控件等諸如此類的的內(nèi)容)都會(huì)在系統(tǒng)中注冊(cè)一個(gè)相應(yīng)的窗口 函數(shù),此窗口函數(shù)從形式上看一個(gè)巨大的switch語句,用以處理由消息循環(huán)發(fā)送到該窗口的各種消息。窗口函數(shù)是一種回調(diào)函數(shù)(Callback Function),也就是說,它是由Windows操作系統(tǒng)負(fù)責(zé)調(diào)用的,而應(yīng)用程序本身不能調(diào)用它。
Windows操作系統(tǒng)中的消息從發(fā)生到被處理一般有5個(gè)步驟:
(1)系統(tǒng)發(fā)生了一個(gè)事件。
(2)Windows系統(tǒng)把事件翻譯為對(duì)應(yīng)的消息,并把它放到消息隊(duì)列中。
(3)應(yīng)用程序從消息隊(duì)列中獲取消息,然后把它封裝在TMsg結(jié)構(gòu)中。
(4)應(yīng)用程序通過消息循環(huán)把消息分派給對(duì)應(yīng)的窗口函數(shù)。
(5)窗口函數(shù)負(fù)責(zé)最終處理這個(gè)消息。
三、C++ Builder中的OnMessage事件
C++ Builder為了方便消息處理,進(jìn)一步把常用的消息封裝成了各種相應(yīng)事件,這樣程序員通常情況下就無需考慮消息的具體細(xì)節(jié),只要編寫相應(yīng)的事件處理函數(shù)即可。這就是所謂的基于事件的Windows程序開發(fā)。
在C++ Builder開發(fā)的應(yīng)用程序中,任何窗體接收到一個(gè)Windows消息都會(huì)觸發(fā)一次OnMessage事件,所以可以通過響應(yīng)TApplication對(duì)象的OnMessage事件來捕獲任何發(fā)送給本程序的Windows消息。
OnMessage事件的處理函數(shù)原型如下:
typedef void __fastcall (__closure *TMessageEvent)(tagMSG &Msg,bool &Handled);
這個(gè)函數(shù)有兩個(gè)參數(shù),其中參數(shù)Msg表示的是被截獲的消息,而參數(shù)Handled則用來指示本消息是否已經(jīng)處理完成。在程序中可以通過設(shè)置參數(shù)Handled為true以避免后續(xù)過程處理這個(gè)消息,反之亦然。
需 要注意的是,OnMessage事件僅僅接受發(fā)送到消息隊(duì)列中的消息,而直接使用API函數(shù) SendMessage()發(fā)送給窗口函數(shù)的消息將不會(huì)被截獲到。另外,當(dāng)程序運(yùn)行的時(shí)候,OnMessage事件被觸發(fā)的頻率一般非常高,所以這個(gè)事件 處理函數(shù)中的代碼執(zhí)行事件將直接影響到整個(gè)程序的運(yùn)行效率。
下面我們通過一個(gè)實(shí)例來演示OnMessage事件的處理過程。在這個(gè)范例中,程序?qū)⒗塾?jì)發(fā)生的OnMessage事件次數(shù)并顯示在窗體上。
我們?cè)谥鞔绑w上放置一個(gè)Label組件,命名為Label1,用于顯示消息發(fā)生次數(shù)。
程序中全部采用默認(rèn)名字。
在窗體類頭文件Unit1.h中添加以下代碼:
private: // User declarations
long num;
void __fastcall AppMessage(tagMSG &Msg,bool &Handled);
然后在Unit1.cpp中添加以下代碼:
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
num=0;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
Application->OnMessage=AppMessage;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::AppMessage(tagMSG &Msg,bool &Handled)
{
num++;
Label1->Caption=AnsiString(num);
Handled=false;
}
//---------------------------------------------------------------------------
其中Application->OnMessage=AppMessage;的作用是把事件處理函數(shù)AppMessage()和OnMessage事件聯(lián)系起來。Handled=false;是為了讓后續(xù)過程能繼續(xù)處理消息,以避免窗口無法正常接收消息而引起死鎖。
最后按F9運(yùn)行程序,可以看到窗體上顯示出了消息事件發(fā)生的次數(shù),當(dāng)你移動(dòng)鼠標(biāo)或按下按鍵的時(shí)候它都會(huì)不斷地變化。
四、利用消息映射截獲消息
C++ Builder提供了一種消息映射機(jī)制,通過消息映射程序能將特定的Windows消息與對(duì)應(yīng)的消息處理函數(shù)聯(lián)系起來,當(dāng)窗口捕獲到這個(gè)消息的時(shí)候就會(huì)自動(dòng)調(diào)用對(duì)應(yīng)的處理函數(shù)。
在程序中使用這樣的消息映射一般需要以下三個(gè)步驟:
(1)聲明消息映射表,把某些消息的處理權(quán)交給自定義的消息處理函數(shù)。
這 樣的消息映射列表應(yīng)該位于一個(gè)組件類的定義中,它以一個(gè)沒有參數(shù)的BEGIN_MESSAGE_MAP宏開始,以END_MESSAGE_MAP宏結(jié)束。 END_MESSAGE_MAP宏的唯一參數(shù)應(yīng)該是組件的父類的名字。通常情況下這個(gè)所謂的父類指的就是TForm。在宏 BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之間可以插入一個(gè)或者是多個(gè)MESSAGE_HANDLER宏。
MESSAGE_HANDLER宏將一個(gè)消息句柄和一個(gè)消息處理函數(shù)聯(lián)系在了一起。它有三個(gè)參數(shù):Windows消息名、消息結(jié)構(gòu)體名和對(duì)應(yīng)的消息處理函數(shù)名。其中,消息結(jié)構(gòu)體名既可以是通用的消息結(jié)構(gòu)體TMessage,也可以是特定的消息結(jié)構(gòu)體比如TWMMouse。
在使用消息映射的時(shí)候應(yīng)該注意以下兩點(diǎn):
一個(gè)窗口類定義中只能有一個(gè)消息映射列表;
消息映射必須位于它所引用的所有消息處理函數(shù)聲明的后面。
一個(gè)典型的消息映射聲明代碼如下:
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(Windows消息名,消息結(jié)構(gòu)體名,消息處理函數(shù)名)
MESSAGE_HANDLER(Windows消息名,消息結(jié)構(gòu)體名,消息處理函數(shù)名)
...
END_MESSAGE_MAP
(2)在窗口類中聲明消息處理函數(shù)
注意,這里的消息處理函數(shù)的名字和參數(shù)都必須和對(duì)應(yīng)的MESSAGE_HANDLER宏一致。
一個(gè)典型的消息處理函數(shù)聲明如下:
void __fastcall 消息處理函數(shù)名(消息結(jié)構(gòu)體名 &Message)
(3)實(shí)現(xiàn)消息處理函數(shù)
消 息處理函數(shù)的編制和普通的窗口類成員函數(shù)沒有太大的差異,唯一不同的是,通常在此函數(shù)的最后都要加上一 句:TForm::Dispatch(&Message),以完成VCL對(duì)于消息的默認(rèn)處理。如果沒有這條語句,消息將會(huì)被完全攔截;這樣可能造 成某些情況下VCL類因得不到消息而無法正常工作。
下面我們通過一個(gè)實(shí)例來演示如何通過消息映射在程序中截獲Windows消息。在這個(gè)范例中,程序?qū)?qiáng)制設(shè)置主窗體的最大、最小尺寸。
我們?nèi)匀徊捎媚J(rèn)命名,將主窗體Form1的Height和Width屬性分別設(shè)置為150和250。在示例程序中我們將要限制窗體的最大長、寬分別為300和200,最小長、寬分別為200和100。
編輯窗體類的頭文件Unit1.h如下:
class TForm1 : public TForm
{
__published: // IDE-managed Components
TLabel *Label1;
private: // User declarations
public: // User declarations
__fastcall TForm1(TComponent* Owner);
void __fastcall WMGetMinMaxInfo(TWMGetMinMaxInfo &Msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_GETMINMAXINFO,TWMGetMinMaxInfo,WMGetMinMaxInfo)
END_MESSAGE_MAP(TForm)
};
在窗體文件Unit1.cpp中只需要實(shí)現(xiàn)一個(gè)函數(shù),其他函數(shù)全部不寫任何代碼。
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::WMGetMinMaxInfo(TWMGetMinMaxInfo &Msg)
{
//當(dāng)用戶單擊最大化按鈕時(shí),限制其大小
Msg.MinMaxInfo->ptMaxSize.x=300;
Msg.MinMaxInfo->ptMaxSize.y=200;
//設(shè)定窗體最大化時(shí)左上角的屏幕坐標(biāo)為當(dāng)前窗體的位置
Msg.MinMaxInfo->ptMaxPosition.x=Left;
Msg.MinMaxInfo->ptMaxPosition.y=Top;
//當(dāng)用戶用鼠標(biāo)拖動(dòng)改變窗體尺寸時(shí),限制其最大值
Msg.MinMaxInfo->ptMaxTrackSize.x=300;
Msg.MinMaxInfo->ptMaxTrackSize.y=200;
//當(dāng)用戶用鼠標(biāo)拖動(dòng)改變窗體尺寸時(shí),限制其最小值
Msg.MinMaxInfo->ptMinTrackSize.x=200;
Msg.MinMaxInfo->ptMinTrackSize.y=100;
//顯示當(dāng)前窗體大小尺寸
Label1->Caption="Width="+AnsiString(Width)+" Height="+AnsiString(Height);
}
//---------------------------------------------------------------------------
程序代碼中的TWMGetMinMaxInfo結(jié)構(gòu)的聲明在頭文件Messages.hpp中,MINMAXINFO結(jié)構(gòu)在WinUser.h中有詳細(xì)說明。
F9運(yùn)行程序后完全符合我們預(yù)先的設(shè)想。
關(guān)于消息映射的實(shí)現(xiàn)細(xì)節(jié),我們可以通過在頭文件$(BCB)\Include\Vcl\sysmac.h中的這些宏的代碼來搞清楚。
五、重載WndProc()函數(shù)
在 上一節(jié)我們學(xué)會(huì)了使用消息映射來捕獲或屏蔽某些特定的消息,當(dāng)然,那種方法并不是唯一的,我們還可以通過重載窗口函數(shù)WndProc()來實(shí)現(xiàn)。因?yàn)橄到y(tǒng) 會(huì)在調(diào)用函數(shù)Dispatch()分派消息之前調(diào)用窗口函數(shù)WndProc(),所以我們可以通過重載此函數(shù)得到一個(gè)在分派消息之前就處理消息的機(jī)會(huì)。
這個(gè)用于處理消息的窗口函數(shù)原型如下:
virtual void __fastcall WndProc(Message::TMessage &Message);
下面我們就通過一個(gè)實(shí)例來演示如何重載它,要實(shí)現(xiàn)的目標(biāo)和上面那個(gè)程序一樣,只發(fā)代碼就可以了,工程設(shè)置也是一樣的。
編輯窗體類的頭文件Unit1.h如下:
class TForm1 : public TForm
{
__published: // IDE-managed Components
TLabel *Label1;
private: // User declarations
void __fastcall WndProc(TMessage &Message);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
編輯窗體函數(shù)文件Unit1.cpp如下:
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::WndProc(TMessage &Message)
{
if(Message.Msg == WM_GETMINMAXINFO)
{
LPMINMAXINFO lpmni = (LPMINMAXINFO) Message.LParam;
lpmni->ptMaxSize.x=300;
lpmni->ptMaxSize.y=200;
lpmni->ptMaxPosition.x=Left;
lpmni->ptMaxPosition.y=Top;
lpmni->ptMaxTrackSize.x=300;
lpmni->ptMaxTrackSize.y=200;
lpmni->ptMinTrackSize.x=200;
lpmni->ptMinTrackSize.y=100;
Label1->Caption="Width="+AnsiString(Width)+" Height="+AnsiString(Height);
}
TForm::WndProc(Message);
}
//---------------------------------------------------------------------------
程 序首先判斷被處理的消息是否為WM_GETMINMAXINFO,如果是才進(jìn)行后續(xù)處理。代碼LPMINMAXINFO lpmni = (LPMINMAXINFO) Message.LParam;的作用是把消息附帶的MINMAXINFO類型的結(jié)構(gòu)體的地址賦值給一個(gè)工作變量lpmni。這個(gè)MINMAXINFO類 型的結(jié)構(gòu)體被用來存儲(chǔ)有關(guān)窗體最大化、最小化以及窗體位置的信息。對(duì)于WM_GETMINMAXINFO而言,這個(gè)指針被存儲(chǔ)在TMessage結(jié)構(gòu)的 LParam成員域中。
按F9運(yùn)行程序,運(yùn)行結(jié)果同樣符合了我們的預(yù)期。
六、非標(biāo)準(zhǔn)消息
1,通知消息
通知消息(Notification message)指的是,當(dāng)一個(gè)窗體的子控件發(fā)生了一些事情后,它通知給其父窗體的消息。需要注意的是,通知消息只發(fā)生在一些標(biāo)準(zhǔn)的Windows控件上,包括按鈕、編輯框、列表框、組合列表框、樹狀視圖控件、列表控件等。
通知消息如同標(biāo)準(zhǔn)的Windows消息一樣,也可以通過消息映射等方法來進(jìn)行處理。
2,自定義消息
自 定義消息一般有兩種方式:直接定義消息常量或調(diào)用API函數(shù)RegisterWindow-Message()向系統(tǒng)注冊(cè)一個(gè)消息。其中比較簡潔的方式是 直接在程序中定義消息的數(shù)值,通常將其定義為WM_USER+XXX或者是WM_APP+XXX。Windows對(duì)于消息編號(hào)的分配情況通常如下所示:
范圍 說明
0到WM_USER-1 系統(tǒng)消息
WM_USER到0x7FFF 為用戶的窗體類保留整數(shù)類型消息
WM_APP到0xBFFF 為應(yīng)用程序保留的消息
0xC000到0xFFFF 為應(yīng)用程序保留的字符類型消息,由函數(shù)RegisterWindowMessage()分配
0xFFFF以上 為將來的系統(tǒng)應(yīng)用保留
一個(gè)典型的消息常量定義如下:
#define WM_MYNOTIFY (WM_APP+100)
這種直接定義消息的方式雖然簡潔,但在有的情況下,如果有大量的組件和應(yīng)用使用這種方式來定義自己的消息編號(hào)時(shí)就有可能引起沖突。此時(shí),可以通過函數(shù)RegisterWindowMessage()來獲取一個(gè)系統(tǒng)中唯一的消息編號(hào)。
函數(shù)RegisterWindowMessage()的原型定義如下:
UINT RegisterWindowMessage(LPCTSTR lpString);
這 個(gè)函數(shù)需要傳輸一個(gè)以null結(jié)束的字符串,并且返回一個(gè)范圍是0xC000~0xFFFF的消息常量。使用它的好處在于,對(duì)于任何給定的字符串都可以得 到一個(gè)系統(tǒng)中惟一的消息常量,從而可靠地避免了自定義消息之間可能的沖突。當(dāng)然,這個(gè)函數(shù)的返回值只有在程序運(yùn)行時(shí)才有意義。
3,VCL內(nèi)部消息
VCL自身也使用了許多內(nèi)部的消息,它們?cè)谝话愕某绦蜷_發(fā)中很少用到,但是在編寫自己的組件時(shí)卻非常有用。這些VCL內(nèi)部消息都具有CM_XXXX的形式,用于處理VCL內(nèi)部的事務(wù)。
七、自己發(fā)送消息
在C++ Builder中提供了幾種自己發(fā)送消息的途徑:使用函數(shù)TControl::Perform()或者API函數(shù)SendMessage()和 PostMessage()向特定窗體發(fā)送消息,或者是使用函數(shù)TWinControl::Broadcast()和API函數(shù) BroadcastSystemMessage()廣播消息。
1,Perform()
Perform()函數(shù)適用于所有由TControl類派生的對(duì)象,可以通過這個(gè)函數(shù)直接向這些組件發(fā)送消息,而這個(gè)組件對(duì)于這個(gè)消息的反應(yīng)就如同它真的從系統(tǒng)接收到了這個(gè)消息一樣。函數(shù)Perform()的原型如下:
int __fastcall Perform(Cardinal Msg,int WParam,int LParam);
其中參數(shù)Msg表示的就是這個(gè)消息的標(biāo)識(shí)符,比如WM_LBUTTONBLCLK。另外兩個(gè)代表的是此消息的兩個(gè)附帶參數(shù),它們對(duì)于不同的消息具有不同的含義。
調(diào)用了此函數(shù)之后,要等到消息處理之后才返回。
在 同一個(gè)應(yīng)用程序的不同窗體和控件之間使用Perform()是非常便捷的。但這個(gè)函數(shù)是TControl類的成員函數(shù)。也就是說,程序必須知道這個(gè)接收消 息的控件類的實(shí)例。而在許多情況下程序并不知道接收消息的窗體的實(shí)例而只是知道它的句柄,比如在不同程序的窗體之間發(fā)送消息就屬于這種情況。這時(shí)就要使用 下面的兩個(gè)函數(shù)了。
2,SendMessage()和PostMessage
這兩個(gè)函數(shù)的功能基本一樣,它們都可以用來向一個(gè)特 定的窗口句柄發(fā)送消息。主要的區(qū)別是:函數(shù)SendMessage()直接把一個(gè)消息發(fā)送給窗口函數(shù),等消息被處理之后才返回;而函數(shù) PostMessage()則只是把消息發(fā)送到消息隊(duì)列然后就立即返回。它們的用法與Perfoem()基本相似,不過多了個(gè)hWnd參數(shù)來表示目標(biāo)窗口 的句柄。
3,Broadcast()和BroadcastSystemMessage()
函數(shù)Broadcast()適用于所有由TWinControl類派生的對(duì)象,它可以向窗體上所有的子控件廣播消息,原型如下:
void __fastcall Broadcast(void *Message);
它只有一個(gè)參數(shù),指向被廣播的TMessage類型的消息結(jié)構(gòu)體。
函 數(shù)Broadcast()只能向C++ Builder應(yīng)用程序中的指定窗體上所有的子控件廣播消息,如果要向系統(tǒng)中的其他應(yīng)用程序或者窗體廣播消息,它就無能為力了。這時(shí)可以使用API函數(shù) BroadcastSystemMessage(),這個(gè)函數(shù)可以向任意的應(yīng)用程序或者組件廣播消息。它的原型如下:
long BroadcastSystemMessage(
DWORD dwFlags,
LPDWORD lpdwRecipients,
UINT uiMessage,
WPARAM wParam,
LPARAM lParam);
更詳細(xì)的用法請(qǐng)查閱幫助文檔。
當(dāng)然,在程序中需要給系統(tǒng)中的所有進(jìn)程廣播消息的情況是很少見的,所以它的使用并不多。但是在程序內(nèi)部給一個(gè)窗體的所有子控件廣播消息的情況則較為普遍,特別是當(dāng)窗體的子控件很多的情況下,用廣播消息的方式顯得非常便捷。(全文完)
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)
點(diǎn)擊舉報(bào)。