原创作者:Billy Leverington
摘自网站:http://www.codeproject.com/KB/wtl/customdrawlist_wtl.aspx
翻译:
使用WTL自绘控件
介绍:
目前已经使用WTL工作一段时间了,我已经意识到关于它的文档是多么的匮乏。在我看来,迄今为止WTL是实现Win32最好的封装。与此同时,如果我们能在网站上多找一些相关文档,能更有助于我们对他的理解。
这篇文章描述如何通过WTL创建自绘控件。
我选择使用listView来和另一篇通过MFC实现相同功能的文章像联系,我选择使用这个简单的例子并为这个文档提供更多的例子来方便您对listView的改进和体会。
MyListControl:
使用WTL创建自绘控件非常容易。关键的技巧就是需要知道在你的代码中什么位置需要什么。我们从定义一个自己的CListViewCtrl说起,这样所有的控件的自绘都可以从一个地方开始。
class MyListView : public CWindowImpl<MyListView, CListViewCtrl>,
public CCustomDraw<MyListView>
{
public:
BEGIN_MSG_MAP(MyListView)
CHAIN_MSG_MAP(CCustomDraw<MyListView>)
END_MSG_MAP()
}
我们必须继承于CWindowImpl类,否则我们将得不到任何的窗体消息,这里需要重点注意的是我们必须还要继承于CCustomDraw。
这个宏定义 CHAIN_MSG_MAP(CCustomDraw
注:将CHAIN_MSG_MAP_ALT加入到BEGIN_MSG_MAP与END_MSG_MAP之间,那么窗体收到消息后,都会把消息传给CHAIN_MSG_MAP_ALT的模板参数处理。如果模板参数的ProcessWindowMessage方法处理了传递的消息,那么消息就不在往下传。
这个CHAIN_MSG_MAP() 宏定义是基类中处理缺省消息的路线。
如果你仔细查看AtlCtrls.h文件中有关于 CCustomDraw
<T
> (自绘控件模板类)的代码时你会发现,控件自绘通知处理是通过NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)来处理的,它把每一条消息分成了组件部分和调用函数部分。,下面这部分的函数可以根据自绘控件的实际情况进行重写。
DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/);
DWORD OnPostPaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/);
DWORD OnPreErase(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/);
DWORD OnPostErase(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/);
DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/);
DWORD OnItemPostPaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/);
DWORD OnItemPreErase(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/);
DWORD OnItemPostErase(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/);
我们将会重写这几个函数,并且实现每秒钟改变listView的背景颜色,使listView每行更容易读。
(注:其实还应该包括DWORD OnSubItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);这些函数默认都是返回CDRF_DODEFAULT,如果想自画控件或返回一个不同的值,就需要重载这些函数)
所以我们需要定义两个重载在我们的MyListViewCtrl工程中。
class MyListView : public CWindowImpl<MyListView, CListViewCtrl>,
public CCustomDraw<MyListView>
{
public:
BEGIN_MSG_MAP(MyListView)
CHAIN_MSG_MAP(CCustomDraw<MyListView>)
END_MSG_MAP()
DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
{
return CDRF_NOTIFYITEMDRAW;
}
DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
{
NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( lpNMCustomDraw );
// This is the prepaint stage for an item. Here's where we set the
// item's text color. Our return value will tell Windows to draw the
// item itself, but it will use the new color we set here for the background
COLORREF crText;
if ( (pLVCD->nmcd.dwItemSpec % 2) == 0 )
crText = RGB(200,200,255);
else
crText = RGB(255,255,255);
// Store the color back in the NMLVCUSTOMDRAW struct.
pLVCD->clrTextBk = crText;
// Tell Windows to paint the control itself.
return CDRF_DODEFAULT;
}
};
说明:
1.自定义绘制返回标志包括以下几项:
自定义绘制返回标志 |
含义 |
CDRF_DEFAULT |
指示控件自行绘制。该值为默认值,不应该将它与其他值组合在一起。 |
CDRF_SKIPDEFAULT |
用于指定控件根本不进行任何绘制。 |
CDRF_NEWFONT |
当代码更改绘制项/子项的字体时使用。 |
CDRF_NOTIFYPOSTPAINT |
使通知信息在控件或每个项/子项绘制后发送。 |
CDRF_NOTIFYITEMDRAW |
指出项(或子项)将进行绘制。注意,它下面的值与 CDRF_NOTIFYSUBITEMDRAW 相同。 |
CDRF_NOTIFYSUBITEMDRAW |
指出子项(或项)将进行绘制。注意,它下面的值与 CDRF_NOTIFYITEMDRAW 相同。 |
CDRF_NOTIFYPOSTERASE |
当删除控件后需要通知代码时使用。 |
2.
如果绘图操作不需要父窗口参与,可以使用该控件的 ON_NOTIFY_REFLECT 宏处理它的NM_CUSTOMDRAW 消息。
它的处理函数的参数中包含 NMHDR,在 CUSTOMDRAW 的通知下 NMHDR 可以被转换成为 NMLVCUSTOMDRAW 结构,该结构包含了列表控件中需要自绘区域的全部信息:
typedef struct tagNMLVCUSTOMDRAW
{
NMCUSTOMDRAW nmcd; // 包含客户自绘控件信息的结构
COLORREF clrText; // 列表视图显示文字的颜色
COLORREF clrTextBk; // 列表视图显示文字的背景色
} NMLVCUSTOMDRAW, *LPNMLVCUSTOMDRAW;
创建这个控件:
正如大多数你在MSDN中查找的例子和其它文章中所说的那样,创建一个控件作为一个资源连接到你的代码中。而在这里却不同,我将直接在主窗口中创建这个列表框。
我们需要把下面的所有代码添加到自动生成的CMainFrame类中,从而创建我们的自绘控件。看一下CHAIN_MSG_MAP_MEMBER(m_listView)宏,如果忽略它,你的类将不能得到NM_CUSTOMDRAW的消息。这个宏的消息循环路线是从默认的数据成员消息图中开始的,这也正是我们想要的。
作为一个说明,我们可以处理NM_CUSTOMDRAW消息在这个基础上。我们可以检查控件的ID号和处理绘制的合理进程。这个也许在很多案例中都适用,与此同时,它更接近于我们需要做的,使我们的代码清楚而整洁。
class CMainFrame : public CFrameWindowImpl<CMainFrame>,
public CUpdateUI<CMainFrame>,
public CMessageFilter, public CIdleHandler
{
public:
// ...
// code left out for readability
// ...
BEGIN_MSG_MAP(CMainFrame)
// ...
// code left out for readability
// ...
CHAIN_MSG_MAP_MEMBER(m_listView)
END_MSG_MAP()
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/,
LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
// code left out for readability
// create a list box
RECT r = {0,0,182,80};
m_listView.Create(m_hWnd,r,CListViewCtrl::GetWndClassName(),
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN | LVS_REPORT, WS_EX_CLIENTEDGE);
// fill in the headers
m_listView.AddColumn(_T("Symbol"), 0);
m_listView.AddColumn(_T("Company "), 1);
m_listView.AddColumn(_T("Price "), 2);
m_listView.AddItem(0,0,"VOD");
m_listView.AddItem(0,1,"Vodaphone Airtouch");
m_listView.AddItem(0,2,"252.25");
m_listView.AddItem(1,0,"LAT");
m_listView.AddItem(1,1,"Lattice Group");
m_listView.AddItem(1,2,"149.9");
m_listView.AddItem(2,0,"CLA");
m_listView.AddItem(2,1,"Claims Direct");
m_listView.AddItem(2,2,"132.50");
return 0;
}
// ...
// code left out for readability
// ...
public :
MyListView m_listView;
};
在这个OnCreate函数中,我们只是创建这个ListView的成员变量,并且添加一些数据,任务完成。