如何在列表视图列标题中添加复选框






4.54/5 (10投票s)
一个如何在列表视图列标题中添加复选框的例子。我们还实现了当用户切换复选框时全选/取消全选的功能。
引言
似乎很少有(如果有的话)关于如何在列表视图控件的列标题中放置复选框的示例代码。如果您在列表视图窗口样式中使用LVS_EX_CHECKBOXES
来显示每个项目旁边的复选框,则可以使用它来添加“选中/取消选中全部”功能。
遗憾的是,这里使用的方法仅适用于 Windows Vista/Server 2008 及更高版本。它在 XP 上运行时似乎可以正常失败(不显示复选框),但尚未进行广泛的测试。
背景
列表视图控件没有提供直接向列标题添加复选框的方法。它创建一个标题控件来显示列标题。可以使用ListView_GetHeader()
宏(或等效消息)获取此控件的句柄,然后您可以对其进行操作。
使用代码
包含的示例项目是使用 VS2010 和 WTL AppWizard 生成的。此技术不需要 ATL 或 WTL 即可工作。与此方法相关的代码实际上是使用 SDK 宏编写的。您当然可以使用 ATL/WTL 助手来简化生活。
示例应用程序只是一个带有列表视图控件的模态对话框应用程序。
我们将在OnInitDialog
方法中添加一些初始化代码。首先,我们将列表视图控件的HWND
保存到成员变量中,以便以后轻松访问。然后,我们向控件添加一些样式,以便它显示复选框并在单击时选择整行
// 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);
接下来,我们将实际创建列。第一列仅包含复选框
// 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);
这就是魔力开始的地方。首先,我们获取列表视图使用的标题控件的HWND
。然后,我们可以修改其窗口样式以添加HDS_CHECKBOXES
样式,这将允许我们在标题中显示复选框。如果我们不这样做,控件将不会渲染复选框。我们还存储控件 ID 以供我们的消息处理程序稍后使用,以便我们可以检测到有人单击复选框
// 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);
最后,我们要求标题控件使用列表视图中第一列的格式填充HDITEM
结构。然后,我们应用HDF_CHECKBOX
格式标志。我们还应用HDF_FIXEDWIDTH
标志,以防止用户调整列的大小。这也是一个 Vista 及更高版本的标志
// 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);
好的,这将在标题中显示复选框。如果我们不处理任何通知,则默认行为是在单击复选框时选择和取消选择列表视图中的项目。这可能不是您想要的,因此我们将使其真正选中和取消选中项目。首先,让我们设置几个通知映射
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()
当用户单击复选框时,标题控件将发送HDN_ITEMSTATEICONCLICK
通知。我们在OnHeaderItemStateIconClick
方法中处理此问题。基本上,我们检查提供的HDITEM
是否包含有关我们的复选框状态的信息。如果包含,我们调用CheckAllItems()
函数来选中列表视图中所有项目的复选框。然后,我们调用SetHeaderCheckbox()
,它设置标题中复选框的状态
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);
}
现在,我们处理用户选中列表中一个项目的情况。如果用户手动选中所有项目,我们希望标题复选框自动选中。我们通过调用SetHeaderCheckbox()
方法来实现这一点
LRESULT OnListItemChanged(int /*idCtrl*/, LPNMHDR pnmh, BOOL& /*bHandled*/) {
LPNMLISTVIEW pnmlv = (LPNMLISTVIEW)pnmh;
if (pnmlv->uChanged & LVIF_STATE) {
SetHeaderCheckbox();
}
return 0;
}
历史
- 2011-02-12:首次发布。