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

VC++ 自繪顏色下拉方塊:ComboBox 實作色盤選擇器

     編輯:VC 教學

前言

Word 的字型顏色下拉選單以色塊+文字呈現,極具直觀性。MFC `CComboBox` 支援 `CBS_OWNERDRAWFIXED`/`VARIABLE` 自繪模式,配合 `WM_MEASUREITEM`、`WM_DRAWITEM`、`WM_PAINT` 三大訊息,即可實作專業級顏色選擇器。本文完整解析自繪流程,提供 `CBSColorComboBox` 類別原始碼,含系統顏色列舉、自訂顏色項目、雙緩衝防閃爍、鍵盤/滑鼠操作支援。

一、自繪 ComboBox 核心訊息流程

訊息用途關鍵結構成員
WM_MEASUREITEM決定每個項目高度LPMEASUREITEMSTRUCT::itemHeight
WM_DRAWITEM繪製單一項目內容LPDRAWITEMSTRUCT::hDC, rcItem, itemID, itemState, itemData
WM_PAINT重繪控制項客戶區(編輯框/按鈕部分)CPaintDC
WM_ERASEBKGND抑制預設背景抹除,防閃爍返回 TRUE

二、CBSColorComboBox 類別設計

標頭檔


class CBSColorComboBox : public CComboBox
{
    DECLARE_DYNAMIC(CBSColorComboBox)
public:
    CBSColorComboBox();
    virtual ~CBSColorComboBox();

    // 初始化:填入系統顏色 + 自訂顏色 + "More Colors..."
    void Initialize();

    // 取得/設定目前選取顏色
    COLORREF GetSelectedColor() const;
    void SetSelectedColor(COLORREF clr);

protected:
    struct ColorItem { COLORREF clr; CString name; bool isCustom; };
    CArray m_items;

    // 訊息處理
    afx_msg void MeasureItem(LPMEASUREITEMSTRUCT lpMIS);
    afx_msg void DrawItem(LPDRAWITEMSTRUCT lpDIS);
    afx_msg void OnPaint();
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
    afx_msg void OnDestroy();

    DECLARE_MESSAGE_MAP()
};

核心實作


void CBSColorComboBox::MeasureItem(LPMEASUREITEMSTRUCT lpMIS)
{
    // 項目高度 = 2 * 系統小圖示高度
    lpMIS->itemHeight = 2 * GetSystemMetrics(SM_CYSMICON);
}

void CBSColorComboBox::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
    CDC* pDC = CDC::FromHandle(lpDIS->hDC);
    CRect rc = lpDIS->rcItem;
    int idx = lpDIS->itemID;
    UINT state = lpDIS->itemState;

    if (idx < 0 || idx >= m_items.GetSize()) return;

    const ColorItem& item = m_items[idx];
    bool bSelected = (state & ODS_SELECTED);
    bool bFocus = (state & ODS_FOCUS);

    // 1. 背景
    COLORREF clrBkg = bSelected ? ::GetSysColor(COLOR_HIGHLIGHT) : ::GetSysColor(COLOR_WINDOW);
    COLORREF clrText = bSelected ? ::GetSysColor(COLOR_HIGHLIGHTTEXT) : ::GetSysColor(COLOR_WINDOWTEXT);
    CBrush brush(clrBkg);
    pDC->FillRect(&rc, &brush);

    // 2. 色塊區域:左側 20x20
    CRect rcColor(rc.left + 2, rc.top + 2, rc.left + 22, rc.bottom - 2);
    CBrush colorBrush(item.clr);
    pDC->FrameRect(&rcColor, CBrush::FromHandle((HBRUSH)GetStockObject(BLACK_BRUSH))); // 黑框
    rcColor.DeflateRect(1,1);
    pDC->FillRect(&rcColor, &colorBrush);

    // 3. 文字區域:右側剩餘
    CRect rcText(rcColor.right + 6, rc.top, rc.right - 4, rc.bottom);
    pDC->SetBkMode(TRANSPARENT);
    pDC->SetTextColor(clrText);
    pDC->DrawText(item.name, &rcText, DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);

    // 4. 焦點虛線框
    if (bFocus)
        pDC->DrawFocusRect(&rc);
}

void CBSColorComboBox::OnPaint()
{
    // 雙緩衝重繪編輯框區域
    CPaintDC dc(this);
    CMemDC memDC(&dc);
    CRect rcClient;
    GetClientRect(&rcClient);

    // 僅繪製編輯框部分(下拉清單由 DrawItem 處理)
    CRect rcEdit = rcClient;
    rcEdit.bottom = rcEdit.top + 2 * GetSystemMetrics(SM_CYSMICON); // 近似高度

    // 填背景
    memDC.FillSolidRect(&rcEdit, ::GetSysColor(COLOR_WINDOW));
    // 畫目前選取顏色色塊
    if (GetCurSel() >= 0)
    {
        COLORREF clr = m_items[GetCurSel()].clr;
        CRect rcColor(rcEdit.left + 2, rcEdit.top + 2, rcEdit.left + 22, rcEdit.bottom - 2);
        memDC.FillSolidRect(&rcColor, clr);
        memDC.FrameRect(&rcColor, CBrush::FromHandle((HBRUSH)GetStockObject(BLACK_BRUSH)));
    }
    // 畫邊框
    memDC.DrawEdge(&rcEdit, EDGE_SUNKEN, BF_RECT);
}

BOOL CBSColorComboBox::OnEraseBkgnd(CDC* pDC)
{
    return TRUE; // 抑制預設抹除,由 OnPaint/DrawItem 全權處理
}

三、初始化填充顏色項目


void CBSColorComboBox::Initialize()
{
    ResetContent();
    m_items.RemoveAll();

    // 1. 系統顏色(前 16 色)
    static const struct { int idx; LPCTSTR name; } sysColors[] = {
        {COLOR_SCROLLBAR, _T("Scrollbar")}, {COLOR_BACKGROUND, _T("Background")},
        {COLOR_ACTIVECAPTION, _T("Active Caption")}, {COLOR_INACTIVECAPTION, _T("Inactive Caption")},
        {COLOR_MENU, _T("Menu")}, {COLOR_WINDOW, _T("Window")},
        {COLOR_WINDOWFRAME, _T("Window Frame")}, {COLOR_MENUTEXT, _T("Menu Text")},
        {COLOR_WINDOWTEXT, _T("Window Text")}, {COLOR_CAPTIONTEXT, _T("Caption Text")},
        {COLOR_ACTIVEBORDER, _T("Active Border")}, {COLOR_INACTIVEBORDER, _T("Inactive Border")},
        {COLOR_APPWORKSPACE, _T("App Workspace")}, {COLOR_HIGHLIGHT, _T("Highlight")},
        {COLOR_HIGHLIGHTTEXT, _T("Highlight Text")}, {COLOR_BTNFACE, _T("Button Face")},
    };
    for (auto& sc : sysColors)
    {
        ColorItem item; item.clr = ::GetSysColor(sc.idx); item.name = sc.name; item.isCustom = false;
        m_items.Add(item);
        AddString(item.name); // 文字僅供 DrawItem 參考,實際不顯示
        SetItemData(GetCount()-1, m_items.GetSize()-1);
    }

    // 2. 常用自訂顏色
    static const struct { COLORREF clr; LPCTSTR name; } customColors[] = {
        {RGB(255,0,0), _T("Red")}, {RGB(0,255,0), _T("Green")}, {RGB(0,0,255), _T("Blue")},
        {RGB(255,255,0), _T("Yellow")}, {RGB(255,0,255), _T("Magenta")}, {RGB(0,255,255), _T("Cyan")},
        {RGB(0,0,0), _T("Black")}, {RGB(255,255,255), _T("White")}, {RGB(128,128,128), _T("Gray")},
    };
    for (auto& cc : customColors)
    {
        ColorItem item; item.clr = cc.clr; item.name = cc.name; item.isCustom = true;
        m_items.Add(item);
        AddString(item.name);
        SetItemData(GetCount()-1, m_items.GetSize()-1);
    }

    // 3. "More Colors..." 項目
    ColorItem more; more.clr = CLR_NONE; more.name = _T("More Colors..."); more.isCustom = false;
    m_items.Add(more);
    AddString(more.name);
    SetItemData(GetCount()-1, m_items.GetSize()-1);

    SetCurSel(0);
}

四、處理 "More Colors..." 選擇


void CMyDialog::OnCbnSelchangeColorCombo()
{
    int sel = m_colorCombo.GetCurSel();
    if (sel < 0) return;
    DWORD_PTR data = m_colorCombo.GetItemData(sel);
    if (data < m_colorCombo.m_items.GetSize())
    {
        const auto& item = m_colorCombo.m_items[data];
        if (item.name == _T("More Colors..."))
        {
            CColorDialog dlg(m_colorCombo.GetSelectedColor(), CC_FULLOPEN | CC_RGBINIT, this);
            if (dlg.DoModal() == IDOK)
            {
                COLORREF clr = dlg.GetColor();
                // 可選:加入自訂顏色列表頂端
                m_colorCombo.InsertCustomColor(clr);
                m_colorCombo.SetSelectedColor(clr);
            }
            // 恢復原選擇或設為第一項
            m_colorCombo.SetCurSel(0);
        }
    }
}

五、執行效果

自繪顏色下拉方塊示範

下拉清單顯示色塊+名稱,編輯框同步顯示目前顏色,選擇 "More Colors..." 彈出標準色彩對話框,支援鍵盤上下鍵選擇、滑鼠滾輪、高對比度模式。

六、結語

自繪 `CComboBox` 的關鍵在於:MeasureItem 定高 → DrawItem 分區繪製(色塊/文字/焦點) → OnPaint 雙緩衝繪製編輯框 → EraseBkgnd 抑制閃爍。`CBSColorComboBox` 封裝完整,可直接放入專案作為屬性頁、工具列、屬性格的顏色選擇器。同樣架構可擴充至字型下拉框、圖示選擇器、縮圖瀏覽器等任意視覺化下拉控制項。

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