Click here to Skip to main content
13,292,913 members (88,095 online)
Click here to Skip to main content
Add your own
alternative version


29 bookmarked
Posted 8 Oct 2007

ListView Alternating Row Colours (Using Windows API)

, 8 Oct 2007
Rate this:
Please Sign up or sign in to vote.
Display alternating row colours in a ListView (Report style) control
Screenshot - AltRowCol.jpg


I've noticed a number of solutions for displaying alternating rows in different colours for ListViews (LVS_REPORT), each with varying levels of success. Most seem to rely on a particular compiler (Visual C++, C#, etc.). Here is a simple solution which uses only Windows API calls so it can be easily converted for use by a variety of compilers and languages.


The code replies on the ability of your chosen programming language to intercept the WM_PAINT and WM_ERASEBKGND messages sent to the ListView control.

Let's take a look at the process involved;

Firstly, we need to identify a number of APIs for ListView controls.

  • ListView_SetTextBkColor (HWND, COLORREF)

    Sets the text background colour. Be aware that this function does not redraw the entire background in the control in the new colour, but simply sets the colour used by any subsequent redraws.

  • ListView_GetTopIndex (HWND)

    Retrieves the row index of the first visible row of the control.

  • ListView_GetCountPerPage (HNWD)

    Retrieves the number of visible rows.

  • ListView_GetItemPosition (HWND, int, RECT *)

    Retrieves the POINT coordinates of a given row.

Secondly, we need a few functions to allow us to work with update (or invalid) rectangles.

  • InvalidateRect (HWND, RECT *, BOOL)

    Invalidates, or marks for updating, a rectangular area of a control.

  • GetUpdateRect HWND, RECT *, BOOL)

    Retrieves the currently invalid update rectangle.

Using the Code

The WM_ERASEBKGND message is sent to a control each time the background (portion of the client area not used by the list columns) requires updating (or redrawing) on the screen.

To give the impression that the whole control is divided into different colours for alternating rows, the background needs to be redrawn accordingly, in response to this message.

We use a loop to iterate through the control from the first visible row to the last (even if only partially visible). For each row, depending on whether the row is odd or even, a rectangle is filled with the appropriate colour (HBRUSH).

void EraseAlternatingRowBkgnds (HWND hWnd, HDC hDC)
//    re-draw row backgrounds with the appropriate background colour
    RECT    rect;        //    row rectangle
    POINT    pt;
    int     iItems,
    HBRUSH    brushCol1,    //    1st colour
            brushCol2;    //    2nd colour

//    create coloured brushes
    brushCol1 = CreateSolidBrush (GetSysColor (COLOR_WINDOW));
    brushCol2 = CreateSolidBrush (colorShade (GetSysColor (COLOR_WINDOW), 95.0));
//    get horizontal dimensions of row
    GetClientRect (hWnd, &rect);
//    number of displayed rows
    iItems = ListView_GetCountPerPage (hWnd);
//    first visible row
    iTop = ListView_GetTopIndex (hWnd);
    ListView_GetItemPosition (hWnd, iTop, &pt);

    for (int i=iTop ; i<=iTop+iItems ; i++) {
//        set row vertical dimensions = pt.y;
        ListView_GetItemPosition (hWnd, i+1, &pt);
        rect.bottom = pt.y;
//        fill row with appropriate colour
        FillRect (hDC, &rect, (i % 2) ? brushCol2 : brushCol1);
//    cleanup
    DeleteObject (brushCol1);
    DeleteObject (brushCol2);

The WM_PAINT message is sent to a control when a (rectangular) area requires updating on the screen. For example, another window overlaps the control. Similar to the previous process, we iterate through the visible rows, but this time, we only update those rows that intersect with the update rectangle. This is achieved by first setting the text background colour, invalidating the row area, then calling on the default action of the WM_PAINT message. This has the effect of redrawing the row, but using the background colour we specify.

void PaintAlternatingRows (HWND hWnd)
//    re-draw rows with the appropriate background colour
    RECT    rectUpd,        	//    rectangle to update
            rectDestin,        	//    temporary storage
            rect;            	//    row rectangle
    POINT    pt;
    int     iItems,
    COLORREF    c;            	//    temporary storage

//    get the rectangle to be updated
    GetUpdateRect (hWnd, &rectUpd, FALSE);
//    allow default processing first
    CallWindowProc (
        (FARPROC) PrevWndFunc, hWnd, WM_PAINT, 0, 0);
//    set the row horizontal dimensions
    SetRect (&rect, rectUpd.left, 0, rectUpd.right, 0);
//    number of displayed rows
    iItems = ListView_GetCountPerPage (hWnd);
//    first visible row
    iTop = ListView_GetTopIndex (hWnd);

    ListView_GetItemPosition (hWnd, iTop, &pt);
    for (int i=iTop ; i<=iTop+iItems ; i++) {
//        set row vertical dimensions = pt.y;
        ListView_GetItemPosition (hWnd, i+1, &pt);
        rect.bottom = pt.y;
//        if row rectangle intersects update rectangle then it requires
//        re-drawing
        if (IntersectRect (&rectDestin, &rectUpd, &rect)) {
//            change text background colour accordingly
            c = (i % 2) ? colorShade (GetSysColor (COLOR_WINDOW), 95.0) :
                GetSysColor (COLOR_WINDOW);
            ListView_SetTextBkColor (hWnd, c);
//            invalidate the row rectangle then...
            InvalidateRect (hWnd, &rect, FALSE);
//            ...force default processing
            CallWindowProc (
                (FARPROC) PrevWndFunc, hWnd, WM_PAINT, 0, 0);

The function colorShade is used to provide an alternate colour:

COLORREF colorShade (COLORREF c, float fPercent)
//    create a lighter shade (by fPercent %) of a given colour
    return RGB ((BYTE) ((float) GetRValue (c) * fPercent / 100.0),
        (BYTE) ((float) GetGValue (c) * fPercent / 100.0),
        (BYTE) ((float) GetBValue (c) * fPercent / 100.0));

Now, in order to get our code working, there's one last thing we have to take care of.

We need to chain the default WndProc (Window Procedure) of the control with our routine which:

  1. provides the message interception, and
  2. gives us the method to force a default action

We do this with the help of the GetWindowLong and SetWindowLong APIs.

(where hWndListView is a HANDLE to the ListView control)

PrevWndFunc = (WNDPROC) GetWindowLong (hWndListView, GWL_WNDPROC); 

Then set the default WndProc function to our WndProc (ListViewWndProc).

SetWindowLong (hWndListView, GWL_WNDPROC, (LONG) ListViewWndProc); 

Note the global variable (WNDPROC prevWndFunc) which stores the default WndProc function pointer.

LRESULT CALLBACK ListViewWndProc (HWND hWnd, UINT iMessage, WPARAM wParam,
                    LPARAM lParam)
//    subclassed window procedure
    switch (iMessage) {
        case WM_PAINT:
//    intercept the WM_PAINT message which is called each time an area
//    of the control's client area requires re-drawing
            PaintAlternatingRows (hWnd);
            return 0;
        case WM_ERASEBKGND:
//    intercept the WM_ERASEBKGRN message which is called each time an area
//    of the control's client area background requires re-drawing
            EraseAlternatingRowBkgnds (hWnd, (HDC) wParam);
            return 0;

// continue with default message processing
    return CallWindowProc (
        (FARPROC) PrevWndFunc, hWnd, iMessage, wParam, lParam);

Points of Interest

The example code is a complete Windows program (using only APIs) which creates a main window with a single ListView control. The colours used by the example are COLOR_WINDOW (which is the default colour for a ListView control) and a shade (95%) of COLOR_WINDOW.

This code works fine with Borland C++ (all versions) but should be easily ported to Visual C++ and the like.

The same procedure can be taken further. For example, to set columns in different colours, or even a combination of colours for different cells.


  • 8th October, 2007: First version


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


About the Author

Web Developer
Australia Australia
No Biography provided

You may also be interested in...


Comments and Discussions

GeneralMy vote of 2 Pin
Shog930-Mar-10 9:28
sitebuilderShog930-Mar-10 9:28 
GeneralOr use NM_CUSTOMDRAW... Pin
nm_1148-Oct-07 10:25
membernm_1148-Oct-07 10:25 
AnswerRe: Or use NM_CUSTOMDRAW... Pin
bcmarkey10-Oct-07 5:03
memberbcmarkey10-Oct-07 5:03 
Yes, you certainly could make use of NM_CUSTOMDRAW by intercepting the WM_NOTIFY message of the parent window (and remove the WM_PAINT reference). I noted the article by Michael Dunn (

For simplicity sake, I used the WM_PAINT method since;
- you will still need to intercept the WM_ERASEBKGND message to paint the client area to the right of the last column and
- intercepting the WM_NOTIFY message at the parent WndProc requires code to determine which control that particular message relates to. I am aware that Visual C++ has a number of 'handlers' to facilitate this, but my aim was to provide a generic solution which does not rely on a particular compiler.

Further, as WM_CUSTOMDRAW is implemented in Custom Draw v4.71 or later of Comctl32.dll (packaged with IE4 or later), systems with earlier versions won't support it. Again, I was after a solution for all versions of Windows. Smile | :)
GeneralRe: Or use NM_CUSTOMDRAW... Pin
nm_11410-Oct-07 11:35
membernm_11410-Oct-07 11:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.171207.1 | Last Updated 8 Oct 2007
Article Copyright 2007 by bcmarkey
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid