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

FastTree 和 FastList

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (32投票s)

2014 年 9 月 18 日

LGPL3

8分钟阅读

viewsIcon

63121

downloadIcon

1016

对标准 WinForm 控件 ListBox, CheckedListBox 和 TreeView 进行快速灵活的替换

引言

该库提供了两个控件:FastListFastTree

这些控件旨在替代标准的 WinForm 控件:ListBoxCheckedListBoxTreeView

由于我通常处理大型数据集,并且需要完美的性能,因此这些控件只实现虚拟模式,这是数据接收的最快方式。控件不存储项目的文本、颜色、图标等。所有这些数据都通过事件或重写的方法传递给控件。因此,您需要将数据存储在控件外部。控件仅存储服务数据,例如:当前复选框状态、选择状态以及折叠/展开状态。

在开发这些控件时,我追求以下目标:

  1. 控件必须能够显示大型数据集。设计上,至少 100 万个项目——无卡顿。
  2. 当数据更改时,控件必须能够无卡顿、无闪烁地重建。所有次要的先前状态(复选框、选择、展开)在重建后必须得到保留。
  3. 控件必须实现虚拟模式,并且不占用大量内存。
  4. 控件必须灵活,易于扩展和继承。
  5. 控件必须包含大量的事件,并具有取消能力。每个用户操作都可以被编程取消(选择、取消选择、更改复选框状态、展开、折叠等)。
  6. 多选、复选框、图标。
  7. 广泛的自定义选项:着色、自定义项目高度、自定义项目绘制、缩进、可见性。
  8. 内置拖放支持。

所有这些要求都已实现。

总体设计

FastTreeFastList 继承自同一个基类 FastListBase

FastListBase 继承自标准的 UserControl,并实现了主要功能:绘制、滚动、鼠标和键盘处理程序、坐标计算等。如果您需要最大的灵活性和/或扩展性,您可以直接继承 FastListBase 来创建您的控件。

FastListBase 绘制项目的垂直列表。它不了解数据结构。它只是绘制项目的线性列表,其中每个项目都有自己的左缩进和自己的高度。

同样,FastListBase 也不包含任何事件(当然,除了继承自 UserControl 的事件)。它通过 virtual 方法接收所有数据,例如 string GetItemText(int itemIndex) 等。

使用 virtual 方法代替事件可以获得最大性能,因为我们不必为事件调用付费。如果子类不重写该方法,FastListBase 将使用其自身 virtual 方法返回的默认值。

此外,FastListBase 存储一些特定数据,例如已选中项目索引的 HashSet<int> 和已选中复选框的项目索引的 HashSet<int>FastListBase 还计算每个项目的坐标并存储它们。

FastListFastListBase 的一个微小封装。此类重写了 FastListBasevirtual 方法并在其中调用用户事件。因此,FastListFastListBase 的一个区别是:它包含事件。

FastTreeFastListBase 的一个较厚的封装,因为它包含用于树的特定逻辑。它也会重写 virtual 方法并调用事件,但在此过程中会添加自己的代码。此外,它还包含用于树数据结构的附加方法和事件。

使用 FastList

使用 FastList 最简单的方法是将其拖到窗体上,并处理 ItemTextNeeded 事件。在处理程序中,您需要按项目索引返回项目的文本。

例如

        private void fl_ItemTextNeeded(object sender, StringItemEventArgs e)
        {
            e.Result = list[e.ItemIndex];//where list - is your data
        }

此外,您还需要定义项目的数量。您可以在设计模式或运行时分配 ItemCount 属性。当分配 ItemCount 时,它会调用 protected 方法 Build() 来重新计算绘制项目的坐标。

注意:如果您的数据已更改,但项目数量保持不变——只需调用 fastList.Invalidate()。因为 FastList 不存储数据,它将从事件中获取数据,并在重绘后显示实际数据。只有当项目数量改变时——才设置 ItemCount 属性。

有关 FastList 用法的更多示例,请参阅 Tester 应用程序。

复选框

复选框有两种模式。默认情况下,FastList 在其内部存储checkbox状态,在内部存储CheckedItemIndex

但是,如果您为 ItemCheckStateNeeded 分配了处理程序,控件将切换到虚拟复选框模式。在此模式下,您需要在 ItemCheckStateNeeded 事件的处理程序中返回项目的选中状态。

此外,您可以在 ItemCheckedStateChanged 事件的处理程序中处理状态更改。

使用 FastTree

FastTree 的使用更复杂,因为它需要树形数据结构。

要开始构建树,请调用 public 方法 void Build(object root),其中 root 是您树的根对象。FastTree 不允许多个根,因此您的所有数据必须包含在 root 对象中(尽管可以通过 ShowRootNode 属性隐藏根节点)。

注意Root 对象仅用于从您的结构中获取树数据。FastTree 不需要特定类型的根,它可以是任何类型的实例,甚至是 null。但在将来,在事件处理程序中,您必须为给定对象返回子项或文本。

接下来,有两种方式:使用 IEnumerable 或使用事件 NodeChildrenNeeded

方式 1:NodeChildrenNeeded 事件处理程序

如果您为 NodeChildrenNeeded 事件分配了处理程序,FastTree 将调用该事件来获取给定节点的子节点。在这种情况下,您需要为给定父对象从处理程序中返回子对象 IEnumerable

例如,构建目录树

        ft.Build(@"c:\") //build tree using string "c:\" as root object
        ...

        private void ft_NodeChildrenNeeded(object sender, NodeChildrenNeededEventArgs e)
        {
            var path = e.Node as string;
            e.Children = Directory.GetDirectories(path);//return subdirectories of parent path
        }

        private void ft_NodeTextNeeded(object sender, FastTreeNS.StringNodeEventArgs e)
        {
            var path = e.Node as string;
            e.Result = Path.GetDirectoryName(path);//return name of directory as text of node
        }

在这里,我们获取父目录的路径并返回其子目录列表。

方式 2:IEnumerable 接口

构建树的另一种方法是使用 IEnumerable 接口。默认情况下,如果未分配 NodeChildrenNeeded 处理程序,则启用此模式。

其思想是数据对象实现其子对象的 IEnumerableFastTree 会尝试将节点转换为 IEnumerable。如果接口存在,它将从中获取子项。否则,它将成为终端节点。

如果使用此方法,您只需要调用方法 void Build(object root) 来构建树。其他树节点将自动构建。

此外,您可以处理 NodeTextNeeded 事件来为节点绘制特定文本。但是,如果未分配 NodeTextNeededFastTree 将使用节点对象的 ToString() 方法。

注意:如果某些节点的文本已更改,您只需调用 fastTree.Invalidate() 方法即可刷新控件。但如果数据结构(例如子节点数量)发生更改——您必须调用 Build(object root) 方法来反映数据更改。所有先前选择/选中/展开的状态都将自动恢复。

有关 FastTree 用法的更多示例,请参阅 Tester 应用程序。

复选框

复选框有两种模式。默认情况下,FastTree 会自行在内部存储 checkbox 状态。

但是,如果您为 NodeCheckStateNeeded 分配了处理程序,控件将切换到虚拟复选框模式。在此模式下,您需要在 NodeCheckStateNeeded 事件的处理程序中返回节点的选中状态。

此外,您可以在 NodeCheckedStateChanged 事件的处理程序中处理状态更改。

有用的事件、属性和方法

事件 NodeTextNeededItemTextNeeded - 处理这些事件以分配节点的文本。

事件 NodeChildrenNeeded - 可以返回节点的子节点。

事件 NodeCheckStateNeededItemCheckStateNeeded - 可以返回节点/项目的 checkbox 状态。

事件 NodeIconNeededItemIconNeeded - 返回节点/项目的图标图像。

事件 NodeHeightNeededItemHeightNeeded - 如果您需要项目的单独高度,请处理此事件。如果未分配处理程序——将使用 ItemHeightDefault。请注意,如果节点的高度已更改,您需要调用 Build() 方法。

事件 NodeBackColorNeededNodeForeColorNeededItemBackColorNeededItemForeColorNeeded - 用于设置节点/项目的文本颜色和背景颜色。

大量事件:CanUnselectNodeNeededCanSelectNodeNeedeCanCheckNodeNeeded 等——这些允许事件可以取消相应的用户操作。

事件 NodeCheckedStateChangedNodeExpandedStateChangedNodeSelectedStateChangedItemCheckedStateChangedItemExpandedStateChangedItemSelectedStateChanged - 通知节点/项目状态已更改。

事件 NodeDragDragOverNodeDropOverNodeItemDragItemOverItemItemDropOverItem - 当用户开始拖动/在节点上拖动/在节点上放置节点时会发生这些事件。请注意,FastTreeFastList 支持虚拟数据模型。因此,如果用户将项目拖入控件,外部事件处理程序必须根据拖动结果更改其数据,并调用 Build() 方法来重建控件。更多示例请参阅 FastListDropItemSampleFastListDragItemSample

属性 AllowDragItems - 启用项目的拖放。

事件 PaintNodePaintItem - 如果您想自定义绘制节点/项目,请处理此事件。

属性 Nodes - 所有可见节点的列表。

属性 ExpandedNodesSelectedNodesCheckedNodes - 展开/选择/选中节点的列表。

属性 SelectedItemIndexCheckedItemIndex - 选择/选中项目索引的哈希集。

属性 MultiSelect - 启用多选。

属性 ItemCount - 获取/设置 FastList 的项目数量。

属性 ItemInterval - 项目之间的距离(以像素为单位)。

属性 NodeHeightDefaultItemHeightDefault - 节点/项目的默认高度(以像素为单位)。

属性 ShowIconsShowCheckBoxes - 显示图标/复选框。

方法 Build(object root) - 重建树结构。如果树的结构发生变化,请调用此方法。如果仅节点的文本发生变化,请不要调用此方法(在这种情况下,请调用 Invalidate())。

热键

  • 向上、向下、PageUp、PageDown、Home、End - 选择下一个/上一个/第一个/最后一个节点/项目
  • (向上、向下、PageUp、PageDown、Home、End) + Ctrl - 滚动控件
  • Enter、Space - 如果启用了 ShowCheckBoxes,则更改复选框状态;否则,展开/折叠节点。
  • 鼠标单击 - 选择节点/项目
  • 鼠标单击 + Shift - 选择项目范围(如果启用了多选)
  • 鼠标单击 + Ctrl - 添加选中的节点/项目(如果启用了多选)
  • 鼠标拖放 - 拖放
  • 鼠标双击 - 展开/折叠节点
  • 鼠标滚轮 - 滚动控件
  • Ctrl + A - 选择所有节点/项目

性能

FastList 可达 1 亿个项目。FastTree 每个节点可达 1000 万个子节点。

历史

  • 2014 年 9 月 18 日 - 首次发布
  • 2014 年 9 月 20 日 - 添加了虚拟 checkbox 模式。添加了示例 FastListVirtualCheckboxesSampleFastTreeDragAndDropSample
  • 2014 年 9 月 23 日 - 改进了内存使用情况,添加了压力测试。
  • 2014 年 10 月 6 日 - 添加了 HotTrackingAllowSelectItems 属性。
© . All rights reserved.