65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 WTL 的自定义绘制控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.08/5 (8投票s)

2000年12月1日

viewsIcon

295322

downloadIcon

3845

如何使用 WTL 创建自定义控件

Sample Image - customdrawlist_wtl.jpg

引言

最近用 WTL 做了一些工作,我发现它缺少很多文档。 我认为 WTL 是目前对 Win32 进行封装的最好方案,网络上的文档越多,对我们所有人就越有好处。

本文介绍了使用 WTL 创建自定义绘制控件的方法。

我选择使用 listview 控件,并结合文章 使用 CustomDraw 的列表控件的技巧,该文章演示了使用 MFC 的相同概念。 我选择保持简单,如果您需要更多关于如何改进列表视图外观的示例,请参考该文档。

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<MyListView>) 宏一起,使我们能够整齐地覆盖 CListViewContol 的绘制过程。

CHAIN_MSG_MAP() 宏将消息路由到基类中的默认消息映射。

如果你查看 AtlCtrls.h 内部的代码中的 CCustomCtrl<T> 类,你会看到它定义了宏 NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw),它处理自定义绘制通知。 它将消息分割成组件部分,并为每个部分调用一个函数。 这些是可以重载以处理控件绘制的函数。

// Overrideables
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 具有条纹外观,使其更容易阅读行。

所以在我们的 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;
    }
};

创建控件

你在 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 数据成员上调用 create 并填充一些演示数据。 任务完成 ...

© . All rights reserved.