水平树控件
一个类似 Vista Explorer 的控件实现,用于表示分层数据。
引言
这是一个自定义的树控件,旨在解决分层数据的垂直显示问题。
背景
我使用 Vista,尽管我不喜欢它的许多方面,但我却钟爱 Windows Explorer 中用于解析磁盘文件夹结构的那个控件。
当用户想要从隐藏的树中选择某项时,会出现一个自定义列表框,如下所示:
如您所见,对于 Explorer 实现,该控件表示文件夹或特殊文件夹(如“我的文档”),每个都有其自定义图标。还使用了额外的图形,我试图模仿它们,但由于我缺乏艺术技能,未能完全复制;所以如果有人有任何建议,我会非常感兴趣。
控件解析
内部数据表示
分层数据的内部表示是类似树的 NodeBase
对象集合。NodeBase
是抽象的,以下是它的有效实现:
Node
用于数据。它包含 DisplayedText
、相关的 Image
和一个 Tag
属性,该属性保存节点实际对象的引用。此对象用于操作节点的激活事件。目前,只提供了一个特殊的节点类:NodeSeperator
。特殊节点类永远不会出现在工具栏上。它们只出现在显示每个节点子节点的选择器中。
视觉表示
上述数据的视觉表示是通过显示一系列控件来完成的。每个控件基本上都派生自 NodeBaseControl
,它是抽象的,并有两个独立的实现。NodeBaseControl
最终持有一个 NodeBase
对象。
NodeLinksControl
在工具栏中只使用一次,即树,它是最左边可见的对象。它有一个显示当前活动节点图像的图像,并且可以显示各种节点的选择,这些节点可能不是分层数据的一部分,例如链接。
NodeControl
用于表示所有其他节点。根节点是一个 NodeControl
。它显示文本和图像以显示选择器。如果没有子节点,则不需要选择器,因此不显示图像。
当添加许多节点时,水平树会像 Vista 的 Explorer 一样隐藏,最靠近根部,并将它们添加到 NodeLinksControl
的选择中。它还会更改图像以显示节点已隐藏的事实。
在任何单一点,当没有节点被选中时,只需点击它或从选择器中选中它,该节点就会成为最后一个可见节点,被激活,并且任何子节点都会从视觉树中移除。Selector
是一个自定义的 ListBox
,用于显示节点的子节点。分隔符节点不能被选中或高亮显示。当请求选择器时,树处于
SelectionMode==true
直到选中一个节点,或在选择器或 NodeBaseControl
外部单击鼠标。为了实现这一点,选择器被添加到 ParentForm
的 Controls
中并置于最前面。它还会捕获鼠标并重定向所需的鼠标消息。
NodeBaseControl 解析
每个 NodeBaseControl
由两个并排的 PartBase
控件显示。PartBase
是抽象的,其子类是:
每个部分有效地了解另一个部分,并且基本上具有一个驱动其外观的 State
。这些部分控件中的每一个都处理来自鼠标的所有输入功能,并考虑到树的状态或其其他部分的 State
。
Using the Code
您可以将控件拖放到表单中(在我的演示中,它被命名为 horizontalTreeControl1
)。并未实现完整的设计时支持,因为我没有时间创建它,而且我对学习如何实现不感兴趣,因为我相信未来在于 WPF。在演示项目中,构建了一个类型化的数据集来模拟类似于磁盘中找到的文件夹结构。要填充 LinksControl
,您可以使用类似以下的代码:
this.horizontalTreeControl1.LinksNode.Suspend();
this.horizontalTreeControl1.LinksNode.AddNode("Link1",
Properties.Resources.Link1, null);
this.horizontalTreeControl1.LinksNode.AddSeperator();
this.horizontalTreeControl1.LinksNode.AddNode("Link2",
Properties.Resources.Link2, null);
Node n = new Node();
this.horizontalTreeControl1.LinksNode.Resume();
Suspend
和 Resume
用于抑制 Paint 事件。每个节点都有一个
- 显示的文本
- Image
- 对象引用,它基本上用作 Windows Forms 控件中的 Tag 属性
要实现工具栏的 RootNode
,请使用类似以下的代码:
n = new Node();
n.DisplayText = ds.Folder[0].Name;
n.Tag = ds.Folder[0];
n.Image = Properties.Resources.Root;
this.horizontalTreeControl1.RootNode = n;
现在,有两种方法可以在每个节点中添加子节点。您可以使用上面为 LinksNode
所述的每个节点上的 AddNode
,或者捕获 NodeActivated
事件以在指定时间动态添加节点。当节点被激活时,就会调用此事件。当没有理由将所有数据添加到树中,或者数据随时间变化时,这非常有用。像这样捕获事件:
this.horizontalTreeControl1.NodeActivated +=
new Sarafian.Framework.ClientSide.UI.Resources.HorizontalTree.
DelegateEnum.DelegateNodeActivated(horizontalTreeControl1_NodeActivated);
并像这样实现它:
void horizontalTreeControl1_NodeActivated(Node node)
{
if (node.Tag is DataRow)
{
node.ClearNodes();
DsTest.FolderRow row = (DsTest.FolderRow)node.Tag;
node.Suspend();
foreach (DsTest.FolderRow subRow in
row.GetChildRows(this.ds.Folder.ChildRelations[0]))
{
node.AddNode(subRow.Name, Properties.Resources.Folder, subRow);
}
node.Resume();
}
else
{
//MessageBox.Show(node.DisplayText);
}
}
如您所见,再次使用了 AddNode
。此事件也用于了解何时激活了节点。例如,如果该节点是一个文件夹,而您想要它的文件。Tag
属性保存节点的实际数据,可以是任何对象。
关注点
此控件并未完全实现 Windows Vista Explorer 的路径选择器。需要另一个带有 TextBox
和自动填充功能的控件,该控件将在水平树控件的前面激活。此控件可以显示任何类型的分层数据。
在开发过程中,我犯了许多错误,导致进行了几次重构尝试,因此代码并不完美。有一些文件是从其他类库添加到类库中的,我在这里导入它们是为了使解决方案更简单。这些文件是:
- Percent.cs
- Rectangle.cs
- Point.cs
历史
- 版本 1.0 创建。