| 引子:本文所談及的技術(shù)內(nèi)容都來(lái)自于Internet的公開(kāi)信息。由筆者在閑暇之際整理 后,貼出來(lái)以飴網(wǎng)友,姑且妄稱(chēng)原創(chuàng)。每次在國(guó)外網(wǎng)站上找到精彩文章的時(shí)候,心中都 會(huì)暗自嘆息,為什么在中文網(wǎng)站難以覓得這類(lèi)文章呢?其實(shí)原因大家都明白。 時(shí)至今日,學(xué)習(xí)Windows編程的兄弟們都知道消息機(jī)制的重要性。所以理解消息機(jī)制也 成了不可或缺的功課。大家都知道,Borland的C++ Builder以及Delphi的核心是VCL。 作為Win32平臺(tái)上的開(kāi)發(fā)工具,封裝Windows的消息機(jī)制當(dāng)然也是必不可少的。那么,在 C++ Builder中處理消息的方法有哪些呢?它們之間的區(qū)別又在哪里?如果您很清楚這 些,呵呵,對(duì)不起啦,請(qǐng)關(guān)掉這個(gè)窗口。如果不清楚那就和我一起深入VCL的源碼看個(gè) 究竟吧。 方法1:使用消息映射(Message Map)重載TObject的Dispatch虛成員函數(shù) 這個(gè)方法大家用的很多。形式如下 BEGIN_MESSAGE_MAP VCL_MESSAGE_HANDLER( … … ) END_MESSAGE_MAP( … ) 但這幾句話(huà)實(shí)在太突兀,C++標(biāo)準(zhǔn)中沒(méi)有這樣的定義。不用講,這顯然又是宏定義。它 們到底怎么來(lái)的呢?CKER第一次見(jiàn)到它們的時(shí)候,百思不得其解。嘿嘿,不深入VCL, 怎么可能理解? 在/Borland/CBuilder5/Include/Vcl找到sysmac.h,其中有如下的預(yù)編譯宏定義: #define BEGIN_MESSAGE_MAP virtual void __fastcall Dispatch(void *Message) / { / switch (((PMessage)Message)->Msg) / { #define VCL_MESSAGE_HANDLER(msg,type,meth) / case msg: / meth(*((type *)Message)); / break; // NOTE: ATL defines a MESSAGE_HANDLER macro which conflicts with VCL's macro. The // VCL macro has been renamed to VCL_MESSAGE_HANDLER. If you are not using ATL, // MESSAGE_HANDLER is defined as in previous versions of BCB. #if !defined(USING_ATL) && !defined(USING_ATLVCL) && !defined(INC_ATL_HEADERS) #define MESSAGE_HANDLER VCL_MESSAGE_HANDLER #endif // ATL_COMPAT #define END_MESSAGE_MAP(base) default: / base: ispatch(Message); / break; / } / } 這樣對(duì)如下的例子: BEGIN_MESSAGE_MAP VCL_MESSAGE_HANDLER(WM_PAINT,TMessage,OnPaint) END_MESSAGE_MAP(TForm1) 在預(yù)編譯時(shí),就被展開(kāi)成如下的代碼 virtual void __fastcall Dispatch(void *Message) { switch (((PMessage)Message)->Msg) { case WM_PAINT: OnPaint(*((TMessage *)Message)); //消息響應(yīng)句柄,也就是響應(yīng)消息的成員函數(shù),在Form1中定義 break; default: TForm1: ispatch(Message); break; } } 這樣就很順眼了,對(duì)吧。對(duì)這種方法有兩點(diǎn)要解釋一下: 1.virtual void __fastcall Dispatch(void *Message) 這個(gè)虛方法的定義最早可以在TObject的定義中找到。打開(kāi)BCB的幫助,查找TForm的方 法(Method),你會(huì)發(fā)現(xiàn)這里很清楚的寫(xiě)著Dispatch方法繼承自TObject。如果您關(guān)心 VCL的繼承機(jī)制的話(huà),您會(huì)發(fā)現(xiàn)TObject是所有VCL對(duì)象的基類(lèi)。TObject的抽象凝聚了 Borland的工程師們的心血。如果有興趣。您應(yīng)該好好查看一下TObject的定義。 很顯然,所有TObject的子類(lèi)都可以重載基類(lèi)的Dispatch方法,來(lái)實(shí)現(xiàn)自己的消息調(diào) 用。如果Dispatch方法找不到此消息的定義,會(huì)將此消息交由TObject:DefaultHandler 方法來(lái)處理。抽象基類(lèi)TObject的DefaultHandler方法實(shí)際上是空的。同樣要由繼承子 類(lèi)重載實(shí)現(xiàn)它們自己的消息處理過(guò)程。 2.很多時(shí)候,我見(jiàn)到的第二行是這樣寫(xiě)的: MESSAGE_HANDLER(WM_PAINT,TMessage,OnPaint) 在這里,您可以很清楚地看到幾行注解,意思是ATL中同樣包含了一個(gè)MESSAGE_HANDLER 的宏定義,這與VCL發(fā)生了沖突。為了解決這個(gè)問(wèn)題,Borland改用 VCL_MESSAGE_HANDLER。當(dāng)您沒(méi)有使用ATL的時(shí)候,MESSAGE_HANDLER將轉(zhuǎn)換成 VCL_MESSAGE_HANDLER。但如果用了ATL就會(huì)有問(wèn)題。所以我建議您始終使用 VCL_MESSAGE_HANDLER的寫(xiě)法,以免出現(xiàn)問(wèn)題。 ? 方法2:重載TControl的WndProc方法 還是先談?wù)刅CL的繼承策略。VCL中的繼承鏈的頂部是TObject基類(lèi)。一切的VCL組件和對(duì) 象都繼承自TObject。 打開(kāi)BCB幫助查看TControl的繼承關(guān)系: TObject->TPersistent->TComponent->TControl 原來(lái)TControl是從TPersistent類(lèi)的子類(lèi)TComponent類(lèi)繼承而來(lái)的。TPersistent抽象基 類(lèi)具有使用流stream來(lái)存取類(lèi)的屬性的能力。 TComponent類(lèi)則是所有VCL組件的父類(lèi)。 這就是所有的VCL組件包括您的自定義組件可以使用dfm文件存取屬性的原因(當(dāng)然要是 TPersistent的子類(lèi),我想您很少需要直接從TObject類(lèi)來(lái)派生您的自定義組件吧)。 TControl類(lèi)的重要性并不亞于它的父類(lèi)們。在BCB的繼承關(guān)系中,TControl類(lèi)的是所有 VCL可視化組件的父類(lèi)。實(shí)際上就是控件的意思吧。所謂可視化是指您可以在運(yùn)行期間 看到和操縱的控件。這類(lèi)控件所具有的一些基本屬性和方法都在TControl類(lèi)中進(jìn)行定 義。 TControl的實(shí)現(xiàn)在/Borland/CBuilder5/Source/Vcl/control.pas中可以找到。(可能 會(huì)有朋友問(wèn)你怎么知道在那里?使用BCB提供的Search -> Find in files很容易找到。 或者使用第三方插件的grep功能。) 好了,進(jìn)入VCL的源碼吧。說(shuō)到這里免不了要抱怨一下Borland。哎,為什么要用pascal 實(shí)現(xiàn)這一切……:-( TControl繼承但并沒(méi)有重寫(xiě)TObject的Dispatch方法。反而提供了一個(gè)新的方法 WndProc。一起來(lái)看看Borland的工程師們是怎么寫(xiě)的吧。 procedure TControl.WndProc(var Message: TMessage); var Form: TCustomForm; begin //由擁有control的窗體來(lái)處理設(shè)計(jì)期間的消息 if (csDesigning in ComponentState) then begin Form := GetParentForm(Self); if (Form <> nil) and (Form.Designer <> nil) and Form.Designer.IsDesignMsg(Self, Message) then Exit; end //如果需要,鍵盤(pán)消息交由擁有control的窗體來(lái)處理 else if (Message.Msg >= WM_KEYFIRST) and (Message.Msg <= WM_KEYLAST) then begin Form := GetParentForm(Self); if (Form <> nil) and Form.WantChildKey(Self, Message) then Exit; end //處理鼠標(biāo)消息 else if (Message.Msg >= WM_MOUSEFIRST) and (Message.Msg <= WM_MOUSELAST) then begin if not (csDoubleClicks in ControlStyle) then case Message.Msg of WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK: Dec(Message.Msg, WM_LBUTTONDBLCLK - WM_LBUTTONDOWN); end; case Message.Msg of WM_MOUSEMOVE: Application.HintMouseMessage(Self, Message); WM_LBUTTONDOWN, WM_LBUTTONDBLCLK: begin if FDragMode = dmAutomatic then begin BeginAutoDrag; Exit; end; Include(FControlState, csLButtonDown); end; WM_LBUTTONUP: Exclude(FControlState, csLButtonDown); end; end // 下面一行有點(diǎn)特別。如果您仔細(xì)的話(huà)會(huì)看到這個(gè)消息是CM_VISIBLECHANGED. // 而不是我們熟悉的WM_開(kāi)頭的標(biāo)準(zhǔn)Windows消息. // 盡管Borland沒(méi)有在它的幫助中提到有這一類(lèi)的CM消息存在。但很顯然這是BCB的 // 自定義消息。呵呵,如果您對(duì)此有興趣可以在VCL源碼中查找相關(guān)的內(nèi)容。一定會(huì)有 不小的收獲。 else if Message.Msg = CM_VISIBLECHANGED then with Message do SendDockNotification(Msg, WParam, LParam); // 最后調(diào)用dispatch方法。 Dispatch(Message); end; 看完這段代碼,你會(huì)發(fā)現(xiàn)TControl類(lèi)實(shí)際上只處理了鼠標(biāo)消息,沒(méi)有處理的消息最后都 轉(zhuǎn)入Dispatch()來(lái)處理。 但這里需要強(qiáng)調(diào)指出的是TControl自己并沒(méi)有獲得焦點(diǎn)Focus的能力。TControl的子類(lèi) TWinControl才具有這樣的能力。我憑什么這樣講?呵呵,還是打開(kāi)BCB的幫助。很多朋 友抱怨BCB的幫助實(shí)在不如VC的MSDN。毋庸諱言,的確差遠(yuǎn)了。而且這個(gè)幫助還經(jīng)常有 問(wèn)題。但有總比沒(méi)有好啊。 言歸正傳,在幫助的The TWinControl Branch 分支下,您可以看到關(guān)于TWinControl類(lèi) 的簡(jiǎn)介。指出TWinControl類(lèi)是所有窗體類(lèi)控件的基類(lèi)。所謂窗體類(lèi)控件指的是這樣一 類(lèi)控件: 1. 可以在程序運(yùn)行時(shí)取得焦點(diǎn)的控件。 2. 其他的控件可以顯示數(shù)據(jù),但只有窗體類(lèi)控件才能和用戶(hù)發(fā)生鍵盤(pán)交互。 3. 窗體類(lèi)控件能夠包含其他控件(容器)。 4. 包含其他控件的控件又稱(chēng)做父控件。只有窗體類(lèi)控件才能夠作為其他控件的父控 件。 5. 窗體類(lèi)控件擁有句柄。 除了能夠接受焦點(diǎn)之外,TWinControl的一切都跟TControl沒(méi)什么分別。這一點(diǎn)意味著 TWinControl可以對(duì)許多的標(biāo)準(zhǔn)事件作出響應(yīng),Windows也必須為它分配一個(gè)句柄。并且 與這個(gè)主題相關(guān)的最重要的是,這里提到是由BCB負(fù)責(zé)來(lái)對(duì)控件進(jìn)行重畫(huà)以及消息處 理。這就是說(shuō),TWinControl封裝了這一切。 似乎扯的太遠(yuǎn)了。但我要提出來(lái)的問(wèn)題是TControl類(lèi)的WndProc方法中處理了鼠標(biāo)消 息。但這個(gè)消息只有它的子類(lèi)TWinControl才能夠得到啊!? 這怎么可以呢……Borland是如何實(shí)現(xiàn)這一切的呢?這個(gè)問(wèn)題實(shí)在很奧妙。為了看個(gè)究 竟,再次深入VCL吧。 還是在control.pas中,TWinControl繼承了TControl的WndProc方法。源碼如下: procedure TWinControl.WndProc(var Message: TMessage); var Form: TCustomForm; KeyState: TKeyboardState; WheelMsg: TCMMouseWheel; begin case Message.Msg of WM_SETFOCUS: begin Form := GetParentForm(Self); if (Form <> nil) and not Form.SetFocusedControl(Self) then Exit; end; WM_KILLFOCUS: if csFocusing in ControlState then Exit; WM_NCHITTEST: begin inherited WndProc(Message); if (Message.Result = HTTRANSPARENT) and (ControlAtPos(ScreenToClient( SmallPointToPoint(TWMNCHitTest(Message).Pos)), False) <> nil) then Message.Result := HTCLIENT; Exit; end; WM_MOUSEFIRST..WM_MOUSELAST: //下面這一句話(huà)指出,鼠標(biāo)消息實(shí)際上轉(zhuǎn)入IsControlMouseMsg方法來(lái)處理了。 if IsControlMouseMsg(TWMMouse(Message)) then begin if Message.Result = 0 then DefWindowProc(Handle, Message.Msg, Message.wParam, Message.lParam); Exit; end; WM_KEYFIRST..WM_KEYLAST: if Dragging then Exit; WM_CANCELMODE: if (GetCapture = Handle) and (CaptureControl <> nil) and (CaptureControl.Parent = Self) then CaptureControl.Perform(WM_CANCELMODE, 0, 0); else with Mouse do if WheelPresent and (RegWheelMessage <> 0) and (Message.Msg = RegWheelMessage) then begin GetKeyboardState(KeyState); with WheelMsg do begin Msg := Message.Msg; ShiftState := KeyboardStateToShiftState(KeyState); WheelDelta := Message.WParam; Pos := TSmallPoint(Message.LParam); end; MouseWheelHandler(TMessage(WheelMsg)); Exit; end; end; inherited WndProc(Message); end; 鼠標(biāo)消息是由IsControlMouseMsg方法來(lái)處理的。只有再跟到IsControlMouseMsg去看看 啦。源碼如下: function TWinControl.IsControlMouseMsg(var Message: TWMMouse): Boolean; var //TControl出現(xiàn)啦 Control: TControl; P: TPoint; begin if GetCapture = Handle then begin Control := nil; if (CaptureControl <> nil) and (CaptureControl.Parent = Self) then Control := CaptureControl; end else Control := ControlAtPos(SmallPointToPoint(Message.Pos), False); Result := False; if Control <> nil then begin P.X := Message.XPos - Control.Left; P.Y := Message.YPos - Control.Top; file://TControl的Perform方法將消息交由WndProc處理。 Message.Result := Control.Perform(Message.Msg, Message.Keys, Longint(PointToSmallPoint(P))); Result := True; end; end; 原來(lái)如此,TWinControl最后還是將鼠標(biāo)消息交給TControl的WndProc來(lái)處理了。這里出 現(xiàn)的Perform方法在BCB的幫助里可以查到,是TControl類(lèi)中開(kāi)始出現(xiàn)的方法。它的作用 就是將指定的消息傳遞給TControl的WndProc過(guò)程。 結(jié)論就是TControl類(lèi)的WndProc方法的消息是由TWinControl類(lèi)在其重載的WndProc方法 中調(diào)用IsControlMouseMsg方法后使用Peform方法傳遞得到的。 由于這個(gè)原因,BCB和Delphi中的TControl類(lèi)及其所有的派生類(lèi)都有一個(gè)先天的而且是 必須的限制。那就是所有的TControl類(lèi)及其派生類(lèi)的Owner必須是TWinControl類(lèi)或者 TWinControl的派生類(lèi)。Owner屬性最早可以在TComponent中找到,一個(gè)組件或者控件是 由它的Owner擁有并負(fù)責(zé)釋放其內(nèi)存的。這就是說(shuō),當(dāng)Owner從內(nèi)存中釋放的時(shí)候,它所 擁有的所有控件占用的內(nèi)存也都被釋放了。Owner最好的例子就是Form。Owner同時(shí)也負(fù) 責(zé)消息的分派,當(dāng)Owner接收到消息的時(shí)候,它負(fù)責(zé)將應(yīng)該傳遞給其所擁有的控件的消 息傳遞給它們。這樣這些控件就能夠取得處理消息的能力。TImage就是個(gè)例子:你可以 發(fā)現(xiàn)Borland并沒(méi)有讓TImage重載TControl的WndProc方法,所以TImage也只有處理鼠標(biāo) 消息的能力,而這種能力正是來(lái)自TControl的。 唧唧崴崴的說(shuō)了一大堆。終于可以說(shuō)處理消息的第二種方法就是重載TControl的 WndProc方法了。例程如下: void __fastcall TForm1::WndProc(TMessage &Message) { switch (Message.Msg) { case WM_CLOSE: OnCLOSE(Message); // 處理WM_CLOSE消息的方法 break; } TForm::WndProc(Message); } 乍看起來(lái),這和上次講的重載Dispatch方法好象差不多。但實(shí)際上還是有差別的。差別 就在先后次序上,從前面TControl的WndProc可以看到,消息是先交給WndProc來(lái)處理, 最后才調(diào)用Dispatch方法的啦。 這樣,重載WndProc方法可以比重載Dispatch方法更早一點(diǎn)點(diǎn)得到消息并處理消息。 好了,這次就說(shuō)到這里。在您的應(yīng)用程序里還有沒(méi)有比這更早得到消息的辦法呢?有, 下次再說(shuō)。 ? 方法3 自TApplication的方法 不用我多廢話(huà),大家都知道TApplication在BCB中的重要性。在BCB的幫助中指出: TApplication、TScreen和TForm構(gòu)成了所有BCB風(fēng)格的Win32 GUI程序的脊梁,他們控制 著您程序的行為。TApplication類(lèi)提供的屬性和方法封裝了標(biāo)準(zhǔn)Windows程序的行為。 TApplication表現(xiàn)了在Windows操作系統(tǒng)中創(chuàng)建、運(yùn)行、支持和銷(xiāo)毀應(yīng)用程序的基本原 理。因此,TApplication大大簡(jiǎn)化了開(kāi)發(fā)者和Windows環(huán)境之間的接口。這正是BCB的 RAD特性。 TApplication封裝的標(biāo)準(zhǔn)Windows行為大致包括如下幾部分: 1> Windows 消息處理 2> 上下文關(guān)聯(lián)的在線幫助 3> 菜單的快捷鍵和鍵盤(pán)事件處理 4> 異常處理 5> 管理由操作系統(tǒng)定義的程序基礎(chǔ)部分,如:MainWindow 主窗口、 WindowClass 窗 口類(lèi) 等。 一般情況下,BCB會(huì)為每個(gè)程序自動(dòng)生成一個(gè)TApplication類(lèi)的實(shí)例。這部分源碼可以 在yourproject.cpp文件中見(jiàn)到(這里假定您的工程名稱(chēng)就叫yourproject.bpr)。 當(dāng)然TApplication是不可見(jiàn)的,他總是在您的Form背后默默的控制著您的程序的行為。 但也不是找不到蛛絲馬跡。如果您新建一個(gè)程序(New Application),然后不作任何改 動(dòng),編譯運(yùn)行的話(huà),你會(huì)發(fā)現(xiàn)程序窗體的Caption是Form1,但在Windows的狀態(tài)條上的 Caption確寫(xiě)著project1的字樣。這就是TApplication存在的證據(jù)。當(dāng)然,這只是一種 臆測(cè),實(shí)戰(zhàn)的方法應(yīng)該打開(kāi)BCB附帶的WinSight來(lái)查看系統(tǒng)的進(jìn)程。您可以清楚的看到 TApplication類(lèi)的存在,他的大小是0(隱藏的嘛),然后才是TForm1類(lèi)。 好了,既然TApplication封裝了消息處理的內(nèi)容。我們就研究一下TApplication的實(shí)際 動(dòng)作吧。實(shí)際上消息到達(dá)BCB程序時(shí),最先得到它們的就是TApplication對(duì)象。經(jīng)由 TApplication之后,才傳遞給Form的。以前的方法都是重載TForm的方法,顯然要比本 文所提到的方法要晚一些收到消息。對(duì)您來(lái)說(shuō),是不是希望在第一時(shí)間收到消息并處理 它們呢? 要清楚的知道TApplication的處理機(jī)制還是深入VCL源碼。首先看一看最最普通的一段 代碼吧。 #include #pragma hdrstop USERES("Project1.res"); USEFORM("Unit1.cpp", Form1); //-------------------------------------------------------------- WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { try { // 初始化Application Application->Initialize(); // 創(chuàng)建主窗口,并顯示 Application->CreateForm(__classid(TForm1), &Form1); // 進(jìn)入消息循環(huán),直到程序退出 Application->Run(); } catch (Exception &exception) { Application->ShowException(&exception); } return 0; } 短短的幾行代碼就可以讓您的BCB程序自如運(yùn)行。因?yàn)橐磺卸家呀?jīng)被VCL在后臺(tái)封裝好 了。Application->Run()方法進(jìn)入程序的消息循環(huán),直到程序退出。一起跟進(jìn)VCL源碼 看個(gè)究竟吧。 TApplication的定義在forms.pas中。 procedure TApplication.Run; begin FRunning := True; try AddExitProc(DoneApplication); if FMainForm <> nil then begin // 設(shè)置主窗口的顯示屬性 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; // 看見(jiàn)了吧,這里有個(gè)循環(huán),直到Terminated屬性為真退出。Terminated什么意思, 就是取消,結(jié)束 repeat HandleMessage until Terminated; end; finally FRunning := False; end; end; 消息處理的具體實(shí)現(xiàn)不在Run方法中,很顯然關(guān)鍵在HandleMessage方法,看看這函數(shù)名 字-消息處理。只有跟進(jìn)HandleMessage瞧瞧嘍。 procedure TApplication.HandleMessage; var Msg: TMsg; begin if not ProcessMessage(Msg) then Idle(Msg); end; 咳,這里也不是案發(fā)現(xiàn)場(chǎng)。程序先將消息交給ProcessMessage方法處理。如果沒(méi)什么要 處理的,就轉(zhuǎn)入Application.Idle方法“程序在空閑時(shí)調(diào)用的方法”。 呼呼,再跟進(jìn)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采用了一套標(biāo)準(zhǔn)的Windows API 函數(shù) PeekMessage .... TranslateMessage;DispatchMessage。 有人說(shuō):Application->OnMessage = MyOnMessage; //不能響應(yīng)SendMessage的消息, 但是可以響應(yīng)PostMessage發(fā)送的消息,也就是消息隊(duì)列里的消息 SendMessage和PostMessage最主要的區(qū)別在于發(fā)送的消息有沒(méi)有通過(guò)消息隊(duì)列。 原因就在這里。ProcessMessage使用了PeekMessage(Msg, 0, 0, 0, PM_REMOVE) 從消 息隊(duì)列中提取消息。然后先檢查是不是退出消息。不是的話(huà),檢查是否存在OnMessage 方法。如果存在就轉(zhuǎn)入OnMessage處理消息。最后才將消息分發(fā)出去。 這樣重載Application的OnMessage方法要比前兩種方法更早得到消息,可以說(shuō)是最快速 的方法了吧。舉個(gè)例子: 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; } 現(xiàn)在可以簡(jiǎn)短的總結(jié)一下VCL的消息機(jī)制了。 標(biāo)準(zhǔn)的BCB程序使用Application->Run()進(jìn)入消息循環(huán),在Application的 ProcessMessage方法中,使用PeekMessage方法從消息隊(duì)列中提取消息,并將此消息從 消息隊(duì)列中移除。然后ProcessMessage 方法檢查是否存在Application->OnMessage方 法。存在則轉(zhuǎn)入此方法處理消息。之后再將處理過(guò)的消息分發(fā)給程序中的各個(gè)對(duì)象。至 此,WndProc方法收到消息,并進(jìn)行處理。如果有無(wú)法處理的交給重載的Dispatch方法 來(lái)處理。要是還不能處理的話(huà),再交給父類(lèi)的Dispatch方法處理。最后Dispatch方法實(shí) 際上將消息轉(zhuǎn)入DefaultHandler方法來(lái)處理。 嘿嘿,實(shí)際上,你一樣可以重載DefaultHandler方法來(lái)處理消息。但是太晚了一點(diǎn)。我 想沒(méi)有人愿意最后一個(gè)處理消息吧...:-) 寫(xiě)到這里似乎可以結(jié)束了。但如果您看過(guò)上一篇的話(huà),一定會(huì)注意到 Application->HookMainWindow方法。這又是怎么一回事呢? 如果您打算使用Application->OnMessage來(lái)捕獲所有發(fā)送至您的應(yīng)用程序的消息的話(huà), 您大概要失望了。原因已經(jīng)講過(guò),它無(wú)法捕獲使用SendMessage直接發(fā)送給窗口的消 息,因?yàn)檫@不通過(guò)消息隊(duì)列。您也許會(huì)說(shuō)我可以直接重載TApplication的WndProc方 法。呵呵,不可以。因?yàn)門(mén)Application的WndProc方法被Borland申明為靜態(tài)的,從而無(wú) 法重載。顯而易見(jiàn),這么做的原因很可能是Borland擔(dān)心其所帶來(lái)的副作用。那該如何 是好呢? 查看TApplication的WndProc的pascal源碼可以看到: procedure TApplication.WndProc(var Message: TMessage); ... // 節(jié)約篇幅,此處與主題無(wú)關(guān)代碼略去 begin try Message.Result := 0; for I := 0 to FWindowHooks.Count - 1 do if TWindowHook(FWindowHooks[I]^)(Message) then Exit; ... // 節(jié)約篇幅,此處與主題無(wú)關(guān)代碼略去 WndProc方法一開(kāi)始先調(diào)用HookMainWindow掛鉤的自定義消息處理方法,然后再調(diào)用缺 省過(guò)程處理消息。這樣使用HookMainWindow就可以在WndProc中間接加入自己的消息處 理方法。使用這個(gè)方法響應(yīng)SendMessage發(fā)送來(lái)的消息很管用。最后提醒一下,使用 HookMainWindow掛鉤之后一定要對(duì)應(yīng)的調(diào)用UnhookMainWindow卸載鉤子程序。給個(gè)例子 : 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); } 這樣,將本文中的兩種方法相結(jié)合,您就可以自如的處理到達(dá)您的應(yīng)用程序的各種消息 了。 |