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

MFC 選單按鈕實作:CMenuButton 結合 DrawItem 與 TrackPopupMenu 完整封裝

     編輯:關於C語言

設計目標

建立一個按鈕控制項,點擊時彈出選單供使用者進一步選擇 (如工具列「新增」分割按鈕、瀏覽器「回上一頁」下拉歷史)。支援:文字/箭頭顯示、多種箭頭方向、選單彈出位置、焦點矩形控制、命令 ID 返回。

核心原理

  1. 自繪按鈕: 對話框模板設定 BS_OWNERDRAW,MFC 呼叫 CButton::DrawItem 繪製外觀。
  2. 彈出選單: BN_CLICKED 處理中取得按鈕螢幕座標,呼叫 CMenu::TrackPopupMenu 顯示選單,TPM_RETURNCMD 旗標使其返回使用者選擇的命令 ID。
  3. 箭頭繪製: 使用系統字型 Marlett,字元 '4' (向下)、'5' (向右)、'6' (向上)、'7' (向左) 繪製完美縮放箭頭。

CMenuButton 類別宣告


class CMenuButton : public CButton {
public:
    enum ArrowType { ARROW_DOWN, ARROW_RIGHT, ARROW_DOWN_SMALL, ARROW_RIGHT_SMALL, ARROW_UP, ARROW_LEFT };
    enum PopupPos { POPUP_TOPLEFT, POPUP_TOPRIGHT, POPUP_BOTTOMLEFT, POPUP_BOTTOMRIGHT };

    CMenuButton();
    void SetArrowType(ArrowType type);       // 預設 ARROW_DOWN
    void SetMenuPopupPos(PopupPos pos);      // 預設 POPUP_BOTTOMLEFT
    void SetDrawFocusRect(BOOL bDraw);       // 預設 TRUE
    void SetStyle(BOOL bDrawArrow);          // FALSE 僅顯示文字
    UINT OnClick(UINT nMenuID, UINT nSubMenu = 0); // 外部呼叫處理點擊

protected:
    virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS);
    virtual void PreSubclassWindow();
    afx_msg void OnClicked(); // BN_CLICKED 處理

    ArrowType m_arrowType;
    PopupPos m_popupPos;
    BOOL m_bDrawFocusRect;
    BOOL m_bDrawArrow;
    DECLARE_MESSAGE_MAP()
};

關鍵實作細節

1. PreSubclassWindow 確保 BS_OWNERDRAW


void CMenuButton::PreSubclassWindow() {
    ModifyStyle(0, BS_OWNERDRAW); // 強制自繪
    CButton::PreSubclassWindow();
}

2. DrawItem 繪製按鈕外觀與箭頭


void CMenuButton::DrawItem(LPDRAWITEMSTRUCT lpDIS) {
    CDC* pDC = CDC::FromHandle(lpDIS->hDC);
    CRect rect = lpDIS->rcItem;
    UINT state = lpDIS->itemState;

    // 1. 繪製按鈕框架 (凸起/按下/焦點)
    UINT uEdge = (state & ODS_SELECTED) ? EDGE_SUNKEN : EDGE_RAISED;
    pDC->DrawEdge(&rect, uEdge, BF_RECT | BF_ADJUST | (m_bDrawFocusRect && (state & ODS_FOCUS) ? BF_FOCUS : 0));

    // 2. 繪製文字 (若不畫箭頭)
    if (!m_bDrawArrow) {
        CString strText; GetWindowText(strText);
        pDC->SetBkMode(TRANSPARENT);
        pDC->DrawText(strText, &rect, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
        return;
    }

    // 3. 繪製 Marlett 箭頭 (置中或偏右)
    CFont fontMarlett;
    fontMarlett.CreateFont(rect.Height()*2/3, 0, 0, 0, FW_NORMAL, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0, _T("Marlett"));
    CFont* pOldFont = pDC->SelectObject(&fontMarlett);
    pDC->SetBkMode(TRANSPARENT);
    pDC->SetTextColor(GetSysColor(COLOR_BTNTEXT));

    TCHAR chArrow = _T('4'); // 預設向下
    switch (m_arrowType) {
        case ARROW_RIGHT: chArrow = _T('5'); break;
        case ARROW_DOWN_SMALL: chArrow = _T('a'); break; // 小箭頭字元 (需查表)
        case ARROW_UP: chArrow = _T('6'); break;
        case ARROW_LEFT: chArrow = _T('7'); break;
    }

    CSize sz = pDC->GetTextExtent(&chArrow, 1);
    CPoint pt((rect.left + rect.right - sz.cx)/2, (rect.top + rect.bottom - sz.cy)/2);
    // 依 m_popupPos 微調位置...
    pDC->TextOut(pt.x, pt.y, &chArrow, 1);
    pDC->SelectObject(pOldFont);
}

3. OnClick 彈出選單並返回命令 ID


UINT CMenuButton::OnClick(UINT nMenuID, UINT nSubMenu) {
    CMenu menu;
    if (!menu.LoadMenu(nMenuID)) return 0;
    CMenu* pPopup = menu.GetSubMenu(nSubMenu);
    if (!pPopup) return 0;

    CRect rect; GetWindowRect(&rect);
    CPoint pt;
    switch (m_popupPos) {
        case POPUP_BOTTOMLEFT: pt = rect.BottomLeft(); break;
        case POPUP_BOTTOMRIGHT: pt = rect.BottomRight(); break;
        case POPUP_TOPLEFT: pt = rect.TopLeft(); break;
        case POPUP_TOPRIGHT: pt = rect.TopRight(); break;
    }

    // TPM_RETURNCMD: 返回選擇的命令 ID;TPM_LEFTALIGN/TPM_RIGHTALIGN 對齊
    UINT nCmd = pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
        pt.x, pt.y, GetParent(), NULL);
    return nCmd; // 0 表示取消/無選擇
}

4. 對話框中使用


// 對話框類別成員
CMenuButton m_btnMenu;

// OnInitDialog
m_btnMenu.SubclassDlgItem(IDC_MENUBUTTON, this);
m_btnMenu.SetArrowType(CMenuButton::ARROW_DOWN);
m_btnMenu.SetMenuPopupPos(CMenuButton::POPUP_BOTTOMLEFT);
m_btnMenu.SetWindowText(_T("選項"));

// BN_CLICKED 處理 (類別精靈添加)
void CMyDialog::OnClickedMenubutton() {
    UINT nCmd = m_btnMenu.OnClick(IDR_MENU_BUTTON, 0);
    if (nCmd) {
        // 處理命令,或轉發
        SendMessage(WM_COMMAND, nCmd);
    }
}

Marlett 字型字元對照

所有安裝 Windows 的電腦皆內建 Marlett,無字型遺失風險。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
字元符號用途
'0' (0x30)□最大化
'1' (0x31)—最小化
'2' (0x32)⧉還原
'3' (0x33)✕關閉
'4' (0x34)▼向下箭頭 (下拉)
'5' (0x35)▶向右箭頭 (子選單)
'6' (0x36)▲向上箭頭
'7' (0x37)◀向左箭頭