TObject → TPersistent → TComponent → TControl → TWinControl → TCustomControl → 具體控制項
TControl:所有可視控制項基類,**無視窗控制代碼 (HWND)**,無法接收焦點TWinControl:擁有 HWND 的視窗型控制項基類,可接收焦點、包含子控制項
procedure TControl.WndProc(var Message: TMessage);
begin
// 1. 設計模式訊息轉給 Form 設計器
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
// 2. 鍵盤訊息轉給父視窗 (若 WantChildKey)
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
// 3. 滑鼠訊息核心處理
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:
if FDragMode = dmAutomatic then begin BeginAutoDrag; Exit; end;
Include(FControlState, csLButtonDown);
WM_LBUTTONUP: Exclude(FControlState, csLButtonDown);
end;
end
// 4. 可見性變更通知 (CM_ 內部訊息)
else if Message.Msg = CM_VISIBLECHANGED then
with Message do SendDockNotification(Msg, WParam, LParam);
// 5. 最後分派給 Dispatch
Dispatch(Message);
end;
答案在 TWinControl:
procedure TWinControl.WndProc(var Message: TMessage);
begin
case Message.Msg of
// 焦點處理...
WM_SETFOCUS, WM_KILLFOCUS: ...
// 滑鼠訊息轉給 IsControlMouseMsg
WM_MOUSEFIRST..WM_MOUSELAST:
if IsControlMouseMsg(TWMMouse(Message)) then begin
if Message.Result = 0 then
DefWindowProc(Handle, Message.Msg, Message.wParam, Message.lParam);
Exit;
end;
// 其他...
end;
inherited WndProc(Message); // 呼叫 TControl.WndProc
end;
function TWinControl.IsControlMouseMsg(var Message: TWMMouse): Boolean;
var
Control: TControl;
P: TPoint;
begin
// 1. 若捕獲滑鼠,優先送給捕獲控制項
if GetCapture = Handle then begin
Control := nil;
if (CaptureControl <> nil) and (CaptureControl.Parent = Self) then
Control := CaptureControl;
end
// 2. 否則依座標找子控制項
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;
// 關鍵:呼叫 Control.Perform 將訊息送入控制項的 WndProc
Message.Result := Control.Perform(Message.Msg, Message.Keys, Longint(PointToSmallPoint(P)));
Result := True;
end;
end;
[系統] → TWinControl.WndProc (擁有 HWND)
→ IsControlMouseMsg (座標轉換 + 找子控制項)
→ TControl.Perform (封裝 TMessage)
→ TControl.WndProc (實際處理滑鼠邏輯)
→ Dispatch (其他訊息)
TControl 衍生類別的 Owner 必須為 TWinControl 或其衍生類Owner 負責:記憶體管理 (銷毀時釋放 Owned 控制項) + 訊息分派 (將訊息轉給子控制項)TImage 未重載 WndProc,僅靠 TControl 繼承的滑鼠處理能力
void __fastcall TMyControl::WndProc(TMessage &Message) {
switch (Message.Msg) {
case WM_LBUTTONDOWN:
// 自訂左鍵按下邏輯
break;
case WM_MOUSEMOVE:
// 自訂移動邏輯
break;
}
TControl::WndProc(Message); // 呼叫繼承實作 (重要!)
}
| 特性 | 重載 Dispatch | 重載 WndProc (TControl) |
|---|---|---|
| 攔截時機 | 較晚 (WndProc 最後呼叫 Dispatch) | 較早 (WndProc 開頭處理) |
| 可處理訊息 | 所有訊息 (經 WndProc 過濾後) | 主要滑鼠、CM_ 內部訊息、設計模式 |
| 適用層級 | 任何 VCL 類別 | TControl 及衍生類別 |
| 鍵盤訊息 | 可處理 | TControl 不處理 (由 TWinControl 處理) |
方法三:TApplication 層級攔截 (OnMessage, HookMainWindow),最早處理全域訊息。