CGridListCtrlEx - 基于CListCtrl的网格控制

时间:2024-03-13 13:45:40

 翻译来源:https://www.codeproject.com/Articles/29064/CGridListCtrlEx-Grid-Control-Based-on-CListCtrl

作者:Rolf Kristensen

目前找到最好的list控件类,还一直在更新

自定义绘制CListCtrl,具有子项目编辑和格式化

介绍 

Microsoft CListCtrl支持使用报告样式在网格中显示数据,但是我们必须进行几项更改才能实现以下功能: 

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

CGridListCtrlEx - 基于CListCtrl的网格控制

GitHub的- CGridListCtrlEx可如果想的Git / Subversion的接入使用,并且也有Doxygen文档

背景

有很多高级网格控件可以扩展CListCtrl,其中之一是增强列表控件(CGfxListCtrl)这个美妙的控件提供了上述所有功能,但无法处理Windows XP和Vista。找到一个很好的替代这个控制不是很容易:

  • MFC Grid Control - 不会继承,CListCtrl所以它不受它的限制,但它不会受益于Microsoft添加到的任何改进CListCtrl
  • 终极网格 - 像MFC网格控件,它不会继承CListCtrl最初一个人买的,但现在可以*使用。
  • CQuickList - 非常接近于一个完美的替代品,但很难添加新的方式来显示数据,这要求LVS_OWNERDATA使排序更难。
  • XListCtrl - 也是一个非常完整CListCtrl,但很难添加新的方式来显示数据,并且它无法支持LVS_OWNERDATA现在必须购买许可证才能获得最新版本。
  • 另一个报表列表控制 - 简单易用,但缺少其他方式来编辑除了使用之外的数据,也缺少CEdit子导航。
  • CListCtrlEx - 实现了很多功能,并且有很好的记录。最初需要LVS_OWNERDRAWFIXED,现在演变成使用自定义画。自定义绘制和所有者绘制的组合导致代码有点复杂,它也不支持LVS_OWNERDATA

所述CGridListCtrlEx插入件称为柱性状一个抽象层处理该细胞绘制和编辑。如果微软CListCtrl再次扩展,那么希望的核心CGridListCtrlEx将继续发挥作用。

如何使用CGridListCtrlEx

CGridListCtrlEx试图留真实的CListCtrl,并且不试图取代的东西CListCtrl已经提供。这意味着我们可以替换一个CListCtrlCGridListCtrlEx而不需要再做任何事情。

建议我们不CGridListCtrlEx直接使用,而是创建一个继承/派生的新类CGridListCtrlEx这将使以后更容易将任何更新迁移到CGridListCtrlEx课程中。

编辑单元格/子项目

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

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

当编辑了一个项目时,LVN_ENDLABELEDIT会发送一个标准的消息CListCtrlCGridListCtrlEx接收到该消息时,它将自动调用虚拟方法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-editor直接。  

如果要获取所选项目的CComboBoxitemdata,则可以覆盖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默认,但一个仍然需要附加CImageList使用CListCtrl::SetImageList()

连接图像后,可以在单元格/子项目中绑定索引CImageList这可以通过CGridListCtrlEx::SetCellImage(),或者如果使用I_IMAGECALLBACK然后通过覆盖返回图像索引CGridListCtrlEx::OnDisplayCellImage()

CGridListCtrlEx也使得扩展样式LVS_EX_GRIDLINES默认情况下,这可能会导致子项目图像重叠网格边界。这可以通过确保图像仅使用16像素中的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

分配CImageList使用时,标签列将自动显示图像列SetImageList()不可能禁用此行为,但可以隐藏标签列InsertHiddenLabelColumn()

CGridColumnTraitImage使用单元格图像绘制复选框,因此不可能在同一列中同时具有单元格图像和复选框。要获取并设置选中/未选中的状态,可以使用GetCellImage()/ SetCellImage()

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

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

超链接支持

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

CGridColumnTraitHyperLink允许一个提供用于CELLTEXT,它允许一个添加额外的协议的细节没有它正在显示的前缀(和后缀):

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用途customdraw,所以只有这些可用的解决方案:

  • 分配CImageList图像具有该行所需高度的位置。
  • 更改网格控件的字体,行高将跟随。CGridListCtrlEx::SetCellMargin()使用这个技巧来增加网格控件的字体,同时保持行字体不变。

更改空标记文本

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

使用CGridListCtrlEx::SetEmptyMarkupText()更改此标记文本。如果提供空文本,那么它的行为就像一个正常的CListCtrl

如果使用CGridListCtrlGroups,它会反应LVN_GETEMPTYMARKUP如果在Windows Vista上运行。

加载并保存列宽和位置

CViewConfigSectionWinApp提供存储宽度,位置以及是否显示列的能力。在已经加入所有可用列的CGridListCtrlEx,分配的一个实例CViewConfigSectionWinApp使用CGridListCtrlEx::SetupColumnConfig(),它会通过恢复上次保存的列配置CWinApp

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

如果在应用程序中使用CGridListCtrlEx了几个地方,那么应该确保CViewConfigSectionWinApp为每个地方创建一个独特的地方。

OLE拖放

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

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

要实现自己的特殊行为来执行放置操作,可以覆盖OnDropSelf()OnDropExternal()根据您想要处理的情况。

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

CGridColumnTrait如何工作?

CGridListCtrlEx试图避免所有关于如何显示和编辑数据的令人讨厌的细节。这些事情是由CGridColumnTrait来处理的,如果我们要修改数据的显示方式,那么“只是”创建一个新CGridColumnTrait就是一个问题

插入列时,我们可以为列分配一个CGridColumnTraitCGridListCtrlEx会**相应的CGridColumnTrait,当我们需要绘制在列的单元格,或修改此列的单元格。

CGridColumnTrait包括被称为元数据的一些特殊成员。这些成员可以由您自己的类使用,当它派生时CGridListCtrlEx,我们可以轻松地向列添加额外的属性。

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

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

CGridRowTrait如何工作?

它基于相同的想法,CGridColumnTrait但在行级别而不是列级别运行。这对于必须修改所有列的显示行为的情况非常有用。

使用代码

源代码包括以下类:

  • 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列表控件

对本项目的贡献非常受欢迎。