深入C++ Builder之編寫自己的元件-深入分析VCL繼承、訊息機制(3) (轉)

amyz發表於2007-08-14
深入C++ Builder之編寫自己的元件-深入分析VCL繼承、訊息機制(3) (轉)[@more@]

這篇文章提及內容可能大家已經在很多地方看到過了,作者也是如此,只不過還看了很多VCL,加上自己實際編寫元件的,拼湊了這麼一篇文章。所以所有言論都是個人觀點、經驗的描述,僅供參考。:namespace prefix = o ns = "urn:schemas--com::office" />

你可轉載,複製,但必須加入作者署名Aweay,如果用於商業目的,必須經過作者同意。

題外話

很多朋友看了我的前兩篇文章後,紛紛來信說能不能介紹一些元件的基礎知識,因為他們根本找不到相關資料,並詢問我是如何知道這些知識的。誠然,網上確實沒有這方面的介紹資料,更何況大家是學BCB的,對於的原始碼學習起來更是困難,對於作者來說也不比大家知道多少,我認為最好的方式就是看VCL原始碼和去Borland的新聞組提問,至少我是這樣解決問題的,希望你也可以。

這裡是Borland新聞組地址,如果你英文夠好,他們基本是有問必答的:

forums.borland.com

對於那些想學習基礎元件知識的朋友,我會在這系列文章的最後部分專門安排2篇文章作為禮物送給你們,一篇是我會實際分析一個專業級元件,來個原始碼解剖,把所有細節展示給大家,第二篇是我會實際編寫一個簡單使用的,並介紹全過程,希望大家喜歡。

更多訊息處理

已經寫了2篇文章了,怎麼還是訊息處理?是的,編寫元件就是處理訊息和表露事件,對於一般的訊息處理,前面2篇文章介紹的內容已經足夠用了,但是很多時候這還是不夠的,比如如果在設計時期你更改了元件的Font屬性,而你又想根據字型重新繪製。很明顯傳統的訊息處理其不到絲毫作用,這樣的訊息通常是WM_XXXX的形式。如果你研究過VCL原始碼,你會發現很多CN_XXXX和CM_XXXX這樣的訊息,如果你要完成我上面提到的訊息處理,這些訊息可以幫助完成任務。

其實,VCL存在一些非訊息以供其內部使用,為什麼要這樣做呢?這要從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 Iocused 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)(T* Sender,int Index);

typedef void __fastcall (__closure *TShapeedEvent)(TObject* Sender,int Index);

是否還記得上面的程式碼?

大概來說那是函式指標的申明,對於初學者來說,上面的申明真的很晦澀,我來解釋一下:THoverShapeEvent是一個函式指標,該函式的返回值是void , 型別是__fastcall,有2個行參,分別是TObject*和int,關鍵在於紅色的__closure關鍵字,什麼意思?

在BCB的幫助我我找到了如下說明:

The key __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)

  {

  …

  }

}

上面的程式碼是不是很簡捷,但這跟編寫元件有什麼關係呢?

我們還是以上篇文章的例子為例:

if(!Down)

  {

  int sh=ShapeAtPos(X,Y);

  if(sh!=-1)

  DoHoverShape(sh);

  return;

  }

這段程式碼很好的說明了問題,可以看出我們需要表露的事件同樣是一個回撥函式,我是這樣呼叫的:

void __fastcall TVecCanvas::DoHoverShape(int index)

{

  //TODO: Add your code here

  if(FOnHoverShape) //如果屬性被賦值,及有相應處理函式

  {

  FOnHoverShape(this,index);

  }

}

我是這樣賦值的:

__property THoverShapeEvent OnHoverShape  = { read=FOnHoverShape, write=FOnHoverShape };

可見事件其實是一個屬性,BCB會自動把On開頭屬性當作事件對待,所以這個屬性就出現在Event列表裡了。

我們來總結一下:我們自定義的事件實際上就是回撥函式,在相應需要觸發的地方呼叫由元件指定了的回撥函式,一句話道破了自定義事件的真諦,但是卻花了一大篇文章來解釋它的原理,即使如此,我仍然相信由很多朋友沒有真正瞭解這其中的奧秘,如果是這樣,你需要看看什麼是CallBack函式,屬性如何定義等等這樣的文章。

最後,這這篇文章的結尾,我留下自己的E:

如果大家有什麼問題可以來信與我討論:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-956502/,如需轉載,請註明出處,否則將追究法律責任。

相關文章