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

使用设计模式(策略)的自绘列表控件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2018年6月1日

CPOL

2分钟阅读

viewsIcon

12318

downloadIcon

911

本文介绍了如何使用策略模式创建自定义控件。

引言

我发现我无法在每个项目中有效地重用列表控件。

设计模式的首要原则是区分哪些在改变,哪些没有改变。

我将这个原则应用到了列表控件。

背景

当您开发 Android 或 iOS 时,您经常使用 Handler。

这是一种典型的策略模式。

如有必要,您也可以参考 GoF 的设计模式书籍。

然后,我们需要找出列表控件中哪些在改变,哪些没有改变。

我们必须专注于改变。

列表控件中哪些是不变的?

列表控件自身的功能(显示列表)不会改变。

可能存在以下情况

  • 添加/删除项目
  • 更改标题控件的高度
  • 更改项目的高度
  • 更改字体
  • 虚拟列表
  • ... 列表控件的其他功能...

列表控件中哪些是改变的?

您在列表控件中进行哪些更改?

显示数据的部分会改变。

  • 打印图标
  • 打印多行
  • 更改所选列的颜色
  • ... 输出用于其他项目目的的数据

大多数是这些。这是我们需要关注的地方。

Using the Code

源配置

  • FlatHeader.h/cpp
  • ImgHelper.h/cpp
  • ListCtrlEx.h/cpp
  • ListHandlerEx.h/cpp

其中,FlatHeaderImghelperListCtrlEx 是不变的部分。 也就是说,它实现了列表控件的功能部分。

ListHandlerEx 提供了处理改变部分(数据)的接口和基本处理程序。

现在让我们看看处理变化部分的接口。

下面的源代码是 ListHandlerEx.h 文件的内容。

#ifndef __LIST_HANDLER_EX__
#define __LIST_HANDLER_EX__

namespace MFC {
    namespace UI {

class CListCtrlEx;
/// \brief <pre>
/// The IListHandlerEx control handles changes to the UI and 
/// handles notifications as a CListCtrlEx control
/// To change the UI, register it through CListCtrlEx :: SetHandler ().
/// Once you register the handler, you can control the UI by each step.
///
/// If the Handler is called, it will be called when the Item in the List is drawn
/// It will be called at each step below.
///
/// 1. DrawItem BG: List Row Called when drawing a background (one whole row)
/// 2. DrawStateIconImage: Called when drawing a State Image List on a List Control.
/// 3. DrawSmallIconImage: Called when drawing a Small Imget List on a List Control.
/// 4. DrawSubItem: This is called when you have finished the above steps 1, 2, and 3 
/// and then draw each column.
/// 
/// * The Onxxxxx method is the part for event handling in the list.
/// * If you set a_bDrawDefault = true, CDefaultListHandler will do the processing.
/// 
/// * DrawEmptyMsg () is the content to be dispatched if there is no item in the list, 
/// and if it returns false, the default message set in CListCtrlEx is output.
///
/// * CDefaultListHandler Provides a default handler, and CDefaultListHandler is not inherited.
/// Only the CListCtrlEx control is accessible.
///
/// ---------------------------------------------------------------------------
/// Date          Author      Comment
/// ---------------------------------------------------------------------------
/// 2009-11-20    YangManWoo  Originally created
/// 
/// </pre>
class IListHandlerEx
{
public:
    virtual ~IListHandlerEx() {}

    virtual bool    DrawEmptyMsg(CDC* a_pDC) { return false; }
    virtual void    DrawItemBG(CDC* a_pDC, int a_nItemIdx, BOOL a_bHighlight, 
                    BOOL a_bFocus, const CRect& a_rcBounds, bool& a_bDrawDefault) = 0;
    virtual void    DrawStateIconImage(CDC* a_pDC, int a_nItemIdx, BOOL a_bHighlight, 
                    BOOL a_bFocus, const CRect& a_rcCol, bool& a_bDrawDefault) = 0;
    virtual void    DrawSmallIconImage(CDC* a_pDC, int a_nItemIdx, BOOL a_bHighlight, 
                    BOOL a_bFocus, const CRect& a_rcIcon, bool& a_bDrawDefault) = 0;
    virtual void    DrawSubItem(CDC* a_pDC, int a_nItemIdx, int a_nSubItemIdx, 
                    BOOL a_bHighlight, BOOL a_bFocus, const CRect& a_rcLabel, 
                    bool& a_bDrawDefault) = 0;    

    virtual void    OnLButtonDown(CPoint a_ptPoint, int a_nItemIdx, int a_nSubItemIdx) {}
    virtual void    OnLButtonUp(CPoint a_ptPoint, int a_nItemIdx, int a_nSubItemIdx) {}
    virtual void    OnRbuttonClick(int a_nItem, int a_nSubItem) {}
    virtual void    OnColumnClick(int a_nIdx, int a_nOldIdx) {}
    virtual void    OnItemClick(int a_nItem, int a_nSubItem) {}
    virtual void    OnItemChange(int a_nItem, int a_nSubItem) {}
    virtual void    OnItemDBClick(int a_nItem, int a_nSubItem) {}
    virtual void    OnLvnGetdispinfo(NMHDR *pNMHDR, LRESULT *pResult) {}
protected:
    virtual void    SetListCtrl(CListCtrlEx* a_pList) { m_pList = a_pList; }
    IListHandlerEx(CListCtrlEx* a_pList) : m_pList(a_pList) {}
    IListHandlerEx(const IListHandlerEx& rhs) : m_pList(rhs.m_pList) {}
    CListCtrlEx*    m_pList;
};

/// \brief <pre>
/// Default Custom List Handler. only use default draw..
/// 
/// ---------------------------------------------------------------------------
/// Date          Author      Comment
/// ---------------------------------------------------------------------------
/// 2009-11-20    YangManWoo  Originally created
/// 
/// </pre>
class CDefaultListHandler : public IListHandlerEx
{
public:    
    ~CDefaultListHandler();

    void    DrawItemBG(CDC* a_pDC, int a_nItemIdx, BOOL a_bHighlight, 
                       BOOL a_bFocus, const CRect& a_rcBounds, bool& a_bDrawDefault);
    void    DrawStateIconImage(CDC* a_pDC, int a_nItemIdx, BOOL a_bHighlight, 
                               BOOL a_bFocus, const CRect& a_rcCol, bool& a_bDrawDefault);
    void    DrawSmallIconImage(CDC* a_pDC, int a_nItemIdx, BOOL a_bHighlight, 
                               BOOL a_bFocus, const CRect& a_rcIcon, bool& a_bDrawDefault);
    void    DrawSubItem(CDC* a_pDC, int a_nItemIdx, int a_nSubItemIdx, BOOL a_bHighlight, 
                        BOOL a_bFocus, const CRect& a_rcLabel, bool& a_bDrawDefault);

    friend    class CListCtrlEx;
private:
    CDefaultListHandler(CListCtrlEx* a_pList);
    CDefaultListHandler(const CDefaultListHandler&rhs) : IListHandlerEx(rhs) {}
};

    }    // end of namespace UI
}    // end of namespace MFC

#endif // __LIST_HANDLER_EX__

IListHandlerEx 接口提供

  • 用于数据呈现的 DrawXXXXX 函数部分
  • 用于列表控件事件的处理部分

CDefaultListHandler 类在用户未指定 Handler 时实现基本数据表示。

我们需要做的事情很简单。

您可以创建继承 IListHandlerEx 的处理程序类,以您想要的方式显示您的数据。

让我们看一下下面的示例代码

#pragma once

#include "ListCtrlEx.h"
#include <vector>

/// \brief
/// Main Dialog
/// \code
/// - 
/// \endcode
/// \warning 
/// \sa      
/// \author  Yangmanwoo
/// \date    2018-05-31  first written
class CMyListCtrlExDlg : public CDialogEx, public MFC::UI::IListHandlerEx
{
public:
    CMyListCtrlExDlg(CWnd* pParent = NULL);    // standard constructor

#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_MYLISTCTRLEX_DIALOG };
#endif

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

private:
    /// IListHandler Interface
    void        OnRbuttonClick(int a_nItem, int a_nSubItem);
    void        OnItemClick(int a_nItem, int a_nSubItem);
    void        DrawItemBG(CDC* a_pDC, int a_nItemIdx, BOOL a_bHighlight, 
                BOOL a_bFocus, const CRect& a_rcBounds, bool& a_bDrawDefault);
    void        DrawStateIconImage(CDC* a_pDC, int a_nItemIdx, BOOL a_bHighlight, 
                BOOL a_bFocus, const CRect& a_rcCol, bool& a_bDrawDefault);
    void        DrawSmallIconImage(CDC* a_pDC, int a_nItemIdx, BOOL a_bHighlight, 
                BOOL a_bFocus, const CRect& a_rcIcon, bool& a_bDrawDefault);
    void        DrawSubItem(CDC* a_pDC, int a_nItemIdx, int a_nSubItemIdx, 
                BOOL a_bHighlight, BOOL a_bFocus, const CRect& a_rcLabel, bool& a_bDrawDefault);    
    void        OnLvnGetdispinfo(NMHDR *pNMHDR, LRESULT *pResult);
    void        OnItemDBClick(int a_nItem, int a_nSubItem);
    //////////////////////////////////////////////////////////////////////////
    void        InitMainUI();
    void        RefreshList(CListCtrl* a_pList);

    typedef struct _member_info {
        CString    m_strDate,
                m_strName,
                m_strTitle,
                m_strAuthor;
    } MEMBER_INFO;
private:
    MFC::UI::CListCtrlEx        m_MainList;
    std::vector<MEMBER_INFO>    m_vList;
    CImageList                  m_ImgList;

protected:
    HICON m_hIcon;

    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnBnClickedButton1();
};

有三个 private 成员变量

  • CListCtrlEx
  • vector => 用于虚拟列表
  • CImageList => 用于绘制图标

我们不能使用默认的 Handler,因为我们需要在单独的列中标记图标。

相反,您必须继承 IListHandlerEx 并单独配置它。

您可以创建一个单独的类文件。

1. 初始化 IListHandlerEx

/// 
/// \brief
/// Constructor, Init IListHandlerEx
/// 
CMyListCtrlExDlg::CMyListCtrlExDlg(CWnd* pParent /*=NULL*/)
    : CDialogEx(IDD_MYLISTCTRLEX_DIALOG, pParent)
    , IListHandlerEx(&m_MainList)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

2. 创建列表控件

/// 
/// \brief
/// Make Main UI.
/// 
void CMyListCtrlExDlg::InitMainUI()
{
    enum { LIST_HEIGHT=40, LIST_HEADER_HEIGHT=30, ID_MAINLIST=10002 };

    CRect rcWnd;
    GetClientRect(&rcWnd);
    int nX = 10, nY = 40;    
    m_MainList.SetListHeight(LIST_HEIGHT);
    // make virtual list(OWNER DATA)
    m_MainList.Create(this, nX, nY, rcWnd.Width()-nX-10, rcWnd.Height()-nY-12, 
                      TRUE, TRUE, TRUE, ID_MAINLIST);
    m_MainList.SetHeaderHeight(LIST_HEADER_HEIGHT);
    m_MainList.SetFont(_T("굴림"), 12);
    m_MainList.AddColumnText(40, _T("Seq"));
    m_MainList.AddColumnText(100, _T("Name"), LVCFMT_LEFT);
    m_MainList.AddColumnText(200, _T("Title"), LVCFMT_LEFT);
    m_MainList.AddColumnText(100, _T("Author"), LVCFMT_LEFT);
    m_MainList.AddColumnText(30, _T("DEL"), LVCFMT_LEFT);
    m_MainList.SetDefaultMsg(_T("No Data."));
    //m_MainList.SetGrid(TRUE, RGB(165,165,165));
    m_MainList.SetUnderLine(TRUE, RGB(165,165,165));
    // set handler.
    m_MainList.SetHandler(this);
    //////////////////////////////////////////////////////////////////////////
    m_ImgList.Create(18, 18, ILC_COLOR32 | ILC_MASK, 1, 1);
    CBitmap bmIcon;
    bmIcon.Attach(LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_CLOSE)));
    m_ImgList.Add(&bmIcon, RGB(255,255,255));    
}

3. 实现 IListHandlerEx(绘制项目和处理一些事件)

/// 
/// \brief <pre>
/// Draw Item BG. 
/// </pre>
/// \param   a_pDC
/// \param   a_nItemIdx
/// \param   a_bHighlight
/// \param   a_bFocus
/// \param   a_rcBounds
/// \param   a_bDrawDefault - true is draw CDefaultHandler
/// 
void CMyListCtrlExDlg::DrawItemBG(CDC* a_pDC, int a_nItemIdx, BOOL a_bHighlight, 
                                  BOOL a_bFocus, const CRect& a_rcBounds, bool& a_bDrawDefault)
{
    a_bDrawDefault = true;
    if( a_bHighlight ) {
        a_pDC->FillRect(a_rcBounds, &CBrush(a_bFocus ? m_pList->GetHilightBGColor() : 
                        RGB(192, 192, 192)));
        a_bDrawDefault = false;
    }
    if( a_bHighlight ) return;
}
/// 
/// \brief <pre>
/// Draw State Icon Image.
/// </pre>
/// \param   a_pDC
/// \param   a_nItemIdx
/// \param   a_bHighlight
/// \param   a_bFocus
/// \param   a_rcCol
/// \param   a_bDrawDefault
/// 
void CMyListCtrlExDlg::DrawStateIconImage(CDC* a_pDC, int a_nItemIdx, 
             BOOL a_bHighlight, BOOL a_bFocus, const CRect& a_rcCol, bool& a_bDrawDefault)
{
    a_bDrawDefault = true;    
}
/// 
/// \brief <pre>
/// Draw Small Icon Image
/// </pre>
/// \param   a_pDC
/// \param   a_nItemIdx
/// \param   a_bHighlight
/// \param   a_bFocus
/// \param   a_rcIcon
/// \param   a_bDrawDefault
/// 
void CMyListCtrlExDlg::DrawSmallIconImage(CDC* a_pDC, int a_nItemIdx, 
              BOOL a_bHighlight, BOOL a_bFocus, const CRect& a_rcIcon, bool& a_bDrawDefault)
{
    a_bDrawDefault = true;
}
/// 
/// \brief <pre>
/// Draw Sub Item. index=4 is icon.
/// </pre>
/// \param   a_pDC
/// \param   a_nItemIdx
/// \param   a_nSubItemIdx
/// \param   a_bHighlight
/// \param   a_bFocus
/// \param   a_rcLabel
/// \param   a_bDrawDefault
/// 
void CMyListCtrlExDlg::DrawSubItem(CDC* a_pDC, int a_nItemIdx, int a_nSubItemIdx, 
          BOOL a_bHighlight, BOOL a_bFocus, const CRect& a_rcLabel, bool& a_bDrawDefault)
{
    a_bDrawDefault = false;    
    CString sLabel = m_pList->GetItemText(a_nItemIdx, a_nSubItemIdx);
    //////////////////////////////////////////////////////////////////////////
    if( a_bHighlight ) a_pDC->SetTextColor(RGB(255,255,255));
    //////////////////////////////////////////////////////////////////////////
    a_pDC->SetBkMode(TRANSPARENT);
    if( a_nSubItemIdx == 4 ) {
        CPoint ptDraw(a_rcLabel.left + (a_rcLabel.Width()/2-18/2), 
                     a_rcLabel.top + (a_rcLabel.Height()/2 - 18/2));
        m_ImgList.Draw(a_pDC, 0, ptDraw, ILD_NORMAL);
    } else {
        UINT nJustify = m_pList->GetColumnFMT(a_nSubItemIdx);
        CRect    rcLabel(a_rcLabel);
        rcLabel.left += 4;
        a_pDC->DrawText(sLabel, -1, rcLabel, nJustify | 
                   DT_SINGLELINE | DT_END_ELLIPSIS | DT_VCENTER);
    }
}
/// 
/// \brief
/// if listcontrol have OWNER_DATA, set text.
/// \param   *pNMHDR
/// \param   *pResult
/// 
void CMyListCtrlExDlg::OnLvnGetdispinfo(NMHDR *pNMHDR, LRESULT *pResult)
{
    NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
    LV_ITEM* pItem= &(pDispInfo)->item;
    switch(pItem->iSubItem)
    {
        case 0:
            lstrcpyn(pItem->pszText, m_vList[pItem->iItem].m_strDate, pItem->cchTextMax);
            break;
        case 1:
            lstrcpyn(pItem->pszText, m_vList[pItem->iItem].m_strName, pItem->cchTextMax);
            break;
        case 2:
            lstrcpyn(pItem->pszText, m_vList[pItem->iItem].m_strTitle, pItem->cchTextMax);
            break;
        case 3:
            lstrcpyn(pItem->pszText, m_vList[pItem->iItem].m_strAuthor, pItem->cchTextMax);
            break;
    }
}
/// 
/// \brief
/// handling Right button click event
/// \param   a_nItem
/// \param   a_nSubItem
/// 
void CMyListCtrlExDlg::OnRbuttonClick(int a_nItem, int a_nSubItem)
{
}
/// 
/// \brief
/// Item click event.
/// \param   a_nItem
/// \param   a_nSubItem
/// 
void CMyListCtrlExDlg::OnItemClick(int a_nItem, int a_nSubItem)
{
    if( a_nItem == -1 ) return;
    if( a_nSubItem == 4 ) {
        if( ::MessageBox(m_hWnd, _T("Are you want delete?"), _T("Info"), 
                                 MB_YESNO | MB_ICONINFORMATION) == IDNO ) return;
        // do it delete.
    }
}
/// 
/// \brief
/// item db click event
/// \param   a_nItem
/// \param   a_nSubItem
/// 
void CMyListCtrlExDlg::OnItemDBClick(int a_nItem, int a_nSubItem)
{
    if( a_nItem == -1 ) return;
}

这允许您创建自己的自定义列表控件。

如果要更改形状,可以通过实现继承类的其中一个来实现 IListHandlerEx

这种类型的策略模式可以成为创建自定义控件的非常有用的方法。

编辑控件、树形控件、按钮、... 适用于所有自定义控件。

希望这篇文章能帮助您。

历史

  • 2009年11月20日 - 要创建标准列表控件,请使用 Handler 设置(应用策略模式)
  • 2018年6月1日 - 更新 CodeProject
© . All rights reserved.