WTL按钮自绘机制

这几天想自己写一个WTL的SkinButton,找了好长时间的资料才搞明白。

在搜索资料在过程中发现,大家都是知道怎么实现,贴出了一大段代码,但是很多人并不明白实现窗体自绘的原理。下面就如何实现窗体自绘我给出自己的解法:

1、第一步就是控件的子类化,这个是用来让自己写的类接受window消息的。这个就不具体讲解了,

可以参考:

http://www.cnblogs.com/wdhust/archive/2010/09/18/1830097.html

http://lordtang.javaeye.com/blog/658489

http://fengshao1020.spaces.live.com/blog/cns!58764EF0C1622309!338.entry

2、在网上大家都说要继承COwnerDraw或CCustomDraw,两者的区别说的很明白,但是对于其原理就没有说明白。

最初我想的很简单,既然实现窗体自绘只需要我写的按钮类,处理WM_DRAWITEM消息就可以了,但是

查了一下MSDN才知道,当子窗体需要自绘的时候,子窗口会发送WM_DRAWITEM消息给父窗口,也就是说,这个消息是子窗口自动发送给父窗口让父窗口来处理的。这样我们写的按钮类就接收不到这个消息了,也就无法自绘了。

互联网上的资料说通过在父窗口的消息映射最后添加  REFLECT_NOTIFICATIONS()
就可以了,查了一下这个宏定义的源码才发现,他是将一些特定的消息原封不动的在传递给子窗口。也就是说当父窗口受到WM_DRAWITEM消息时,会将这个消息反向传递给发送方,即谁发给父窗口,父窗口在原封不动的发送给谁。

在查看COwnerDraw的源码发现,其消息映射中有下面一个映射,

 MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)

显然父窗口反向传递消息时候,消息的参数没有变,只不过是消息标志变了,原来是WM_现在是OCM_

下面是COwnerDraw类的一些代码

 LRESULT OnDrawItem(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
 {
  T* pT = static_cast(this);
  pT->SetMsgHandled(TRUE);
  pT->DrawItem((LPDRAWITEMSTRUCT)lParam);
  bHandled = pT->IsMsgHandled();
  return (LRESULT)TRUE;
 }

。。。。。。。。。

// Overrideables
 void DrawItem(LPDRAWITEMSTRUCT /*lpDrawItemStruct*/)
 {
  // must be implemented
  ATLASSERT(FALSE);
 }

研究才发现,如果继承了这个类,就必须重载 DrawItem,否则就会出现编译错,讲到这里大家就应该明白窗体自绘的原理了吧。好了我们梳理一下思路,窗体自绘原理如下:

 

(1)想实现控件自绘,控件就必须有处理WM_DRAWITEM消息的能力,但是这个消息是子控件用来通知父窗体的,所以必须有某种机制让父窗体将这个消息反向发送给子控件。这个可以用  REFLECT_NOTIFICATIONS()宏来实现,其实也就是调用SendMessage而已。

(2)父窗体反向传递回来的消息是以OCM开头的,所以子控件应该处理OCM_DRAWITEM消息,这样子控件的消息映射中应该添加MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)
当然映射的末尾最好添加DEFAULT_REFLECTION_HANDLER()

(3)在函数

 LRESULT
  OnDrawItem(
  UINT uMsg,
  WPARAM wParam,
  LPARAM lParam,
  BOOL& bHandled);

中实现控件的自绘。

(4)至此一个完整的控件自绘的功能就实现了,而且我们也并没有继承复杂的COwnerDraw这个类,当然在更复杂的控件自绘中也可以继承这个类,这样可以简化编码。

 

 

 

 

 

你可能感兴趣的:(原创)