下载本文示例代码
在上一篇文章我们介绍了一些与对话框和控件有关的WTL的特性,它们和MFC的相应的类作用相同。本文将介绍一些新类实现高级界面特性新类:控件自画和自定外观控件,新的WTL控件,UI updating和对话框数据验证(DDV)。 特别的自画和外观定制类 由于自画和定制外观控件在图形用户界面中是很常用的手段,所以WTL提供了几个嵌入类来完成这些令人厌烦的工作。我接着就会介绍它们,事实上我们在上一个例子工程ControlMania2的结尾部分已经这么做了。如果你正随着我的讲解用应用程序生成向导创建新工程,请不要忘了使用无模式对话框,为了使正常工作必须使用无模式对话框,我会在对话框中控件的UI Updating部分详细解释为什么这样作。 COwnerDraw 控件的自画需要响应四个消息:WM_MEASUREITEM, WM_DRAWITEM, WM_COMPAREITEM, 和WM_DELETEITEM,在atlframe.h头文件中定义的COwnerDraw类可以简化这些工作,使用这个类就不需要处理这四个消息,你只需将消息链入COwnerDraw,它会调用你的类中的重载函数。 如何将消息链入COwnerDraw取决与你是否将消息反射给控件,两种方法有些不同。下面是COwnerDraw类的消息映射链,它使得两种方法的差别更加明显:
template <class T> class COwnerDraw{ public: BEGIN_MSG_MAP(COwnerDraw<T>) MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem) MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem) MESSAGE_HANDLER(WM_COMPAREITEM, OnCompareItem) MESSAGE_HANDLER(WM_DELETEITEM, OnDeleteItem) ALT_MSG_MAP(1) MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem) MESSAGE_HANDLER(OCM_MEASUREITEM, OnMeasureItem) MESSAGE_HANDLER(OCM_COMPAREITEM, OnCompareItem) MESSAGE_HANDLER(OCM_DELETEITEM, OnDeleteItem) END_MSG_MAP()}; 注意,消息映射链的主要部分处理WM_*消息,而ATL部分处理反射的消息,OCM_*。自画的通知消息就像WM_NOTIFY消息一样,你可以在父窗口处理它们,也可以将它们反射会控件,如果你使用前一种方法,消息被直接链入COwnerDraw:
class CSomeDlg : public COwnerDraw<CSomeDlg>, ...{ BEGIN_MSG_MAP(CSomeDlg) //... CHAIN_MSG_MAP(COwnerDraw<CSomeDlg>) END_MSG_MAP() void DrawItem ( LPDRAWITEMSTRUCT lpdis );}; 当然,如果你想要控件自己处理这些消息,你需要使用CHAIN_MSG_MAP_ALT宏将消息链入ALT_MSG_MAP(1)部分:
class CSomeButtonImpl : public COwnerDraw<CSomeButtonImpl>, ...{ BEGIN_MSG_MAP(CSomeButtonImpl) //... CHAIN_MSG_MAP_ALT(COwnerDraw<CSomeButtonImpl>, 1) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() void DrawItem ( LPDRAWITEMSTRUCT lpdis );}; COwnerDraw类将对消息传递的参数展开,然后调用你的类中的实现函数。上面的例子中,我们自己的类实现DrawItem()函数,当有WM_DRAWITEM或OCM_DRAWITEM消息被链入COwnerDraw时,这个函数就会被调用。你可以重载的方法有:
void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);int CompareItem(LPCOMPAREITEMSTRUCT lpCompareItemStruct);void DeleteItem(LPDELETEITEMSTRUCT lpDeleteItemStruct); 如果你不想处理某个消息,你可以调用SetMsgHandled(false),消息会被传递给消息映射链中的其他响应者。SetMsgHandled()事实上是COwnerDraw类的成员函数,但是它的作用和在BEGIN_MSG_MAP_EX()中使用SetMsgHandled()一样。 对于ControlMania2,它从ControlMania1中的树控件开始,添加了自画按钮处理反射的WM_DRAWITEM消息,下面是资源编辑器中的新按钮:
现在我们需要一个新类实现自画按钮:
class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton>,public COwnerDraw<CODButtonImpl>{ public: BEGIN_MSG_MAP_EX(CODButtonImpl) CHAIN_MSG_MAP_ALT(COwnerDraw<CODButtonImpl>, 1) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() void DrawItem ( LPDRAWITEMSTRUCT lpdis );}; DrawItem()使用了像BitBlt()这样的GDI函数向按钮的表面画位图,代码应该很容易理解,因为WTL使用的类名和函数名都和MFC类似。
void CODButtonImpl::DrawItem ( LPDRAWITEMSTRUCT lpdis ){ // NOTE: m_bmp is a CBitmap init''ed in the constructor. CDCHandle dc = lpdis->hDC; CDC dcMem; dcMem.CreateCompatibleDC ( dc ); dc.SaveDC(); dcMem.SaveDC(); // Draw the button''s background, red if it has the focus, blue if not. if ( lpdis->itemState & ODS_FOCUS ) dc.FillSolidRect ( &lpdis->rcItem, RGB(255,0,0) ); else dc.FillSolidRect ( &lpdis->rcItem, RGB(0,0,255) ); // Draw the bitmap in the top-left, or offset by 1 pixel if the button // is clicked. dcMem.SelectBitmap ( m_bmp ); if ( lpdis->itemState & ODS_SELECTED ) dc.BitBlt ( 1, 1, 80, 80, dcMem, 0, 0, SRCCOPY ); else dc.BitBlt ( 0, 0, 80, 80, dcMem, 0, 0, SRCCOPY ); dcMem.RestoreDC(-1); dc.RestoreDC(-1);} 我们的按钮看起来是这个样子:
CCustomDraw CCustomDraw类使用和COwnerDraw类相同的方法处理NM_CUSTOMDRAW消息,对于自定绘制的每个阶段都有相应的重载函数:
DWORD OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnPreErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnPostErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnItemPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnItemPreErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnItemPostEraset(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnSubItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD); 这些函数默认都是返回CDRF_DODEFAULT,如果想自画控件或返回一个不同的值,就需要重载这些函数: 你可能注意到上面的屏幕截图将“道恩”(Dawn:女名)显示成绿色,这是因为CBuffyTreeCtrl将消息链入CCustomDraw并重载了OnPrePaint()和OnItemPrePaint()方法。向树控件中添加节点时,节点的item data字段被设置成1,OnItemPrePaint()检查这个值,然后改变文字的颜色。
DWORD CBuffyTreeCtrl::OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD){ return CDRF_NOTIFYITEMDRAW;}DWORD CBuffyTreeCtrl::OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD){ if ( 1 == lpNMCD->lItemlParam ) pnmtv->clrText = RGB(0,128,0); return CDRF_DODEFAULT;} CCustomDraw类也有SetMsgHandled()函数,你可以像在COwnerDraw类那样使用这个函数。共5页。 1 2 3 4 5 :
在上一篇文章我们介绍了一些与对话框和控件有关的WTL的特性,它们和MFC的相应的类作用相同。本文将介绍一些新类实现高级界面特性新类:控件自画和自定外观控件,新的WTL控件,UI updating和对话框数据验证(DDV)。 特别的自画和外观定制类 由于自画和定制外观控件在图形用户界面中是很常用的手段,所以WTL提供了几个嵌入类来完成这些令人厌烦的工作。我接着就会介绍它们,事实上我们在上一个例子工程ControlMania2的结尾部分已经这么做了。如果你正随着我的讲解用应用程序生成向导创建新工程,请不要忘了使用无模式对话框,为了使正常工作必须使用无模式对话框,我会在对话框中控件的UI Updating部分详细解释为什么这样作。 COwnerDraw 控件的自画需要响应四个消息:WM_MEASUREITEM, WM_DRAWITEM, WM_COMPAREITEM, 和WM_DELETEITEM,在atlframe.h头文件中定义的COwnerDraw类可以简化这些工作,使用这个类就不需要处理这四个消息,你只需将消息链入COwnerDraw,它会调用你的类中的重载函数。 如何将消息链入COwnerDraw取决与你是否将消息反射给控件,两种方法有些不同。下面是COwnerDraw类的消息映射链,它使得两种方法的差别更加明显:
template <class T> class COwnerDraw{ public: BEGIN_MSG_MAP(COwnerDraw<T>) MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem) MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem) MESSAGE_HANDLER(WM_COMPAREITEM, OnCompareItem) MESSAGE_HANDLER(WM_DELETEITEM, OnDeleteItem) ALT_MSG_MAP(1) MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem) MESSAGE_HANDLER(OCM_MEASUREITEM, OnMeasureItem) MESSAGE_HANDLER(OCM_COMPAREITEM, OnCompareItem) MESSAGE_HANDLER(OCM_DELETEITEM, OnDeleteItem) END_MSG_MAP()}; 注意,消息映射链的主要部分处理WM_*消息,而ATL部分处理反射的消息,OCM_*。自画的通知消息就像WM_NOTIFY消息一样,你可以在父窗口处理它们,也可以将它们反射会控件,如果你使用前一种方法,消息被直接链入COwnerDraw:
class CSomeDlg : public COwnerDraw<CSomeDlg>, ...{ BEGIN_MSG_MAP(CSomeDlg) //... CHAIN_MSG_MAP(COwnerDraw<CSomeDlg>) END_MSG_MAP() void DrawItem ( LPDRAWITEMSTRUCT lpdis );}; 当然,如果你想要控件自己处理这些消息,你需要使用CHAIN_MSG_MAP_ALT宏将消息链入ALT_MSG_MAP(1)部分:
class CSomeButtonImpl : public COwnerDraw<CSomeButtonImpl>, ...{ BEGIN_MSG_MAP(CSomeButtonImpl) //... CHAIN_MSG_MAP_ALT(COwnerDraw<CSomeButtonImpl>, 1) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() void DrawItem ( LPDRAWITEMSTRUCT lpdis );}; COwnerDraw类将对消息传递的参数展开,然后调用你的类中的实现函数。上面的例子中,我们自己的类实现DrawItem()函数,当有WM_DRAWITEM或OCM_DRAWITEM消息被链入COwnerDraw时,这个函数就会被调用。你可以重载的方法有:
void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);int CompareItem(LPCOMPAREITEMSTRUCT lpCompareItemStruct);void DeleteItem(LPDELETEITEMSTRUCT lpDeleteItemStruct); 如果你不想处理某个消息,你可以调用SetMsgHandled(false),消息会被传递给消息映射链中的其他响应者。SetMsgHandled()事实上是COwnerDraw类的成员函数,但是它的作用和在BEGIN_MSG_MAP_EX()中使用SetMsgHandled()一样。 对于ControlMania2,它从ControlMania1中的树控件开始,添加了自画按钮处理反射的WM_DRAWITEM消息,下面是资源编辑器中的新按钮:
现在我们需要一个新类实现自画按钮:
class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton>,public COwnerDraw<CODButtonImpl>{ public: BEGIN_MSG_MAP_EX(CODButtonImpl) CHAIN_MSG_MAP_ALT(COwnerDraw<CODButtonImpl>, 1) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP() void DrawItem ( LPDRAWITEMSTRUCT lpdis );}; DrawItem()使用了像BitBlt()这样的GDI函数向按钮的表面画位图,代码应该很容易理解,因为WTL使用的类名和函数名都和MFC类似。
void CODButtonImpl::DrawItem ( LPDRAWITEMSTRUCT lpdis ){ // NOTE: m_bmp is a CBitmap init''ed in the constructor. CDCHandle dc = lpdis->hDC; CDC dcMem; dcMem.CreateCompatibleDC ( dc ); dc.SaveDC(); dcMem.SaveDC(); // Draw the button''s background, red if it has the focus, blue if not. if ( lpdis->itemState & ODS_FOCUS ) dc.FillSolidRect ( &lpdis->rcItem, RGB(255,0,0) ); else dc.FillSolidRect ( &lpdis->rcItem, RGB(0,0,255) ); // Draw the bitmap in the top-left, or offset by 1 pixel if the button // is clicked. dcMem.SelectBitmap ( m_bmp ); if ( lpdis->itemState & ODS_SELECTED ) dc.BitBlt ( 1, 1, 80, 80, dcMem, 0, 0, SRCCOPY ); else dc.BitBlt ( 0, 0, 80, 80, dcMem, 0, 0, SRCCOPY ); dcMem.RestoreDC(-1); dc.RestoreDC(-1);} 我们的按钮看起来是这个样子:
CCustomDraw CCustomDraw类使用和COwnerDraw类相同的方法处理NM_CUSTOMDRAW消息,对于自定绘制的每个阶段都有相应的重载函数:
DWORD OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnPreErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnPostErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnItemPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnItemPreErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnItemPostEraset(int idCtrl, LPNMCUSTOMDRAW lpNMCD);DWORD OnSubItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD); 这些函数默认都是返回CDRF_DODEFAULT,如果想自画控件或返回一个不同的值,就需要重载这些函数: 你可能注意到上面的屏幕截图将“道恩”(Dawn:女名)显示成绿色,这是因为CBuffyTreeCtrl将消息链入CCustomDraw并重载了OnPrePaint()和OnItemPrePaint()方法。向树控件中添加节点时,节点的item data字段被设置成1,OnItemPrePaint()检查这个值,然后改变文字的颜色。
DWORD CBuffyTreeCtrl::OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD){ return CDRF_NOTIFYITEMDRAW;}DWORD CBuffyTreeCtrl::OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD){ if ( 1 == lpNMCD->lItemlParam ) pnmtv->clrText = RGB(0,128,0); return CDRF_DODEFAULT;} CCustomDraw类也有SetMsgHandled()函数,你可以像在COwnerDraw类那样使用这个函数。共5页。 1 2 3 4 5 :
下载本文示例代码
MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类MFC程序员的WTL指南之高级界面类