Windows 窗体 TreeView 不知道如何绑定到 IHierarchyData 实例,这并不奇怪,因为 IHierarchyData 和相关接口旨在供 Web 控件(尤其是站点地图)使用。
但是,构建自己的数据绑定类真的不是太难。这似乎是一个有趣的问题,所以我把一个放在一起只是为了好玩。我将带您了解内部工作原理。
首先,创建一个基本的组件类。 Visual Studio 将从以下代码开始:
public partial class TreeViewHierarchyBinding : Component
{
public TreeViewHierarchyBinding()
{
InitializeComponent();
}
public TreeViewHierarchyBinding(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
这个组件需要有一个明显的“状态”是从每个TreeNode 到它的IHierarchyData 的映射。现在我们可以通过将它扔到TreeNode 的Tag 属性中来解决这个问题,但是让我们的目标是使这个组件尽可能非侵入性并跟踪它自己的状态。因此,我们将使用字典。将此字段添加到类中:
private Dictionary<TreeNode, IHierarchyData> nodeDictionary = new
Dictionary<TreeNode, IHierarchyData>();
现在,该组件至少需要知道如何从对应绑定的IHierarchyData 中填充TreeView 类的特定父类TreeNode,所以接下来让我们编写该代码:
private void PopulateChildNodes(TreeNodeCollection parentCollection,
IHierarchicalEnumerable children)
{
parentCollection.Clear();
foreach (object child in children)
{
IHierarchyData childData = children.GetHierarchyData(child);
TreeNode childNode = new TreeNode(childData.ToString());
if (childData.HasChildren)
{
childNode.Nodes.Add("Dummy"); // Make expandable
}
nodeDictionary.Add(childNode, childData);
parentCollection.Add(childNode);
}
}
private void UpdateRootNodes(TreeView tv, IHierarchyData hierarchyData)
{
if (tv == null)
{
return;
}
tv.Nodes.Clear();
if (hierarchyData != null)
{
IHierarchicalEnumerable roots = hierarchyData.GetChildren();
PopulateChildNodes(tv.Nodes, roots);
}
}
这部分应该很简单。第一种方法只是使用IHierarchyEnumerable 接口使用从IHierarchyData 实例获得的层次结构填充TreeNodeCollection(即TreeNode 的Nodes 属性)。这个方法唯一真正有趣的事情是:
当IHierarchyData 实例有子节点时添加一个虚拟节点;这使得“+”在树视图中可见,否则我们将无法进一步扩展;和
将新添加的节点添加到与其匹配的 IHierarchyData 实例的字典中。
第二种方法更简单,它完成初始的“绑定工作”,将树的 根 中的所有内容替换为我们的顶级 IHierarchyData 实例。
我们的组件需要做的下一件事是挂钩来自TreeView 的加载事件以执行延迟加载。这是执行此操作的代码:
private void RegisterEvents(TreeView tv)
{
tv.BeforeExpand += TreeViewBeforeExpand;
}
private void UnregisterEvents(TreeView tv)
{
tv.BeforeExpand -= TreeViewBeforeExpand;
}
private void TreeViewBeforeExpand(object sender, TreeViewCancelEventArgs e)
{
if (e.Node.Checked)
{
return;
}
IHierarchyData hierarchyData;
if (nodeDictionary.TryGetValue(e.Node, out hierarchyData))
{
PopulateChildNodes(e.Node.Nodes, hierarchyData.GetChildren());
e.Node.Checked = true;
}
}
前两种方法应该是不言自明的,第三种方法是实际的延迟加载代码。我们在这里有点作弊,使用TreeNode.Checked 属性来描述子节点是否已经加载,因此我们不会进行任何不必要的重新加载。当我实现延迟加载树时,我总是这样做,因为根据我的经验,我几乎从不使用 TreeNode.Checked 属性。但是,如果您确实需要将此属性用于其他用途,您可以使用不同的属性(如 Tag),创建另一个字典来保存扩展状态,或者修改现有字典以保存复合类(包含IHierarchyData 以及 Expanded 属性)。我暂时保持简单。
如果您之前已经在树中实现了延迟加载,那么其余部分对您来说应该已经很有意义了,所以让我们跳过。现在唯一要做的就是实现一些设计器/用户属性,这些属性将实际连接树和数据:
private IHierarchyData dataSource;
private TreeView treeView;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IHierarchyData DataSource
{
get { return dataSource; }
set
{
if (value != dataSource)
{
dataSource = value;
nodeDictionary.Clear();
UpdateRootNodes(treeView, value);
}
}
}
[Category("Behavior")]
[DefaultValue(null)]
[Description("Specifies the TreeView that the hierarchy should be bound to.")]
public TreeView TreeView
{
get { return treeView; }
set
{
if (value != treeView)
{
if (treeView != null)
{
UnregisterEvents(treeView);
}
treeView = value;
nodeDictionary.Clear();
RegisterEvents(value);
UpdateRootNodes(treeView, dataSource);
}
}
}
简单易懂。我们有一个接受根IHierarchyData 的DataSource 属性和一个您可以从设计器访问的TreeView 属性。再说一次,这里很简单,当DataSource 属性更新时,我们只需重置查找并重新填充根。当 TreeView 属性更新时,我们必须做更多的工作,注册事件,确保从旧树视图中取消注册事件,并在数据源更改时执行我们所做的所有相同操作。
这就是它的全部内容!打开 Windows 窗体设计器,删除 TreeView,然后删除 TreeViewHierarchyBinding 并将其 TreeView 属性设置为您刚刚删除的树视图。最后,在您的代码中某处(即在Form_Load 事件中),给它一个数据源:
private void Form1_Load(object sender, EventArgs e)
{
DirectoryInfo dir = new DirectoryInfo("C:\\");
treeViewHierarchyBinding1.DataSource = new FileSystemHierarchyData(dir);
}
(注意 - 这使用了 MSDN page for IHierarchyData 上的示例 FileSystemHierarchyData。该示例不是很健壮,它不检查 UnauthorizedAccessException 或任何东西,但足以证明这一点)。
就是这样。运行您的应用程序并观察它的绑定。您现在可以在任何地方重用 TreeViewHierarchyBinding 组件 - 只需将其放在表单上,为其分配一个 TreeView,然后为其提供一个 IHierarchyData 实例作为数据源。
如果你想要复制粘贴版本,我已经输入了complete code on PasteBin。
玩得开心!