【问题标题】:how to insert a child item in a treeview C# WPF如何在树视图 C# WPF 中插入子项
【发布时间】:2015-09-06 08:22:43
【问题描述】:

我想在之前添加的 TreeViewItem 中添加一个子项。像这样的代码的问题:

How to insert a child node in a TreeView Control in WPF?

或我尝试使用的许多其他变体,例如:

for (int i = 1; i <= dataTreeview.Items.Count; i++)
        {
             TreeViewItem tempTVI = (TreeViewItem)dataTreeview.Items.GetItemAt(i); 
        }

我得到一个 InvalidCastException 异常是因为项目(在另一个 stackoverflow 问题中)或 tempTVI 是字符串而不是 TreeViewItem

我不知道为什么会这样,而且我的想法已经用完了。

如果有帮助,我正在预览版中使用 Visual Studio 2015 社区。

感谢您的帮助。

【问题讨论】:

  • 不要以 WinForms 方式使用 WPF 控件。 WPF 旨在与数据绑定一起使用。请阅读有关 ItemsControl、项目容器和数据绑定的信息 - SO 上有许多文章、博客文章和问题。
  • 我之前确实看过如何做到这一点。问题是,如果搜索您所说的内容,只会找到这样的示例: TreeViewItem parent = new TreeViewItem("Parent"); TreeViewItem child = new TreeViewItem("Child");父项。添加(子项);这又不是我要找的东西

标签: c# wpf treeview


【解决方案1】:

WPF 中的TreeViewItemsControl 的扩展。基本上有两种方法可以使用这些控件,一种使动态变化的树易于管理,另一种使完全静态的树易于设置。

动态树

TreeView 的设计使用方式遵循 MVVM 设计模式。这是一个简单的例子。

首先,在使用 MVVM 时,您总是需要implements property change notification 的视图模型的某种基类。这是最基本的例子:

internal class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

然后,您需要一个类来表示树中单个节点的数据。例如:

internal class Node : ObservableObject
{
    private ObservableCollection<Node> mChildren;

    // Add all of the properties of a node here. In this example,
    // all we have is a name and whether we are expanded.
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                _name = value;
                NotifyPropertyChanged();
            }
        }
    }
    private string _name;

    public bool IsExpanded
    {
        get { return _isExpanded; }
        set
        {
            if (_isExpanded != value)
            {
                _isExpanded = value;
                NotifyPropertyChanged();
            }
        }
    }
    private bool _isExpanded;

    // Children are required to use this in a TreeView
    public IList<Node> Children { get { return mChildren; } }

    // Parent is optional. Include if you need to climb the tree
    // from code. Not usually necessary.
    public Node Parent { get; private set; }

    public Node(Node parent = null)
    {
        mChildren = new ObservableCollection<Node>();
        IsExpanded = true;
        Parent = parent;
    }
}

现在,为您的控件创建一个视图模型,其中包含这些节点的集合。在本例中,视图模型用于应用程序的主窗口:

internal class MainWindowVM : ObservableObject
{
    private ObservableCollection<Node> mRootNodes;

    public IEnumerable<Node> RootNodes { get { return mRootNodes; } }

    public MainWindowVM()
    {
        mRootNodes = new ObservableCollection<Node>();

        // Test data for example purposes
        Node root = new Node() { Name = "Root" };
        Node a = new Node(root) { Name = "Node A" };
        root.Children.Add(a);
        Node b = new Node(root) { Name = "Node B" };
        root.Children.Add(b);
        Node c = new Node(b) { Name = "Node C" };
        b.Children.Add(c);
        Node d = new Node(b) { Name = "Node D" };
        b.Children.Add(d);
        Node e = new Node(root) { Name = "Node E" };
        root.Children.Add(e);
        mRootNodes.Add(root);
    }
}

最后,创建TreeView 实例并将其设置为使用您的数据。在本例中,TreeView 是主应用程序窗口中的唯一内容:

<Window x:Class="WpfTreeViewExample.MainWindow"
        x:ClassModifier="internal"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfTreeViewExample"
        SizeToContent="WidthAndHeight"
        Title="MainWindow">
    <Window.DataContext>
        <local:MainWindowVM />
    </Window.DataContext>
    <TreeView
        Margin="10"
        ItemsSource="{Binding RootNodes}">
        <ItemsControl.ItemContainerStyle>
            <Style
                TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
                <!-- Could also put IsSelected here if we needed it in our Node class -->
            </Style>
        </ItemsControl.ItemContainerStyle>
        <ItemsControl.ItemTemplate>
            <HierarchicalDataTemplate
                DataType="{x:Type local:Node}"
                ItemsSource="{Binding Children}">
                <!-- Can build any view we want here to be used for each node -->
                <!-- Simply displaying the name in a text block for this example -->
                <TextBlock Text="{Binding Name}" />
            </HierarchicalDataTemplate>
        </ItemsControl.ItemTemplate>
    </TreeView>
</Window>

完成此设置后,从现在开始您所要做的就是操作视图模型中的数据,TreeView 将自动更新以反映这些更改。这样,您就无需直接操作控件。

这是结果视图:

静态树

如果提前知道整棵树并且永远不会改变,您可以像这样简单地设置它:

<TreeView
    x:Name="mTreeView"
    Margin="10">
    <TreeViewItem Header="Root">
        <TreeViewItem Header="Node A" />
        <TreeViewItem Header="Node B">
            <TreeViewItem Header="Node C" />
            <TreeViewItem Header="Node D" />
        </TreeViewItem>
        <TreeViewItem Header="Node E" />
    </TreeViewItem>
</TreeView>

这种方法的问题在于,当您想以编程方式修改树时,它变得难以管理,因为您必须处理 TreeView 本身。但是,它可以从代码隐藏中实现。例如,如果我想在“Node C”下添加一个名为“New Node”的新子节点,我可以这样做:

((TreeViewItem)((TreeViewItem)((TreeViewItem)mTreeView.Items[0]).Items[1]).Items[0]).Items.Add(new TreeViewItem() { Header = "New Node" });

不过,这样工作会变得一团糟。由于我们在数据中没有树的并行表示,因此我们必须不断通过控件访问事物并对其进行强制转换。

其他一些设置

看看你的问题,你似乎没有遵循这两种方法,而是有一个基本上像这样设置的 TreeView:

<TreeView>
    <sys:String>Node A</sys:String>
    <sys:String>Node B</sys:String>
</TreeView>

所以,你有一个充满字符串的TreeView。在内部,ItemsControl 可以获取任何对象并将其包装在项目容器中。 TreeView 会将这些字符串包装在 TreeViewItem 实例中。但是,这些项目仍以字符串形式存储,访问TreeView.Items 将返回您添加的字符串。

TreeView 中获取与任意项目相关联的TreeViewItem 实际上相当困难,因为您必须在根级别获取每个项目的容器,然后深入每个项目并获取其项目的容器,以此类推,直到找到要查找的项目。

您可以找到有关如何查找项目容器here 的示例。请注意,您不能在 TreeView 中使用 virtualization 以使其可靠地工作。另外,我建议不要以这种方式工作,因为你会让自己变得更难。

【讨论】:

  • 我还有一个问题:当用户点击一个子节点时,如何获取子节点的索引?我的意思是:单击节点 A -> 获取 0 单击节点 B -> 获取 1 单击节点 E -> 获取 2 我不使用三级数据,所以我不关心 C 和 D这是非常非常重要的
  • 没关系,我终于找到了答案。再次感谢泽维尔
【解决方案2】:

你得到String 因为这是TreeView 的源必须绑定到的。

使用此方法,您可以遍历项目并检索它们所在的 TreeViewItem 容器:

 List<TreeViewItem> GetChildren(TreeViewItem parent)
    {
      List<TreeViewItem> children = new List<TreeViewItem>();

      if (parent != null)
      {
        foreach (var item in parent.Items)
        {
          TreeViewItem child = item as TreeViewItem;

          if (child == null)
          {
            child = parent.ItemContainerGenerator.ContainerFromItem(child) as TreeViewItem;
          }

          children.Add(child);
        }
      }

      return children;
    }

请注意,他们在转换 TreeViewItem 后检查它是否为空。这是一种很好的做法,因为它可以防止空引用异常在出现问题时使您的应用程序崩溃。

来源:https://social.msdn.microsoft.com/Forums/vstudio/en-US/595f0c84-01e7-4534-b73b-704b41713fd5/traversing-the-children-of-a-treeviewitem

【讨论】:

  • 我相信我没有很清楚地问我的问题。我想要的是得到父母,而不是孩子。更好的是,我正在徘徊如何遍历树视图父母,以便能够添加一个孩子,如果它的名字是正确的
【解决方案3】:

也许你忘记了标签? 如果您的 xaml 文件中有以下内容:

    <TreeView x:Name="MyTreeView">
        <TreeViewItem>Hello</TreeViewItem>
        World
    </TreeView>

以及代码隐藏中的以下内容:

var a = MyTreeView.Items.GetItemAt(0) as string;
var b = MyTreeView.Items.GetItemAt(0) as TreeViewItem;
var c = MyTreeView.Items.GetItemAt(1) as string;
var d = MyTreeView.Items.GetItemAt(1) as TreeViewItem;

变量 a 和 d 将为空,而 b 将是 TreeViewItem,c 将是字符串。

【讨论】:

  • 这给了我一个 NullRecerenceException
  • 您是否在 标签中包围了您的项目?
  • 我做不到。它们未添加到 xaml 代码中。它们添加有类似结构的形式。所以,这不是一个选择
猜你喜欢
  • 2011-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-19
相关资源
最近更新 更多