MFC 菜单自绘

背景:由于项目要求将现有MFC MDI框架的程序作一次界面优化,首要的就是系统菜单的优化。具体要求有菜单项尺寸、菜单背景色,菜单栏背景色等。工作期间深刻体会到了MFC做界面的痛苦,这里把这期间所使用到的自绘的相关知识作个梳理总结,希望对大家有个参考价值。


一、要想自绘菜单,首要的就是子类化菜单类,拥有自己的自定义菜单类。

也有大神子类化CWnd。。。这应该属于高度定制了,表示驾驭不了,还是看子类化的吧。 关于自绘菜单类很多大神有分享,由于自己水平有限,也只能是拿来主义。

1.总结起来,这个类通常必需要重写的方法有

1.1、BOOL CMyMenu::OnDrawItem(int nIDCtl,DRAWITEMSTRUCT*pDIS)

该方法用于自绘制每一菜单项,在菜单显示,刷新时调用。通常做法是根据菜单项属性,判断属于哪一类菜单,再决定如何绘制,一般分为顶层菜单项,一般菜单项、分隔符、系统菜单等。

1.2、BOOL CMyMenu:OnMeasureItem(int nIDCtl,MEASUREITEMSTRUCT*pMIS)

该方法用于设置菜单项的尺寸,在菜单创建时调用。如可以简单地设置,菜单项宽高为

        pMIS->itemHeight    =20;
        pMIS->itemWidth     =200;

2.重写了最重要的两个类,就有了绘制菜单的基础工具,但是还需要我们添加具体的绘制方法 ,如绘制顶层菜单的方法,绘制一般菜单、绘制分割符、绘制系统菜单图标、绘制背景等。

3.有了以上方法,我们绘制菜单的工具算是齐备了,但是还少一些东西,那就是菜单的原料了,比如配色,字体,图标等属性。

另外:这些类中比较重要的是有一套绘制逻辑,比如如何匹配要加载的图标,根据菜单属性分类绘制顶层菜单,一般菜单等不同菜单项的逻辑等。不过大牛已经有现成的给我了,大家可以参考资源中的不同实现方法。

参考代码1:http://download.csdn.net/detail/u010679316/9552044
参考代码2:http://download.csdn.net/download/exceed_me/2750768


二、在MFC中使用自定义菜单类

1.在应用菜单窗口中,如CMainFrame添加消息映射:

ON_WM_DRAWITEM()
ON_WM_MEASUREITEM()

2.然后重写它们对应的函数:

void CMainFrame::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) 
{//CustomMenu为自定义菜单类的实例
    m_CustomMenu.DrawItem(lpDrawItemStruct);
}
void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) 
{
    m_CustomMenu.MeasureItem(lpMeasureItemStruct);
}

3.创建自定义菜单类实例,将其与主窗口绑定
m_CustomMenu.Attach(this->GetMenu()->GetSafeHmenu());
或者 SetMenu(m_CustomMenu);

这样,自绘菜单基本上就算完成了


三、相关细节的处理

虽然有前人铺路,但是完全拿来用做项目的应用总是不现实的,总有些效果还需要自己来调。。。
现在来看,比较容易做到的是改变菜单项的背景,字体的颜色和字号,菜单贴图等。但是整体看来,菜单效果并不理想,主要在以下几个方面:

1.菜单栏背景的绘制

网上的例程中,有些只做了顶层每个菜单项的背景设置,而菜单右侧仍然是系统默认的丑陋的背景色;
如图:
这里写图片描述
有一种关于菜单栏背景色绘制的思路是,在绘制顶层菜单时,额外绘制菜单的右侧区域,虽然可以实现显示效果,但是感觉上这样做不科学,额外产生多次绘制。重要的是系统默认背景色如果和你自己设置的菜单项背景色差很大的话,当频繁刷新菜单,系统配色和你的配色切换时就会产生闪烁,这不是我们想要的。
所以,如果有一种办法,能够在绘制菜单项前就将菜单栏背景色设置为同样的颜色,就会避免了闪烁了。具体如何做呢?上代码:

::MENUINFO mnInfo;
mnInfo.cbSize = sizeof(MENUINFO);
mnInfo.fMask = MIM_BACKGROUND;
mnInfo.hbrBack = (HBRUSH)mnBgBrush;//mnBgBrush 背景色画刷
::SetMenuInfo(menu->m_hMenu, &mnInfo);//menu指向要绘制的菜单

也就是说,需要在绘制每项菜单时,先价格整个菜单背景被设置为相同颜色,就不会出现菜单重绘产生的闪烁的问题了。上图:
这里写图片描述
另外:这个设置还意外地解决了弹出菜单默认的粗边框的问题:如图我们可以看到示例程序中,如果不执行上述代码,只是将每个菜单项的背景设置为了黑色,弹出菜单项会显示一个比较丑的粗的白色边框,这个边框颜色应该是系统默认色。
MFC 菜单自绘_第1张图片
之前查了好久,去掉这个粗边框的办法。虽然网上有说可以用消息钩子重绘非客户等一些办法,但是这些总会产生一些其他绘制问题,最终没有使用。但是只要在绘制菜单项背景时确保菜单的背景已经设置为和菜单项相似的颜色,就会只显示一个细边框,看起来就比较舒服了。如图:
MFC 菜单自绘_第2张图片
其实,如果我们将绘制菜单项的背景色和上述代码中背景画刷的颜色设为不同颜色就会看到,菜单绘制的具体过程。如果背景画刷为红色,可以看到:
MFC 菜单自绘_第3张图片
经过单步调试发现,菜单首先获取画刷,绘制一个背景,然后再绘制每一菜单项。我们之前看到的粗边框其实有一部分是整个菜单的背景,也就是没有被单个菜单项遮挡的部分。
因为自己不清楚菜单绘制机制,走了好多弯路。。。

2、菜单快捷键显示

完成自绘之后,菜单的快捷键均显示下划线。我们希望的是,在菜单没有获得焦点时,不能响应快捷键,不显示下划线,当按下Alt键时才显示下划线。
解决思路:在菜单没有焦点时,将菜单文本中的“&”去掉,也就不显示下划线。在获得焦点时,显示下划线。
绘制文本的代码:

if(state &ODS_NOACCEL)
{//没有快捷键,不显示下划线
  CString str = pText;//pText 为菜单文本
  str.Replace(_T("&"),_T(""));
  DrawItemText(pCdc,str,Rect,state,TRUE);//绘制文本
}
else
{//有快捷键,显示下划线
  DrawItemText(pCdc,pText,Rect,state,TRUE);//绘制文本
}

四、菜单栏高度的修改

通过子类话CMenu只能改变菜单项的尺寸和外观,菜单栏的高度并不能改变。偶然发现,windows控制面板中有修改菜单项
MFC 菜单自绘_第4张图片
于是思考有没有相应API可以在代码中改变菜单栏高度,方法如下:

    NONCLIENTMETRICS cs = { sizeof(NONCLIENTMETRICS) };
    cs.iMenuHeight = 50;
    SystemParametersInfo(SPI_SETNONCLIENTMETRICS, sizeof(cs), &cs, SPIF_SENDCHANGE);

这样就将菜单栏高度改为50,不过是将系统中所有软件的菜单栏都改为了50。

你可能感兴趣的:(C++/MFC)