線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:7287
推到 Plurk!
推到 Facebook!

[推薦]深入VCL 理解BCB的消息機制(全)

 
axsoft
版主


發表:681
回覆:1056
積分:969
註冊:2002-03-13

發送簡訊給我
#1 引用回覆 回覆 發表時間:2002-07-31 11:04:40 IP:61.218.xxx.xxx 未訂閱

深入VCL 理解BCB的消息機制

2002-07-08‧ ‧ ‧‧C builder資源中心 資料來源: http://www.yesky.com/20020702/1618597.shtml http://www.yesky.com/20020702/1618598.shtml http://www.yesky.com/20020702/1618598_1.shtml 時至今日,學習Windows編程的兄弟們都知道消息機制的重要性。所以理解消息機制也成了不可或缺的功課。 大家都知道,Borland的C Builder以及Delphi的核心是VCL。作為Win32平台上的開發工具,封裝Windows的消息機制當然也是必不可少的。 那麼,在C Builder中處理消息的方法有哪些呢?它們之間的區別又在哪里?如果您很清楚這些,呵呵,對不起啦,請關掉這個窗口。 如果不清楚那就和我一起深入VCL的源碼看個究竟吧。『注:BCB只有Professional和Enterprise版本才帶有VCL源碼。當然,大伙的版本都有源碼的。我沒猜錯吧 :-)』 方法1。使用消息映射(Message Map)重載TObject的Dispatch虛成員函數 這個方法大家用的很多。形式如下 BEGIN_MESSAGE_MAP VCL_MESSAGE_HANDLER( … …) END_MESSAGE_MAP( …) 但這幾句話實在太突兀,C 標准中沒有這樣的定義。不用講,這顯然又是宏定義。它們到底怎麼來的呢?CKER第一次見到它們的時候,百思不得其解。嘿嘿,不深入VCL,怎麼可能理解?
在\Borland\CBuilder5\Include\Vcl找到sysmac.h,其中有如下的預編譯宏定義:    #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.
file://
#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::Dispatch(Message); \
break; \
} \
}      這樣對如下的例子:    BEGIN_MESSAGE_MAP    VCL_MESSAGE_HANDLER(WM_PAINT,TMessage,OnPaint)    END_MESSAGE_MAP(TForm1)      在預編譯時,就被展開成如下的代碼    virtual void __fastcall Dispatch(void *Message)
{
switch (((PMessage)Message)->Msg)
{
case WM_PAINT:
OnPaint(*((TMessage *)Message)); //消息響應句柄,也就是響應消息的成員函數,在Form1中定義
break;    default:
Form1::Dispatch(Message);
break;
}
}
這樣就很順眼了,對吧。對這種方法有兩點要解釋一下: 1。virtual void __fastcall Dispatch(void *Message) 這個虛方法的定義最早可以在TObject的定義中找到。打開BCB的幫助,查找TForm的Method(方法),你會發現這里很清楚的寫著Dispatch方法繼承自TObject。如果您關心VCL的繼承機制的話,您會發現TObject是所有VCL對象的基類。TObject的抽象凝聚了Borland的工程師們的心血。如果有興趣。您應該好好查看一下TObject的定義。 很顯然,所有Tobject的子類都可以重載基類的Dispatch方法,來實現自己的消息調用。如果Dispatch方法找不到此消息的定義,會將此消息交由TObject::DefaultHandler方法來處理。抽象基類TObject的DefaultHandler方法實際上是空的。同樣要由繼承子類重載實現它們自己的消息處理過程。 2。很多時候,我見到的第二行是這樣寫的: MESSAGE_HANDLER(WM_PAINT,TMessage,OnPaint) 在這里,您可以很清楚的看到幾行注解,意思是ATL中同樣包含了一個MESSAGE_HANDLER的宏定義,這與VCL發生了衝突。為了解決這個問題,Borland改用VCL_MESSAGE_HANDLER這樣的寫法。 當您沒有使用ATL的時候,MESSAGE_HANDLER將轉換成VCL_MESSAGE_HANDLER。但如果您使用了ATL的話,就會有問題。所以我建議您始終使用VCL_MESSAGE_HANDLER的寫法,以免出現問題。 方法2。重載TControl的WndProc方法 還是先談談VCL的繼承策略。VCL中的繼承鏈的頂部是TObject基類。一切的VCL組件和對象都繼承自TObject。 打開BCB幫助查看TControl的繼承關系: TObject->TPersistent->TComponent->TControl 呵呵,原來TControl是從TPersistent類的子類TComponent類繼承而來的。TPersistent抽象基類具有使用流stream來存取類的屬性的能力。 TComponent類則是所有VCL組件的父類。 這就是所有的VCL組件包括您的自定義組件可以使用dfm文件存取屬性的原因『當然要是TPersistent的子類,我想您很少需要直接從TObject類來派生您的自定義組件吧』。 TControl類的重要性並不亞于它的父類們。在BCB的繼承關系中,TControl類的是所有VCL可視化組件的父類。實際上就是控件的意思吧。所謂可視化是指您可以在運行期間看到和操縱的控件。這類控件所具有的一些基本屬性和方法都在TControl類中進行定義。 TControl的實現在\Borland\CBuilder5\Source\Vcl\control.pas中可以找到。『可能會有朋友問你怎麼知道在那里?使用BCB提供的Search -> Find in files很容易找到。或者使用第三方插件的grep功能。』 好了,進入VCL的源碼吧。說到這里免不了要抱怨一下Borland。哎,為什麼要用pascal實現這一切.....:-( TControl繼承但並沒有重寫TObject的Dispatch()方法。反而提供了一個新的方法就是xycleo提到的WndProc()。一起來看看Borland的工程師們是怎麼寫的吧。
procedure TControl.WndProc(var Message: TMessage);
var
Form: TCustomForm;
begin
//由擁有control的窗體來處理設計期間的消息
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
//如果需要,鍵盤消息交由擁有control的窗體來處理
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
//處理鼠標消息
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
// 下面一行有點特別。如果您仔細的話會看到這個消息是CM_VISIBLECHANGED. // 而不是我們熟悉的WM_開頭的標准Windows消息. // 盡管Borland沒有在它的幫助中提到有這一類的CM消息存在。但很顯然這是BCB的 // 自定義消息。呵呵,如果您對此有興趣可以在VCL源碼中查找相關的內容。一定會有不小的收獲。
else if Message.Msg = CM_VISIBLECHANGED then
with Message do
SendDockNotification(Msg, WParam, LParam);
// 最后調用dispatch方法。
Dispatch(Message);
end;
看完這段代碼,你會發現TControl類實際上只處理了鼠標消息,沒有處理的消息最后都轉入Dispatch()來處理。 但這里需要強調指出的是TControl自己並沒有獲得焦點Focus的能力。TControl的子類TWinControl才具有這樣的能力。我憑什麼這樣講?呵呵,還是打開BCB的幫助。很多朋友抱怨BCB的幫助實在不如VC的MSDN。毋庸諱言,的確差遠了。而且這個幫助還經常有問題。但有總比沒有好啊。 言歸正傳,在幫助的The TWinControl Branch 分支下,您可以看到關于TWinControl類的簡介。指出TWinControl類是所有窗體類控件的基類。所謂窗體類控件指的是這樣一類控件: 1. 可以在程序運行時取得焦點的控件。 2. 其他的控件可以顯示數據,但只有窗體類控件才能和用戶發生鍵盤交互。 3. 窗體類控件能夠包含其他控件(容器)。 4. 包含其他控件的控件又稱做父控件。只有窗體類控件才能夠作為其他控件的父控件。 5. 窗體類控件擁有句柄。 除了能夠接受焦點之外,TWinControl的一切都跟TControl沒什麼分別。這一點意味著TwinControl可以對許多的標准事件作出響應,Windows也必須為它分配一個句柄。並且與這個主題相關的最重要的是,這里提到是由BCB負責來對控件進行重畫以及消息處理。這就是說,TwinControl封裝了這一切。 似乎扯的太遠了。但我要提出來的問題是TControl類的WndProc方法中處理了鼠標消息。但這個消息只有它的子類TwinControl才能夠得到啊!? 這怎麼可以呢... Borland是如何實現這一切的呢?這個問題實在很奧妙。為了看個究竟,再次深入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:
//下面這一句話指出,鼠標消息實際上轉入IsControlMouseMsg方法來處理了。
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;
鼠標消息是由IsControlMouseMsg方法來處理的。只有再跟到IsControlMouseMsg去看看啦。源碼如下:
function TWinControl.IsControlMouseMsg(var Message: TWMMouse): Boolean;
var
//TControl出現啦
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;
原來如此,TWinControl最后還是將鼠標消息交給TControl的WndProc來處理了。這里出現的Perform方法在BCB的幫助里可以查到是TControl類中開始出現的方法。它的作用就是將指定的消息傳遞給TControl的WndProc過程。 結論就是TControl類的WndProc方法的消息是由TwinControl類在其重載的WndProc方法中調用IsControlMouseMsg方法后使用Peform方法傳遞得到的。 由于這個原因,BCB和Delphi中的TControl類及其所有的派生類都有一個先天的而且是必須的限制。那就是所有的TControl類及其派生類的Owner必須是TwinControl類或者TWinControl的派生類。Owner屬性最早可以在TComponent中找到,一個組件或者控件是由它的Owner擁有並負責釋放其內存的。這就是說,當Owner從內存中釋放的時候,它所擁有的所有控件占用的內存也都被釋放了。Owner最好的例子就是Form。Owner同時也負責消息的分派,當Owner接收到消息的時候,它負責將應該傳遞給其所擁有的控件的消息傳遞給它們。這樣這些控件就能夠取得處理消息的能力。TImage就是個例子:你可以發現Borland並沒有讓TImage重載TControl的WndProc方法,所以TImage也只有處理鼠標消息的能力,而這種能力正是來自TControl的。 唧唧崴崴的說了一大堆。終于可以說處理消息的第二種方法就是重載TControl的WndProc方法了。例程如下:
void __fastcall TForm1::WndProc(TMessage &Message)
{
switch (Message.Msg)
{
case WM_CLOSE:
OnCLOSE(Message); // 處理WM_CLOSE消息的方法
break;
}
TForm::WndProc(Message);
}
乍看起來,這和上次講的重載Dispatch方法好象差不多。但實際上還是有差別的。差別就在先后次序上,從前面TControl的WndProc可以看到,消息是先交給WndProc來處理,最后才調用Dispatch方法的啦。 這樣,重載WndProc方法可以比重載Dispatch方法更早一點點得到消息並處理消息。 時間就是金錢---[ 發問前請先找找舊文章]
系統時間:2024-10-02 2:25:38
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!