用戶: 密碼:     忘記密碼 | 會員註冊
  

C++ Builder中消息機制的研究與應用技巧

8.5
出處:2345軟件大全 時間:2011-06-27 人氣:1106

核心提示:C++ Builder作為一種RAD方式的程序開發工具,提供了功能強大的集成開發環境。C++ Builder提供的VCL組件

  Windows消息處理 BCB

  C++ Builder作為一種RAD方式的程序開發工具,提供了功能強大的集成開發環境。C++ Builder提供的VCL組件,封裝了Windows的底層API和具體實現細節,也提供了對組件消息機制的良好封裝。比如,可以對按鈕控件(TButton)的單擊、按下、拖動等事件消息設置相應的處理函數,並在對應的函數體中實現對該消息的處理與響應。雖然C++ Builder對VCL組件的消息處理提供了一套良好的處理機制,且封裝了許多常用的消息,但是當開發者需要處理未定義的Windows消息或自定義消息時,C++ Builder不能提供直接的支持。這就需要開發者對Windows 消息驅動機制和C++ Builder 中的消息處理機制能有一個深入的認識。

  1 Windows 消息驅動機制

  Windows是以消息驅動的操作系統,Windows 消息提供了應用程序與應用程序以及應用程序與Windows系統之間進行通訊的手段。Windows 中有一個系統消息隊列,對於每一個正在執行的Windows應用程序,系統為其建立一個“消息隊列”,用來存放該程序可能創建的各種窗口的消息。應用程序中含有一段稱作“消息循環”的代碼,用來從消息隊列中檢索這些消息並把它們分發到相應的窗口函數中。

  消息循環代碼是應用程序中主函數winmain ( )中類似如下的程序段: while(GetMessage(&msg,NULL,NULL,NULL)) 
{
…… 
//從消息隊列中取得消息後,檢索並生成字符消息 
TranslateMessage(&msg); 
//將消息發送給相應的窗口函數
DispatchMessage(&msg); 
}

  
由此可見,所謂“消息循環”,實際是程序循環。Windows 應用程序創建的每個窗口都在系統核心註冊一個相應的窗口函數,窗口函數程序代碼形式上是一個switch-case 語句,用以處理由消息循環發送到該窗口的消息。 Switch(msg)
{
case … : …
break;
……
default : …
break;
}

  窗口函數由Windows 採用消息驅動的形式隱式地調用(由系統調用),而不是由應用程序顯示調用的,窗口函數處理完消息後又將控制權返回給Windows.

  Windows消息處理過程實質包括以下四個步驟:(1) 系統發生事件;(2) 根據事件產生消息,並放入消息隊列;(3) 應用程序從消息隊列中取得消息,並封裝後,通過消息循環把消息分派給對應的處理函數;(4) 處理函數最終處理這個消息。

  2 C++ Builder 中的消息處理

  在類 Application中封裝、實現了Windows 程序框架,包括一些初始化、消息循環代碼等。一般用C++ Builder 編寫的Windows GUI 應用程序,缺省生成如下代碼: 
//Windows 應用程序主函數
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) 

try 

//作初始化
Application-〉Initialize();
Application-〉CreateForm(__classid(TForm1), &&Form1); 
//其中封裝了消息循環
Application-〉Run(); 
}
//例外處理
catch (Exception &&exception)  

Application-〉ShowException(&&exception); 

return 0; 

  
對於消息處理,C++ Builder採用基於組件(component)的程序設計模式,每種控件都繼承一套完整的消息派送體系。 它為每一種類型的控件都註冊一個名為 MainWndProc的方法函數作為窗口函數,接受“消息循環”派送來的消息,它是一個非虛擬方法,不對任何特定消息作特別處理,它僅僅調用WndProc方法函數,並作一些例外處理。不同控件對消息處理的定制發生在WndProc 方法中,因為它是一個虛擬方法,每一種控件可以通過覆蓋它來適應特別的情況。WndProc 方法檢查不同的條件,作不同的處理,從而能夠濾掉不希望處理的各種消息。最終,WndProc 調用 Dispatch 方法,它是一個從所有控件的始祖TObject 繼承而來的虛擬方法,它確定調用哪個方法處理傳來的消息。

  以上是消息在控件中的傳遞過程,C++ Builder對消息處理作了進一步的封裝,把常用的消息封裝成相應的事件屬性,開發者只要把精力放在響應函數設計和建立消息與處理函數的映射關係上。

  3 對VCL事件的處理

  在C++ Builder中,VCL事件包含了許多Windows消息,所以在通常情況下對Windows消息的響應就轉化為對VCL事件的響應處理。VCL事件與響應函數之間的映射關係可以是靜態的(程序設計時確定),也可以是動態的(在程序運行時確定)。下面分別以建立動態和靜態的響應函數映射關係。

  在靜態映射關係條件下,建立VCL事件的響應處理非常簡單。我們以一個實例來說明。

  在一個新建工程中,放入一個按鈕控件(TButton),在對像觀察器中選擇事件標籤,在該標籤中選擇OnClick事件,寫入響應函數,通常雙擊該事件選項,則可以很方便地建立事件與函數的對應關係和響應函數框架。 void __fastcall TMsgExp::Button1Click(TObject *Sender)
{
//對單擊按鈕事件的響應 
ShowMessage("你觸發了一個單擊事件!"); 
}

  
程序設計完成後,消息映射就確定下來,不會改變。

  在動態映射關係條件下,需要在運行時設定事件與函數的對應關係。C++ Builder的應用程序中的任何窗體收到一個Windows就會觸發一個OnMessage事件,通過該事件來捕獲發送給程序的消息,並動態地建立消息與響應函數之間的映射關係,也以一個實例來說明。

  按上面提到的例子,建立一個按鈕單擊事件的響應,如下: void __fastcall TMsgExp::Button2Click(TObject *Sender)
{
//根據單選按鈕的不同狀態,動態建立消息與響應函數的映射
if (RadioButton1->Checked)
Application->OnMessage=ClickMouse;
if (RadioButton2->Checked)
Application->OnMessage=Move;
}
……
void __fastcall TMsgExp::ClickMouse(tagMSG & Msg, bool & Handled)
{
//對單擊鼠標事件的處理
if(Msg.message==WM_LBUTTONDOWN)
{
num++;
Label1->Caption="你一共點擊了"+AnsiString(num)+"次";
//允許後繼過程繼續處理該事件 
Handled=false;
}
}
……
void __fastcall TMsgExp::Move(tagMSG & Msg, bool & Handled)
{
//對鼠標移動事件的處理
if(Msg.message==WM_MOUSEMOVE)
{
//從消息中過去鼠標的位置參數
WORD xPos = LOWORD(Msg.lParam);
WORD yPos = HIWORD(Msg.lParam);
Label1->Caption="當前鼠標位置 x:"+AnsiString(xPos)+" y:"+AnsiString(yPos);
//允許後繼過程繼續處理該事件 
Handled=false;
}
}

  
上面的例子利用Application 的OnMessage事件實現了對鼠標移動和單擊事件的動態響應。需要說明的是OnMessage事件僅僅接受發送到消息隊列中的消息,對於利用API直接發送給窗口函數的消息將不與理會。

  4 對自定義消息的處理

  在有的情況下,程序需要發送自己定義的消息,它既可以用在兩個應用程序之間的通訊,也可以用在一個程序內部的不同窗體和組件之間的通信。使用自定義消息有兩個優點:一是發消息時,無須知道接受者的具體類型,只要知道窗口的句柄;二是消息可以廣播給多個接受者。它也有靜態和動態的兩種函數響應映射關係。

  同樣舉一個例子,在窗體類的頭文件(。h)中加入自定義消息MY_SELFDEFINE,並靜態的對這個自定義消息建立函數映射關係 ……
#define MY_SELFDEFINE (WM_USER+100)
……
//定義響應函數
void __fastcall Handle(TMessage & Msg);
……
//建立響應函數(Handle)與自定義消息(MY_SELFDEFINE)的映射
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(MY_SELFDEFINE,TMessage,Handle)
END_MESSAGE_MAP(TForm)
……
在窗體類的實現文件中(.cpp)分別實現定義的響應函數(Handle)和發送自定義的消息(MY_SELFDEFINE)
……
//響應函數的實現
void __fastcall TMsgExp::Handle(TMessage & Msg)
{
ShowMessage("攔截到自定義消息MY_SELFDEFINE");
}
……
//發送自定義的消息(MY_SELFDEFINE)
this->Perform(MY_SELFDEFINE,0,0);

  
應用程序接受到自定義的消息後,根據靜態建立的映射關係,將消息交由響應函數處理。

  接著上面的例子,再建立一個動態對自定義消息進行響應的例子。分別建立兩個響應函數: OpenForm(打開新窗口)和CloseForm(關閉新窗口)。 void __fastcall TMsgExp::OpenForm(TMessage & Msg)
{
//截獲到MY_SELFDEFINE消息,則打開新窗口,否則交由原來的函數處理
if (Msg.Msg == MY_SELFDEFINE)
NewWindows->Show();
else
WndProc(Msg);
}
……
void __fastcall TMsgExp::CloseForm(TMessage & Msg)
{
//截獲到MY_SELFDEFINE消息,則關閉新窗口,否則交由原來的函數處理
if (Msg.Msg == MY_SELFDEFINE)
NewWindows->Hide();
else
WndProc(Msg);
}
……
//根據單選按鈕的不同狀態,動態建立自定義消息與響應函數的映射
if (RadioButton3->Checked)
/*利用TControl控件的WindowProc屬性,動態地設置
對處理消息的響應函數*/
this->WindowProc=OpenForm;
if (RadioButton4->Checked)
this->WindowProc=CloseForm;
//發送自定義的消息(MY_SELFDEFINE) 
this->Perform(MY_SELFDEFINE,0,0);
……  
5 結束語

  本文分別探討了對Windows消息和自定義消息建立靜態和動態響應函數映射的技巧和方法,闡述了由事件到消息,再由消息映射到響應函數處理的整個機制,具有很大的實用價值和理論探討價值。示例程序在Windows2000/C++ Builder6.0環境下編譯通過。