WTL-Menu自绘

  windows下控件很多,但常用的其实没几个。我们先从菜单说起。

效果预览

  先看最终效果,有个直观感受。

  这个是最普通的二级菜单,每个菜单项画了一张图片。

WTL-Menu自绘_第1张图片

图一


  我们经常看到下面这种菜单:左边是个小图标,右边是文字,这样的效果我们也可以做出来,见图三:


WTL-Menu自绘_第2张图片

图二

  唯一不同的是……我们的图片和菜单项……个头儿都很大……


WTL-Menu自绘_第3张图片

图三

  

  上边都是单列的,能不能多列呢?能!在资源编辑器中把要选择一个菜单项,设置其“Break”属性为"Column",那么这个菜单项的下一项将另起一列。代码里也能修改这个属性。效果见图四:

WTL-Menu自绘_第4张图片

图四


菜单的基本概念

  有几个基本概念,后面会用上,请大家耐心看完。请看图:

WTL-Menu自绘_第5张图片

图五

  红框所在位置叫MenuBar,直译过来就是“菜单栏”。MainMenu、Menu2、SubMenuA这些叫MenuItem,直译就是“菜单项”

  蓝框里的是被弹出来的叫SubMenu,直译就是“子菜单”,就是大家日常看到的、直接使用的东西。

 

菜单自绘

步骤:

  1.  在WM_CONTEXTMENU消息响应里,用LoadMenu载入菜单栏,用GetSubMenu载入要显示的子菜单,用TrackPopupMenu(或TrackPopupMenuEx)弹出菜单

  2.  在WM_INITMENUPOPUP中修改各MenuItem为OwnerDraw风格

  3.  在WM_MEASUREITEM中按个人需要修改MenuItem宽、高

  4.  在WM_DRAWITEM中使用DRAWITEMSTRUCT结构体中的hDc和rcItem,结合GDI函数,绘制MenuItem

  接下来,我们将以对话框程序,右键弹出菜单的形式,讲解菜单自绘方法。

WM_CONTEXT中弹出弹出菜单

首先新建一个基于对话框的WTL项目,关于WTL开发环境的配置、向导的安装请自行google

然后在资源编辑器中新增一个菜单,为了演示的丰富一些,我们设计了一个二级菜单,中文和英文菜单项都有。我们菜单的IDIDR_MENU1,如图所示:


WTL-Menu自绘_第6张图片

图六


  最后,在代码里响应WM_CONTEXT消息,在消息响应函数中用CMenu类加载菜单,用CMenuHandle类操作子菜单,弹出菜单。在WM_CONTEXT的响应函数里写上如下代码:

CMenumenu;

menu.LoadMenu(IDR_MENU1);//此处menu代表的是根菜单

//以IDR_MENU1为例,它里面包含两个子菜单,GetSubMenu就是按索引选择一个,用于显示

CMenuHandlemenuHandle = menu.GetSubMenu(0);//此处是IDR_MENU1下拉出来的子菜单 menuHandle.TrackPopupMenuEx(TPM_LEFTBUTTON|TPM_LEFTALIGN|TPM_TOPALIGN,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam), m_hWnd);

  之前说过,菜单即可以在资源编辑器中事先设计好,也可以用代码动态创建。我们的示例代码是事先编辑好的。

动态创建的话,只需要调用CMenuHandle::CreatePopupMenu()创建,InsertMenuItem函数插入菜单项,最后TrackPopuMenu显示菜单即可,这几个函数都是CMenuHandle都是对同名API的简单封装,具体用法请参见WTL源码与MSDN

  编译,执行。右键在我们程序上单击,菜单出来了。

 

修改菜单为OwnerDraw风格

  既然是自绘,我们要想办法把菜单的风格改成OwnerDraw,不幸的是,VC的资源编辑器干不了这事儿,我们只能用代码修改,这也是MSDN上明确提到的。具体在哪儿改呢?

  有两个地方:一是在TrackPopupMenu函数调用之前;另一个方法是在WM_INITMENUPOPUP消息响应函数中;

修改menu为自绘时,需要一个一个的修改MenuItem,没有集中修改的方法。在一个菜单中,自绘型菜单项和非自绘型(也即windows标准类型)可以并存。

  修改为自绘菜单项时,用到一个结构体(MENUITEMINFO)和一个APISetMenuItemInfo),由于WTL中有同名封装类,我们代码里就不直接调用API了。

  此处示例选择在WM_INITMENUPOPUP消息响应函数中修改菜单项为自绘。代码示意如下:

CMenuHandle menuHandle = HMENU(wParam);

for (intn = 0; n < menuHandle.GetMenuItemCount(); n++)

{

CMenuItemInfo ItemTempInfo;

ItemTempInfo.fMask = MIIM_TYPE;

// 获取旧信息

menuHandle.GetMenuItemInfo(n, TRUE, &ItemTempInfo);

//设置自绘标志

ItemTempInfo.fType |= MFT_OWNERDRAW;

//保存到菜单项

menuHandle.SetMenuItemInfo(n, TRUE, &itemInfo);

}

  上边代码大意就是:拿到菜单的句柄,然后挨个把它的MenuItem修改为MFT_OWNERDRAW。如果一个MenuItem有下级菜单(如示例中的SubMenuC),那这个菜单项的MENUITEMINFO::hSubMenu句柄将不为空,代表着其下级菜单,用上边同样的代码就可修改下级菜单为自绘。无论菜单有几级,都是这一原理。

 

调整菜单宽高、正式绘制

  修改完自绘风格后,菜单在真正的“显示在显示器上”之前,windows会先后给我们的程序发WM_MEASUREITEM和WM_DRAWITEM消息。WM_MEASUREITEM中用于设置菜单项的宽度和高度,WM_DRAWITEM中进行正式的绘制工作。

  下面是WM_MEASUREITEM的响应代码

LPMEASUREITEMSTRUCTlpMeasureItemStruct = (LPMEASUREITEMSTRUCT)lParam;

if(lpMeasureItemStruct->CtlType == ODT_MENU)

{

lpMeasureItemStruct->itemHeight= 110;

lpMeasureItemStruct->itemWidth= 374;

}

  把lParam转换为MEASUREITEMSTRUCT指针,这是MSDN里说明的标准流程,微软就这样设计的,没什么道理可讲。接下来就是设置这个结构体中的字段,我们用到宽度和高度,这里设置完后,显示出来的菜单项就是这个宽和高。关于结构体的详细说明,请自行MSDN

  如果你不关心宽度和高度,打算直接用默认的值,可以不管WM_MEASUREITEM消息。接下来我们响应WM_DRAWITEM消息,在响应函数中编写绘制代码。原理很简单:WM_DRAWITEM消息的lParam转换成DRAWITEMSTRUCT的指针,这个结构体中的菜单项的dc和菜单项所在的矩形,如我们系列文件的第一篇所说,有了DCRect,万事具备了就!剩下的就是用GDI函数开始画了。

  下面是我们的绘制代码,在WM_DRAWITEM消息响应函数里。主要做了三件事:

  第一件:从DRAWITEMSTRUCT中取到菜单项的DC

  第二件:载入一个位图,示例位图是我事先准备好的,大家按自己喜好找张位图就行。

  第三件:用GDI函数画到DRAWITEMSTRUCT中指定的矩形范围内。

  这里我们用到所谓的双缓冲技术,就是先往一个内存DC上画,全都画完后,再把整个画完的图直接Copy到目标DC

LPDRAWITEMSTRUCTlpdis = (LPDRAWITEMSTRUCT)lParam;

BOOLbSelected = lpdis->itemState & ODS_SELECTED;

 

//转换成类,便于调用GDI函数

CDCHandlehdc = lpdis->hDC;

CDCmemDc;

memDc.CreateCompatibleDC(hdc);

CBitmapbmpBk;

HBITMAPhOld = NULL;

 

hdc.SetBkMode(TRANSPARENT);//画文字时背景透明

if(bSelected)

{

if(bmpBk.LoadBitmap(IDB_BITMAP1) == NULL)

{

strError.Format(_T("error%d"), GetLastError());

}

}

else

{

if(bmpBk.LoadBitmap(IDB_BITMAP2) == NULL)

{

strError.Format(_T("error%d"), GetLastError());

}

}

hOld= memDc.SelectBitmap(bmpBk);

SIZEsize;

bmpBk.GetSize(size);

hdc.StretchBlt(lpdis->rcItem.left,lpdis->rcItem.top, lpdis->rcItem.right-lpdis->rcItem.left,lpdis->rcItem.bottom-lpdis->rcItem.top, memDc, 0, 0, size.cx, size.cy ,SRCCOPY);

memDc.SelectBitmap(hOld);

  最后绘制的效果如本文开头所示。

 

  本例出于简单、直观的目的,没有什么封装的东西,几乎都是最直接的代码。涉及到的什么CDCHandleCDC都是WTL封装的类,里面都是直接调用的同名API,大家可以去看源码。

  至此,我们WMLMenu自绘的主要内容就讲完了。主体思路如前文所述:先修改菜单项为OwnerDraw风格,然后在WM_MEASUREITEMWM_DRAWITEM中设置宽高、具体绘制。

 

  示例绘制代码中直接画了一张图片在菜单项上,主要是为了简单起见,防止代码过长影响阅读、影响理解。

 

关于菜单文字的获取

  MENUITEMINFO结构体中有个dwItemData字段,它是个指针,指向的就是菜单原来显示的文字

 

小结

  其实不光是菜单自绘,只要是OwnerDraw风格的控件,都会涉及这两个消息,但要注意一点,就是有的控件自绘时不会触发WM_MEASUREITEM消息,只有WM_DRAWITEM消息,关于这一点,MSDN中有详细说明。在我们系列文章总结篇中,也将提及这一点,请大家留意,谨记!


  最后给出源码工程链接:http://pan.baidu.com/share/link?shareid=2407747361&uk=1980187499

  注意:要想编译运行,请自行google找办法搭建vs2008+wtl8.1的开发环境。



你可能感兴趣的:(WTL界面自绘)