承接前兩篇(繼承體系、繪圖與虛擬函數),本文深入 VCL 內部訊息機制核心:**CN_* / CM_* 非標準訊息**與**自訂訊息/事件**的實現。這是編寫專業級元件、實現設計時期行為、整合 IDE 設計器的關鍵。
Windows 標準訊息(WM_COMMAND、WM_NOTIFY、WM_DRAWITEM 等)**發送給父視窗**,而非產生訊息的子控件。VCL 為了讓控件自身處理這些訊息,引入了轉發機制:
WM_COMMAND 等。TWinControl.WndProc 中,將訊息 ID 加上 CN_BASE($B000)形成 CN_* 訊息(如 CN_COMMAND、CN_DRAWITEM)。Perform(CN_*, ...) 分發給**實際子控件**的 WndProc。WndProc 或使用訊息映射處理 CN_*。同理,VCL 內部狀態變更(Font、Color、Showing、Enabled 等)定義 CM_* 訊息(CM_BASE = $B000),由控件自身或父控件觸發,實現屬性變更的級聯更新。
| 標準訊息 | VCL 轉發訊息 | 典型用途 |
|---|---|---|
WM_COMMAND | CN_COMMAND | 按鈕點擊、選單命令、編輯變更通知 |
WM_NOTIFY | CN_NOTIFY | 通用控件通知(ListView、TreeView、ToolTip 等) |
WM_DRAWITEM | CN_DRAWITEM | OwnerDraw 控件繪製(TBitBtn 圖文繪製即處理此訊息) |
WM_MEASUREITEM | CN_MEASUREITEM | OwnerDraw 項目尺寸測量 |
WM_DELETEITEM | CN_DELETEITEM | OwnerDraw 項目刪除清理 |
WM_HSCROLL/VSCROLL | CN_HSCROLL/VSCROLL | 捲軸通知轉發 |
| — | CM_FONTCHANGED | Font 屬性變更,觸發 Invalidate 重繪 |
| — | CM_COLORCHANGED | Color 變更 |
| — | CM_ENABLEDCHANGED | Enabled 狀態變更 |
| — | CM_VISIBLECHANGED | Visible 狀態變更 |
| — | CM_PARENTFONTCHANGED | 父控件 Font 變更傳播 |
| — | CM_SHOWINGCHANGED | Showing 狀態變更(可見性實際改變) |
| — | CM_TEXTCHANGED | Caption/Text 變更 |
| — | CM_DESIGNHITTEST | 設計時期點擊測試(支援非矩形選取) |
因無對應虛擬函數,必須用 **VCL_MESSAGE_HANDLER** 宏映射:
class TMyButton : public TButton {
protected:
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER(CN_DRAWITEM, TWMDrawItem, CNDrawItem)
VCL_MESSAGE_HANDLER(CM_FONTCHANGED, TMessage, CMFontChanged)
END_MESSAGE_MAP(TButton)
void __fastcall CNDrawItem(TWMDrawItem &Msg);
void __fastcall CMFontChanged(TMessage &Msg);
};
void __fastcall TBitBtn::CNDrawItem(TWMDrawItem &Msg) {
TDrawItemStruct *dis = (TDRAWITEMSTRUCT)Msg.DrawItemStruct;
FCanvas->Handle = dis->hDC;
TRect R = ClientRect;
// ... 計算圖文佈局、按下偏移、焦點矩形 ...
TButtonGlyph(FGlyph).Draw(FCanvas, R, Point(0,0), Caption, FLayout,
FMargin, FSpacing, State, False,
DrawTextBiDiModeFlags(0));
if (IsFocused && IsDefault) {
R = ClientRect; InflateRect(R, -4, -4);
FCanvas->Pen->Color = clWindowFrame;
FCanvas->Brush->Color = clBtnFace;
DrawFocusRect(FCanvas->Handle, R);
}
FCanvas->Handle = 0;
}
本質上就是在 hDC 上繪圖,與 WM_PAINT 邏輯相似,但針對 OwnerDraw 控件。
typedef void __fastcall (__closure *THoverShapeEvent)(TObject *Sender, int Index);
typedef void __fastcall (__closure *TShapeSelectedEvent)(TObject *Sender, int Index);
__published:
__property THoverShapeEvent OnHoverShape = {read=FOnHoverShape, write=FOnHoverShape};
__property TShapeSelectedEvent OnShapeSelected = {read=FOnShapeSelected, write=FOnShapeSelected};
if (FOnHoverShape) FOnHoverShape(this, Index);
if (FOnShapeSelected) FOnShapeSelected(this, Index);
在 CM_DESIGNHITTEST 或滑鼠移動處理中,呼叫上述觸發代碼,IDE 設計器即可即時響應(如顯示提示、改變游標)。
END_MESSAGE_MAP(ParentClass) 指定父類,自動繼承父類映射。掌握 CN/CM 訊息機制,是從「會用元件」進階到「寫出專業元件」的分水嶺。它讓你:
下一篇將實戰剖析一個專業級元件完整源碼,並手把手帶你從零編寫一個可用元件。