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

MFC 控制項顏色自訂完全指南:父視窗 OnCtlColor vs 訊息反射 CtlColor 兩大方案深度對比

     編輯:關於C語言

控制項著色機制:WM_CTLCOLOR 訊息流程

當控制項需繪製時,Windows 發送 WM_CTLCOLORXXX (如 WM_CTLCOLOREDIT, WM_CTLCOLORLISTBOX) 給**父視窗**。父視窗可返回 **HBRUSH** (背景筆刷),並在此前設定 CDC::SetTextColor、SetBkColor、SetBkMode。MFC 封裝為虛擬函數 CWnd::OnCtlColor。

方案一:父視窗統一處理 (傳統、集中管理)

實作


// 父視窗類別 (如 CDialog 派生類)
HBRUSH CMyDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); // 基類預設處理

    // 針對所有編輯控制項設定紅字
    if (nCtlColor == CTLCOLOR_EDIT) {
        pDC->SetTextColor(RGB(255, 0, 0)); // 紅色文字
        // pDC->SetBkColor(RGB(255, 255, 0)); // 黃背景 (需返回對應筆刷)
        // return m_yellowBrush; // 需成員變數 CBrush m_yellowBrush
    }
    // 針對特定控制項 ID
    if (pWnd->GetDlgCtrlID() == IDC_SPECIAL_EDIT) {
        pDC->SetTextColor(RGB(0, 0, 255)); // 藍字
    }

    return hbr;
}

優缺點

優點缺點
集中管理所有子控制項顏色違反封裝:父視窗需知曉子控制項細節
無需修改控制項類別控制項類別復用性差 (換父視窗顏色失效)
動態依條件變色容易父視窗類別膨脹、耦合度高

方案二:訊息反射 - 控制項自我管理 (MFC 4.0+、面向物件)

原理

父視窗 OnCtlColor 未處理 (返回預設) → MFC 將訊息**反射**回控制項 → 控制項處理 WM_CTLCOLOR_REFLECT (映射為 CtlColor 成員函數)。

實作步驟 (以自訂彩色列表框為例)

  1. ClassWizard 建立 CMyListBox : public CListBox。
  2. 加入顏色/筆刷成員:

class CMyListBox : public CListBox
{
    // ...
private:
    COLORREF m_clrFore; // 前景色
    COLORREF m_clrBack; // 背景色
    CBrush   m_brush;   // 背景筆刷
};
  1. 建構函數初始化:

CMyListBox::CMyListBox()
{
    m_clrFore = RGB(255, 255, 0); // 黃字
    m_clrBack = RGB(0, 0, 255);   // 藍底
    m_brush.CreateSolidBrush(m_clrBack);
}
  1. ClassWizard 加入 **Reflected Messages** → WM_CTLCOLOR (顯示為 =WM_CTLCOLOR) → 生成 CtlColor:

HBRUSH CMyListBox::CtlColor(CDC* pDC, UINT nCtlColor)
{
    pDC->SetTextColor(m_clrFore);
    pDC->SetBkColor(m_clrBack);
    return (HBRUSH)m_brush.GetSafeHandle(); // 返回筆刷句柄
}
  1. 對話框編輯器將列表框控制項類別改為 CMyListBox (或動態 SubclassDlgItem)。

優缺點

優點缺點
控制項封裝完整,隨處可用需建立自訂控制項類別
符合 OO 封裝原則每種控制項類型需個別衍生類
狀態隨控制項生命週期動態換色需額外介面 (SetColor 方法)

進階:動態換色介面設計


class CMyListBox : public CListBox
{
public:
    void SetColors(COLORREF clrFore, COLORREF clrBack)
    {
        m_clrFore = clrFore;
        m_clrBack = clrBack;
        m_brush.DeleteObject();
        m_brush.CreateSolidBrush(m_clrBack);
        Invalidate(); // 觸發重繪
    }
    // ...
};

控制項類型與 WM_CTLCOLOR 代碼對照

控制項類型WM_CTLCOLOR 代碼nCtlColor 值
Edit / ComboBox 編輯區WM_CTLCOLOREDITCTLCOLOR_EDIT
ListBoxWM_CTLCOLORLISTBOXCTLCOLOR_LISTBOX
ComboBox 下拉列表WM_CTLCOLORLISTBOXCTLCOLOR_LISTBOX
Button (僅 Owner Draw)WM_CTLCOLORBTNCTLCOLOR_BTN
Static (文字/框架/圖片)WM_CTLCOLORSTATICCTLCOLOR_STATIC
Dialog 背景WM_CTLCOLORDLGCTLCOLOR_DLG
MessageBoxWM_CTLCOLORMSGBOXCTLCOLOR_MSGBOX
ScrollBarWM_CTLCOLORSCROLLBARCTLCOLOR_SCROLLBAR

常見坑與解決

  • 筆刷未建立/重複建立:CreateSolidBrush 失敗返回 NULL,返回 NULL 筆刷導致繪製異常。成員 CBrush 自動管理生命週期。
  • SetBkColor 無效:必須返回對應背景色筆刷,且 SetBkMode(OPAQUE) (預設)。透明模式 TRANSPARENT 會忽略背景色。
  • 靜態文字 (Static) 文字色不變:SS_NOTIFY 樣式或 SS_OWNERDRAW 影響。純文字 Static 用 CTLCOLOR_STATIC 設 SetTextColor 有效。
  • 反射訊息未進入:父視窗 OnCtlColor 必須調用基類實作 (返回預設筆刷),不可直接返回自訂筆刷攔截所有。若父視窗需處理部分控制項,**僅處理目標控制項,其餘調用基類**。
  • 高 DPI / Dark Mode:Win10 1809+ 深色模式下系統筆刷變化,自訂筆刷需響應 WM_SETTINGCHANGE 重建。

總結選型建議

  • 全域/條件式統一風格 (如「所有編輯框紅字警示」):用**父視窗 OnCtlColor**。
  • 可重用控制項元件、元件庫開發:用**訊息反射 CtlColor**,封裝於控制項類別內。
  • 混合策略:父視窗處理全域規則,特定自訂控制項用反射自我管理,互不干擾。

MFC 訊息反射機制讓控制項「自己負責自己的外觀」,是從「父視窗管一切」向「組件化封裝」演進的關鍵一步。

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