Delphi--TControl與Windows消息的封裝
TControl是從TPersistent類的子類TComponent類繼承而來的。TPersistent抽象基類具有使用流stream來存取類的屬性的能力。
TComponent類則是所有VCL組件的父類。
這就是所有的VCL組件包括您的自定義組件可以使用dfm文件存取屬性的原因(當然要是TPersistent的子類,我想您很少需要直接從TObject類來派生您的自定義組件吧)。
TControl類的重要性並不亞於它的父類們。在BCB的繼承關系中,TControl類的是所有VCL可視化組件的父類。實際上就是控件的意思吧。所謂可視化是指您可以在運行期間看到和操縱的控件。這類控件所具有的一些基本屬性和方法都在TControl類中進行定義。
TControl的實現在\Borland\CBuilder5\Source\Vcl\control.pas中可以找到。
TControl繼承但並沒有重寫TObject的Dispatch方法。反而提供了一個新的方法WndProc。一起來看看Borland的工程師們是怎麼寫的吧。
[delphi]
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; 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。毋庸諱言,的確差遠了。而且這個幫助還經常有問題。但有總比沒有好啊。
Delphi消息的發送有三種方法:
1.Tcontrol類的Perform對象方法。可以向任何一個窗體或控件發送消息,只需要知道窗體或控件的實例。其聲明如下:
function Tcontrol.Perform(Msg:Cardinal;Wparam,Lparam:Longint):Longint
2.Windows的API函數SendMessage()和Postmessage()。其聲明如下:
function SendMessage(hWnd: HWND; Msg: UINT;wParam:WPARAM; lParam: LPARAM):LRESULT;stdcall;
function SendMessage(hWnd: HWND; Msg: UINT;wParam: WPARAM; lParam:LPARAM):LRESULT;stdcall
PostMessage函數將消息添加到應用程序的消息隊列中去。應用程序的消息循環會從消息隊列中提取登記的該消息,再發送到相應的窗口中。
TControl與Windows消息的封裝
TObject提供了最基本的消息分發和處理的機制,而VCL真正對Windows系統消息的封裝則是在TControl中完成的。
TControl將消息轉換成VCL的事件,以將系統消息融入VCL框架中。
消息分發機制在4.2節已經介紹過,那麼系統消息是如何變成事件的呢?
現在,通過觀察TControl的一個代碼片段來解答這個問題。在此只以鼠標消息變成鼠標事件的過程來解釋,其余的消息封裝基本類似。
先摘取TControl聲明中的一個片段:
TControl = class(TComponent)
Private
……
FOnMouseDown: TMouseEvent;
……
procedure DoMouseDown(var Message: TWMMouse; Button: TMouseButton;
Shift: TShiftState);
……
procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer); dynamic;
……
procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
procedure WMRButtonDown(var Message: TWMRButtonDown); message WM_RBUTTONDOWN;
procedure WMMButtonDown(var Message: TWMMButtonDown); message WM_MBUTTONDOWN;
……
protected
……
property OnMouseDown: TMouseEvent read FOnMouseDown write
FOnMouseDown;
……
end;
這段代碼是TControl組件類的聲明。如果你從沒有接觸過類似的VCL組件代碼的代碼,不明白那些property、read、write的意思,那麼可以先跳轉到5.1節閱讀一下相關的基礎知識,然後再回過頭來到此處繼續。
TControl聲明了一個OnMouseDown屬性,該屬性讀寫一個稱為FOnMouseDown的事件指針。因此FOnMouseDown會指向OnMouseDown事件的用戶代碼。
TControl聲明了WMLButtonDown、WMRButtonDown、WMMButtonDown 3個消息 處理函數,它們分別處理WM_LBUTTONDOWN、WM_RBUTTONDOWN、WM _MBUTTONDOWN 3個Windows消息,對應於鼠標的左鍵按下、右鍵按下、中鍵按下3個硬件事件。
另外,還有一個DoMouseDown()方法和一個MouseDown()的dynamic方法,它們與消息處理函數之間2是什麼樣的關系呢?
現在,就來具體看一下這些函數的實現。
這裡是3個消息的處理函數:
procedure TControl.WMLButtonDown(var Message: TWMLButtonDown);
begin
SendCancelMode(Self);
inherited;
if csCaptureMouse in ControlStyle then
MouseCapture := True;
if csClickEvents in ControlStyle then
Include(FControlState, csClicked);
DoMouseDown(Message, mbLeft, []);
end;
procedure TControl.WMRButtonDown(var Message: TWMRButtonDown);
begin
inherited;
DoMouseDown(Message, mbRight, []);
end;
procedure TControl.WMMButtonDown(var Message: TWMMButtonDown);
begin
inherited;
DoMouseDown(Message, mbMiddle, []);
end;
當TObject.Dispatch()將WM_LBUTTONDOWN消息、WM_RBUTTONDOWN消息或WM_MBUTTONDOWN消息分發給TControl的派生類的實例後,WMLButtonDown()、WMRButtonDown()或WMMButtonDown()被執行,然後它們都有類似這樣
DoMouseDown(Message, mbRight, []); 的代碼來調用DoMouseDown():
procedure TControl.DoMouseDown(var Message: TWMMouse; Button: TMouseButton; Shift: TShiftState);
begin
if not (csNoStdEvents in ControlStyle) then
with Message do
if (Width > 32768) or (Height > 32768) then
with CalcCursorPos do
MouseDown(Button, KeysToShiftState(Keys) + Shift, X, Y)
else
MouseDown(Button, KeysToShiftState(Keys) + Shift, Message.XPos, Message.Ypos );
end;
在DoMouseDown()中進行一些必要的處理工作後(特殊情況下重新獲取鼠標位置),就會調用MouseDown():
procedure TControl.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Assigned(FOnMouseDown) then
FOnMouseDown(Self, Button, Shift, X, Y);
end;
在MouseDown()中,才會通過FOnMouseDown事件指針真正去執行用戶定義的OnMouseDown事件的代碼。
由此,完成了Windows系統消息到VCL事件的轉換過程。
因此,從TControl派生的類都可以擁有OnMouseDown事件,只不過該事件屬性在TControl中被定義成protected,只有其派生類可見,並且在派生類中可以自由選擇是否公布這個屬性。要公布該屬性只需要簡單地將其聲明為published即可。如:
TMyControl = class(TControl)
published
property OnMouseDown;
end;
這些函數過程的調用關系如圖4.3所示。
DoMouseDown()
MouseDown()
程序員的OnMouseDown事件代碼
WMMouseDown()
Dispatch(WM_LBUTTONDOWN); Dispatch(WM_LBUTTONDOWN);
圖4.3 WM_LBUTTONDOWN消息到OnMouseDown事件的轉換過程
在此,只是以OnMouseDown事件為例。其實,VCL對Windows各個消息的封裝大同小異,以此一例足以說明事件模型的原理。
另外,值得注意的是,在上例中的MouseDown()函數是一個dynamic方法,因此可以通過在TControl派生類中覆蓋MouseDown()來處理自己所編寫組件的鼠標按下事件,然後通過
inherited;
語句調用TControl的MouseDown()來執行使用組件的程序員所編寫的OnMouseDown的代碼。具體內容會在第5章中展開。
至此,讀者應該已經了解了VCL事件與Windows消息的對應關系,應該知道平時為組件寫的事件代碼是如何被執行的