Tuesday, February 15, 2011

How to Add a Checkbox to a List View Column Header

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.

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 equivilent 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 it's 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);

Read more: Codeproject