这几天想自己写一个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
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这个类,当然在更复杂的控件自绘中也可以继承这个类,这样可以简化编码。