這幾天想自己寫一個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<T*>(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這個類,當然在更復雜的控件自繪中也可以繼承這個類,這樣可以簡化編碼。