钢琴曲欢乐颂改编:WM_MEASUREITEM

来源:百度文库 编辑:中财网 时间:2024/05/07 02:06:25

CWnd::OnMeasureItem

afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );

参数: nIDCtl 控件的ID。 lpMeasureItemStruct 指向一个MEASUREITEMSTRUCT数据结构,其中包含自画控件的大小。
说明:
当控件被创建的时候,框架为自画按钮、组合框、列表框或菜单项调用这个成员函数。
重载这个函数并填充lpMeasureItemStruct指向的MEASUREITEMSTRUCT数据结构,然后返回;这将通知Windows控件的大小,并使Windows能够正确地处理控件的用户交互。
如果列表框或组合框是用LBS_OWNERDRAWVARIABLE或CBS_OWNERDRAWVARIA_BLE风格创建的,则框架为控件中的每一个项调用这个函数;否则这个函数只被调用一次。
在发送WM_INITDIALOG消息之前,Windows为用OWNERDRAWFIXED风格创建的组合框和列表框的拥有者发出对OnMeasureItem的调用。其结果是,当拥有者接收到这个调用时,Windows还没有确定在控件中使用的字体的高度和宽度;需要这些值的函数调用和计算应该发生在应用程序或库的主函数中。
如果要测量的的项是CMenu,CListBox或CComboBox对象,则将调用适当的类的虚函数MeasureItem。重载适当的控件类的MeasureItem成员函数以计算并设置每个项的大小。
仅当控件类是在运行时创建,或者它是用LBS_OWNERDRAWVARIABLE或CBS_OWNERDRAWVARIABLE风格创建的时候,OnMeasureItem才会被调用。这是因为WM_MEASUREITEM消息时在控件创建过程的早期被发送的。
如果你使用DDX_Control,SubclassDlgItem或SubclassWindow进行了子类化,则子类化过程通常发生在创建过程之后。因此,在控件的OnChildNotify函数中无法处理WM_MEASUREITEM消息,这是MFC用来实现ON_WM_MEASUREITEM_REFLECT的机制。
注意 框架调用这个成员函数以允许你的应用程序处理一个Windows消息。传递给你的成员函数的参数反映了接收到消息时框架接收到的参数。如果你调用了这个函数的基类实现,则该实现将使用最初传递给消息的参数(而不是你提供给这个函数的参数)。

仅仅WM_DRAWITEM还是不够的,对于一些特殊的控件,如ListBox,系统在发送WM_DRAWITEM消息前,还发送WM_MEASUREITEM消息,需要你设置ListBox中每个项目的高度。

WM_DRAWITEM的映射函数原型如下:

afx_msg void OnMeasureItem( int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct );

nIDCtl 该控件的ID,如果该元素为菜单,则nIDCtl为0

lpMeasureItemStruct指向MEASUREITEMSTRUCT结构对象的指针,MEASUREITEMSTRUCT的结构定义如下:

typedef struct tagMEASUREITEMSTRUCT
  1. {
  2. UINT CtlType;
  3. UINT CtlID;
  4. UINT itemID;
  5. UINT itemWidth;
  6. UINT itemHeight;
  7. DWORD itemData
  8. } MEASUREITEMSTRUCT;

CtlType指定了控件的类型,其取值如表6所示:

类型值 含义

ODT_COMBOBOX 组合框控件

ODT_LISTBOX 列表框控件

ODT_MENU 菜单项

表6 CtlType的类型值与含义

CtlID 指定自绘控件的ID值,该成员不适用于菜单项

itemID表示菜单项ID,也可以表示可变高度的列表框或组合框中某项的索引值。该成员不适用于固定高度的列表框或组合框。

itemWidth 指定菜单项的宽度

itemHeight指定菜单项或者列表框中某项的的高度,最大值为255

itemData

对于菜单项,该成员的取值为由CMenu::AppendMenu、CMenu::InsertMenu、CMenu::ModifyMenu等函数传递给菜单的值。

对于列表框或这组合框,该成员的取值为由ComboBox::AddString、CComboBox::InsertString、CListBox::AddString或者CListBox::InsertString等函数传递给控件的值。

图示出了OnMeasureItem的效果:

图10 利用WM_MEASUREITEM消息美化界面

相应的OnMeasureItem()实现如下:

  1. void CUi7Dlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
  2. {
  3. if(nIDCtl == IDC_COLOR_PICKER)
  4. {
  5. //设定高度为30
  6. lpMeasureItemStruct->itemHeight = 30;
  7. return;
  8. }
  9. CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
  10. }

同样别忘了指定列表框的Owner draw属性:

图11 指定下拉框的Owner draw属性

3.3.6 NM_CUSTOMDRAW

大家也许熟悉WM_NOTIFY,控件通过WM_NOTIFY向父窗口发送消息。在WM_NOTIFY消息体中,部分控件会发送NM_CUSTOMDRAW告诉父窗口自己需要绘图。

可以反射NM_CUSTOMDRAW消息,如:

ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)

afx_msg void OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult);

参数:

pNMHDR 说到底只是一个指针,大多数情况下它指向一个NMHDR结构对象,NMHDR结构如下:

typedef struct tagNMHDR
  1. {
  2. HWND hwndFrom;
  3. UINT idFrom;
  4. UINT code;
  5. } NMHDR;

其中:

hwndFrom 发送方控件的窗口句柄

idFrom 发送方控件的ID

code 通知代码

对于某些控件来说,pNMHDR则会解释成其它内容更丰富的结构对象的指针,如:对于列表控件来说,pNMHDR常常指向一个NMCUSTOMDRAW对象,NMCUSTOMDRAW结构如下:

typedef struct tagNMCUSTOMDRAWINFO
  1. {
  2. NMHDR hdr;
  3. DWORD dwDrawStage;
  4. HDC hdc;
  5. RECT rc;
  6. DWORD dwItemSpec;
  7. UINT uItemState;
  8. LPARAM lItemlParam;
  9. } NMCUSTOMDRAW, FAR * LPNMCUSTOMDRAW;

hdr NMHDR对象

dwDrawStage 当前绘制状态,其取值如表7所示:

类型值 含义

CDDS_POSTERASE 擦除循环结束

CDDS_POSTPAINT 绘制循环结束

CDDS_PREERASE 准备开始擦除循环

CDDS_PREPAINT 准备开始绘制循环

CDDS_ITEM 指定dwItemSpec, uItemState, lItemlParam参数有效

CDDS_ITEMPOSTERASE 列表项擦除结束

CDDS_ITEMPOSTPAINT 列表项绘制结束

CDDS_ITEMPREERASE 准备开始列表项擦除

CDDS_ITEMPREPAINT 准备开始列表项绘制

CDDS_SUBITEM 指定列表子项

表7 dwDrawStage的类型值与含义

hdc指定了绘制操作所使用的设备环境。

rc指定了将被绘制的矩形区域。

dwItemSpec 列表项的索引

uItemState 当前列表项的状态,其取值如表8所示:

类型值 含义

CDIS_CHECKED 标记状态。

CDIS_DEFAULT 默认状态。

CDIS_DISABLED 禁止状态。

CDIS_FOCUS 焦点状态。

CDIS_GRAYED 灰化状态。

CDIS_SELECTED 选中状态。

CDIS_HOTLIGHT 热点状态。

CDIS_INDETERMINATE 不定状态。

CDIS_MARKED 标注状态。

表8 uItemState的类型值与含义

lItemlParam 当前列表项的绑定数据

pResult 指向状态值的指针,指定系统后续操作,依赖于dwDrawStage:

当dwDrawStage为CDDS_PREPAINT,pResult含义如表9所示:

类型值 含义

CDRF_DODEFAULT 默认操作,即系统在列表项绘制循环过程不再发送NM_CUSTOMDRAW。

CDRF_NOTIFYITEMDRAW 指定列表项绘制前后发送消息。

CDRF_NOTIFYPOSTERASE 列表项擦除结束时发送消息。

CDRF_NOTIFYPOSTPAINT 列表项绘制结束时发送消息。

表9 pResult的类型值与含义(一)

当dwDrawStage为CDDS_ITEMPREPAINT,pResult含义如表10所示:

类型值 含义

CDRF_NEWFONT 指定后续操作采用应用中指定的新字体。

CDRF_NOTIFYSUBITEMDRAW 列表子项绘制时发送消息。

CDRF_SKIPDEFAULT 系统不必再绘制该子项。

表10 pResult的类型值与含义(二)

以下是一个利用NM_CUSTOMDRAW消息绘制出的多色列表框的例子:

图12 利用NM_CUSTOMDRAW消息美化界面

对应代码如下:

void CCoolList::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
  1. {
  2. //类型安全转换
  3. NMLVCUSTOMDRAW* pLVCD = reinterpret_cast(pNMHDR);
  4. *pResult = 0;
  5. //指定列表项绘制前后发送消息
  6. if(CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage)
  7. {
  8. *pResult = CDRF_NOTIFYITEMDRAW;
  9. }
  10. else if(CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage)
  11. {
  12. //奇数行
  13. if(pLVCD->nmcd.dwItemSpec % 2)
  14. pLVCD->clrTextBk = RGB(255, 255, 128);
  15. //偶数行
  16. else
  17. pLVCD->clrTextBk = RGB(128, 255, 255);
  18. //继续
  19. *pResult = CDRF_DODEFAULT;
  20. }
  21. }

注意到上例采取了3.1所推荐的第2种实现方法,派生了一个新类CCoolList。

3.4 使用MFC类的虚函数机制

修改Windows界面,除了从Windows消息机制下功夫,也可以从MFC类下功夫,这应该得益于类的虚函数机制。为了防止诸如“面向对象技术”等术语在此泛滥,以下仅举一段代码作为例子:

void CView::OnPaint()
  1. {
  2. // standard paint routine
  3. CPaintDC dc(this);
  4. OnPrepareDC(&dc);
  5. OnDraw(&dc);
  6. }

这是MFC中viewcore.cpp中的源代码,很多读者总不明白OnDraw()和OnPaint()之间的关系,从以上的代码中很容易看出,CView的WM_PAINT消息响应函数OnPaint()会自动调用CView::OnDraw()。而作为开发者的用户,可以通过简单的OnDraw()的重载实现对WM_PAINT的处理。所以说,对MFC类的虚函数的重载是对消息机制的扩展。

以下列出了与界面美化相关的虚函数,参数说明略去:

CButton::DrawItem

CCheckListBox::DrawItem

CComboBox::DrawItem

CHeaderCtrl::DrawItem

CListBox::DrawItem

CMenu::DrawItem

CStatusBar::DrawItem

CStatusBarCtrl::DrawItem

CTabCtrl::DrawItem

virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );

Owner draw元素自绘函数

很显然,位图菜单都是通过这个DrawItem画出来的。限于篇幅,在此不再附以例程。