這兩天在菜單欄,需要改變菜單欄字體的大小,但是CMenu類沒有提供相應的方法,自己上網找了點,找到了兩種方法,具體的使用情況還有待研究,先做點筆記吧
方法一:在MainFrame的OnCreate函數中增加如下代碼
// start
LOGFONT m_lf;
memset(&m_lf, 0, sizeof(LOGFONT));
m_lf.lfHeight = 26;
m_lf.lfWeight = 700;//設置字體爲粗體(一般爲400,粗體爲700)
_tcsncpy_s(m_lf.lfFaceName, LF_FACESIZE, _T("Arial"), 7); //字符拷貝函數,使用的如果是UNICODE編碼,則採用wcscpy_s()函數,如果是多字節編碼,則採用strcpy_s()函數 ,m_lf.lfFaceName設置字體名稱
m_wndMenuBar.SetMenuFont(&m_lf);
// end
或者這麼寫:
LOGFONT logfont = {0};
::SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &logfont, 0);
logfont.lfHeight = 26;
logfont.lfWeight = 700;
afxGlobalData.SetMenuFont(&logfont, true);
這裏似乎只能改變主窗口的菜單字體,並且主窗口其他地方的體統字體都會隨之改變。那麼如果在一個彈出對話框中,要改變其菜單的字體如何實現呢?請看方法二
方法二:
首先從CMenu派生出一個子類CNewMenu(利用嚮導建立新的MFC類時,選擇基類時沒有CMenu,而CMenu派生自CObject,所以可以先將CNewMenu派生自CObject,然後再聲明和定義的地方將CObject改成CMenu),然後往這個類添加三個成員函數,MeasureItem(設置菜單寬高),
DrawItem(自繪菜單),ChangeMenuItem(修改菜單項類型)
三個函數分別定義如下:
void CNewMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
void CNewMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
void CNewMenu::ChangeMenuItem(CMenu *pMenu)
其中MeasureItem和DrawItem是CMenu類的虛函數。
各函數的代碼如下:
void CNewMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
lpMeasureItemStruct->itemHeight=25;//項高
lpMeasureItemStruct->itemWidth=120;//項寬
}
void CNewMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// TODO: Add your code to draw the specified item
CRect rect=lpDrawItemStruct->rcItem;//菜單項矩形區域
CRect rectCheck=lpDrawItemStruct->rcItem;
rectCheck.right = rectCheck.left + 25;//icon或checkmark的矩形區域
CDC dc;
dc.Attach(lpDrawItemStruct->hDC);
dc.FillSolidRect(rect,/*RGB(255,255,255)*/dc.GetBkColor());//設置菜單項的文本背景色
dc.DrawEdge(&rect, EDGE_ETCHED, BF_TOP);//繪製菜單項邊界
CFont Font;
Font.CreatePointFont(125,"宋體");//創建字體
dc.SelectObject(&Font);
CString *pText=(CString *)lpDrawItemStruct->itemData;
rect.left += 25;
if(lpDrawItemStruct->itemState&ODS_SELECTED)
{
dc.FillSolidRect(rect,RGB(0,255,0));//設置菜單被選中時的背景色
}
dc.SetTextColor(RGB(10,0,181));//設置文本顏色
dc.DrawText(*pText,rect,DT_VCENTER|DT_LEFT|DT_SINGLELINE);//繪製文字
//通過菜單項ID來確定是否顯示icon以及是否checked
switch(lpDrawItemStruct->itemID)
{
case IDM_MENU21:
//DrawMenuItemIcon(&dc,rectCheck,FALSE/*,IDB_BITMAP1*/);
DrawCheckMark(&dc,rectCheck,TRUE);
//DrawCheckMark(&dc,rectCheck,FALSE);
break;
case IDM_MENU22:
DrawMenuItemIcon(&dc,rectCheck,TRUE,IDB_BITMAP1);
//DrawCheckMark(&dc,rectCheck,TRUE);
break;
case IDM_MENU31:
DrawMenuItemIcon(&dc,rectCheck,TRUE,IDB_BITMAP1);
break;
case IDM_MENU13:
DrawMenuItemIcon(&dc,rectCheck,TRUE,IDB_BITMAP1);
break;
}
dc.Detach();
}
void CNewMenu::ChangeMenuItem(CMenu * pMenu)
{
int itemCount=pMenu->GetMenuItemCount();//獲取彈出菜單項個數
for(int i=0;i<itemCount;i++)
{
CString *pText=new CString;
UINT itemID=pMenu->GetMenuItemID(i);//通過菜單項的編號位置獲取菜單項ID號
pMenu->GetMenuString(i,*pText,MF_BYPOSITION);//獲取菜單文本
//ModifyMenu函數最後一個參數對應DRAWITEMSTRUCT結構裏的itemData變量
pMenu->ModifyMenu(i,MF_OWNERDRAW|MF_BYPOSITION|MF_STRING,itemID,(LPSTR)pText);
if(itemID==-1)//如果是一個彈出式菜單
{
ChangeMenuItem(pMenu->GetSubMenu(i));
}
}
}
另外在類中添加兩個繪製菜單項icon和checkMark的函數:
void CNewMenu::DrawMenuItemIcon(CDC * pDC, CRect rect, BOOL bSelected,UINT nIDBmpResource)
{
//如果不繪製icon,則用背景色填充icon所在區域
if(!bSelected)
{
CBrush MenuItemBKBrush;
MenuItemBKBrush.CreateSysColorBrush(COLOR_BTNFACE);
FillRect(pDC->GetSafeHdc(),&rect,(HBRUSH)MenuItemBKBrush);
return;
}
CBitmap bmp;
if (bmp.LoadBitmap(nIDBmpResource))
{
//獲取位圖屬性
BITMAP bmpInfo;
bmp.GetBitmap(&bmpInfo);
// 創建一個與當前DC兼容的內存DC
CDC dcMemory;
dcMemory.CreateCompatibleDC(pDC);
// 將位圖選入內存DC
CBitmap* pOldBitmap = dcMemory.SelectObject(&bmp);
//獲取bmp圖片繪製的適當位置
int nX = rect.left + (rect.Width() - bmpInfo.bmWidth) / 2;
int nY = rect.top + (rect.Height() - bmpInfo.bmHeight) / 2;
// 將內存DC中的位圖拷貝到當前DC進行顯示
pDC->BitBlt(nX ,nY , bmpInfo.bmWidth, bmpInfo.bmHeight, &dcMemory,
0, 0, SRCCOPY);
//dcMemory.SelectObject(pOldBitmap);
}
}
void CNewMenu::DrawCheckMark(CDC * pDC, CRect rect, BOOL bSelected)
{
//這是自繪的方式,但是使用資源圖片直接貼上去會更簡單
//if(!bSelected)
// return;
//const int nCheckDots = 8;
//CPoint pt1, pt2, pt3; //3 point of the checkmark
//pt1.x = 0; // 5/18 of the rect width
//pt1.y = 3;
//pt2.x = 2;
//pt2.y = 5;
//pt3.x = 7;
//pt3.y = 0;
//int xOff = (rect.Width()-nCheckDots)/2 + rect.left ;
//int yOff = (rect.Height()-nCheckDots)/2 + rect.top;
//pt1.Offset(xOff, yOff);
//pt2.Offset(xOff, yOff);
//pt3.Offset(xOff, yOff);
//CPen pen(PS_SOLID, 1, RGB(0, 0, 0));
//CGdiObject *pOldPen = pDC->SelectObject(&pen);
//pDC->MoveTo(pt1);
//pDC->LineTo(pt2);
//pDC->LineTo(pt3);
//pt1.Offset(0, 1);
//pt2.Offset(0, 1);
//pt3.Offset(0, 1);
//pDC->MoveTo(pt1);
//pDC->LineTo(pt2);
//pDC->LineTo(pt3);
//pt1.Offset(0, 1);
//pt2.Offset(0, 1);
//pt3.Offset(0, 1);
//pDC->MoveTo(pt1);
//pDC->LineTo(pt2);
//pDC->LineTo(pt3);
//pDC->SelectObject(pOldPen);
DrawMenuItemIcon(pDC,rect,bSelected,IDB_CHECKMARK);
}
必須讓每個菜單項具有MF_OWNERDRAW屬性,不然接不到WM_MEASUREITEM和WM_DRAWITEM消息,而且菜單項不具有MF_OWNERDRAW屬性, 即使接到消息,也無法自繪,所以上面的ChangeMenuItem函數就是用於修改菜單項屬性
WM_MEASUREITEM和WM_DRAWITEM消息不是直接發給菜單窗口的,會被父窗口給收到,所以得處理父窗口的WM_MEASUREITEM和WM_DRAWITEM消息,給話框類添加這兩個消息處理函數,兩個函數裏的代碼分別如下:
void CFirstDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
// TODO: Add your message handler code here and/or call default
if(lpMeasureItemStruct->CtlType==ODT_MENU)//如果類型是菜單
newMenu.MeasureItem(lpMeasureItemStruct);//調用CNewMenu類的MeasureItem成員函數
else
CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
void CFirstDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// TODO: Add your message handler code here and/or call default
if(lpDrawItemStruct->CtlType==ODT_MENU)
newMenu.DrawItem(lpDrawItemStruct);
else
CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
}
接着我們在對話類添加一個成員變量:
CNewMenu newMenu; (記得要包含頭文件:"NewMenu.h"),然後在對話框類的OnInitDialog函數添加如下代碼:
newMenu.LoadMenu(IDR_MENU1);
SetMenu(&newMenu);
//只更改下主菜單下的第一項,更改全部:newMenu.ChangeMenuItem(&newMenu);
newMenu.ChangeMenuItem(newMenu.GetSubMenu(1));
newMenu.CheckMenuItem(IDM_MENU12,MF_CHECKED);
newMenu.ChangeMenuItem(newMenu.GetSubMenu(2));
現在可以運行了,但是還是存在一些問題,請看下圖:
菜單名的大小並沒有變,這個問題還有待研究
這裏繪製菜單的文字和icon都是在類的DrawItem中完成的,由於剛剛接觸MFC不久,還不知道怎麼寫好點的接口實現動態設置icon,希望得到大家的指點