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

CGridListCtrlEx - 基于 CListCtrl 的网格控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (136投票s)

2008 年 9 月 3 日

CPOL

12分钟阅读

viewsIcon

3902685

downloadIcon

67026

一个自定义绘制的 CListCtrl,支持子项编辑和格式化

介绍 

Microsoft 的 CListCtrl 支持使用报表样式显示网格中的数据,但我们需要进行多项更改才能实现以下功能: 

本文演示了如何使用 CGridListCtrlEx,它实现了上述所有功能,同时保持了 Windows XP/Vista 的外观。

如果您需要 Git/Subversion 访问权限,可以使用 GitHub - CGridListCtrlEx,此外还有 Doxygen 文档

背景

有许多高级网格控件可以扩展 CListCtrl,其中之一就是 增强列表控件 (CGfxListCtrl)。这个出色的控件提供了上述所有功能,但未能很好地处理 Windows XP 和 Vista。找到一个好的替代品并不容易

  • MFC 网格控件 - 不继承自 CListCtrl,因此不受其限制,但它也无法从 Microsoft 为 CListCtrl 添加的任何改进中受益。
  • Ultimate Grid - 与 MFC 网格控件一样,它不继承自 CListCtrl。最初需要购买,但现在可以免费使用。
  • CQuickList - 非常接近完美的替代品,但难以添加新的数据显示方式,并且需要 LVS_OWNERDATA,这使得排序稍显困难。
  • XListCtrl - 也是一个非常完整的 CListCtrl,但难以添加新的数据显示方式,并且不支持 LVS_OWNERDATA。现在需要购买许可证才能获得最新版本。
  • 另一个报表列表控件 - 简单易用,但除了使用 CEdit 之外,缺乏其他编辑数据的方式,并且缺少子项导航。
  • CListCtrlEx - 实现了很多功能并且文档齐全。最初需要 LVS_OWNERDRAWFIXED,现在已发展为使用自定义绘制。自定义绘制和所有者绘制的结合使得代码有点复杂,并且它也不支持 LVS_OWNERDATA

CGridListCtrlEx 插入了一个名为列特征(column traits)的抽象层,该层负责单元格的绘制和编辑。如果 Microsoft 再次扩展其 CListCtrl,那么 CGridListCtrlEx 的核心应该会继续工作。

如何使用 CGridListCtrlEx

CGridListCtrlEx 尽量保持与 CListCtrl 的一致性,并且不试图替换 CListCtrl 已提供的任何功能。这意味着我们可以直接用 CGridListCtrlEx 替换 CListCtrl,而无需进行任何其他操作。

建议不要直接使用 CGridListCtrlEx,而是创建一个新的派生自 CGridListCtrlEx 的类。这样可以更轻松地迁移将来对 CGridListCtrlEx 类的任何更新。

编辑单元格/子项

默认情况下,在 CGridListCtrlEx 中插入列时,它们将被配置为只读,无法编辑。通过使用 CGridListCtrlEx::InsertColumnTrait(),我们可以提供一个 CGridColumnTrait 类,该类指定了应使用的编辑器类型。

CGridColumnTrait* pTrait = new CGridColumnTraitEdit;
m_ListCtrl.InsertColumnTrait(nCol, title.c_str(), LVCFMT_LEFT, 100, nCol, pTrait);

编辑完项目后,将向 CListCtrl 发送标准的 LVN_ENDLABELEDIT 消息。当 CGridListCtrlEx 收到此消息时,它将自动调用虚方法 CGridListCtrlEx::OnEditComplete(),允许派生类验证输入并可能更新底层数据模型。

使用组合框编辑单元格/子项

通过使用 CGridListCtrlEx::InsertColumnTrait(),我们还可以提供一个 CGridColumnTrait 类,该类充当 CComboBox

CGridColumnTraitCombo* pTrait = new CGridColumnTraitCombo;
pTrait->AddItem(0, "Hello");
pTrait->AddItem(1, "Goodbye");
m_ListCtrl.InsertColumnTrait(nCol, title.c_str(), LVCFMT_LEFT, 100, nCol, pTrait);

我们可以在插入列时指定 CComboBox 的项目(如上所示)。如果想动态提供 CComboBox 项目,则可以重写 CGridListCtrlEx::OnEditBegin()。使用 dynamic_cast<> 调用列特征方法 CGridColumnTraitCombo::LoadList(),或直接处理返回的 CComboBox 编辑器。

如果要获取所选 CComboBox 项目的 itemdata,可以重写 CGridListCtrlEx::OnEditComplete() 并检查参数值 pLVDI->item.lParam。由于 itemdata 无法存储在 CListCtrl 的本地数据模型中,因此需要将其保存在其他地方。

排序行

默认情况下,GridListCtrlEx 将对所有列启用排序,并执行简单的文本比较。可以通过列特征实现自定义排序,重写 CGridColumnTrait::OnSortRows()

配置列特征以进行数字比较排序

CGridColumnTraitEdit* pTrait = new CGridColumnTraitEdit;
pTrait->SetSortFormatNumber(true);	// Numeric column
m_ListCtrl.InsertColumnTrait(nCol, title.c_str(), LVCFMT_LEFT, 100, nCol, pTrait);

列特征 CGridColumnTraitDateTime 将自动尝试按日期进行排序。

我们还可以选择重写 CGridListCtrlEx::SortColumn() 方法。然后,只需选择正确的排序方式即可。另请参阅 CListCtrl 和排序行

显示工具提示

默认情况下,CGridListCtrlEx 将仅将单元格内容显示为工具提示。如果想在工具提示中显示其他内容,可以重写 CGridListCtrlEx::OnDisplayCellTooltip() 方法。

格式化单元格/子项

如果想更改前景色/背景色或字体样式(粗体、斜体、下划线),可以重写 CGridListCtrlEx::OnDisplayCellColor()CGridListCtrlEx::OnDisplayCellFont() 方法。

bool MyGridCtrl::OnDisplayCellColor(int nRow, int nCol, COLORREF& textColor, COLORREF& backColor)
{
   if (nRow == 3 && nCol == 3)
   {
      textColor = RGB(0,255,0);
      backColor = RGB(0,0,255);
      return true;  // I want to override the color of this cell
   }
   return false;  // Use default color
}

显示单元格/子项图像

CGridListCtrlEx 默认启用扩展样式 LVS_EX_SUBITEMIMAGES,但仍需要使用 CListCtrl::SetImageList() 附加 CImageList

附加图像后,就可以使用 CGridListCtrlEx::SetCellImage() 将单元格/子项与 CImageList 中的索引绑定。或者,如果使用 I_IMAGECALLBACK,则可以通过重写 CGridListCtrlEx::OnDisplayCellImage() 返回图像索引。

CGridListCtrlEx 还默认启用了扩展样式 LVS_EX_GRIDLINES,这可能导致子项图像与网格边框重叠。可以通过确保图像仅使用 15 个像素(第一个像素透明)来解决此问题。

当使用子项图像并在 Windows XP 或经典样式上运行应用程序时,选定的行将显示白色背景。可以使用 CGridRowTraitXP 来修复此问题。

m_ListCtrl.SetDefaultRowTrait(new CGridRowTraitXP);

复选框支持

CListCtrl 支持标签列的复选框。只需应用扩展样式 LVS_EX_CHECKBOXES

m_ListCtrl.SetExtendedStyle(m_ListCtrl.GetExtendedStyle() | LVS_EX_CHECKBOXES);

请注意不要使用 InsertHiddenLabelColumn(),因为它会隐藏标签列及其复选框。可以使用 GetCheck() / SetCheck() 来检索/修改复选框值。

如果想为多个列设置复选框,可以使用 CGridColumnTraitImage(及其特例)。

// Appends the unchecked/checked state images to the list control image list
int nStateImageIdx = CGridColumnTraitImage::AppendStateImages(m_ListCtrl, m_ImageList);
m_ListCtrl.SetImageList(&m_ImageList, LVSIL_SMALL);
// Creates an image column, that can switch between the 2 images
CGridColumnTrait* pTrait = new CGridColumnTraitImage(nStateIdx, 2);
m_ListCtrl.InsertColumnTrait(nCol, title.c_str(), LVCFMT_LEFT, 20, nCol, pTrait);
for(int i=0; i < m_ListCtrl.GetItemCount(); ++i)
	m_ListCtrl.SetCellImage(i, nCol, nStateImageIdx);	// Uncheck item

当使用 SetImageList() 分配 CImageList 时,标签列会自动显示图像列。无法禁用此行为,但可以使用 InsertHiddenLabelColumn() 隐藏标签列。

CGridColumnTraitImage 使用单元格图像来绘制复选框,因此无法在同一列中同时拥有单元格图像和复选框。要获取和设置已勾选/未勾选状态,可以使用 GetCellImage() / SetCellImage()

CGridColumnTraitImage 支持根据复选框是否启用进行排序。使用 CGridColumnTraitImage::SetSortImageIndex() 来启用此功能。

CGridColumnTraitImage 还支持切换所有选定行的复选框。使用 CGridColumnTraitImage::SetToggleSelection() 来启用此功能。

超链接支持

超链接列可以将单元格内容显示为链接,这些链接可以被点击并启动外部应用程序,例如默认的 Web 浏览器(http)或邮件客户端(mailto)。

CGridColumnTraitHyperLink 允许提供单元格文本的前缀(和后缀),这使得我们可以在不显示额外协议详情的情况下添加它们。

CGridColumnTraitHyperLink* pHyperLinkTrait = new CGridColumnTraitHyperLink;
pHyperLinkTrait->SetShellFilePrefix(_T("http://en.wikipedia.org/wiki/UEFA_Euro_"));
m_ListCtrl.InsertColumnTrait(nCol, title.c_str(), LVCFMT_LEFT, 100, nCol, pHyperLinkTrait);

还可以使用 CGridColumnTraitHyperLink::SetShellApplication() 指定一个自定义应用程序来启动,而不仅仅是基于协议前缀启动的默认应用程序。

超链接还可以用来模拟按钮。点击时会发送 LVN_ENDLABELEDIT 通知,可以将其视为父视图中的按钮点击通知。

更改行高

CGridListCtrlEx 使用自定义绘制,因此只有以下解决方案可用:

  • 分配一个 CImageList,其中图像的高度与所需的行高一致。
  • 更改网格控件的字体,行高将随之改变。CGridListCtrlEx::SetCellMargin() 使用此技巧来增加网格控件的字体,同时保持行字体不变。

更改空标记文本

CGridListCtrlEx 不包含任何项目时,它将显示标记文本以指示列表为空。

使用 CGridListCtrlEx::SetEmptyMarkupText() 来更改此标记文本。如果提供空文本,则其行为将与普通 CListCtrl 相同。

如果使用 CGridListCtrlGroups,它将在 Windows Vista 上运行时响应 LVN_GETEMPTYMARKUP

加载和保存列宽和位置

CViewConfigSectionWinApp 提供了存储列的宽度、位置和显示状态的功能。在将所有可用列添加到 CGridListCtrlEx 后,使用 CGridListCtrlEx::SetupColumnConfig() 分配 CViewConfigSectionWinApp 的实例,它将通过 CWinApp 恢复上次保存的列配置。

m_ListCtrl.SetupColumnConfig(new CViewConfigSectionWinApp("MyList"));

如果应用程序中的多个地方使用 CGridListCtrlEx,则应确保为每个地方创建唯一的 CViewConfigSectionWinApp

OLE 拖放

CGridListCtrlEx 既可以作为 OLE 拖动源,也可以作为 OLE 放置目标。这允许在 CGridListCtrlEx 与其他窗口和应用程序之间进行拖放操作。

CGridListCtrlEx 支持在进行内部拖放时重新排序行。这是通过特殊的排序操作实现的,因此项目不会被删除/插入。

要为放置操作实现自己的特殊行为,请根据要处理的情况重写 OnDropSelf()OnDropExternal()

要控制启动拖动时放置在拖动源中的内容,请重写 OnDisplayToDragDrop()

CGridColumnTrait 如何工作

CGridListCtrlEx 尽量避免处理所有关于如何显示和编辑数据的细节。这些细节由 CGridColumnTrait 类处理,如果我们想修改数据显示方式,那么“只需”创建一个新的 CGridColumnTrait 类即可。

在插入列时,我们可以为该列分配一个 CGridColumnTrait。当需要绘制该列中的单元格或编辑该列中的单元格时,CGridListCtrlEx 将激活相应的 CGridColumnTrait

CGridColumnTrait 包含一些称为元数据(meta-data)的特殊成员。这些成员可以被派生自 CGridListCtrlEx 的类使用,因此我们可以轻松地为列添加额外的属性。

继承自 CGridColumnTrait 时,我们必须考虑以下几点:

  • 如果执行自定义绘制,我们还必须处理选择和焦点着色。
  • 如果执行编辑,我们必须确保编辑器在失去焦点时关闭,并在编辑完成后发送 LVN_ENDLABELEDIT 消息。

CGridRowTrait 如何工作

它的想法与 CGridColumnTrait 相同,但操作级别是行而不是列。这对于需要修改所有列的显示行为的情况很有用。

Using the Code

源代码包括以下类:

  • CGridListCtrlEx - 特别的 CListCtrl
  • CGridListCtrlGroups - 扩展了 CGridListCtrlEx,支持分组
  • CGridColumnTrait - 指定列特征的接口
    • CGridColumnTraitText - 实现单元格格式化
      • CGridColumnTraitImage - 通过在图像之间切换来实现单元格编辑(可以模拟复选框)
        • CGridColumnTraitEdit - 使用 CEdit 实现单元格编辑
        • CGridColumnTraitCombo - 使用 CComboBox 实现单元格编辑
        • CGridColumnTraitDateTime - 使用 CDateTimeCtrl 实现单元格编辑
        • CGridColumnTraitHyperLink - 实现单元格作为超链接的功能
  • CGridRowTrait - 指定行特征的接口
    • CGridRowTraitText - 实现行格式化
    • CGridRowTraitXP - 使用经典或 XP 样式绘制子项图像背景
  • CViewConfigSection - 用于持久化列设置的抽象接口
    • CViewConfigSectionWinApp - 实现接口并可以在多个列设置之间切换。

待办事项

CGridListCtrlEx 尽量避免自行绘制。这意味着以下功能/错误将不会得到太多关注:

  • 进度条支持 - 需要一个绘制整个单元格的 CGridColumnTrait 类。

实现一个绘制整个单元格的 CGridColumnTrait 类,可能需要借鉴 ListCtrl - 具有 Windows Vista 样式项目选择的 WTL 列表控件 的代码。

非常欢迎向此项目贡献。

历史

  • 版本 1.0 (2008-09-04) 首次发布
  • 版本 1.1 (2008-09-18)
    • 添加了对 CGridListCtrlGroups 分组的支持
    • 添加了 CDateTimeCtrl 编辑器
    • 修复了在使用经典和 XP 样式时的绘制错误
      • 修复了选定子项的图像背景色(不再是白色)
      • 修复了滚动左右时网格边框消失的问题
    • 当列表没有项目时显示列表为空的指示
    • 扩展了 CComboBox 编辑器,使其能够自动调整下拉列表的宽度以适应内容
  • 版本 1.2 (2008-09-24)
    • CGridRowTraitXP 替换了 CGridListCtrlXP
    • 修复了一些报告的错误
  • 版本 1.3 (2008-10-09)
    • 修复了在 CView 中使用时的扩展样式
    • 修复了使用键盘快捷方式 (SHIFT+F10) 时上下文菜单的定位
    • 修复了使用 Visual Studio 6 (VC6) 时出现的编译器错误
  • 版本 1.4 (2008-11-07)
    • 添加了剪贴板支持,用于复制选定单元格/行的内容
    • 将“Callback”函数重命名为“OnDisplay”,以模仿 MFC 的命名约定
    • 修复了一些报告的错误
  • 版本 1.5 (2009-03-29)
    • 添加了列管理器 CGridColumnManager
    • 添加了对 VC6 和平台 SDK 的分组支持
    • 为不同版本的 Visual Studio 添加了示例项目
    • 通过 Doxygen 注释改进了文档
  • 1.6 版 (2009-09-13)
    • 添加了 OLE 拖放支持
    • 在 LVS_OWNERDATA 中添加了对复选框样式 LVS_EX_CHECKBOX 的支持
    • 为 LVS_OWNERDATA 添加了更好的键盘搜索支持
    • 修复了多个错误
  • 1.7 版 (2009-12-12)
    • 添加了 CGridColumnTraitImage,它可以模仿任何列的复选框编辑
    • OnTraitEditBegin() 重命名为 OnEditBegin()
    • OnTraitEditComplete() 重命名为 OnEditComplete()
    • OnTraitCustomDraw() 重命名为 OnCustomDrawCell()
    • 修复了多个错误(主要是行和单元格着色)
  • 1.8 版 (2010-10-01)
    • CGridColumnTraitImage 设为所有编辑器列特征的基类,以便它们都可以模拟复选框支持
    • 实现了多选复选框支持,以便为所有选定行翻转复选框
    • 通过基类列特征实现了最小和最大列宽
    • 修复了多个错误
  • 版本 1.9 (2011-05-30)
    • CGridColumnTrait::OnSortRows 的参数从字符串更改为 LVITEM
    • CGridColumnConfig 重命名为 CViewConfigSection
    • 移除了 CGridColumnManager,并将 LoadState/SaveState 移至 CGridListCtrlEx(破坏性更改)
  • 版本 2.0 (2012-05-01)
    • CDateTimeCtrl 编辑器添加了复制/粘贴支持(DTS_APPCANPARSE
    • 当按列分组时,列标题单击现在进行排序而不是按列分组
    • 默认情况下,行排序更改为不区分大小写
    • 修复了单元格编辑器丢弃使用鼠标执行的更改(通过右键菜单复制/粘贴)的错误
    • 修复了多个编译器警告和小错误
  • 版本 2.1 (2012-06-20)
    • 修复了 2.0 版本引入的错误,即分组项在 WinXP 上不再工作。
    • 提高了对项目组进行排序的性能,尤其是在 Vista/Win7+ 上。
    • 添加了新的列特征 CGridColumnTraitHyperLink,它可以将单元格文本显示为 Web 链接。更新了演示应用程序以显示新的列类型。
    • 添加了新的列特征 CGridColumnTraitMultilineEdit,它可以编辑包含换行符的单元格文本(仍显示为单行)。
    • 修复了多个小错误
  • 版本 2.2 (2012-11-11)
    • CGridColumnTraitCombo 现在可以选择在编辑开始时显示下拉列表(SetShowDropDown
    • 所有列特征编辑器现在都可以选择在第一次鼠标单击时启动编辑器(SetSingleClickEdit
    • CGridColumnTraitHyperLink 在鼠标单击时会向父级发送 LVN_ENDLABELEDIT 通知(模拟按钮点击) 
    • Windows 7/8 上子项使用的复选框状态图标已得到改进,避免了图像缩放 
    • 修复了多个小错误
  • 版本 2.3 (2015-03-17)
    • 由于 Google Code 即将关闭,项目已迁移到 GitHub。
    • 实现了对 IDropSource::GiveFeedback 的支持,允许在拖放期间更改鼠标光标
    • 单元格焦点着色现在默认禁用。调用 SetInvertCellSelection(true) 可启用。
    • 修复了多个小错误
© . All rights reserved.