【问题标题】:C# TreeView design - best way to display a tree structure?C# TreeView 设计 - 显示树结构的最佳方式?
【发布时间】:2009-02-06 18:58:02
【问题描述】:

我正在尝试使用 TreeView 来显示对象的树结构。我有一棵包含四种类型对象的树,Company(根节点)、City、Store 和 Employee。

该界面旨在添加/删除 Cities/Stores/Employees,因此 TreeView 必须更新以反映任何更改。

我想知道让 TreeView 显示树结构并在它发生变化时接收更新的正确方法。

我认为 Company 对象应该有事件,例如 company.CityAdded 和 company.CityRemoved,那么我在 TreeView 周围放置的任何包装器都会响应这些事件?当 TreeView 建立后,每个城市/商店/员工都会有一个节点。然后每个节点可以响应它在树中表示的节点的事件。

这是正确的想法吗?还是有更好的方法?

【问题讨论】:

    标签: c# oop treeview


    【解决方案1】:

    我只是想补充一点,如果 WPF 是一个选项,那么使用分层数据绑定和 observablecollections 将变得非常简单。它基本上会为您处理所有事件,并让您只与业务对象进行交互。

    【讨论】:

      【解决方案2】:

      听起来你是在正确的道路上。我不得不做类似的事情,我想分享一些指针:

      1. 将对象引用存储在 TreeNode 标记属性中。

      2. 给每个Treenode一个可以轻松识别对象的唯一名称,例如:对象哈希码、公司ID等

      通过这种方式,您可以在对象状态发生变化时轻松找到并更新 TreeNode。当用户选择一个节点时,您可以从 Tag 属性中获取它所代表的对象。

      祝你好运。

      【讨论】:

        【解决方案3】:

        您在监听事件的概念上是正确的(这是标准的发布者/订阅者模式)。

        对于树视图的实际更新,我倾向于有两种方法:AddOrUpdateTreeItemRemoveTreeItem。 add 或 update 方法执行它所说的,查找树项(基于路径)并更新或添加它。当然,如果模型是在创建表单的线程之外的线程上更新的,您将需要使用 Control.BeginInvoke() 编组调用。

        如果您在 form_load 或其他地方填充完整的树,这种方法可能会有点慢,因此您可能有不同的方法用于初始填充,并使用我在此处描述的概念进行后续更新。

        我对列表视图做同样的事情,这是一个典型的例子。添加树项的主要区别在于,您可能需要添加父节点才能添加请求的节点,这使得它有点递归。试试看吧。

        private void AddOrUpdateListItem(DomainModelObject item)
        {
            ListViewItem li = lvwListView.Items[GetKey(item)];
        
            if (li == null)
            {
                li = new ListViewItem
                         {
                             Name = GetKey(item), 
                             Tag = item
                         };
                li.SubItems.Add(new ListViewItem.ListViewSubItem());
                li.SubItems.Add(new ListViewItem.ListViewSubItem());
                li.SubItems.Add(new ListViewItem.ListViewSubItem());
                li.ImageIndex = 0;
                lvwListView.Items.Add(li);
            }
        
            li.Text = [Itemtext];
            li.SubItems[1].Text = [Itemtext];
            li.SubItems[2].Text = [Itemtext];
            li.SubItems[3].Text = [Itemtext];
        }
        

        以下是如何实现BeginInvoke() 的示例:

        public class MyForm : Form
        {
            ...
        
            void data_Changed(object sender, DataChangedEventArgs e)
            {
                if (this.InvokeRequired)
                {
                    this.BeginInvoke(new EventHandler<DataChangedEventArgs>(data_Changed), sender, e);
                    return;
                }
        
                AddOrUpdateListItem(e.DataItem);
            }
        
            ...
        }
        

        【讨论】:

        • +1 用于提及 BeginInvoke()。但是,您可能希望修改您的示例以显示这一点。
        【解决方案4】:

        而不是...

        • 业务对象订阅 UI 事件
        • 命令更新 UI
        • 更新 UI 时更新业务对象

        ...您也可以反过来做(即命令更新业务对象树,从而导致相应的 UI 更新)。

        【讨论】:

          【解决方案5】:

          更新的发布/订阅模式的部分关键是如何包装事件触发时要做什么的信息。

          当代表“Store X”的对象更新为一个新名称,并触发一个事件来宣布这件事发生时,哪个对象会使用该事件?

          同理,当添加城市 Y 时,应该通知哪个对象创建?

          一种常见的方法是使用某种大型 uber-manager 类来处理整个流程 - 它订阅所有事件并执行所有操作。

          另一种方法,我用过的效果很好,是创建更简单的包装器/协调器对象,只处理拼图的一部分。通常,我在这些类的名称后面加上“Editor”。

          因此,您可以拥有一个 CityEditor 类,其构造函数同时采用 City 对象和代表该对象的 TreeNodeCityEditor 将订阅 City 对象和 TreeNode 上的事件,并负责使用标题填充 TreeNode 并选择一个图标。

          当 City 对象更新时,CityEditor 通过更新 TreeNode 来响应触发的事件。当 City 对象被移除时,CityEditor 确保从Treeview 中移除节点。

          当一个新的 Store 对象被添加到 City 时,CityEditor 可以负责创建一个 StoreEditor 来协调该级别的更新。同样,当Employee 被添加到Store 时,EmployeeEditor 的实例会处理Treeview 的更新。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-12-15
            • 1970-01-01
            相关资源
            最近更新 更多