Click here to Skip to main content
Click here to Skip to main content

How to Add a Checkbox to a List View Column Header

, 13 Feb 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
An example of how to add a checkbox to a list view column header. We also implement select/unselect all when a user toggles the checkbox.

Introduction

There doesn't appear to be much, if any, example code of how to place a checkbox in the column header of a list view control. This can be used to add "check/uncheck all" functionality if you're using LVS_EX_CHECKBOXES in your list view window style to display a check box next to each item.

Unfortunately, the method used here only works with Windows Vista/Server 2008 and later. It appears to fail gracefully (no checkbox is displayed) when run on XP, but extensive testing has not been done.

screenshot.png

Background

The list view control does not expose a way to add a checkbox to a column heading directly. It creates a Header control to display the column headings. A handle to this control can be obtained with the ListView_GetHeader() macro (or equivalent message) with which you can then manipulate.

Using the code

The sample project included was generated with VS2010 using the WTL AppWizard. Neither ATL nor WTL are needed for this technique to work. The code relevant to this method has actually been written using the SDK macros. You can of course use ATL/WTL helpers to make life easier.

The sample app is simply a modal dialog application with a list view control.

We'll throw some initialization code in our OnInitDialog method. First, we'll save the HWND of the list view control to a member variable for easy access later. Then, we add some styles to the control so it will display checkboxes and select the full row when clicked:

// Grab the window handle for our list view
m_list = GetDlgItem(IDC_LIST);

// Set some styles for the list view control.
// We want checkboxes and full row select.
ListView_SetExtendedListViewStyle(m_list, 
         LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT);

Next, we'll actually create the columns. The first column will hold only the checkbox:

// Add some columns to the list view control
LVCOLUMN lvc = {0};
ListView_InsertColumn(m_list, 0, &lvc);
lvc.mask = LVCF_TEXT;
lvc.iSubItem++;
lvc.pszText = _T("First Name");
ListView_InsertColumn(m_list, 1, &lvc);
lvc.iSubItem++;
lvc.pszText = _T("Last Name");
ListView_InsertColumn(m_list, 2, &lvc);
lvc.iSubItem++;
lvc.pszText = _T("Company");
ListView_InsertColumn(m_list, 3, &lvc);

// Set column widths
ListView_SetColumnWidth(m_list, 0, LVSCW_AUTOSIZE_USEHEADER);
ListView_SetColumnWidth(m_list, 1, LVSCW_AUTOSIZE_USEHEADER);
ListView_SetColumnWidth(m_list, 2, LVSCW_AUTOSIZE_USEHEADER);
ListView_SetColumnWidth(m_list, 3, LVSCW_AUTOSIZE_USEHEADER);

And here's where the magic starts. First, we obtain the HWND to the header control used by the list view. Then, we can modify its window style to add the HDS_CHECKBOXES style which will allow us to display a checkbox in the header. If we don't do this, the control will not render a checkbox. We also store the control ID for later use by our message handler so we can detect when someone clicks the checkbox:

// Here's where we can add the checkbox to the column header
// First, we need to snag the header control and give it the
// HDS_CHECKBOXES style since the list view doesn't do this for us
HWND header = ListView_GetHeader(m_list);
DWORD dwHeaderStyle = ::GetWindowLong(header, GWL_STYLE);
dwHeaderStyle |= HDS_CHECKBOXES;
::SetWindowLong(header, GWL_STYLE, dwHeaderStyle);

// Store the ID of the header control so we can handle its notification by ID
m_HeaderId = ::GetDlgCtrlID(header);

Finally, we ask the header control to populate an HDITEM struct with the format for the first column in our list view. We then apply the HDF_CHECKBOX format flag. We also apply the HDF_FIXEDWIDTH flag which prevents users from resizing the column. This is also a Vista and later flag:

// Now, we can update the format for the first header item,
// which corresponds to the first column
HDITEM hdi = { 0 };
hdi.mask = HDI_FORMAT;
Header_GetItem(header, 0, &hdi);
hdi.fmt |= HDF_CHECKBOX | HDF_FIXEDWIDTH;
Header_SetItem(header, 0, &hdi);

Okay, that will get the checkbox to display in the header. If we don't handle any of the notifications, the default behavior is to select and unselect the items in the list view when clicking the checkbox. This probably isn't what you want, so we'll make it actually check and uncheck the items. First, let's set up a couple notification mappings:

BEGIN_MSG_MAP(CMainDlg)
    MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
    COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
    COMMAND_ID_HANDLER(IDOK, OnOK)
    COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
    NOTIFY_HANDLER(m_HeaderId, HDN_ITEMSTATEICONCLICK, OnHeaderItemStateIconClick)
    NOTIFY_HANDLER(IDC_LIST, LVN_ITEMCHANGED, OnListItemChanged)
END_MSG_MAP()

The header control will send a HDN_ITEMSTATEICONCLICK notification when the user clicks the checkbox. We handle this in our OnHeaderItemStateIconClick method. Basically, we check to see if the provided HDITEM contains information about the state of our checkbox. If it does, we call our CheckAllItems() function to check the checkboxes of all of the items in the list view. Then, we call SetHeaderCheckbox() which sets the state of the checkbox in the header:

LRESULT OnHeaderItemStateIconClick(int /*idCtrl*/, 
        LPNMHDR pnmh, BOOL& /*bHandled*/) {
    LPNMHEADER pnmHeader = (LPNMHEADER)pnmh;

    if (pnmHeader->pitem->mask & HDI_FORMAT && 
              pnmHeader->pitem->fmt & HDF_CHECKBOX) {
        CheckAllItems(!(pnmHeader->pitem->fmt & HDF_CHECKED));
        SetHeaderCheckbox();
        return 1;
    }

    return 0;
}

void CheckAllItems(BOOL fChecked) {
    for (int nItem = 0; nItem < ListView_GetItemCount(m_list); nItem++) {
        ListView_SetCheckState(m_list, nItem, fChecked);
    }
}

void SetHeaderCheckbox(void) {
    // Loop through all of our items. If any of them are
    // unchecked, we'll want to uncheck the header checkbox.
    BOOL fChecked = TRUE;
    for (int nItem = 0; nItem < ListView_GetItemCount(m_list); nItem++) {
        if (!ListView_GetCheckState(m_list, nItem)) {
            fChecked = FALSE;
            break;
        }
    }

    // We need to get the current format of the header
    // and set or remove the HDF_CHECKED flag
    HWND header = ListView_GetHeader(m_list);
    HDITEM hdi = { 0 };
    hdi.mask = HDI_FORMAT;
    Header_GetItem(header, 0, &hdi);
    if (fChecked) {
        hdi.fmt |= HDF_CHECKED;
    } else {
        hdi.fmt &= ~HDF_CHECKED;
    }
    Header_SetItem(header, 0, &hdi);
}

Now, we handle when the user checks one of the items in the list. We want the header checkbox to check itself if the user manually checks all of the items. We do this by just calling the SetHeaderCheckbox() method:

LRESULT OnListItemChanged(int /*idCtrl*/, LPNMHDR pnmh, BOOL& /*bHandled*/) {
    LPNMLISTVIEW pnmlv = (LPNMLISTVIEW)pnmh;

    if (pnmlv->uChanged & LVIF_STATE) {
        SetHeaderCheckbox();
    }
    return 0;
}

History

  • 2011-02-12: Initial release.

License

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

Share

About the Author

Vince Valenti

United States United States
No Biography provided

Comments and Discussions

 
QuestionSetting the Sorting property of a ListView clears any check boxes Pinmemberjrhoads2326-Mar-14 20:04 
GeneralMultiSelect is true, Checkbox on Column will disappear PinmemberZhuJinYong13-Jun-11 1:43 
General"the method used here only works with Windows Vista/Server 2008 and later" PinmemberVictor Nijegorodov14-Feb-11 21:49 
GeneralRe: "the method used here only works with Windows Vista/Server 2008 and later" PinmemberIvo Beltchev15-Feb-11 6:40 
GeneralRe: "the method used here only works with Windows Vista/Server 2008 and later" PinmemberVince Valenti15-Feb-11 13:36 
QuestionList View... at this modern age? PinmemberMukit, Ataul13-Feb-11 18:02 
AnswerRe: List View... at this modern age? PinmemberVince Valenti13-Feb-11 19:15 
GeneralBrowse code page is empty [modified] PinmemberMika Wendelius13-Feb-11 9:01 
QuestionHow about a 3-state checkbox? PinmemberIvo Beltchev13-Feb-11 5:11 
AnswerRe: How about a 3-state checkbox? PinmemberVince Valenti13-Feb-11 7:24 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.141015.1 | Last Updated 13 Feb 2011
Article Copyright 2011 by Vince Valenti
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid