65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.54/5 (10投票s)

2011 年 2 月 13 日

CPOL

3分钟阅读

viewsIcon

117491

downloadIcon

6118

一个如何在列表视图列标题中添加复选框的例子。我们还实现了当用户切换复选框时全选/取消全选的功能。

引言

似乎很少有(如果有的话)关于如何在列表视图控件的列标题中放置复选框的示例代码。如果您在列表视图窗口样式中使用LVS_EX_CHECKBOXES来显示每个项目旁边的复选框,则可以使用它来添加“选中/取消选中全部”功能。

遗憾的是,这里使用的方法仅适用于 Windows Vista/Server 2008 及更高版本。它在 XP 上运行时似乎可以正常失败(不显示复选框),但尚未进行广泛的测试。

screenshot.png

背景

列表视图控件没有提供直接向列标题添加复选框的方法。它创建一个标题控件来显示列标题。可以使用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:首次发布。
© . All rights reserved.