Click here to Skip to main content
12,831,243 members (46,922 online)
Click here to Skip to main content
Add your own
alternative version


195 bookmarked
Posted 27 Aug 2004


, 24 Jan 2006
Rate this:
Please Sign up or sign in to vote.
A custrom-draw virtual list control. Support for subitem editing, images, button (checkboxes, radio buttons), custom colors, and "column navigation".

Sample Image - quicklist.gif



CQuickList is another owner draw CListCtrl derived control. The major difference between this control and other owner draw list controls at CodeProject is that this is a virtual list. This means that you don't insert items in the list. Instead, the list control will ask the parent when it needs information. So, you could make a large complex list very fast without using a lot of memory. If you haven't had used a virtual list before, it might be a good idea to have a look on the article "Virtual list" where I try to explain the idea.


For a while ago, I needed a list where I could have images in subitems. If it was possible to edit subitems, that would be great too. But the most important for me was that the list was virtual, and since I didn't find something like that, I started to create my own. And this is the result, so far.

However, even if I didn't find what I searched for, I found other controls, which have been a great help for me. So, thanks to all other authors at CodeProject :-).


The most important features of CQuickList are:

  • Subitem editing.
  • Images in subitems.
  • Buttons in list (like checkboxes, radio buttons).
  • Progress bar.
  • Customizing colors.
  • Tooltips.
  • Column navigation.
  • Bold/italic text.
  • Show message if the list is empty.
  • Automatic handling of the LVN_ODFINDITEM message.
  • Small code. Use #define to remove unused features.
  • Unicode support.
  • Support for themes in Windows XP.

Creating a CQuickList

Creating a CQuickList is quite simple. Add a list control in the resource editor and add a CListCtrl variable to this control. Replace "CListCtrl" to "CQuickList" in the header file, and you are done.

Make sure that you have checked the style "Owner data" and have the view in Report mode. Make also sure that "Owner draw fixed" is not checked.

Add items to the list

Let's say m_list is the control variable for the list. Normally, you add data to the list like this:

m_list.InsertItem(0, _T("Hello world"));

But in a virtual list (like CQuickList), this will not work. Instead, it is up to you to handle the data. Instead of adding, you change the number of elements the list is showing:

//"Add" 100 elements

If you set the item count to 100 or 1,000,000, it doesn't matter, the time to run this command will still be practically zero. In a non-virtual list, adding a million elements could take hours.


A normal virtual list sends LVN_GETDISPINFO to the parent when it needs information. This message is also sent when you are using CQuickList, but that message isn't important. Instead, you should handle the WM_QUICKLIST_GETLISTITEMDATA message. Add this in the header file:

afx_msg LRESULT OnGetListItem(WPARAM wParam, LPARAM lParam);

Add a message handler in the message map:

    //...other messages here...

And finally, add the function:

LRESULT CMyListCtrlDlg::OnGetListItem(WPARAM wParam, LPARAM lParam)
    //wParam is a handler to the list
    //Make sure message comes from list box
    ASSERT( (HWND)wParam == m_list.GetSafeHwnd() );

    //lParam is a pointer to the data that 
    //is needed for the element
    CQuickList::CListItemData* data = 
        (CQuickList::CListItemData*) lParam;

    //Get which item and subitem that is asked for.
    int item = data->GetItem();
    int subItem = data->GetSubItem();

    //...insert information that is needed in "data"...

    return 0;


CQuickList::CListItemData is a very simple, but important class. In this class, there are several member variables. These variables are used to draw the item. The public part in this class is:

class CQuickList::CListItemData

    //Some obvius functions
    int GetItem() const;
    int GetSubItem() const;
    bool IsSelected() const;
    bool IsHot() const;

    //The item text
    CString m_text;

    //Tool tip text. Note: Don't forget to call EnableToolTips()
    //to enable tool tips.
    CString m_tooltip;

    //Set this to true if you don't want to draw a selection mark
    //even if this item is selected.
    //Default value: false
    bool m_noSelection;

    //Set this to true if the item is available for editing
    //Default value: false
    bool m_allowEdit;

    //Information about which text style that should be used.
    struct CListTextStyle
        //Default value: false
        bool m_bold;

        //Default value: false
        bool m_italic;

        //Default value:
        //See CDC:DrawText in MSDN
        UINT m_textPosition; 
    } m_textStyle;

    //Information about the image
    struct CListImage
        //The image position in the image list.
        //-1 if no image.
        //Default value: -1
        int m_imageID;

        //The image list where the image is.
        //Default value: A pointer to the image list in the list
        //control that is used small images (LVSIL_SMALL)
        CImageList* m_imageList;

        //Set true if you don't want to draw selection mark if the
        //item is selection
        //Default value: true
        bool m_noSelection;

        //Center the image. Useful if no text.
        //Default value: false;
        bool m_center;

        //Blend if the image is selected. Use ILD_BLEND25 or
        //ILD_BLEND50, or 0 if you don't want to use this feature.
        //Default value: ILD_BLEND25
        int m_blend;

    } m_image;

    //Information about the button
    struct CListButton
        //The style to use to draw the control.
        //Default value: DFCS_BUTTONCHECK
        //Use DFCS_CHECKED to draw the check mark.
        //Use DFCS_BUTTONRADIO for radio button, DFCS_BUTTONPUSH
        //for push button.
        //See CDC::DrawFrameControl for details.
        int m_style; 

        //If you want to draw a button, set this to true
        //Default value: false
        bool m_draw;

        //Center the check box is the column. Useful if no text
        //Default value: false
        bool m_center;

        //Set this to true if you don't want to draw selection
        //mark under the control.
        //Default value: true
        bool m_noSelection;

    } m_button;

    //Information about the progress bar
    struct CListProgressbar
        //Note: The m_text member specifies the text in the
        //progress bar

        //The max value of progress bar. Use -1 to disable
        //progress bar. The min value is supposed to be 0.
        //Default value: -1
        int m_maxvalue;

        //The value the progress bar has. The width of the
        //progress bar is calculated with use m_value and
        //Default value: 0
        int m_value;

        //The color the progress bar should be drawn with. 
        //Default value: DEFAULTCOLOR
        COLORREF m_fillColor;

        //The color of the text on the progress bar
        //Default value: DEFAULTCOLOR
        COLORREF m_fillTextColor;

        //How to draw the edge. Use 0 for no edge.
        //See CDC::DrawEdge for different styles.
        //Default value: EDGE_SUNKEN
        UINT m_edge;
    } m_progressBar;

    //Information about the colors to use
    struct CListColors
        //Default value for all: DEFAULTCOLOR
        COLORREF m_textColor;
        COLORREF m_backColor;
        COLORREF m_hotTextColor;
        COLORREF m_selectedTextColor;
        COLORREF m_selectedBackColor;
        COLORREF m_selectedBackColorNoFocus;

        //These colors are used to draw selected items in
        //the "navigation column"
        COLORREF m_navigatedTextColor;
        COLORREF m_navigatedBackColor;
    } m_colors;


As you see, there are several settings to use. However, you will probably use only a few of them. I will try to explain most of the settings in the following text. data in the following text is a pointer to a CQuickList::CListItemData object.


The simplest setting is m_text. This is the text to be drawn:

data->m_text = _T("Hello world");

Here the text Hello world will be drawn for the current item.

Tool tip

Tooltip sample

Tool tips could sometimes be useful. If you place the mouse cursor above an item, the text you set here will be shown as a tool tip. Example:

data->m_tooltip = _T("Tip: Hello world");

Here "Tip: Hello world" will be shown.

Note 1: To activate tooltips, you must call EnableToolTips(TRUE).

Note 2: If you don't use this feature, you could define QUICKLIST_NOTOOLTIP to make the application a little bit smaller.

Note 3: Unfortunately, there are some problems when tooltips are used. See points of interest.

Draw no selection

No selection sample. Column 1 is not selected.

As default, an item will be drawn as selected if it is selected. However, if you want to draw an item as unselected even if it is selected, then do this:

data->m_noSelection = false;

The item will not now be drawn as selected even if it is. But what is this good for? This may seem like a useless feature, but sometimes this could be pretty nice to use. For example, if you have several columns in a list, some columns could be drawn as not selected. In the image, all items in column 1 are drawn as not selected even if they are selected.

Allow edit

If you want to allow an item to be edited, do this:

data->m_allowEdit = true;

Note 1: You must handle some messages as well to make editing possible. I discuss this later.

Note 2: If you don't use this feature, you could define QUICKLIST_NOEDIT to make the application a little bit smaller.

Text style

Text style sample

Sometimes, it's useful to draw the text as bold or italic. This is simple to do:

data->m_textStyle.bold = true;
data->m_textStyle.italic = true;

You could also specify in which position the text should be drawn. Example:

//Left align text:
data->m_textStyle.m_textPosition = 

//Center text:
data->m_textStyle.m_textPosition = 

But you can do much more settings than this. Look in MSDN about CDC::DrawText.

Note: If you will not use this feature, you could define QUICKLIST_NOTEXTSTYLE to make the application a little bit smaller.


Image sample

Images are useful to have. If you have an image list connected to the list (called SetImageList), you only have to do this:

//Use image 2 in the list
data->m_image.m_imageID = 2;

Then the default image list will be used. However, you could specify which image list you want to use, like this:

//Use another image list
data->m_image.m_imageList = &m_mySecondImagelist;

If the item is selected, the image will not be drawn as selected, as default. But you can change this:

data->m_image.m_noSelection = false;

As you see on the image, the selected images are a little bit more blue than the images that are not selected. If you don't want this, you could change m_blend setting:

//Don't "blend" the image:
data->m_image.m_blend = 0;
//Other possible values are ILD_BLEND25 and ILD_BLEND50 (default).

The image will be drawn to the left as default. But if you don't have anything else than an image, why not center it?

//Center image
data->m_image.m_center = true;

Note: If you will not use this feature, you could define QUICKLIST_NOIMAGE to make the application a little bit smaller.


Button sample

If you want to draw a check box or a radio button, you could use the m_button variable:

//We want to draw a button
data->m_button.m_draw = true;

//Check box, not checked:
data->m_button.m_style = DFCS_BUTTONCHECK
//Check box, checked:
data->m_button.m_style = DFCS_BUTTONCHECK|DFCS_CHECKED;

//Radio button, not checked:
data->m_button.m_style = DFCS_BUTTONRADIO
//Radio button, checked:
data->m_button.m_style = DFCS_BUTTONRADIO|DFCS_CHECKED;

Just like images, buttons are not drawn as selected. They could also be centered:

//Draw as selected
data->m_button.m_noSelection = false;

data->m_button.m_center = true;

Note 1: Buttons aren't drawn with themes in XP as default. To solve this, you should call SetThemeManager(). Read here.

Note 2: If you will not use this feature, you could define QUICKLIST_NOBUTTON to make the application a little bit smaller.

Progress bar

Progressbar sample

Progress bars are rarely used in list controls, but they could be useful. To use it, first specify the max value:

//Max value is 100
data->m_progressBar.m_maxvalue = 100;

The minimum value is 0. Then specify which value the progress bar has:

//Fill half the progress bar:
data->m_progressBar.m_value = 50;

You can also change the edge. The default value for the edge is EDGE_SUNKEN. If you don't want any edge, set the value to 0.

//No edge:
data->m_progressBar.m_edge = 0;

See CDC::DrawEdge in MSDN for more settings.

You could also specify which fill color and text color to use. The default for these settings are DEFAULTCOLOR, which means that Windows should decide which color to use.

//Red fill color
data->m_progressBar.m_fillColor = RGB(255,0,0);
//White text color
data->m_progressBar.m_fillColor = RGB(255,255,255);

Note 1: If you have specified any text in m_text, the text will be drawn in the progress bar.

Note 2: If you will not use this feature, you could define QUICKLIST_NOPROGRESSBAR to make the application a little bit smaller.


Default colors Specified colors

As default, Windows colors will be used, but you can change this with the m_colors:

//Green text
data->m_colors.m_textColor = RGB(0,255,0);

//Black background
data->m_colors.m_backColor = RGB(0,0,0);

//If the item is "hot", use purple color
data->m_colors.m_hotTextColor = RGB(0,255,255);

//If the item is selected, the text should 
//be drawn in white color
data->m_colors.m_selectedTextColor = RGB(255,255,255);

//If the item is selected, the background should
//be drawn in green color
data->m_colors.m_selectedBackColor = RGB(0,128,0);

//If the item is selected but the list hasn't focus,
//the background should be drawn in gray color
data->m_colors.m_selectedBackColorNoFocus = RGB(64,64,64);

//If the item is "navigated", text will be drawn in red
data->m_colors.m_navigatedTextColor = RGB(255,0,0);

//If the item is "navigated", background will be drawn in blue
data->m_colors.m_navigatedBackColor = RGB(0,0,128);

When you set the background color, you could use a transparent color by using TRANSPARENTCOLOR. This is useful if you have a background image.

Navigation between subitems

Navigation sample

In a normal list, you could select items with the mouse or keyboard. But it's not possible to select a subitem, which may be quite useful. However, in CQuickList, that is possible :-). To specify which column is currently selected, call:

//Enable navigation (it is enabled as default)

//Set column 2 as "navigated".

OK, that's easy. But let's say you have three columns, and you don't want it to be possible to navigate to column 2. To solve this, add a message handler for the message WM_QUICKLIST_NAVIGATIONTEST (add a function in the header, and connect to it in the message handler). Then, write the function like this:

LRESULT CMyListCtrlDlg::OnNavigationTest(WPARAM wParam, LPARAM lParam)
    //Make sure message comes from list box
    ASSERT( (HWND)wParam == m_list.GetSafeHwnd() );

    CQuickList::CNavigationTest* test = 
        (CQuickList::CNavigationTest*) lParam;

    //The previous column is in test->m_previousColumn.

    //Don't allow navigation to column 2
    if(test->m_newColumn == 2)
        test->m_allowChange = false;

    return 0;

Now, it will not be possible to navigate to column 2.

Note: If you will not use this feature, you could define QUICKLIST_NONAVIGATION to make the application a little bit smaller.


As you might know, it's possible to find in item in a normal list by writing in the list (read here for more information). To make this possible in virtual list, you have to handle the LVN_GETDISPINFO message. But when you are using CQuickList that is not necessary, the list will handle this for you. But you can specify which column the list will search in when it tries to find an item. Example:

//Search in column 1:

You can use KEYFIND_CURRENTCOLUMN to search in the current navigated column. If you want the parent to handle this message, use KEYFIND_DISABLED.

Note: If you will not use this feature, you could define QUICKLIST_NOKEYFIND to make the application a little bit smaller.

Click on image/button

When you click on a check box, you expect that it will toggle. But in a virtual list, the list can't change the value. To solve this, the list sends a message to the parent that has to do the work. Add a message handler for the message WM_QUICKLIST_CLICK (add a function in the header, and connect to it in the message handler). Then, write the function like this:

LRESULT CMyListCtrlDlg::OnListClick(WPARAM wParam, LPARAM lParam)
    //Make sure message comes from list box
    ASSERT( (HWND)wParam == m_list.GetSafeHwnd() );

    CQuickList::CListHitInfo *hit= 
        (CQuickList::CListHitInfo*) lParam;

    //Item:    hit->m_item
    //Subitem: hit->m_subitem

    //Hit button?
        //...toggle check box in the database...

        //Redraw check box
        m_list.RedrawCheckBoxs( hit->m_item, 
    else //Hit image?
        //... toggle image ...

        //Redraw image

    return 0;

As you can see, it's possible to see if an image was hit.

Note: Another way to solve this is to handle the NM_LCLICK message. Call then CQuickList::HitTest to see if a button or image was hit.

Empty list

Empty list sample

If the list is empty, it could be nice, so show a little message in the list. This is easy to do:

//Show "Hello world" is the list is empty
m_list.SetEmptyMessage(_T("Hello world"));

Note: If you will not use this feature, you could define QUICKLIST_NOEMPTYMESSAGE to make the application a little bit smaller.

Right click on column header

Right click sample

As far as I know, there is no easy way to catch a right click in the column header in CListCtrl. This is pretty sad since it would be a great way to show the user a context menu where he, for example, could hide a menu. When you are using CQuickList, the list will send WM_QUICKLIST_HEADERRIGHTCLICK to the parent when a right click appears in the column header. WPARAM is a handle to the list, and LPARAM is a pointer to a CQuickList::CHeaderRightClick object. That object includes the mouse position (m_mousePos) and which column was clicked (m_column).

A function that pops up a menu would look something like this:

LRESULT CMyListCtrlDlg::OnHeaderRightClick(WPARAM wParam, LPARAM lParam)
    //Make sure message comes from list box
    ASSERT( (HWND)wParam == m_list.GetSafeHwnd() );

    CQuickList::CHeaderRightClick *hit= 
        (CQuickList::CHeaderRightClick*) lParam;

    //Load menu
    CMenu menu;

    //Pop up sub menu 0
    CMenu* popup = menu.GetSubMenu(0);

    popup->TrackPopupMenu( TPM_LEFTALIGN | TPM_RIGHTBUTTON,

    return 0;

Edit subitems

Editing subitems in CQuickList doesn't differ much from a CListCtrl. Before editing starts, the message OnBeginlabeleditList is sent to the parent. Unless you want to specify another text than you specify in m_text, you can ignore this message. When editing is done, the message LVN_ENDLABELEDIT is sent. You must add a handler for this message if you want to save the text. A function will look something like this:

void CMyListCtrlDlg::OnEndlabeleditList(NMHDR* pNMHDR, LRESULT* pResult) 

    // If pszText is NULL, editing was canceled
    if(pDispInfo->item.pszText != NULL)
        //Item:    pDispInfo->item.iItem
        //Subitem: pDispInfo->item.iSubItem
        //... save the text ...

    *pResult = 0;

You could call CQuickList::GetLastEndEditKey to see which key was pressed when editing ended. For example, if the user pressed enter (VK_RETURN), it might be a good idea to start editing the next item in the list.

The edit box is closed when it is losing focus. If you call CQuickList::SetEndEditOnLostFocus(false), it will not close when it is losing focus. Instead, the parent will receive the message WM_QUICKLIST_EDITINGLOSTFOCUS. (I guess this feature is strange, but I need it in one of my programs, so I added this :-)).

Editing will start when F2 or ENTER is pressed, or when the user double clicks on the item. You could change this by calling the functions SetEditOnEnter, SetEditOnF2, and SetEditOnDblclk. You can also call EditSubItem to start editing an item.

Themes in XP

CQuickList supports themes in Window XP. Themes are used when drawing buttons (check button, radio buttons...). If you don't use this, you could ignore this.

To enable themes, you should call SetThemeManager() with a pointer to a CTheme object. If you don't do this, buttons will be drawn in the traditional way.

In the demo project is a CTheme class. I have based this on the article XP Style CBitmapButton (CHoverBitmapButton). The good thing about this is that programs run fine on other systems than Windows XP. Unfortunately, we must add some code in the main window. Look in the demo project to see how to do this. Make sure to define "USEXPTHEMES" in StdAfx.h. You will need a pretty new version of Platform SDK to compile the code with theme support.

Note: If you don't use this feature, you could define QUICKLIST_NOXPTHEME to make the application a little bit smaller.


Since I haven't done any similar work before, I'm very glad there have been other projects that have been a great help for me. I have looked on and even copied some code from other projects. The most useful project for me has been "XListCtrl - A custom-draw list control with subitem formatting". But I also want to thank:

Another nice control is Virtual Grid Control. It's quite similar to CQuickList and worth to look at.

To do

CQuickList works well, but there are some things I want to be fixed/implemented:

  • Fix tool tip problems (see Points of interest).
  • I want to set the LVS_OWNERDATA setting when the CQuickList control is created. But neither Create nor OnCreate is called. Suggestion, someone?
  • Specify height of items.
  • When you double click in the column header between two columns, the column width should be set to the widest item. This doesn't work perfectly, especially if buttons or images are used.
  • Support for drag and drop. I have tried to make a function that creates a drag image (CreateDragImageEx), but that doesn't work at all.

Points of Interest

CQuickList is mostly designed with full row select, but it works also when you don't use this. However, it might be some minor drawing problems, so my recommendation is to use full row select.

When I used the list in Windows XP with a manifest file, I had some problems. One was that the "hot item" was changed when the mouse pointer was over an item. The solution to this was to handle the LVN_HOTTRACK message. Another problem was that the list was drawn over the edit box when the mouse pointer was moved over the list, I solved this by handling the message WM_MOUSEMOVE.

Another strange behavior in XP is that there are some drawing problems when tooltips are used. When you use tooltips, you may notice that the list is flickering a little bit. The problem is in OnToolHitTest. This functions calls ListView_SubItemHitTest, and for some very, very strange reason, this forces the list to make some redrawing (probably only in the first column). If you move the mouse pointer over the column header, the header will temporarily disappear and you will see the item under it, very weird. I haven't figured out why this happens. If you have this problem, the simplest way to solve it is to not use tool tips.


  • 22 January, 2006 - Version 1.01. Fixed problem when LVS_EX_HEADERDRAGDROP is used.
  • 10 September, 2004 - Version 1.0. Solved the "hot item" problem in Windows XP. Added support for themes. Added message when user right clicks on the column header.
  • 28 August, 2004 - Version 0.9. Initial version.


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


About the Author

Sweden Sweden
PEK is one of the millions of programmers that sometimes program so hard that he forgets how to sleep (this is especially true when he has more important things to do). He thinks that there are not enough donuts in the world. He likes when his programs works as they should do, but dislikes when his programs is more clever than he is.

You may also be interested in...

Comments and Discussions

GeneralGreate Job Man Pin
Anonymous1-Sep-04 6:21
sussAnonymous1-Sep-04 6:21 
GeneralRe: Greate Job Man Pin
PEK2-Sep-04 8:09
memberPEK2-Sep-04 8:09 
QuestionCould you fix the horizontal scrolling issue? Pin
WREY31-Aug-04 3:01
memberWREY31-Aug-04 3:01 
AnswerRe: Could you fix the horizontal scrolling issue? Pin
PEK31-Aug-04 6:22
memberPEK31-Aug-04 6:22 
GeneralAs usual... Pin
prcarp30-Aug-04 11:01
memberprcarp30-Aug-04 11:01 
GeneralRe: As usual... Pin
prcarp30-Aug-04 11:11
memberprcarp30-Aug-04 11:11 
GeneralRe: As usual... Pin
PEK31-Aug-04 12:36
memberPEK31-Aug-04 12:36 
GeneralAwesome Pin
Peter Mares30-Aug-04 4:39
memberPeter Mares30-Aug-04 4:39 
I've just read your article, and although I will probably not use it since I am stuck in WTL development nowadays, I have to compliment you on the layout and general article quality!
Well done... am looking forward to reading more from you in the future

[Glossary Manager] [AfterThought Backup Lite]

All good things were meant to be improved
GeneralRe: Awesome Pin
PEK30-Aug-04 6:48
memberPEK30-Aug-04 6:48 

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
Web02 | 2.8.170326.1 | Last Updated 24 Jan 2006
Article Copyright 2004 by PEK
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid