Alternate Colors in ListView

ListView is a standard Windows control that displays a set of values per line, in particular in this article we will see how to alternate the colors in a report view.
In a report, you can color the lines simply by listening for a notification of CustomDraw, in the event you can have both entire rows, and the individual cells. To create the example given, we will need a single document MFC project with a ListView. Once built the project should set the values​of simple styles and extended to our control, so we’re going to populate the rows and columns. Let the class code of sight, only to be changed and adapted to our needs.

// File ColorListView.h

#pragma once

class CColorListView : public CListView
{
protected:
   CColorListView();
   DECLARE_DYNCREATE(CColorListView)

public:
   CColorListDoc* GetDocument() const;

public:
   virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
   virtual void OnInitialUpdate();

public:
   virtual ~CColorListView();
#ifdef _DEBUG
   virtual void AssertValid() const;
   virtual void Dump(CDumpContext& dc) const;
#endif

protected:
   DECLARE_MESSAGE_MAP()
   afx_msg void OnNMCustomdraw(NMHDR *pNMHDR, LRESULT *pResult);
};

#ifndef _DEBUG  // debug version in ColorListView.cpp
inline CColorListDoc* CColorListView::GetDocument() const
   { return reinterpret_cast(m_pDocument); }
#endif

// ColorListView.cpp

#include "stdafx.h"
#include "ColorList.h"
#include "ColorListDoc.h"
#include "ColorListView.h"

// CColorListView

IMPLEMENT_DYNCREATE(CColorListView, CListView)

BEGIN_MESSAGE_MAP(CColorListView, CListView)
   ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnNMCustomdraw)
END_MESSAGE_MAP()

CColorListView::CColorListView()
{
}

CColorListView::~CColorListView()
{
}

BOOL CColorListView::PreCreateWindow(CREATESTRUCT& cs)
{
   cs.style |= LVS_REPORT;

   return CListView::PreCreateWindow(cs);
}

void CColorListView::OnInitialUpdate()
{
   CListView::OnInitialUpdate();

   CListCtrl& rList = GetListCtrl();

   rList.SetExtendedStyle(LVS_EX_GRIDLINES|LVS_EX_FULLROWSELECT|
        LVS_EX_LABELTIP|LVS_EX_BORDERSELECT);

   // Insert columns
   rList.InsertColumn(0, _T("First Column"), LVCFMT_LEFT, 200);
   rList.InsertColumn(1, _T("Second Column"), LVCFMT_LEFT, 200);
   rList.InsertColumn(2, _T("Third Column"), LVCFMT_LEFT, 200);
   rList.InsertColumn(3, _T("Fourth Column"), LVCFMT_LEFT, 200);
   rList.InsertColumn(4, _T("Fifth Column"), LVCFMT_LEFT, 200);

   // Insert values
   CString buffer;
   for (int i = 0; i < 50; ++i)
   {
      buffer.Format(_T("Row %d Column 1"), i + 1);
      rList.InsertItem(i, buffer);
      buffer.Format(_T("Row %d Column 2"), i + 1);
      rList.SetItemText(i, 1, buffer);
      buffer.Format(_T("Row %d Column 3"), i + 1);
      rList.SetItemText(i, 2, buffer);
      buffer.Format(_T("Row %d Column 4"), i + 1);
      rList.SetItemText(i, 3, buffer);
      buffer.Format(_T("Row %d Column 5"), i + 1);
      rList.SetItemText(i, 4, buffer);
   }
   rList.SetItemState(0, LVIS_SELECTED|LVIS_FOCUSED,
         LVIS_SELECTED|LVIS_FOCUSED);
 }
 #ifdef _DEBUG
 void CColorListView::AssertValid() const
 {
    CListView::AssertValid();
 }
 void CColorListView::Dump(CDumpContext& dc) const
 {
    CListView::Dump(dc);
 }
 CColorListDoc* CColorListView::GetDocument() const
 {
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CColorListDoc)));
    return (CColorListDoc*)m_pDocument;
}
#endif //_DEBUG

void CColorListView::OnNMCustomdraw(NMHDR *pNMHDR,
       LRESULT *pResult)
{
   LPNMLVCUSTOMDRAW pLVCD = reinterpret_cast(pNMHDR);
   *pResult = CDRF_DODEFAULT;

   switch (pLVCD->nmcd.dwDrawStage)
   {
   case CDDS_PREPAINT:
      *pResult = CDRF_NOTIFYITEMDRAW;
      break;
   case CDDS_PREPAINT|CDDS_ITEM:
        {
	COLORREF crEven = RGB(96, 132, 132);
	COLORREF crOdd = RGB(61, 84, 84);
	COLORREF crText = RGB(250, 250, 250);

	if ((pLVCD->nmcd.dwItemSpec % 2) == 0)
	    pLVCD->clrTextBk = crEven;
	else
	    pLVCD->clrTextBk = crOdd;
	pLVCD->clrText = crText;
         }
         break;
    }
}

As you can see from the code, we need to intercept the notification when it shows the stage of drawing and returning a value to make sure we have a new notification for drawing the line. If we were to color every cell instead we should return a value that allows for a notification before the same color.

...
switch (pLVCD->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
   *pResult = CDRF_NOTIFYITEMDRAW;
   break;
case CDDS_PREPAINT|CDDS_ITEM:
   *pResult = CDRF_NOTIFYSUBITEMDRAW;
   break;
case CDDS_PREPAINT|CDDS_ITEM|CDDS_SUBITEM:
...

Study well the code and put it inside the compiler, you will see that there are even lines of one color and odd ones of another, a very efficient method to improve the readability of the rows in a list.
From this example you can take inspiration for many other applications, such as darker color the column ordered.

This entry was posted in Programming. Bookmark the permalink.