Click here to Skip to main content
15,885,216 members
Articles / Desktop Programming / MFC

XListCtrl - A custom-draw list control with subitem formatting

Rate me:
Please Sign up or sign in to vote.
4.92/5 (261 votes)
6 Sep 2006CPOL9 min read 3.4M   34.2K   665  
A custom-draw list control with support for subitem colors, bold font, progress bars, and checkboxes.
// XListCtrlTestDlg.cpp : implementation file
//

#include "stdafx.h"
#include "XListCtrlTest.h"
#include "XListCtrlTestDlg.h"
#include "about.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

///////////////////////////////////////////////////////////////////////////////
// CXListCtrlTestDlg dialog

BEGIN_MESSAGE_MAP(CXListCtrlTestDlg, CDialog)
//{{AFX_MSG_MAP(CXListCtrlTestDlg)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_TIMER()
	ON_BN_CLICKED(IDC_BUTTON1, OnButton1)
	ON_WM_DESTROY()
	ON_NOTIFY(NM_CLICK, IDC_LIST, OnClick)
	ON_NOTIFY(NM_DBLCLK, IDC_LIST, OnDblClick)
	ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST, OnColumnClick)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST, OnItemChanged)
	ON_BN_CLICKED(IDC_CLEAR, OnClear)
	ON_BN_CLICKED(IDC_BUTTON2, OnButton2)
	ON_BN_CLICKED(IDC_CHECK1, OnGridlines)
	//}}AFX_MSG_MAP
	ON_REGISTERED_MESSAGE(WM_XLISTCTRL_COMBO_SELECTION, OnComboSelection)
	ON_REGISTERED_MESSAGE(WM_XLISTCTRL_CHECKBOX_CLICKED, OnCheckbox)
	ON_REGISTERED_MESSAGE(WM_XLISTCTRL_EDIT_END, OnEditEnd)
	
END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////////
// ctor
CXListCtrlTestDlg::CXListCtrlTestDlg(CWnd* pParent /*=NULL*/)
: CDialog(CXListCtrlTestDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CXListCtrlTestDlg)
	m_bGridlines = FALSE;
	//}}AFX_DATA_INIT
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_bRunning = FALSE;
	m_bGridlines = TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// DoDataExchange
void CXListCtrlTestDlg::DoDataExchange(CDataExchange* pDX)
{
	///////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////
	//
	// comment out the DDX_Control line to create CXListCtrl dynamically -
	// remember also to include lines in OnInitDialog to create CXListCtrl,
	// and remove the list control from IDD_LISTCTRLTEST_DIALOG in the rc file
	//
	///////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////

	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CXListCtrlTestDlg)
	DDX_Control(pDX, IDC_LOG, m_Log);
	DDX_Control(pDX, IDC_LIST, m_List);	// comment out this line if creating dynamically
	DDX_Check(pDX, IDC_CHECK1, m_bGridlines);
	//}}AFX_DATA_MAP
}

///////////////////////////////////////////////////////////////////////////////
// OnInitDialog
BOOL CXListCtrlTestDlg::OnInitDialog()
{
	TRACE(_T("in CXListCtrlTestDlg::OnInitDialog\n"));

	CDialog::OnInitDialog();

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		CString strAboutMenu;
		strAboutMenu.LoadString(IDS_ABOUTBOX);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//	when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE); 		// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	///////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////
	//
	// include the following lines to create CXListCtrl dynamically -
	// remember also to comment out DDX line, and remove the list control
	// from IDD_LISTCTRLTEST_DIALOG in the rc file
	//
	///////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////

#if 0  // -----------------------------------------------------------
	DWORD dwStyle = LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS |
		WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP;

	CRect rect, rectbutton;
	GetClientRect(&rect);
	GetDlgItem(IDC_BUTTON1)->GetClientRect(&rectbutton);
	rect.DeflateRect(5, 5);
	rect.top = rectbutton.bottom + 15;

	TRACE("   calling CreateEx\n");

	// you may also use Create(), but I prefer WS_EX_CLIENTEDGE style
	VERIFY(m_List.CreateEx(WS_EX_CLIENTEDGE, _T("SysListView32"), _T(""),
						dwStyle, rect, this, IDC_LIST, NULL));
	CFont *pFont = GetFont();
	if (pFont)
		m_List.SetFont(pFont);
#endif // -----------------------------------------------------------

	// XListCtrl must have LVS_EX_FULLROWSELECT if combo or edit boxes are used

	DWORD dwExStyle = LVS_EX_FULLROWSELECT
					/*| LVS_EX_TRACKSELECT*/;	// for hot tracking
	if (m_bGridlines)
		dwExStyle |= LVS_EX_GRIDLINES;

	m_List.SetExtendedStyle(dwExStyle);

	// call EnableToolTips to enable tooltip display
	m_List.EnableToolTips(TRUE);

	InitListCtrl(&m_List);

	return TRUE;  // return TRUE  unless you set the focus to a control
}

///////////////////////////////////////////////////////////////////////////////
// OnSysCommand
void CXListCtrlTestDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlg;
		dlg.DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

///////////////////////////////////////////////////////////////////////////////
// OnPaint
void CXListCtrlTestDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

///////////////////////////////////////////////////////////////////////////////
// OnQueryDragIcon
HCURSOR CXListCtrlTestDlg::OnQueryDragIcon()
{
	return (HCURSOR) m_hIcon;
}

///////////////////////////////////////////////////////////////////////////////
// OnTimer
void CXListCtrlTestDlg::OnTimer(UINT nIDEvent)
{
	static int nPct = 0;
	static int nItem = -1;
	static int nBlink = 0;

	if (nIDEvent == 1)
	{
		KillTimer(nIDEvent);
		m_List.SetProgress(1, 2);
		nPct = 0;
		SetTimer(2, 80, NULL);
		SetTimer(4, 400, NULL);
	}
	else if (nIDEvent == 2)
	{
		nPct += 10;
		m_List.UpdateProgress(1, 2, nPct);
		if (nPct >= 100)
		{
			KillTimer(nIDEvent);
			SetTimer(3, 2000, NULL);
		}
	}
	else if (nIDEvent == 3)
	{
		CString str;
		str = m_List.GetComboText(0, 2);

		KillTimer(nIDEvent);
		m_List.DeleteProgress(1, 2);
		SetTimer(1, 2000, NULL);
	}
	else if (nIDEvent == 4)
	{
		if (nItem != -1)
		{
			// remove previous color & bold from columns 1 and 5
			m_List.SetItemText(nItem, 5, NULL, (COLORREF)-1, (COLORREF)-1);
			m_List.SetBold(nItem, 1, FALSE);
		}

		// blink item 3
		if (nItem == 3)
		{
			nBlink++;
			if (nBlink >= 12)
			{
				nBlink = 0;
				nItem++;
				m_List.SetItemText(nItem, 5, NULL, RGB(255,255,255), RGB(255,0,0));
				m_List.SetBold(nItem, 1, TRUE);
			}
			else
			{
				if (nBlink & 1)		// blink every other time
				{
					m_List.SetItemText(nItem, 5, NULL, RGB(255,255,255), RGB(255,0,0));
					m_List.SetBold(nItem, 1, TRUE);
				}
			}
		}
		else
		{
			nItem++;
			if (nItem >= 10)
				nItem = 0;
			m_List.SetItemText(nItem, 5, NULL, RGB(255,255,255), RGB(255,0,0));
			m_List.SetBold(nItem, 1, TRUE);
		}
	}
	
	CDialog::OnTimer(nIDEvent);
}

///////////////////////////////////////////////////////////////////////////////
// OnButton1
void CXListCtrlTestDlg::OnButton1()
{
	if (m_bRunning)
	{
		GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE);
		KillTimer(1);
		KillTimer(2);
		KillTimer(3);
		KillTimer(4);
		m_bRunning = FALSE;
		GetDlgItem(IDC_BUTTON1)->SetWindowText(_T("Start"));
		GetDlgItem(IDC_BUTTON1)->EnableWindow(TRUE);
	}
	else
	{
		GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE);
		FillListCtrl(&m_List);

		// show a disabled item
		m_List.SetEnabled(5, FALSE);
		m_List.SetItemText(5, 1, _T("This row is disabled"));

#if 0  // -----------------------------------------------------------
		// demonstrate handling of WM_SYSCOLORCHANGE
		int nElement = COLOR_WINDOW;
		COLORREF crWindow = ::GetSysColor(nElement);
		if (::SetSysColors(1,				// number of elements
						   &nElement,		// array of elements
						   &crWindow))		// array of RGB values
		{
			TRACE(_T("SetSysColors ok\n"));
		}
		else
		{
			TRACE(_T("SetSysColors failed\n"));
		}
#endif // -----------------------------------------------------------

		SetTimer(1, 1000, NULL);

		m_bRunning = TRUE;
		GetDlgItem(IDC_BUTTON1)->SetWindowText(_T("Stop"));
		GetDlgItem(IDC_BUTTON1)->EnableWindow(TRUE);
	}
}

///////////////////////////////////////////////////////////////////////////////
// OnButton2 - mark all subitems as unmodified
void CXListCtrlTestDlg::OnButton2() 
{
	m_List.SetListModified(FALSE);

	for (int nItem = 0; nItem < m_List.GetItemCount(); nItem++)
	{
		for (int nSubItem = 0; nSubItem < m_List.GetColumns(); nSubItem++)
		{
			m_List.SetModified(nItem, nSubItem, FALSE);

			COLORREF crText, crBackground;
			m_List.GetItemColors(nItem, nSubItem, crText, crBackground);
			m_List.SetItemColors(nItem, nSubItem, RGB(0,0,0), crBackground);

			// is this a checkbox?
			if (m_List.GetItemCheckedState(nItem, nSubItem) != -1)
			{
				if (nSubItem == 0)
					m_List.SetItemText(nItem, nSubItem, _T(" "));	// erase *
				else
					m_List.SetItemText(nItem, nSubItem, _T("OK"));
			}
		}
	}

	m_List.RedrawWindow();

	CString strCaption = _T("");
	strCaption.LoadString(AFX_IDS_APP_TITLE);
	SetWindowText(strCaption);
}

///////////////////////////////////////////////////////////////////////////////
// OnClear - clear m_Log listbox
void CXListCtrlTestDlg::OnClear() 
{
	m_Log.ResetContent();
}

///////////////////////////////////////////////////////////////////////////////
// OnGridlines
void CXListCtrlTestDlg::OnGridlines() 
{
	UpdateData(TRUE);

	DWORD dwExStyle = m_List.GetExtendedStyle();

	if (m_bGridlines)
		dwExStyle |= LVS_EX_GRIDLINES;
	else
		dwExStyle &= ~LVS_EX_GRIDLINES;

	m_List.SetExtendedStyle(dwExStyle);
}

///////////////////////////////////////////////////////////////////////////////
// OnDestroy
void CXListCtrlTestDlg::OnDestroy()
{
	KillTimer(1);
	KillTimer(2);
	KillTimer(3);
	KillTimer(4);
	CDialog::OnDestroy();
}

///////////////////////////////////////////////////////////////////////////////
// OnClick
//
// This method shows how to handle NM_CLICK messages from XListCtrl
//
void CXListCtrlTestDlg::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMITEMACTIVATE pNMIA = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	int nItem = -1;
	int nSubItem = -1;
	if (pNMIA)
	{
		nItem = pNMIA->iItem;
		nSubItem = pNMIA->iSubItem;
	}

	if (nItem >= 0 && nSubItem >= 0)
	{
		CString strText = m_List.GetItemText(nItem, nSubItem);
		Log(_T("OnClick at (%d,%d):  '%s'"), nItem, nSubItem, strText);
	}

	*pResult = 0;
}

///////////////////////////////////////////////////////////////////////////////
// OnDblClick
//
// This method shows how to handle NM_DBLCLK messages from XListCtrl
//
void CXListCtrlTestDlg::OnDblClick(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMITEMACTIVATE pNMIA = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	int nItem = -1;
	int nSubItem = -1;
	if (pNMIA)
	{
		nItem = pNMIA->iItem;
		nSubItem = pNMIA->iSubItem;
	}

	if (nItem >= 0 && nSubItem >= 0)
	{
		CString strText = m_List.GetItemText(nItem, nSubItem);
		Log(_T("OnDblClick at (%d,%d):  '%s'"), nItem, nSubItem, strText);
	}

	*pResult = 0;
}

///////////////////////////////////////////////////////////////////////////////
// OnColumnClick
//
// This method shows how to handle LVN_COLUMNCLICK messages from XListCtrl
//
void CXListCtrlTestDlg::OnColumnClick(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
	int nItem = -1;
	int nSubItem = -1;
	if (pNMLV)
	{
		nItem = pNMLV->iItem;
		nSubItem = pNMLV->iSubItem;
	}

	Log(_T("OnColumnClick on column %d"), nSubItem);

	CString strCaption = _T("");
	strCaption.LoadString(AFX_IDS_APP_TITLE);
	strCaption += _T(" *");
	SetWindowText(strCaption);

	*pResult = 0;
}

///////////////////////////////////////////////////////////////////////////////
// OnItemChanged
//
// This method shows how to handle LVN_ITEMCHANGED messages from XListCtrl
//
void CXListCtrlTestDlg::OnItemChanged(NMHDR* pNMHDR, LRESULT* pResult)
{
	LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
	int nItem = -1;
	int nSubItem = -1;
	if (pNMLV)
	{
		nItem = pNMLV->iItem;
		nSubItem = pNMLV->iSubItem;
	}
	//TRACE(_T("in CXListCtrlTestDlg::OnItemChanged:  %d, %d\n"), nItem, nSubItem);

	if (pNMLV && (pNMLV->uNewState == (UINT)(LVIS_FOCUSED|LVIS_SELECTED)))
	{

		CString strText = m_List.GetItemText(nItem, nSubItem);
		Log(_T("Selection changed to item %d"), nItem);
	}

	*pResult = 0;
}

///////////////////////////////////////////////////////////////////////////////
// OnEditEnd
//
// This method shows how to handle WM_XLISTCTRL_EDIT_END messages 
// from XListCtrl
//
LRESULT CXListCtrlTestDlg::OnEditEnd(WPARAM nItem, LPARAM nSubItem)
{
	if (nItem >= 0 && nSubItem >= 0)
	{
		CString strText = m_List.GetItemText(nItem, nSubItem);
		Log(_T("OnEditEnd at (%d,%d): '%s'"), nItem, nSubItem, strText);

		COLORREF crText, crBackground;
		m_List.GetItemColors(nItem, nSubItem, crText, crBackground);
		if (m_List.GetModified(nItem, nSubItem))
		{
			// subitem was modified - color it red
			m_List.SetItemText(nItem, nSubItem, strText, 
				RGB(255,0,0), crBackground);
			CString strCaption = _T("");
			strCaption.LoadString(AFX_IDS_APP_TITLE);
			strCaption += _T(" *");
			SetWindowText(strCaption);
		}
		else
		{
			// subitem not modified - color it black -
			// note that once modified, a subitem will remain
			// marked as modified
			m_List.SetItemText(nItem, nSubItem, strText, 
				RGB(0,0,0), crBackground);
		}
	}

	return 0;
}

///////////////////////////////////////////////////////////////////////////////
// OnComboSelection
//
// This method shows how to handle WM_XLISTCTRL_COMBO_SELECTION messages 
// from XListCtrl
//
LRESULT CXListCtrlTestDlg::OnComboSelection(WPARAM nItem, LPARAM nSubItem)
{
	if (nItem >= 0 && nSubItem >= 0)
	{
		CString strText = m_List.GetItemText(nItem, nSubItem);
		Log(_T("OnComboSelection at (%d,%d): '%s'"), nItem, nSubItem, strText);

		COLORREF crText, crBackground;
		m_List.GetItemColors(nItem, nSubItem, crText, crBackground);
		if (m_List.GetModified(nItem, nSubItem))
		{
			// subitem was modified - color it red
			m_List.SetItemText(nItem, nSubItem, strText, 
				RGB(255,0,0), crBackground);
			CString strCaption = _T("");
			strCaption.LoadString(AFX_IDS_APP_TITLE);
			strCaption += _T(" *");
			SetWindowText(strCaption);
		}
		else
		{
			// subitem not modified - color it black -
			// note that once modified, a subitem will remain
			// marked as modified
			m_List.SetItemText(nItem, nSubItem, strText, 
				RGB(0,0,0), crBackground);
		}
	}

	return 0;
}

///////////////////////////////////////////////////////////////////////////////
// OnCheckbox
//
// This method shows how to handle WM_XLISTCTRL_CHECKBOX_CLICKED messages 
// from XListCtrl
//
LRESULT CXListCtrlTestDlg::OnCheckbox(WPARAM nItem, LPARAM nSubItem)
{
	if (nItem >= 0 && nSubItem >= 0)
	{
		Log(_T("OnCheckbox at (%d,%d)"), nItem, nSubItem);

		COLORREF crText, crBackground;
		m_List.GetItemColors(nItem, nSubItem, crText, crBackground);
		if (m_List.GetModified(nItem, nSubItem))
		{
			// subitem was modified - color it red
			if (nSubItem == 0)
				m_List.SetItemText(nItem, nSubItem, _T("*"), 
					RGB(255,0,0), crBackground);
			else
				m_List.SetItemText(nItem, nSubItem, _T("Error"), 
					RGB(255,0,0), crBackground);

			CString strCaption = _T("");
			strCaption.LoadString(AFX_IDS_APP_TITLE);
			strCaption += _T(" *");
			SetWindowText(strCaption);
		}
		else
		{
			// subitem not modified - color it black -
			// note that once modified, a subitem will remain
			// marked as modified
			if (nSubItem == 0)
				m_List.SetItemText(nItem, nSubItem, _T(" "), 
					RGB(0,0,0), crBackground);
			else
				m_List.SetItemText(nItem, nSubItem, _T("OK"), 
					RGB(255,0,0), crBackground);
		}
	}

	return 0;
}

///////////////////////////////////////////////////////////////////////////////
// Log - log a message to the m_Log listbox
void _cdecl CXListCtrlTestDlg::Log(LPCTSTR lpszFmt, ...)
{
	ASSERT(lpszFmt);

	TCHAR buf[2000], fmt[2000];
	va_list marker;

	// format string was passed as parameter
	_tcsncpy(fmt, lpszFmt, sizeof(fmt)/sizeof(TCHAR)-1);
	fmt[sizeof(fmt)/sizeof(TCHAR)-1] = _T('\0');

	// combine output string and variables
	va_start(marker, lpszFmt);
	_vsntprintf(buf, (sizeof(buf)/sizeof(TCHAR))-1, fmt, marker);
	va_end(marker);
	buf[sizeof(buf)/sizeof(TCHAR)-1] = _T('\0');

	CString strMsg(buf);

	// don't display \r or \n characters
	int i = 0;
	while ((i = strMsg.FindOneOf(_T("\r\n"))) != -1)
		strMsg.SetAt(i, _T(' '));

	i = m_Log.AddString(strMsg);

	TRACE(_T("%s\n"), strMsg);

	// scroll into view
	if (i >= 0)
	{
		m_Log.SetTopIndex(i);
	}

	m_Log.SetCurSel(-1);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.

Recently, I have moved to Los Angeles where I am doing consulting and development work.

For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions