(BCB) 覆寫 WndProc 進行視窗訊息處理
MSN Space、Google Doc 、Google Blog
2007.06.26
測試環境
1. Windows XP Pro + SP2
2. Borland C++ Builder 6.0
BCB 的訊息機制
所有 C++ Builder 的類別都具有內建的訊息處理機制,簡單的說,當類別收到訊息時會依據收到的訊息類型再分配給特定的成員函數處理,如果訊息沒有對應的處理函數,則會使用預設的處理函數。下面是 BCB 的訊息分派流程:
Windows 訊息 -> MainWndProc -> WndProc -> Dispatch -> 訊息處理函數
Windows 訊息含有許多有用的資料紀錄,其中最重要的是用來識別訊息的整數。這些識別碼定義在 messages.hpp。當應用程式建立視窗(CreateWindow)時,會將 Windows Procedure 註冊到 Windows 核心(RegisterClass),Windows Procedure 是視窗訊息處理副程式,傳統作法是用一個很大的 switch 來處理每一個訊息。 BCB 在許多方面簡化了訊息分派方式:
1. 每個元件都繼承完整的訊息分派系統
2. 訊息分派系統都具有預設的處理函數。所以,我們只要定義必要的訊息即可。
3. 可以編修訊息處理函數的一小部份,其餘的交給繼承下來的訊息處理函數
BCB 註冊一個 MainWndProc 函數作為應用程式中各類別的 Window Procedure。MainWndProc 包含一個例外處理區塊,把 Windows 訊息資料結構傳給 WndProc 虛擬方法,並呼叫 TApplication 的 HandleException 方法來處理例外狀況。MainWndProc 不是一個虛擬函數,他對任何訊息沒有特定的處理方法。在 WndProc 中才會加以自訂,因為每個元件都可以覆蓋 WndProc 來達到自訂需求。
WndProc 可以檢查特定訊息並加以處理,使得能夠抓住任何訊息。WndProc 最後會呼叫 TObject 繼承來的非虛擬函數 Dispatch 將訊息非派到綴中處理函數。Dispatch 使用訊息資料結構中的 Msg 成員來決定訊息分派,如果元件定義這個訊息的處理函數,Dispatch 會將控制權移交給這個函數,否則會透過 DefaultHandler 處理。
攔截特定訊息
BCB 要修改元件處理某一訊息的方式,只要覆蓋該訊息的訊息處理函數即可,但如果元件預設不提供該訊息處理,此時可以宣告一個自訂訊息處理函數來處理。首先在元件中宣告新的訊息處理函數,如:
void __fastcall OnMyMove(TMessage& Message);
接著利用下面的巨集將上述處理函數與實際的 Windows 訊息進行關聯
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(視窗訊息, 訊息資料結構, 訊息處理函數名稱)
END_MESSAGE_MAP(TForm)
上述巨集中可以包含多個 MESSAGE_HANDLER。下面以一個簡單的範例驗證上述的說明。
Unit1.h (視窗類別宣告) |
//--------------------------------------------------------------------------- #ifndef Unit1H #define Unit1H //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> //--------------------------------------------------------------------------- // 自訂 Windows 訊息 const DWORD MY_MSG = WM_USER + 1; class TForm1 : public TForm { __published: // IDE-managed Components TStaticText *StaticText1; TButton *Button1; void __fastcall Button1Click(TObject *Sender); private: // User declarations // 宣告訊息的處理函數 void __fastcall OnMyMove(TMessage& Message); void __fastcall OnMyMsg(TMessage& Message); // Windows 訊息攔截定義 BEGIN_MESSAGE_MAP MESSAGE_HANDLER(WM_MOVE, TMessage, OnMyMove) MESSAGE_HANDLER(MY_MSG, TMessage, OnMyMsg) END_MESSAGE_MAP(TForm) public: // User declarations __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- #endif |
Unit1.cpp (視窗類別實作) |
#include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- /** * WM_MOVE 訊息處理 * * @see TMessage * @see WM_MOVE * @param Message 訊息資訊 */ void __fastcall TForm1::OnMyMove(TMessage& Message){ AnsiString s; s += (int)(short) LOWORD(Message.LParam); // X 座標 s += ", "; s += (int)(short) HIWORD(Message.LParam); // Y 座標 StaticText1->Caption = s; TForm::Dispatch(&Message); } /** * 自訂訊息處理 * * @see TMessage * @see WM_MOVE * @param Message 訊息資訊 */ void __fastcall TForm1::OnMyMsg(TMessage& Message){ StaticText1->Caption = "WM_MYMSG"; TForm::Dispatch(&Message); } /** * 送出自訂訊息 * */ void __fastcall TForm1::Button1Click(TObject *Sender) { SendMessage(this->Handle, MY_MSG, 0, 0); } |
執行結果
1. WM_MOVE
2. WM_MYMSG
阻斷特定訊息
在某些情況可能想要讓元件忽略特定訊息,也就是讓訊息不分派到訊息處理函數。可以透過改寫(Override) 虛擬函數 WndProc 來達成。下面是阻斷滑鼠的 WM_LBUTTONUP 和 WM_LBUTTONDOWN 訊息範例,當過濾進行中,FormClick 訊息處理函數就不會進行。
Unit1.h (視窗類別宣告) |
//--------------------------------------------------------------------------- #ifndef Unit1H #define Unit1H //--------------------------------------------------------------------------- #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> //--------------------------------------------------------------------------- class TForm1 : public TForm { __published: // IDE-managed Components void __fastcall FormClick(TObject *Sender); private: // User declarations protected: // Override WndProc virtual void __fastcall WndProc(Messages::TMessage &Message); public: // User declarations __fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------- extern PACKAGE TForm1 *Form1; //--------------------------------------------------------------------------- #endif |
Unit1.cpp (視窗類別實作) |
#include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::WndProc(Messages::TMessage &Message){ // 阻斷訊息 if(Message.Msg == WM_LBUTTONUP || Message.Msg == WM_LBUTTONDOWN){ return; } // 繼續原始訊息的分派 TForm::WndProc(Message); } void __fastcall TForm1::FormClick(TObject *Sender) { Caption = "Hello"; } |
參考資料
[1] C++ Builder 研究, 攔截Windows消息
[2] C++ Builder 研究, 在CB中響應消息及自定義消息
[3] C++ Builder 研究,如何捕獲VCL沒有處理的Windows消息
[4] C++ Builder 研究, CB中消息處理過程及應用
[5] Borland C++ Builder 5 進階程式設計手冊