場景
- 在開發
WTL
程序時,菜單基本都是標配,比如菜單欄菜單,右鍵上下文菜單,按鈕菜單等等。但是如何設置菜單的字體,大小,顏色,或者說自繪菜單?
說明
- 我們看
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)
{ }
- 我們再看看
CMenu
的AppendMenu
方法,也只是調用了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);
}
-
MF_OWNERDRAW
: 指定這個菜單項是自繪的菜單項。在菜單顯示之前,擁有菜單的窗口接收到WM_MEASUREITEM
消息查找菜單項的寬和高。而在菜單項更新時,WM_DRAWITEM
消息會被髮送大這個窗口的窗口處理函數裏,可以通過這個消息繪製菜單項. -
以下的樣式不能用在一起, 因爲
MF_STRING
和MF_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);
- 簡單來說在
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: