科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件MFC程序员的WTL指南之高级界面类

MFC程序员的WTL指南之高级界面类

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文将介绍一些新类实现高级界面特性新类:控件自画和自定外观控件

作者:lithe 来源:blog 2007年10月19日

关键字: MFC WTL 高级界面

  • 评论
  • 分享微博
  • 分享邮件
上一篇文章我们介绍了一些与对话框和控件有关的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类那样使用这个函数。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章