首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

深入 VCL 理解 BCB 訊息機制 (二):重載 TControl.WndProc 與 IsControlMouseMsg

     編輯:關於C++

VCL 繼承鏈與 WndProc 角色

TObject → TPersistent → TComponent → TControl → TWinControl → TCustomControl → 具體控制項
  • TControl:所有可視控制項基類,**無視窗控制代碼 (HWND)**,無法接收焦點
  • TWinControl:擁有 HWND 的視窗型控制項基類,可接收焦點、包含子控制項

TControl.WndProc 源碼分析 (control.pas)


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;

關鍵發現:TControl 無 HWND 卻處理滑鼠訊息?

答案在 TWinControl:

TWinControl.WndProc 片段


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;

IsControlMouseMsg 核心邏輯


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 (其他訊息)

重要限制:Owner 必須是 TWinControl

  • 所有 TControl 衍生類別的 Owner 必須為 TWinControl 或其衍生類
  • Owner 負責:記憶體管理 (銷毀時釋放 Owned 控制項) + 訊息分派 (將訊息轉給子控制項)
  • 例如 TImage 未重載 WndProc,僅靠 TControl 繼承的滑鼠處理能力

方法二:重載 TControl.WndProc 實作範例


void __fastcall TMyControl::WndProc(TMessage &Message) {
    switch (Message.Msg) {
        case WM_LBUTTONDOWN:
            // 自訂左鍵按下邏輯
            break;
        case WM_MOUSEMOVE:
            // 自訂移動邏輯
            break;
    }
    TControl::WndProc(Message);  // 呼叫繼承實作 (重要!)
}

比較:Dispatch vs WndProc 重載

特性重載 Dispatch重載 WndProc (TControl)
攔截時機較晚 (WndProc 最後呼叫 Dispatch)較早 (WndProc 開頭處理)
可處理訊息所有訊息 (經 WndProc 過濾後)主要滑鼠、CM_ 內部訊息、設計模式
適用層級任何 VCL 類別TControl 及衍生類別
鍵盤訊息可處理TControl 不處理 (由 TWinControl 處理)

下一篇預告

方法三:TApplication 層級攔截 (OnMessage, HookMainWindow),最早處理全域訊息。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved