FastTree 和 FastList






4.91/5 (32投票s)
对标准 WinForm 控件 ListBox, CheckedListBox 和 TreeView 进行快速灵活的替换
引言
该库提供了两个控件:FastList
和 FastTree
。
这些控件旨在替代标准的 WinForm 控件:ListBox
、CheckedListBox
和 TreeView
。
由于我通常处理大型数据集,并且需要完美的性能,因此这些控件只实现虚拟模式,这是数据接收的最快方式。控件不存储项目的文本、颜色、图标等。所有这些数据都通过事件或重写的方法传递给控件。因此,您需要将数据存储在控件外部。控件仅存储服务数据,例如:当前复选框状态、选择状态以及折叠/展开状态。
在开发这些控件时,我追求以下目标:
- 控件必须能够显示大型数据集。设计上,至少 100 万个项目——无卡顿。
- 当数据更改时,控件必须能够无卡顿、无闪烁地重建。所有次要的先前状态(复选框、选择、展开)在重建后必须得到保留。
- 控件必须实现虚拟模式,并且不占用大量内存。
- 控件必须灵活,易于扩展和继承。
- 控件必须包含大量的事件,并具有取消能力。每个用户操作都可以被编程取消(选择、取消选择、更改复选框状态、展开、折叠等)。
- 多选、复选框、图标。
- 广泛的自定义选项:着色、自定义项目高度、自定义项目绘制、缩进、可见性。
- 内置拖放支持。
所有这些要求都已实现。
总体设计
FastTree
和 FastList
继承自同一个基类 FastListBase
。
FastListBase
继承自标准的 UserControl
,并实现了主要功能:绘制、滚动、鼠标和键盘处理程序、坐标计算等。如果您需要最大的灵活性和/或扩展性,您可以直接继承 FastListBase
来创建您的控件。
FastListBase
绘制项目的垂直列表。它不了解数据结构。它只是绘制项目的线性列表,其中每个项目都有自己的左缩进和自己的高度。
同样,FastListBase
也不包含任何事件(当然,除了继承自 UserControl
的事件)。它通过 virtual
方法接收所有数据,例如 string GetItemText(int itemIndex)
等。
使用 virtual
方法代替事件可以获得最大性能,因为我们不必为事件调用付费。如果子类不重写该方法,FastListBase
将使用其自身 virtual
方法返回的默认值。
此外,FastListBase
存储一些特定数据,例如已选中项目索引的 HashSet<int>
和已选中复选框的项目索引的 HashSet<int>
。FastListBase
还计算每个项目的坐标并存储它们。
FastList
是 FastListBase
的一个微小封装。此类重写了 FastListBase
的 virtual
方法并在其中调用用户事件。因此,FastList
与 FastListBase
的一个区别是:它包含事件。
FastTree
是 FastListBase
的一个较厚的封装,因为它包含用于树的特定逻辑。它也会重写 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
处理程序,则启用此模式。
其思想是数据对象实现其子对象的 IEnumerable
。FastTree
会尝试将节点转换为 IEnumerable
。如果接口存在,它将从中获取子项。否则,它将成为终端节点。
如果使用此方法,您只需要调用方法 void Build(object root)
来构建树。其他树节点将自动构建。
此外,您可以处理 NodeTextNeeded
事件来为节点绘制特定文本。但是,如果未分配 NodeTextNeeded
,FastTree
将使用节点对象的 ToString()
方法。
注意:如果某些节点的文本已更改,您只需调用 fastTree.Invalidate()
方法即可刷新控件。但如果数据结构(例如子节点数量)发生更改——您必须调用 Build(object root)
方法来反映数据更改。所有先前选择/选中/展开的状态都将自动恢复。
有关 FastTree
用法的更多示例,请参阅 Tester 应用程序。
复选框
复选框有两种模式。默认情况下,FastTree
会自行在内部存储 checkbox
状态。
但是,如果您为 NodeCheckStateNeeded
分配了处理程序,控件将切换到虚拟复选框模式。在此模式下,您需要在 NodeCheckStateNeeded
事件的处理程序中返回节点的选中状态。
此外,您可以在 NodeCheckedStateChanged
事件的处理程序中处理状态更改。
有用的事件、属性和方法
事件 NodeTextNeeded
和 ItemTextNeeded
- 处理这些事件以分配节点的文本。
事件 NodeChildrenNeeded
- 可以返回节点的子节点。
事件 NodeCheckStateNeeded
和 ItemCheckStateNeeded
- 可以返回节点/项目的 checkbox
状态。
事件 NodeIconNeeded
和 ItemIconNeeded
- 返回节点/项目的图标图像。
事件 NodeHeightNeeded
和 ItemHeightNeeded
- 如果您需要项目的单独高度,请处理此事件。如果未分配处理程序——将使用 ItemHeightDefault
。请注意,如果节点的高度已更改,您需要调用 Build()
方法。
事件 NodeBackColorNeeded
、NodeForeColorNeeded
、ItemBackColorNeeded
、ItemForeColorNeeded
- 用于设置节点/项目的文本颜色和背景颜色。
大量事件:CanUnselectNodeNeeded
、CanSelectNodeNeede
、CanCheckNodeNeeded
等——这些允许事件可以取消相应的用户操作。
事件 NodeCheckedStateChanged
、NodeExpandedStateChanged
、NodeSelectedStateChanged
、ItemCheckedStateChanged
、ItemExpandedStateChanged
、ItemSelectedStateChanged
- 通知节点/项目状态已更改。
事件 NodeDrag
、DragOverNode
、DropOverNode
、ItemDrag
、ItemOverItem
、ItemDropOverItem
- 当用户开始拖动/在节点上拖动/在节点上放置节点时会发生这些事件。请注意,FastTree
和 FastList
支持虚拟数据模型。因此,如果用户将项目拖入控件,外部事件处理程序必须根据拖动结果更改其数据,并调用 Build()
方法来重建控件。更多示例请参阅 FastListDropItemSample
和 FastListDragItemSample
。
属性 AllowDragItems
- 启用项目的拖放。
事件 PaintNode
和 PaintItem
- 如果您想自定义绘制节点/项目,请处理此事件。
属性 Nodes
- 所有可见节点的列表。
属性 ExpandedNodes
、SelectedNodes
、CheckedNodes
- 展开/选择/选中节点的列表。
属性 SelectedItemIndex
、CheckedItemIndex
- 选择/选中项目索引的哈希集。
属性 MultiSelect
- 启用多选。
属性 ItemCount
- 获取/设置 FastList
的项目数量。
属性 ItemInterval
- 项目之间的距离(以像素为单位)。
属性 NodeHeightDefault
、ItemHeightDefault
- 节点/项目的默认高度(以像素为单位)。
属性 ShowIcons
、ShowCheckBoxes
- 显示图标/复选框。
方法 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
模式。添加了示例FastListVirtualCheckboxesSample
和FastTreeDragAndDropSample
。 - 2014 年 9 月 23 日 - 改进了内存使用情况,添加了压力测试。
- 2014 年 10 月 6 日 - 添加了
HotTracking
和AllowSelectItems
属性。