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





5.00/5 (2投票s)
本文介绍了如何使用策略模式创建自定义控件。
引言
我发现我无法在每个项目中有效地重用列表控件。
设计模式的首要原则是区分哪些在改变,哪些没有改变。
我将这个原则应用到了列表控件。
背景
当您开发 Android 或 iOS 时,您经常使用 Handler。
这是一种典型的策略模式。
如有必要,您也可以参考 GoF 的设计模式书籍。
然后,我们需要找出列表控件中哪些在改变,哪些没有改变。
我们必须专注于改变。
列表控件中哪些是不变的?
列表控件自身的功能(显示列表)不会改变。
可能存在以下情况
- 添加/删除项目
- 更改标题控件的高度
- 更改项目的高度
- 更改字体
- 虚拟列表
- ... 列表控件的其他功能...
列表控件中哪些是改变的?
您在列表控件中进行哪些更改?
显示数据的部分会改变。
- 打印图标
- 打印多行
- 更改所选列的颜色
- ... 输出用于其他项目目的的数据
大多数是这些。这是我们需要关注的地方。
Using the Code
源配置
- FlatHeader.h/cpp
- ImgHelper.h/cpp
- ListCtrlEx.h/cpp
- ListHandlerEx.h/cpp
其中,FlatHeader
、Imghelper
和 ListCtrlEx
是不变的部分。 也就是说,它实现了列表控件的功能部分。
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