[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;
	}
}