翻译来源:https://www.codeproject.com/Articles/29064/CGridListCtrlEx-Grid-Control-Based-on-CListCtrl
作者:Rolf Kristensen
目前找到最好的list控件类,还一直在更新
自定义绘制CListCtrl,具有子项目编辑和格式化
介绍
Microsoft CListCtrl
支持使用报告样式在网格中显示数据,但是我们必须进行几项更改才能实现以下功能:
本文演示如何使用CGridListCtrlEx
,它实现了所有上述功能,同时维护Windows
XP / Vista外观。
在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
已经提供。这意味着我们可以替换一个CListCtrl
,CGridListCtrlEx
而不需要再做任何事情。
建议我们不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
会发送一个标准的消息CListCtrl
。当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
-editor直接。
如果要获取所选项目的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
默认,但一个仍然需要附加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
类就是一个问题。
插入列时,我们可以为列分配一个CGridColumnTrait
。该CGridListCtrlEx
会**相应的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列表控件。
对本项目的贡献非常受欢迎。