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

Visual C++ MFC 訊息映射機制深度解析:從按鈕到捲軸的事件驅動程式設計

     編輯:關於VC++

MFC 訊息映射核心架構

MFC 應用程式無傳統 main() 或訊息迴圈,所有事件處理封裝於 CWinApp 內部。開發者需透過訊息映射機制,將感興趣的 Windows 訊息對應至自訂處理函數。

訊息映射三要素

  1. 宣告巨集:類別定義中 DECLARE_MESSAGE_MAP()
  2. 對應表:BEGIN_MESSAGE_MAP(類別, 基底類別) ... END_MESSAGE_MAP()
  3. 處理函數:afx_msg void 函數名(參數),必須為 void 回傳、符合特定參數規範

CButton 按鈕控制項實戰

建立按鈕基本步驟

// 標頭檔
class CButtonWindow : public CFrameWnd {
    CButton* m_pButton;
public:
    CButtonWindow();
    afx_msg void OnButtonClick();
    DECLARE_MESSAGE_MAP()
};

// 實作檔
CButtonWindow::CButtonWindow() {
    Create(NULL, _T("Button Demo"), WS_OVERLAPPEDWINDOW, CRect(0,0,300,200));
    CRect r; GetClientRect(&r); r.DeflateRect(20,20);
    m_pButton = new CButton();
    m_pButton->Create(_T("Click Me"), WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, r, this, IDC_MYBUTTON);
}

BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
    ON_BN_CLICKED(IDC_MYBUTTON, OnButtonClick)
END_MESSAGE_MAP()

void CButtonWindow::OnButtonClick() {
    MessageBeep(-1);  // 系統預設音效
}

關鍵點解析

  • IDC_MYBUTTON:資源 ID (>100 避開系統保留),按鈕傳送 WM_COMMAND 時攜帶此 ID
  • ON_BN_CLICKED:等同 ON_COMMAND 但更語義化,自動解析通知碼 BN_CLICKED
  • 按鈕樣式:BS_PUSHBUTTON、BS_DEFPUSHBUTTON、BS_CHECKBOX、BS_RADIOBUTTON、BS_OWNERDRAW 等 11 種
  • 自動反白:按鈕按下時 MFC 自動處理視覺回饋

視窗尺寸變更處理 (WM_SIZE)

動態調整子控制項佈局

class CButtonWindow : public CFrameWnd {
    CButton* m_pButton;
public:
    afx_msg void OnButtonClick();
    afx_msg void OnSize(UINT nType, int cx, int cy);  // 三參數固定簽名
    DECLARE_MESSAGE_MAP()
};

BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
    ON_BN_CLICKED(IDC_MYBUTTON, OnButtonClick)
    ON_WM_SIZE()  // 無參數,自動對應 OnSize
END_MESSAGE_MAP()

void CButtonWindow::OnSize(UINT nType, int cx, int cy) {
    CFrameWnd::OnSize(nType, cx, cy);  // 呼叫基底類別
    if (m_pButton && m_pButton->GetSafeHwnd()) {
        CRect r; GetClientRect(&r); r.DeflateRect(20,20);
        m_pButton->MoveWindow(r);  // 一次完成移動+縮放
    }
}

ON_WM_* 巨集對應表

巨集對應函數參數觸發時機
ON_WM_SIZEOnSizeUINT, int, int視窗大小改變
ON_WM_MOVEOnMoveint, int視窗位置移動
ON_WM_PAINTOnPaint無需重繪客戶區
ON_WM_TIMEROnTimerUINT_PTR計時器觸發
ON_WM_HSCROLLOnHScrollUINT, UINT, CScrollBar*水平捲軸操作
ON_WM_VSCROLLOnVScrollUINT, UINT, CScrollBar*垂直捲軸操作

計時器應用 (WM_TIMER)

// 建構函數中啟動計時器
SetTimer(IDT_MYTIMER, 1000, NULL);  // 1000ms = 1秒,NULL 表示由視窗處理

// 訊息映射
ON_WM_TIMER()

// 處理函數
void CButtonWindow::OnTimer(UINT_PTR nIDEvent) {
    if (nIDEvent == IDT_MYTIMER) {
        MessageBeep(-1);  // 每秒嗶一聲
        // 實際應用:更新狀態、輪詢資料、動畫幀...
    }
    CFrameWnd::OnTimer(nIDEvent);
}

// 記得銷毀時殺掉計時器
KillTimer(IDT_MYTIMER);

CScrollBar 捲軸控制項完整實作

建立與初始化

class CScrollBarWindow : public CFrameWnd {
    CScrollBar* m_pScrollBar;
    static const int MIN_POS = 0, MAX_POS = 100;
public:
    CScrollBarWindow();
    afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
    DECLARE_MESSAGE_MAP()
};

CScrollBarWindow::CScrollBarWindow() {
    Create(NULL, _T("ScrollBar Demo"), WS_OVERLAPPEDWINDOW, CRect(0,0,400,300));
    CRect r; GetClientRect(&r);
    m_pScrollBar = new CScrollBar();
    m_pScrollBar->Create(WS_CHILD|WS_VISIBLE|SBS_HORZ, 
                         CRect(10,10,r.Width()-10,30), this, IDC_MYSCROLL);
    m_pScrollBar->SetScrollRange(MIN_POS, MAX_POS, TRUE);
}

BEGIN_MESSAGE_MAP(CScrollBarWindow, CFrameWnd)
    ON_WM_HSCROLL()
END_MESSAGE_MAP()

捲軸事件完整處理

void CScrollBarWindow::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) {
    int pos = m_pScrollBar->GetScrollPos();
    
    switch (nSBCode) {
        case SB_LINELEFT:   pos -= 1; break;      // 左箭頭
        case SB_LINERIGHT:  pos += 1; break;      // 右箭頭
        case SB_PAGELEFT:   pos -= 10; break;     // 滑桿左側點擊
        case SB_PAGERIGHT:  pos += 10; break;     // 滑桿右側點擊
        case SB_LEFT:       pos = MIN_POS; break; // Home/最左
        case SB_RIGHT:      pos = MAX_POS; break; // End/最右
        case SB_THUMBTRACK: pos = nPos; break;    // 拖曳滑桿 (即時)
        case SB_THUMBPOSITION: pos = nPos; break; // 拖曳釋放
        default: return;
    }
    
    // 邊界檢查
    pos = max(MIN_POS, min(MAX_POS, pos));
    m_pScrollBar->SetScrollPos(pos, TRUE);  // TRUE = 重繪
}

捲軸通知碼對照

訊息映射底層原理與優勢

為什麼不用虛擬函數?

  • CWnd 擁有 200+ 成員函數,若全為虛擬函數 → 每個衍生類別需 200+ 虛擬函數表項目
  • 30+ 衍生控制項類別 × 200 虛擬函數 = 龐大記憶體開銷,多數從未覆寫

訊息映射運作機制

  1. 編譯期:巨集展開為靜態陣列 AFX_MSGMAP_ENTRY,含訊息碼、處理函數指標
  2. 執行期:CWnd::WindowProc 攔截訊息 → 搜尋自身訊息映射 → 找到則呼叫處理函數
  3. 未找到 → 遞迴搜尋基底類別映射 (由 BEGIN_MESSAGE_MAP 第二參數指定)
  4. 仍無匹配 → DefWindowProc 預設處理

記憶體效率對比

通知碼使用者動作nPos 含義
SB_LINELEFT/UP點擊左/上箭頭無意義
SB_LINERIGHT/DOWN點擊右/下箭頭無意義
SB_PAGELEFT/UP點擊滑桿軌道 (頁面)無意義
SB_PAGERIGHT/DOWN點擊滑桿軌道 (頁面)無意義
SB_LEFT/TOPHome/最頂無意義
SB_RIGHT/BOTTOMEnd/最底無意義
SB_THUMBTRACK拖曳滑桿中當前拖曳位置
SB_THUMBPOSITION釋放滑桿最終位置
機制記憶體開銷彈性
虛擬函數表固定:類別數 × 函數數 × 指標大小編譯期綁定
訊息映射動態:僅佔用實際宣告的訊息項目執行期可動態修改 (罕用)

ClassWizard 整合開發

實務開發中,使用 ClassWizard (VC6) / 屬性視窗 (VS2005+) 自動產生:

  • 類別精靈添加訊息處理器 → 自動生成 afx_msg 宣告、映射項目、函數骨架
  • 資源編輯器設計對話框/視窗 → 關聯控制項 ID 與變數
  • 屬性頁面快速設定控制項樣式、事件對應

最佳實踐總結

  1. 所有訊息處理函數:afx_msg void Func(標準參數),放在 public/protected
  2. 複雜佈局改用 CWnd::RepositionBars 或 CDialog::OnSize 統一管理
  3. 計時器、Socket、執行緒訊息皆走訊息映射,保持單執行緒 UI 安全
  4. 自訂訊息:#define WM_MYMSG (WM_USER + 100),用 ON_MESSAGE(WM_MYMSG, OnMyMsg) 對應
  5. 記得 KillTimer、DeleteObject、解除掛鉤,避免資源洩漏
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved