Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Owner Draw List Control with Design Pattern (Strategy)

0.00/5 (No votes)
1 Jun 2018 1  
This article describes how to create custom controls using strategy patterns.

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;
/// \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__

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>

/// \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();
};

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

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

2. Make List Control

/// 
/// \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. Implement IListHandlerEx (Draw Item and Handling Some Event)

/// 
/// \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;
}

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here