程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 深入VCL理解BCB的消息機制3

深入VCL理解BCB的消息機制3

編輯:關於C++

方法3 來自TApplication的方法

不用我多廢話,大家都知道TApplication在BCB中的重要性。在BCB的幫助中指出:TApplication、TScreen和TForm構成了所有BCB風格的Win32 GUI程序的脊梁,他們控制著您程序的行為。TApplication類提供的屬性和方法封裝了標准Windows程序的行為。TApplication表現了在Windows操作系統中創建、運行、支持和銷毀應用程序的基本原理。因此,TApplication大大簡化了開發者和Windows環境之間的接口。這正是BCB的RAD特性。

TApplication封裝的標准Windows行為大致包括如下幾部分:

1> Windows 消息處理

2> 上下文關聯的在線幫助

3> 菜單的快捷鍵和鍵盤事件處理

4> 異常處理

5> 管理由操作系統定義的程序基礎部分,如:MainWindow 主窗口、WindowClass 窗口類等。

一般情況下,BCB會為每個程序自動生成一個TApplication類的實例。這部分源碼可以在yourproject.cpp文件中見到(這裡假定您的工程名稱就叫yourproject.bpr)。

當然TApplication是不可見的,他總是在您的Form背後默默的控制著您的程序的行為。但也不是找不到蛛絲馬跡。如果您新建一個程序(New Application),然後不作任何改動,編譯運行的話,你會發現程序窗體的Caption是Form1,但在Windows的狀態條上的Caption確寫著project1的字樣。這就是TApplication存在的證據。當然,這只是一種臆測,實戰的方法應該打開BCB附帶的WinSight來查看系統的進程。您可以清楚的看到TApplication類的存在,他的大小是0(隱藏的嘛),然後才是TForm1類。

好了,既然TApplication封裝了消息處理的內容。我們就研究一下TApplication的實際動作吧。實際上消息到達BCB程序時,最先得到它們的就是TApplication對象。經由TApplication之後,才傳遞給Form的。以前的方法都是重載TForm的方法,顯然要比本文所提到的方法要晚一些收到消息。對您來說,是不是希望在第一時間收到消息並處理它們呢?

要清楚的知道TApplication的處理機制還是深入VCL源碼。首先看一看最最普通的一段代碼吧。

#include <vcl.h> #pragma hdrstop USERES("Project1.res"); USEFORM("Unit1.cpp", Form1);
//--------------------------------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{ try { // 初始化Application Application->Initialize(); // 創建主窗口,並顯示
Application->CreateForm(__classid(TForm1), &Form1); // 進入消息循環,直到程序退出Application->Run();
}
catch (Exception &exception)
{ Application->ShowException(&exception);
}
return 0;
}

短短的幾行代碼就可以讓您的BCB程序自如運行。因為一切都已經被VCL在後台封裝好了。Application->Run()方法進入程序的消息循環,直到程序退出。一起跟進VCL源碼看個究竟吧。TApplication的定義在forms.pas中。procedure TApplication.Run;

begin FRunning := True;
try AddExitProc(DoneApplication);
if FMainForm <> nil then begin // 設置主窗口的顯示屬性
case CmdShow of
SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized; SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
end;
if FShowMainForm then
if FMainForm.FWindowState = wsMinimized then
Minimize else
FMainForm.Visible := True;
// 看見了吧,這裡有個循環,直到Terminated屬性為真退出。Terminated什麼意思,就是取消,結束
repeat
HandleMessage
until Terminated;
end;
finally
FRunning := False;
end;
end;

消息處理的具體實現不在Run方法中,很顯然關鍵在HandleMessage方法,看看這函數名字-消息處理。只有跟進HandleMessage瞧瞧喽。

procedure TApplication.HandleMessage;
var
Msg: TMsg;
begin
if not ProcessMessage(Msg) then Idle(Msg);
end;

咳,這裡也不是案發現場。程序先將消息交給ProcessMessage方法處理。如果沒什麼要處理的,就轉入Application.Idle方法“程序在空閒時調用的方法”。

呼呼,再跟進ProcessMessage方法吧。

function TApplication.ProcessMessage(var Msg: TMsg): Boolean;
var
Handled: Boolean;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end
else
FTerminate := True;
end;
end;

哎呀呀,終於有眉目了。ProcessMessage采用了一套標准的Windows API 函數PeekMessage .... TranslateMessage;DispatchMessage。

有人說:Application->OnMessage = MyOnMessage; //不能響應SendMessage的消息,但是可以響應PostMessage發送的消息,也就是消息隊列裡的消息

SendMessage和PostMessage最主要的區別在於發送的消息有沒有通過消息隊列。

原因就在這裡。ProcessMessage使用了PeekMessage(Msg, 0, 0, 0, PM_REMOVE) 從消息隊列中提取消息。然後先檢查是不是退出消息。不是的話,檢查是否存在OnMessage方法。如果存在就轉入OnMessage處理消息。最後才將消息分發出去。

這樣重載Application的OnMessage方法要比前兩種方法更早得到消息,可以說是最快速的方法了吧。舉個例子:

void __fastcall TForm1::MyOnMessage(tagMSG &Msg, bool &Handled)
{
TMessage Message;
switch (Msg.message)
{
case WM_KEYDOWN:
Message.Msg = Msg.message;
Message.WParam = Msg.wParam;
Message.LParam = Msg.lParam;
MessageDlg("You Pressed Key!", mtWarning, TMsgDlgButtons() << mbOK, 0);
Handled = true;
break;
}
}
void __fastcall TForm1::FormCreate(TObject *Sender)
Application->OnMessage = MyOnMessage;
}

現在可以簡短的總結一下VCL的消息機制了。

標准的BCB程序使用Application->Run()進入消息循環,在Application的ProcessMessage方法中,使用PeekMessage方法從消息隊列中提取消息,並將此消息從消息隊列中移除。然後ProcessMessage 方法檢查是否存在Application->OnMessage方法。存在則轉入此方法處理消息。之後再將處理過的消息分發給程序中的各個對象。至此,WndProc方法收到消息,並進行處理。如果有無法處理的交給重載的Dispatch方法來處理。要是還不能處理的話,再交給父類的Dispatch方法處理。最後Dispatch方法實際上將消息轉入DefaultHandler方法來處理。

“嘿嘿,實際上,你一樣可以重載DefaultHandler方法來處理消息。但是太晚了一點。我想沒有人願意最後一個處理消息吧...:-)”

寫到這裡似乎可以結束了。但如果您看過上一篇的話,一定會注意到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 <<mbNo, 0)? 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);
}

這樣,將本文中的兩種方法相結合,您就可以自如的處理到達您的應用程序的各種消息了。(全文完)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved