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

另一个灵活的 ListView 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.59/5 (11投票s)

2010年6月23日

CPOL

5分钟阅读

viewsIcon

39053

downloadIcon

2344

一个高度面向对象的 ListView 控件,具有可变高度的项目和复杂数据类型的支持。

引言

我正在开发一个与数学相关的应用程序,需要在屏幕上显示复杂的数学公式。理想情况下,ListView 很适合这个任务,但原生的 Windows ListView 不支持复杂的单元格结构(例如 CheckBox、图像),更糟糕的是,它不允许项目的高度变化,这在我的应用程序中是必须的。

在浏览了 CodeProject 和 Google 文章后,我发现第二个问题无法通过修改现有的 ListView 来解决。它的设计就是所有项目的高度都是相等的。变通方法是修改字体……但这对我来说确实很糟糕。所以我决定编写自己的 ListView,它基于更简单但某些方面更通用的 ListBox 控件。我将其命名为 GListView,这是基于它是 ListView 并且使用 C# 泛型作为改进面向对象的机制的事实。

从这个控件中,你可以发现两个主要的实际好处:

  1. 它支持原生 ListView 不支持的可变高度项目。
  2. 它支持复杂的数据类型:图像、复选框。更重要的是,这些数据类型可以扩展。

请看下面的截图

GListView.png

Using the Code

本文附带源代码和演示。

如果您需要一个开箱即用的 ListView,支持可变项目高度、复杂单元格结构以及可能更面向对象的用法,那么演示项目应该能满足您的需求。阅读演示的源代码,了解如何使用。

请注意,GListView 是一个 abstract 泛型类。基本上,使用它包括三个步骤:

  1. 确定您的底层数据结构。从它派生一个非泛型类,提供适当的类型参数。
    class MathProblem { ... } // Our data structure
    class MathListView : GListView<MathProblem> { ... } // Our non-generic ListView
  2. 确定您的 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)
            });
        }
    }
  3. 确定如何将一个对象(属于底层数据结构)转换为屏幕上的视觉线索。这是通过重写 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(我的第一篇文章 :))
© . All rights reserved.