[Visual C++] MFC CMenu Custom

MFC 에서 CMenu의 색상을 원하는 대로 바꿀 수 있다.

https://msdn.microsoft.com/en-us/library/z25as7e5(v=vs.120).aspx

에서 보듯이 CMenu::DrawItem 함수를 오버라이딩 해서 사용한다.

https://www.codeproject.com/Articles/7073/How-to-create-owner-drawn-menus-Step-by-Step

or
ownmenu_src.zip (GoogleDrive)

위 사이트에서 간략한 예제가 나와 있다.

그리고 아래의 사이트에서는 CMenu의 border 를 제거하는 방법을 소개한다.

CMenu의 border 색상 칠하기

위 방식에서 소개된 방법은 CMenu를 내부적으로 동적으로 생성하기 때문에 외부 환경 값을 저장 할땐 static 멤버로 저장한다.

아래는 배경색,글자색,Hover색,경계색,폰트 의 5가지를 수정할 수 있는 간단한 예시 코드이다. BMDLMenu.h

#pragma once
#include<afxwin.h>
#include<vector>
class BMDLMenu : public CMenu {  
public:  
    //constructor,destructor
    BMDLMenu(CWnd* wnd);
    virtual ~BMDLMenu();
private:  
    //Inner class
    struct MenuObject {
        HICON m_hIcon;
        CString m_strCaption;
        BOOL bFirstMenu;
    };
    std::vector<DWORD> deleteItem;
    std::vector<DWORD> deleteMenu;
public:  
    CWnd* m_parent_wnd;
    void MakeItemsOwnDraw();
    void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)override;
    void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)override;
    //static member
    static CString m_font_string;
    static COLORREF m_color_bk;
    static COLORREF m_color_text;
    static COLORREF m_color_hover;
    static COLORREF m_color_border;
    static void SetStyle(CString font_string, COLORREF color_bk, COLORREF color_text, COLORREF color_hover, COLORREF color_border);
};

BMDLMenu.cpp

#include"stdafx.h"
#include"BMDLMenu.h"
#include"stdmfc.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;  
#endif
///static member
CString BMDLMenu::m_font_string = TEXT("Arial");  
COLORREF BMDLMenu::m_color_bk = RGB(255, 255, 255);  
COLORREF BMDLMenu::m_color_text = RGB(0, 0, 0);  
COLORREF BMDLMenu::m_color_hover = RGB(199, 199, 199);  
COLORREF BMDLMenu::m_color_border = RGB(103, 153, 255);  
///constructor
BMDLMenu::BMDLMenu(CWnd* wnd) :CMenu() {  
    m_parent_wnd = wnd;
}
///destructor
BMDLMenu::~BMDLMenu() {  
    for (int i = 0; i < deleteItem.size(); i++) {
        delete ((MenuObject*)deleteItem[i]);
    }
    for (int i = 0; i < deleteMenu.size(); i++) {
        delete ((BMDLMenu*)deleteMenu[i]);
    }
}
///static metnod
void BMDLMenu::SetStyle(CString font_string, COLORREF color_bk, COLORREF color_text, COLORREF color_hover, COLORREF color_border) {  
    BMDLMenu::m_font_string = font_string;
    BMDLMenu::m_color_bk = color_bk;
    BMDLMenu::m_color_text = color_text;
    BMDLMenu::m_color_hover = color_hover;
    BMDLMenu::m_color_border = color_border;
}
///method
void BMDLMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) {  
    CRect rect(lpDrawItemStruct->rcItem);
    COLORREF color_bk = BMDLMenu::m_color_bk;
    if (((MenuObject*)lpDrawItemStruct->itemData)->bFirstMenu) {
        //일반 상태
        color_bk = BMDLMenu::m_color_bk;
    }
    if ((lpDrawItemStruct->itemState & ODS_SELECTED) &&
        (lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE))) {
        //Hover 상태
        color_bk = BMDLMenu::m_color_hover;
    }
    CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
    ::FillRect(pDC->GetSafeHdc(), &rect, CreateSolidBrush(color_bk));
    CFont font;
    int h = stdmfc::MeasureFontHeight(m_font_string, 20, pDC);
    font.CreatePointFont(h, m_font_string);
    CFont* old_font = pDC->SelectObject(&font);
    pDC->SetBkColor(color_bk);
    pDC->TextOut(rect.left, rect.top, ((MenuObject*)lpDrawItemStruct->itemData)->m_strCaption);
}

void BMDLMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) {  
    static CWindowDC dc(m_parent_wnd);
    lpMeasureItemStruct->itemHeight = 20;
    CFont font;
    int h = stdmfc::MeasureFontHeight(m_font_string, lpMeasureItemStruct->itemHeight, &dc);
    font.CreatePointFont(h, m_font_string);
    CFont* old_font = dc.SelectObject(&font);
    CString str = ((MenuObject*)lpMeasureItemStruct->itemData)->m_strCaption;
    CSize sz;
    ::GetTextExtentPoint32W(dc.GetSafeHdc(), str, str.GetLength(), &sz);

    lpMeasureItemStruct->itemWidth = sz.cx < 100 ? 100 : sz.cx;
}

void BMDLMenu::MakeItemsOwnDraw() {  
    //https://stackoverflow.com/questions/30353644/cmenu-border-color-on-mfc-solved
    MENUINFO MenuInfo = { 0 };
    MenuInfo.cbSize = sizeof(MENUINFO);
    GetMenuInfo(&MenuInfo);
    MenuInfo.hbrBack = ::CreateSolidBrush(BMDLMenu::m_color_border);
    MenuInfo.fMask = MIM_BACKGROUND | MIM_STYLE;
    MenuInfo.dwStyle = MIM_APPLYTOSUBMENUS;
    SetMenuInfo(&MenuInfo);

    int iMaxItems = GetMenuItemCount();
    for (int i = 0; i < iMaxItems; i++) {
        CString nameHolder;
        MenuObject* pObject = new MenuObject;
        deleteItem.push_back((DWORD)pObject);
        pObject->m_hIcon = NULL;
        pObject->bFirstMenu = TRUE;
        GetMenuString(i, pObject->m_strCaption, MF_BYPOSITION);
        MENUITEMINFO mInfo;
        ZeroMemory(&mInfo, sizeof(MENUITEMINFO));
        //I dont use GetMenuItemID because it doesn't return 0/-1 when it's a Popup (so the MSDN is wrong)
        UINT uID = mInfo.wID;
        ModifyMenu(i, MF_BYPOSITION | MF_OWNERDRAW,
                   uID, (TCHAR*)pObject);
        if (GetSubMenu(i)) {
            BMDLMenu* pSubMenu = new BMDLMenu(m_parent_wnd);
            deleteMenu.push_back((DWORD)pSubMenu);
            pSubMenu->Attach(GetSubMenu(i)->GetSafeHmenu());
            pSubMenu->MakeItemsOwnDraw();
        }
    }
}

stdmfc.h

namespace stdmfc {  
    inline int MeasureFontHeight(CString font_str, int h, CDC* pdc) {
        int L = 0;
        int R = 1000;
        while (L <= R) {
            int M = (L + R) / 2;
            CFont font;
            font.CreatePointFont(M, font_str);
            CFont* old_font = pdc->SelectObject(&font);
            CSize sz;
            ::GetTextExtentPoint32W(pdc->GetSafeHdc(), TEXT("A"), 1, &sz);
            if (sz.cy == h) {
                break;
            } else if (sz.cy > h) {
                R = M - 1;
            } else {
                L = M + 1;
            }
            pdc->SelectObject(old_font);
            font.DeleteObject();
        }
        return (L + R) / 2;
    }
}