【问题标题】:WPF TreeView - How do I bind a Parent/Child classWPF TreeView - 如何绑定父/子类
【发布时间】:2018-05-02 20:31:50
【问题描述】:

我有一个要绑定到 TreeView 的表。

NODE    NAME        PARENT
----    -----       -------
1       Bill        NULL
2       Jane        NULL
3       John        1
4       Mike        2
5       Bob         4
6       Jody        1
7       Larry       5
8       Heather     2
9       Steve       8

这是我的课:

public class Node
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int? Parent { get; set; }

    public Node()
    {
    }

    public Node(int id, string name)
    {
        ID = id;
        Name = name;
        Parent = null;
    }

    public Node(int id, string name, int? parent)
    {
        ID = id;
        Name = name;
        Parent = parent;
    }

    public override string ToString()
    {
        return string.Format("[{0}] {1}", ID, Name);
    }

}

所以最终的结果应该是这样的:

---[1] Bill
  |-----[3] John
  |-----[4] Jody

---[2] Jane
  |-----[4] Mike
  |    |------[5] Bob
  |          |------[7] Larry
  |-----[8] Heather
       |------[9] Steve

目前我正在使用后面的代码构建它。 首先,我找到所有没有父项的项目。然后从代码中将它们添加到 TreeView:

    var TopNodes = Nodes.Where(n => n.Parent == null);

    foreach (Node n in TopNodes)
    {
        TreeViewItem newItem = new TreeViewItem();
        newItem.Tag = n.ID;
        newItem.Header = n.Name;

        newItem.Items.Add("..Loading..");
        tvTest.Items.Add(newItem);
    }

然后,当项目展开时,我填充父项,如下所示:

private void tvTest_Expanded(object sender, RoutedEventArgs e)
{
                item.Items.Clear();

        int parentID = (int)item.Tag;

        var children = Nodes.Where(x => x.Parent == parentID);

        foreach (Node n in children)
        {
            TreeViewItem newItem = new TreeViewItem();
            newItem.Tag = n.ID;
            newItem.Header = n.Name;

            newItem.Items.Add("..Loading..");
            item.Items.Add(newItem);
        }

}

如果我可以直接绑定这些数据,我觉得我会有更大的灵活性。尤其是当我开始着手对数据实施 CRUD 功能时。

感谢您给我的任何建议。

【问题讨论】:

    标签: wpf treeview


    【解决方案1】:

    您可以使用 HierarchicalDataTemplate 实现此目的。

    1. 修改您的 Node 类,使其包含一个名为 Children 的属性,类型为 List<Node>。这将包含该节点的所有子节点的列表。
    2. 然后在您的视图模型中公开一个名为 TopNodes 的公共属性,类型为 List<Node>,它将返回顶级节点的列表。
    3. 最后在xaml视图中,定义TreeView如下:

      <TreeView Name="TreeViewNodes" ItemsSource="{Binding TopNodes}">
          <TreeView.ItemTemplate>
              <HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding Children}">
                  <TextBlock Text="{Binding Path=Name}" />
              </HierarchicalDataTemplate>
          </TreeView.ItemTemplate>
      </TreeView>
      

    您仍然必须从视图模型中相应地填充您的节点对象,但这样您就不必直接从视图模型中引用 TreeViewItem,因为这不是遵循 MVVM 模式时推荐的方式。

    我希望这能回答你的问题:)

    【讨论】:

    • 感谢@Richard 的回复。我还没有深入研究 MVVM。我的数据将以我发布的格式来自 sql 表。我需要使用子列表构建每个节点的 ViewModel 吗?
    • 视图模型在视图和数据模型(在您的情况下是您的数据库实体)之间进行调解。为此,创建一个 TreeNodeViewModel 视图模型,并在其中声明 Children 属性,如上所述。还实现一个bool IsExpanded 属性,并在设置器中执行填充子项的逻辑。这允许您执行按需加载。如果您希望在非常大/深度嵌套的树结构的情况下尽量减少传输的数据量,您甚至可以在此阶段执行数据库查询。
    • 最后将 IsExpanded 属性绑定到 Treeview。也不要忘记在视图模型中添加更改通知。在 MVVM 中,重要的是不要在视图模型中引用控件,并且我个人尽量不在视图后面的代码中实现任何代码,因为这会使您的代码进行单元测试非常困难。我还建议您阅读以下代码项目article。如果您仍然有困难,我可以提供有关如何完成此任务的要点。希望这能回答您的疑问:)
    【解决方案2】:

    希望下面的例子对你有帮助

    Xaml 前端

               <TreeView Grid.Row="1" Background="Transparent" ItemsSource="{Binding Directories}" Margin="0,10,0,0" Name="FolderListTreeView"
                    Height="Auto" HorizontalAlignment="Stretch" Width="300"  local:ControlBehaviour.ControlEvent="TreeView.SelectedItemChanged" >
                    <TreeView.Resources>
                        <HierarchicalDataTemplate DataType="{x:Type local:FileSystem}" ItemsSource="{Binding SubDirectories}">
                            <Label Content="{Binding Path= Name}" Name="NodeLabel" />
                        </HierarchicalDataTemplate>
                    </TreeView.Resources>
                </TreeView>
    

    包含子目录及其子目录的类

    public class FileSystem :NotifyChange, IEnumerable
    {
        #region Private members
        private ObservableCollection<FileSystem> subDirectoriesField;
        #endregion
    
        #region Public properties
        /// <summary>
        /// Gets and sets all the Files in the current folder
        /// </summary>
        public ObservableCollection<FileSystem> SubDirectories
        {
            get
            {
                return subDirectoriesField;
            }
            set
            {
                if (subDirectoriesField != value)
                {
                    subDirectoriesField = value;
                    RaisePropertyChanged("SubDirectories");
                }
            }
        }
        /// <summary>
        /// Gets or sets name of the file system 
        /// </summary>
        public string Name
        {
            get;
            set;
        }
        /// <summary>
        /// Gets or sets full path of the file system
        /// </summary>
        public string FullPath
        {
            get;
            set;
        }
        /// <summary>
        /// object of parent, null if the current node is root
        /// </summary>
        public FileSystem Parent
        {
            get;
            set;
        }
        public FileSystem(string fullPath, FileSystem parent)
        {
            Name = fullPath != null ? fullPath.Split(new char[] { System.IO.Path.DirectorySeparatorChar },
                StringSplitOptions.RemoveEmptyEntries).Last()
            FullPath = fullPath;
            Parent = parent;
            FileType = type;
            AddSubDirectories(fullPath);
        }
    
        public IEnumerator GetEnumerator()
        {
            return SubDirectories.GetEnumerator();
        }
    
        private void AddSubDirectories(string fullPath)
        {
            string[] subDirectories = Directory.GetDirectories(fullPath);
            SubDirectories = new ObservableCollection<FileSystem>();
            foreach (string directory in subDirectories)
            {
                SubDirectories.Add(new FileSystem(directory, this));
            }
        }
    }
    

    然后视图模型将如下所示

    public class ViewModel:NotifyChange
    {
       private ObservableCollection<FileSystem> directories;
       public ObservableCollection<FileSystem> Directories
        {
            get
            {
                return directoriesField;
            }
            set
            {
                directoriesField = value;
                RaisePropertyChanged("Directories");
            }
        }
        public ViewModel()
        {
           //The below code has to be moved to thread for better user expericen since when UI is loaded it might not respond for some time since it is looping through all the drives and it;s directories
           Directories=new  ObservableCollection<FileSystem>();
           Directories.Add(new FileSystem("C:\\", null);
           Directories.Add(new FileSystem("D:\\", null);
           Directories.Add(new FileSystem("E:\\", null);
        }
    }
    

    将 DataContext 设置为 ViewModel

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多