另一个灵活的 ListView 控件






4.59/5 (11投票s)
一个高度面向对象的 ListView 控件,具有可变高度的项目和复杂数据类型的支持。
引言
我正在开发一个与数学相关的应用程序,需要在屏幕上显示复杂的数学公式。理想情况下,ListView
很适合这个任务,但原生的 Windows ListView
不支持复杂的单元格结构(例如 CheckBox
、图像),更糟糕的是,它不允许项目的高度变化,这在我的应用程序中是必须的。
在浏览了 CodeProject 和 Google 文章后,我发现第二个问题无法通过修改现有的 ListView
来解决。它的设计就是所有项目的高度都是相等的。变通方法是修改字体……但这对我来说确实很糟糕。所以我决定编写自己的 ListView
,它基于更简单但某些方面更通用的 ListBox
控件。我将其命名为 GListView
,这是基于它是 ListView
并且使用 C# 泛型作为改进面向对象的机制的事实。
从这个控件中,你可以发现两个主要的实际好处:
- 它支持原生
ListView
不支持的可变高度项目。 - 它支持复杂的数据类型:图像、复选框。更重要的是,这些数据类型可以扩展。
请看下面的截图

Using the Code
本文附带源代码和演示。
如果您需要一个开箱即用的 ListView
,支持可变项目高度、复杂单元格结构以及可能更面向对象的用法,那么演示项目应该能满足您的需求。阅读演示的源代码,了解如何使用。
请注意,GListView
是一个 abstract
泛型类。基本上,使用它包括三个步骤:
- 确定您的底层数据结构。从它派生一个非泛型类,提供适当的类型参数。
class MathProblem { ... } // Our data structure class MathListView : GListView<MathProblem> { ... } // Our non-generic ListView
- 确定您的
ListView
将显示哪些列,并使用SetColumns(GListViewColumnSchema schema)
方法进行设置。通常,该方法位于派生类的构造函数中。class MathListView : GListView<MathProblem> { ... public MathListView() { this.SetColumns(new GListViewColumnSchema() { new GCheckBoxColumn(), // A CheckBox column, with no text on header new GTextColumn("ID", GCellAlignment.Center), // A text column new GImageColumn("Question", GCellAlignment.Left), // A column // displaying images new GImageColumn("Answer", GCellAlignment.Left), // Another column // with images new GTextColumn("Type", GCellAlignment.Left), new GTextColumn("Difficulty", GCellAlignment.Center) }); } }
- 确定如何将一个对象(属于底层数据结构)转换为屏幕上的视觉线索。这是通过重写
abstract
方法Translate(T obj, object[] fields)
来完成的,该方法负责使用来自obj
的数据填充fields
数组,该数组稍后用于显示。class MathListView : GListView<MathProblem> { ... protected override void Translate(MathProblem obj, object[] fields) { fields[1] = obj.Index.ToString(); fields[2] = obj.Question; fields[3] = obj.Answer; fields[4] = obj.Type; fields[5] = obj.Difficulty; } }
此外,DLL 还包含另一个控件 ProperListBox
,它是一个普通的 ListBox
,但没有闪烁,并且对其水平滚动条有更完整的控制。如果您正在开发一个 ListView
风格的控件,您可能会发现这个补充很有用。它*只*支持 OwnerDraw
模式。
一个要点:如果您想使用 Visual Studio 的拖放 Winforms 设计,您可能会遇到问题,因为 GListView
基于泛型,而 Visual Studio 的 IDE 不喜欢这一点。具体来说,如果您的控件继承自泛型类型,并且您将其放置在 Form
上,将会出现一些错误消息。一个解决方法是创建一个容器(例如 Panel
控件)来容纳 GListView
,然后,不定位 GListView
本身,而是定位其容器。最后,将 GListView
派生控件的 Dock
属性设置为 DockStyle.Fill
,并手动设置 GListView
的相关事项(列、事件处理程序、填充数据)。
最后,如果您好奇代码是如何工作的,或者想扩展它,您可能想看看 GListView
的源代码。我仔细地格式化和重构了它,但它有点长。我可以想象的最现实的场景是扩展它以支持新型列,并且该控件*就是为此设计的*。
关注点
本节解释了我在开发此控件期间遇到的困难以及我找到的解决方案。我非常喜欢开发 GUI 组件。GListView
是我开发的一个非常有趣的控件。
ListView
控件在我开发的应用程序中得到了广泛使用,也许你们大多数人都发现它对于显示大量记录很有用。通常的方法是将数据附加到 Tag
属性中的项目,或者有时继承 ListViewItem
类。但我发现这种方式笨拙、类型不安全且不太面向对象。这就是我使用泛型导出解决方案的原因。GListView
的子类完全了解其底层数据。并且内部工作(即从底层对象到视觉数据的转换)在 GListView
外部是不可见的。所有访问和操作都是使用*对象*完成的,而不是使用 ListViewItem
。设计类以便控件可扩展且易于使用也是一项具有挑战性的任务,并进行了大量的重构。
标题是花费了我相当多时间实现的一个功能。ListBox
不原生支持标题。我的解决方案是使用一个面板,标题完全是手工实现的:处理鼠标事件、绘制分隔线和标题文本。GListView
标题的行为详细复制了 Windows Explorer 的详细信息模式。
ListBox
的闪烁也是一个问题。这通过 Les Potter 的解决方案解决了。
最后一个问题是渲染复选框和图像。这是通过 ListBox
的所有者绘制功能实现的。我也花了一些时间学习 .NET 已经提供了哪些绘图功能。
未来发展
如上所述,GListView
在我的一个个人项目中得到使用,并且是为了满足我的需求而设计的,它确实做到了。因此,它肯定会有一些方面是缺失的。我能想到的一些方面是排序能力和列重排。我不确定将来是否能添加这些功能,因为这需要大量的努力。也许这取决于该控件能为社区带来多大的好处。
欢迎提供 bug 报告和评论。但我不能保证能很好地回复或修复它们。
历史
- 2010 年 6 月 24 日:文章已提交到 CodeProject(我的第一篇文章 :))