題外話
很多朋友看了我的前兩篇文章後,紛紛來信說能不能介紹一些元件入門的基礎知識,因為他們根本找不到相關資料,並詢問我是如何知道這些知識的。誠然,網上確實沒有這方面的介紹資料,更何況大家是學BCB的,對於Delphi的源代碼學習起來更是困難,對於作者來說也不比大家知道多少,我認為最好的方式就是看VCL源代碼和去Borland的新聞組提問,至少我是這樣解決問題的,希望你也可以。
這裡是Borland新聞組地址,如果你英文夠好,他們基本是有問必答的:
forums.borland.com
對於那些想學習基礎元件知識的朋友,我會在這系列文章的最後部分專門安排2篇文章作為禮物送給你們,一篇是我會實際分析一個專業級元件,來個源代碼解剖,把所有細節展示給大家,第二篇是我會實際編寫一個簡單使用的組件,並介紹全過程,希望大家喜歡。
更多消息處理
已經寫了2篇文章了,怎麼還是消息處理?是的,編寫元件就是處理消息和表露事件,對於一般的消息處理,前面2篇文章介紹的內容已經足夠用了,但是很多時候這還是不夠的,比如如果在設計時期你更改了元件的Font屬性,而你又想根據字體重新繪制。很明顯傳統的Windows消息處理其不到絲毫作用,這樣的消息通常是WM_XXXX的形式。如果你研究過VCL源代碼,你會發現很多CN_XXXX和CM_XXXX這樣的消息,如果你要完成我上面提到的消息處理,這些消息可以幫助完成任務。
其實,VCL存在一些非API消息以供其內部使用,為什麼要這樣做呢?這要從WM_COMMAND & WM_NOTIFY消息說起,我們說WM_COMMAND消息並不是直接發給實際產生消息的窗體,而是發送到它的父窗體。但是父窗體幾乎不可能用通常方法處理這些根本不知道如何處理的消息,於是父窗體把這個消息加上CN_BASE在分發到實際的子窗體中,然後由實際的子窗體處理。
比如TBitBtn元件為了在按鈕表面繪制圖象,處理了CN_DRAWITEM消息,這個消息處理函數是這樣寫的:
FCanvas.Handle := DrawItemStruct.hDC;
R := ClientRect;
… //省略一部分
if IsDown then
OffsetRect(R, 1, 1);
TButtonGlyph(FGlyph).Draw(FCanvas, R, Point(0,0), Caption, FLayout, FMargin,
FSpacing, State, False, DrawTextBiDiModeFlags(0));
if IsFocused and IsDefault then
begin
R := ClientRect;
InflateRect(R, -4, -4);
FCanvas.Pen.Color := clWindowFrame;
FCanvas.Brush.Color := clBtnFace;
DrawFocusRect(FCanvas.Handle, R);
end;
FCanvas.Handle := 0;
可以看出這和通常處理Paint的方法差不多,其實都是在HDC上作圖。如果你學習過SDK的話,其實我們可以自己處理WM_NOTIFY消息來處理那些由控件產生的消息,只不過VCL替我們封裝了一下而已。
還有一些消息是VCL內部控件而產生的,這類消息通常是CM_XXXX的格式,比如CM_FONTCHANGED這個消息就是當字體改變的時候觸發,詳細的定義你可以在Controls.pas文件中找到,這裡就不再詳細介紹了
對於上面的CM_FONTCHANGED消息,通常是這樣處理的:
procedure TBitBtn.CMFontChanged(var Message: TMessage);
begin
inherited;
Invalidate;
end;
通過上面的討論,得出一個結論,所有CN/CM消息都可以自己處理,但是他們沒有對應的虛函數,所以我們只好用老方法,所以消息映射宏在這裡是最好得解決方案,比如像這樣:
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(CN_DRAWITEM, TWMDrawItem, CNDrawItem)
END_MESSAGE_MAP(TCustomControl)
Void __fastcall CNDrawItem(TWMDrawItem Msg);
定義自己的消息
在上篇文章的結束,我示范了一段元件代碼,如果你還記憶猶新的話:
typedef void __fastcall (__closure *THoverShapeEvent)(TObject* Sender,int Index);
typedef void __fastcall (__closure *TShapeSelectedEvent)(TObject* Sender,int Index);
是否還記得上面的代碼?
大概來說那是函數指針的申明,對於初學者來說,上面的申明真的很晦澀,我來解釋一下:THoverShapeEvent是一個函數指針,該函數的返回值是void , 調用類型是__fastcall,有2個行參,分別是TObject*和int,關鍵在於紅色的__closure關鍵字,什麼意思?
在BCB的幫助我我找到了如下說明:
The keyword __closure was added to support the VCL and is used when declaring event handler functions.
就是如此簡單,幾乎沒有提供任何信息,只知道__closure提供對事件處理函數的支持,下面我來詳細介紹一下:
不知道你有沒有寫過這樣的代碼:
我們設計了一個類,比如遍歷磁盤,有一個數據成員是回調函數指針,當我們遍歷磁盤的的函數找到了一個文件時調用這個回調函數,通常情況下,我們這個回調函數需要申明在類的外面,那麼還是指針需要這樣申明:
typedef void __fastcall (*BDCallBack)(String path,int type);
但是這顯然不符合OO設計原則,如果你想把一個類的成員函數指定為這個成員函數,那麼你將需要這樣申明:
typedef void __fastcall (base::* BDCallBack)(String path,int type);
同時你需要這樣賦值:
BDCallBack m=&bass::func;
語法越來越晦澀了,這還不是最重要的,如果有很多類的成員函數都需要指定為回調函數呢?你需要為每一個類申明一個類似的函數指針,我想你已經崩潰了。
__closure這個時候就有用武之地了,如果你這樣申明:
typedef void __fastcall (__closure *BDCallBack)(String path,int type);
那麼所有問題都解決了,它可以方便的透過對象直接訪問成員函數,在所有的類中你都可以這樣做:
class A
{
BDCallBack func;
Void DoSometing()
{
…
func(“Find it”,0);
}
};
class B
{
Funcb()
{
A a;
a.func=this.callback;
}
void __fastcall callback(String path,int type)
{
%