【问题标题】:IHierarchyData and IHierarchicalEnumerable in WinformsWinforms 中的 IHierarchyData 和 IHierarchicalEnumerable
【发布时间】:2010-03-13 13:08:32
【问题描述】:

目前,我知道如何在树视图控件中执行节点加载过程的延迟实现,并阅读stackoverflow中的相关问题,但我也在阅读asp.net中的IHierarchyData和IHierarchicalEnumerable接口(我不知道编写 asp.net 代码)允许将集合绑定到树视图以便自动显示项目。

想知道我是否可以在 winforms 和 C# 中做同样的事情。我认为前面提到的接口在winforms中是不可用的。

谢谢。

【问题讨论】:

    标签: c# winforms treeview hierarchical-data


    【解决方案1】:

    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 的映射。现在我们可以通过将它扔到TreeNodeTag 属性中来解决这个问题,但是让我们的目标是使这个组件尽可能非侵入性并跟踪它自己的状态。因此,我们将使用字典。将此字段添加到类中:

    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(即TreeNodeNodes 属性)。这个方法唯一真正有趣的事情是:

    1. IHierarchyData 实例有子节点时添加一个虚拟节点;这使得“+”在树视图中可见,否则我们将无法进一步扩展;和

    2. 将新添加的节点添加到与其匹配的 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);
            }
        }
    }
    

    简单易懂。我们有一个接受根IHierarchyDataDataSource 属性和一个您可以从设计器访问的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

    玩得开心!

    【讨论】:

    • 除了使用 Checked 属性,您还可以使用不可能或不可能的虚拟子节点名称(例如在文件系统树中具有 \)。这样,IsLoaded 验证可能是 If TreeNodeCollection.Nodes.Count == 1 and TreeNodeCollection.Nodes[0].Text == "\MyDummyValue/"
    【解决方案2】:

    接口可用,但需要您添加对 System.Web.UI 的引用。 (它可能还需要您使用完整的 .NET Framework 可再发行组件而不是客户端配置文件,尽管我对此不确定。)

    更大的问题是:WinForms TreeView 控件是否自动理解如何使用这些接口?我相信该问题的答案是“否”,但您需要对此进行测试/验证。

    【讨论】:

      【解决方案3】:

      有一篇有趣的文章here 向您展示了如何构建扩展方法来实现我认为您正在寻找的东西。 System.Windows.Forms.TreeView 中没有本机可用性来绑定到我能找到的集合。

      您可以在项目中包含 System.Web.UI 以使 IHierarchyData 和 IHierarchicalEnumerable 接口可用,但如果没有扩展方法,TreeView 将无法附加到它们。

      来自网站的示例源代码可让您将任何 IDictionary 集合绑定到 TreeView。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-23
        • 2023-03-29
        • 1970-01-01
        • 2015-06-08
        • 1970-01-01
        • 2011-12-09
        相关资源
        最近更新 更多