Introduction
I found that I cannot efficiently reuse list controls on every project.
The first principle of design patterns is to distinguish what is changing and what is not.
I applied this principle to List Control.
Background
When you are developing Android or iOS, you often use Handler.
This is a typical strategy pattern.
If necessary, you can also refer to GoF's Design Pattern book.
Then, we need to find out what changes in List Control and what does not change.
We must focus on changing.
What is Unchange in List Control?
The List Control's own functionality (showing the list) does not change.
There may be something like the following:
- Add/Delete Item
- Change the height of the Header Control
- Change the height of the Item
- Change Font
- Virtual List
- ... Other features of List Control...
What is Change in List Control?
What changes do you make in List Control?
The part where the data is displayed changes.
- Print Icon
- Print Multiline
- Change the color of the selected column
- ... Output the data for other project purposes
Most of these things. This is where we need to focus.
Using the Code
Source configuration:
- FlatHeader.h/cpp
- ImgHelper.h/cpp
- ListCtrlEx.h/cpp
- ListHandlerEx.h/cpp
Of these, FlatHeader
, Imghelper
, and ListCtrlEx
are unchanged parts. That is, it implements the functional part of List Control.
ListHandlerEx
provides an interface and basic handler for handling the changing parts (data).
Now let's look at the interface that handles the changing parts.
The source code below is the content of the ListHandlerEx.h file.
#ifndef __LIST_HANDLER_EX__
#define __LIST_HANDLER_EX__
namespace MFC {
namespace UI {
class CListCtrlEx;
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;
};
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) {}
};
}
}
#endif // __LIST_HANDLER_EX__
The IListHandlerEx
Interface provides:
- Part for data presentation DrawXXXXX Function
- Processing part for List Control Event
The CDefaultListHandler
class implements a basic data representation when the user does not specify a Handler.
What we need to do is simple.
You can create handler classes that inherit IListHandlerEx
to display your data the way you do.
Let's look at the sample code below:
#pragma once
#include "ListCtrlEx.h"
#include <vector>
class CMyListCtrlExDlg : public CDialogEx, public MFC::UI::IListHandlerEx
{
public:
CMyListCtrlExDlg(CWnd* pParent = NULL);
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_MYLISTCTRLEX_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX);
private:
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();
};
There are three private
member variables:
CListCtrlEx
vector
=> for virtual list CImageList
=> for draw icon
We cannot use the Default Handler because we need to mark the icon in a separate column.
Instead, you must inherit IListHandlerEx
and configure it separately.
You can create a separate class file.
1. Init IListHandlerEx
CMyListCtrlExDlg::CMyListCtrlExDlg(CWnd* pParent )
: CDialogEx(IDD_MYLISTCTRLEX_DIALOG, pParent)
, IListHandlerEx(&m_MainList)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
2. Make List Control
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);
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.SetUnderLine(TRUE, RGB(165,165,165));
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. Implement IListHandlerEx (Draw Item and Handling Some Event)
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;
}
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;
}
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;
}
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);
}
}
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;
}
}
void CMyListCtrlExDlg::OnRbuttonClick(int a_nItem, int 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;
}
}
void CMyListCtrlExDlg::OnItemDBClick(int a_nItem, int a_nSubItem)
{
if( a_nItem == -1 ) return;
}
This allows you to create your own custom list control.
If you want to change the shape, you can implement IListHandlerEx
by implementing one of the inherited classes.
This type of strategy pattern can be a very useful way to create custom controls.
Edit Control, Tree Control, Button, ... applicable to all custom controls.
I hope this post will help you.
History
- 11/20/2009 - To create a standard list control, use the Handler setting (apply Strategy pattern)
- 06/01/2018 - Update CodeProject