[ATL/WTL]_[初級]_[自定義菜單項字體]

場景

  1. 在開發WTL程序時,菜單基本都是標配,比如菜單欄菜單,右鍵上下文菜單,按鈕菜單等等。但是如何設置菜單的字體,大小,顏色,或者說自繪菜單?

說明

  1. 我們看WTL提供的CMenu類,並不是一個窗口類,它只是封裝了一個HMENU菜單句柄,並不是我們所熟悉的窗口句柄HWND。所以它不會接收到WM_PAINT消息。
template <bool t_bManaged>
class CMenuT
{
public:
// Data members
	HMENU m_hMenu;

// Constructor/destructor/operators
	CMenuT(HMENU hMenu = NULL) : m_hMenu(hMenu)
	{ }
  1. 我們再看看CMenuAppendMenu方法,也只是調用了Win32的API ::AppendMenu 函數,關鍵這個方法參數nFlags的意義,我們看到有一個樣式是MF_OWNERDRAW.
BOOL AppendMenu(UINT nFlags, UINT_PTR nIDNewItem = 0, LPCTSTR lpszNewItem = NULL)
{
    ATLASSERT(::IsMenu(m_hMenu));
    return ::AppendMenu(m_hMenu, nFlags, nIDNewItem, lpszNewItem);
}
  1. MF_OWNERDRAW: 指定這個菜單項是自繪的菜單項。在菜單顯示之前,擁有菜單的窗口接收到WM_MEASUREITEM消息查找菜單項的寬和高。而在菜單項更新時,WM_DRAWITEM消息會被髮送大這個窗口的窗口處理函數裏,可以通過這個消息繪製菜單項.

  2. 以下的樣式不能用在一起, 因爲MF_STRINGMF_OWNERDRAW不能用在一起,所以我們在創建菜單項時單獨使用MF_OWNERDRAW.

MF_BITMAP, MF_STRING, and MF_OWNERDRAW
MF_CHECKED and MF_UNCHECKED
MF_DISABLED, MF_ENABLED, and MF_GRAYED
MF_MENUBARBREAK and MF_MENUBREAK
CMenu menu;
menu.CreatePopupMenu();

menu.AppendMenu(MF_OWNERDRAW,ID_EDIT_COPY,L"Copy-2");
menu.AppendMenu(MF_SEPARATOR);
menu.AppendMenu(MF_OWNERDRAW,ID_EDIT_CUT,L"Cut-2");
menu.AppendMenu(MF_OWNERDRAW,ID_EDIT_PASTE,L"Paste-2");

menu.TrackPopupMenu(TPM_LEFTALIGN|TPM_TOPALIGN|TPM_LEFTBUTTON,pt.x,pt.y,m_hWnd,NULL);
  1. 簡單來說在WM_MEASUREITEM消息裏我們設置菜單項的寬和高。在WM_DRAWITEM消息裏我們繪製菜單項,比如菜單項背景色,字體,字體顏色,選中顏色等。而這兩個消息是屬於OWNERDRAW類型的,在WTL了提供了一個類COwnerDraw映射了這兩個消息,所以我們只需要繼承這個類,並且重載以下兩個方法即可:
void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);

例子

View.h

// View.h : interface of the CView class
//
/////////////////////////////////////////////////////////////////////////////

#pragma once

#include <utility>
#include <string>
#include <functional>
#include <atlmisc.h>
#include <atlctrls.h>
#include <GdiPlus.h>

enum
{
	kMyStaticId = WM_USER+1,
};

class CView : public CWindowImpl<CView>,public COwnerDraw<CView>
{
public:
	DECLARE_WND_CLASS(NULL)

	BOOL PreTranslateMessage(MSG* pMsg);

	BEGIN_MSG_MAP_EX(CView)
		MSG_WM_CREATE(OnCreate)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
		COMMAND_ID_HANDLER_EX(ID_EDIT_COPY, OnOperate)
		COMMAND_ID_HANDLER_EX(ID_EDIT_CUT, OnOperate)
		COMMAND_ID_HANDLER_EX(ID_EDIT_PASTE, OnOperate)
		MSG_WM_CONTEXTMENU(OnContextMenu)
		COMMAND_HANDLER_EX(kMyStaticId,STN_CLICKED,OnStaticClick)
		CHAIN_MSG_MAP_ALT(COwnerDraw<CView>, 0)
		REFLECT_NOTIFICATIONS()
	END_MSG_MAP()

// Handler prototypes (uncomment arguments if needed):
//	LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//	LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//	LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
	int OnCreate(LPCREATESTRUCT lpCreateStruct);
	LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
	void UpdateLayout();
	void OnContextMenu(HWND hwnd, CPoint point);
	void OnStaticClick(UINT uNotifyCode, int nID, HWND wndCtl);
	void OnOperate(UINT uNotifyCode, int nID, HWND wndCtl);

	void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
	void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);

private:
	std::wstring GetControlText(HWND hwnd,wchar_t* buf = NULL);

	CFont font_normal_;
	CStatic text_logo_;
	HICON icon_;

	CBrushHandle brush_white_;
	CBrushHandle brush_hollow_;
	CBrush brush_red_;

public:
};

View.cpp

// View.cpp : implementation of the CView class
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "resource.h"
#include <utility>
#include <assert.h>

#include "View.h"
#include <CommCtrl.h>
#include <string>
#include <regex>


BOOL CView::PreTranslateMessage(MSG* pMsg)
{
	return FALSE;
}

LRESULT CView::OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
	CPaintDC dc(m_hWnd);
	CMemoryDC mdc(dc,dc.m_ps.rcPaint);

	CRect rect_client;
	GetClientRect(&rect_client);
	mdc.FillSolidRect(rect_client,RGB(255,255,255));
	//TODO: Add your drawing code here

	return 0;
}

// https://docs.microsoft.com/en-us/windows/win32/menurc/using-menus#setting-fonts-for-menu-item-text-strings
void CView::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
	if(lpDrawItemStruct->CtlType == ODT_MENU){

		COLORREF crSelText = GetSysColor(COLOR_HIGHLIGHTTEXT);
		COLORREF crSelBkgnd = GetSysColor(COLOR_HIGHLIGHT);
		COLORREF crText = -1;
		COLORREF crBkgnd = -1;
		BOOL fSelected = FALSE;
		CDCHandle cdc(lpDrawItemStruct->hDC);
		auto& rcItem = lpDrawItemStruct->rcItem;
		auto wCheckX = GetSystemMetrics(SM_CXMENUCHECK); 
		CRect rect(rcItem.left,rcItem.top,rcItem.right,rcItem.bottom);

		crBkgnd = cdc.GetBkColor();
		cdc.FillSolidRect(rect,crBkgnd);
		if(lpDrawItemStruct->itemState & ODS_SELECTED){ 
			crText = cdc.SetTextColor(crSelText); 
			crBkgnd = cdc.SetBkColor(crSelBkgnd);
			cdc.FillSolidRect(rect,crSelBkgnd);
			fSelected = TRUE; 
		} 

		auto text = (LPCTSTR)lpDrawItemStruct->itemData;
		cdc.SelectFont(font_normal_);
		CSize size;
		cdc.GetTextExtent(text,wcslen(text),&size);
		rect.left += wCheckX;
		rect.top += ((rect.Height()-size.cy)/2);
		cdc.DrawText(text,wcslen(text),rect,DT_LEFT);

		if (fSelected){ 
			cdc.SetTextColor(crText); 
			cdc.SetBkColor(crBkgnd);
		}
		SetMsgHandled(TRUE);
	}else{	
		SetMsgHandled(FALSE);
	}
	
}

void CView::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
	if(lpMeasureItemStruct->CtlType == ODT_MENU){
		auto hdc = GetDC();
		CDCHandle cdc(hdc);
		cdc.SelectFont(font_normal_);
		auto text = (LPCTSTR)lpMeasureItemStruct->itemData;
		CSize size;
		cdc.GetTextExtent(text,wcslen(text),&size);

		lpMeasureItemStruct->itemWidth = size.cx+4;
		lpMeasureItemStruct->itemHeight = size.cy+4;
		SetMsgHandled(TRUE);
	}else{
		SetMsgHandled(FALSE);
	}
}

void CView::OnContextMenu(HWND hwnd, CPoint pt)
{
	CMenu menu;
	menu.CreatePopupMenu();
	
	menu.AppendMenu(MF_OWNERDRAW,ID_EDIT_COPY,L"Copy-2");
	menu.AppendMenu(MF_SEPARATOR);
	menu.AppendMenu(MF_OWNERDRAW,ID_EDIT_CUT,L"Cut-2");
	menu.AppendMenu(MF_OWNERDRAW,ID_EDIT_PASTE,L"Paste-2");
	
	menu.TrackPopupMenu(TPM_LEFTALIGN|TPM_TOPALIGN|TPM_LEFTBUTTON,pt.x,pt.y,m_hWnd,NULL);
}

static HFONT GetFont(int pixel,bool bold,const wchar_t* font_name)
{
	LOGFONT lf; 
	memset(&lf, 0, sizeof(LOGFONT)); // zero out structure 
	lf.lfHeight = pixel; // request a 8-pixel-height font
	if(bold)
	{
		lf.lfWeight = FW_BOLD;  
	}
	lstrcpy(lf.lfFaceName, font_name); // request a face name "Arial"
	
	HFONT font = ::CreateFontIndirect(&lf);
	return font;
}

void CView::OnOperate(UINT uNotifyCode, int nID, HWND wndCtl)
{
	switch(nID)
	{
	case ID_EDIT_COPY:
		{
			MessageBox(L"Copy-2");
		}
		break;
	case ID_EDIT_CUT:
		MessageBox(L"Cut-2");
		break;
	case ID_EDIT_PASTE:
		MessageBox(L"Paste-2");
		break;
	}
}

std::wstring CView::GetControlText(HWND hwnd,wchar_t* buf)
{
	auto length = ::GetWindowTextLength(hwnd);
	bool bufNull = false;
	if(!buf){
		buf = new wchar_t[length+1]();
		bufNull = true;
	}
	
	::GetWindowText(hwnd,buf,length+1);
	std::wstring str(buf);

	if(bufNull)
		delete []buf;

	return str;
}

static std::wstring GetProductBinDir()
{
	static wchar_t szbuf[MAX_PATH];  
	GetModuleFileName(NULL,szbuf,MAX_PATH);  
    PathRemoveFileSpec(szbuf);
	int length = lstrlen(szbuf);
	szbuf[length] = L'\\';
	szbuf[length+1] = 0;
	return std::wstring(szbuf);
}

int CView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	font_normal_ = ::GetFont(16,false,L"Arial");

	// 1.單擊icon彈出菜單.
	icon_ = AtlLoadIcon(IDR_MAINFRAME);
	text_logo_.Create(m_hWnd,0,L"",WS_CHILD|WS_VISIBLE|SS_ICON|SS_NOTIFY,0,kMyStaticId);
	text_logo_.SetIcon(icon_);

	brush_hollow_ = AtlGetStockBrush(HOLLOW_BRUSH);
	brush_white_ = AtlGetStockBrush(WHITE_BRUSH);
	brush_red_.CreateSolidBrush(RGB(255,0,0));
	UpdateLayout();

	return 0;
}

void CView::OnStaticClick(UINT uNotifyCode, int nID, HWND wndCtl)
{
	CMenu menu;
	menu.CreatePopupMenu();

	menu.AppendMenu(MF_STRING,ID_EDIT_COPY,L"Copy");
	menu.AppendMenu(MF_STRING,ID_EDIT_CUT,L"Cut");
	menu.AppendMenu(MF_STRING,ID_EDIT_PASTE,L"Paste");

	CRect rect;
	text_logo_.GetWindowRect(&rect);
	auto ret = menu.TrackPopupMenu(TPM_LEFTALIGN|TPM_TOPALIGN|
		TPM_RETURNCMD|TPM_LEFTBUTTON,rect.left,rect.bottom,m_hWnd,NULL);
	switch(ret)
	{
	case ID_EDIT_COPY:
		MessageBox(L"Copy");
		break;
	case ID_EDIT_CUT:
		MessageBox(L"Cut");
		break;
	case ID_EDIT_PASTE:
		MessageBox(L"Paste");
		break;
	}
}

void CView::UpdateLayout()
{
	CRect rect;
	GetClientRect(&rect);

	CClientDC dc(m_hWnd);
	dc.SelectFont(font_normal_);
	
	CSize size_control;
	wchar_t* buf = new wchar_t[MAX_PATH]();

	CRect rect_control = CRect(CPoint(100,100),CSize(32,32));
	text_logo_.MoveWindow(rect_control);
}

輸出

標準的菜單項

圖1:
在這裏插入圖片描述

自定義的菜單項

圖2:

在這裏插入圖片描述

參考

  1. Setting Fonts for Menu-Item Text Strings

  2. Using Menus

  3. AppendMenu function

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章