如何在树视图 C# WPF 中插入子项
how to insert a child item in a treeview C# WPF
我想在之前添加的 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 异常是因为项目(在另一个 Whosebug 问题中)或 tempTVI 是字符串而不是 TreeViewItem
我不知道这是为什么,我 运行 没主意了。
如果有帮助,我正在使用 Visual Studio 2015 社区预览版。
感谢您的帮助。
也许您忘记了标签?
如果您的 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 将为 null,而 b 将为 TreeViewItem,而 c 将为字符串。
您得到的是 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 是否为 null。这是一个很好的做法,因为它可以防止空引用异常在出现问题时使您的应用程序崩溃。
TreeView in WPF is an extension of ItemsControl。基本上有两种方法可以使用这些控件,一种使动态变化的树易于管理,另一种使完全静态的树易于设置。
动态树
TreeView
的设计使用方式遵循 MVVM 设计模式。这是一个简单的例子。
首先,在使用 MVVM 时,您总是希望 implements property change notification 的视图模型有某种基础 class。这是最基本的例子:
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));
}
}
}
然后,您需要一个 class 来表示树中单个节点的数据。例如:
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;
}
}
现在,为您的控件创建一个视图模型,其中包含这些节点的集合。在此示例中,视图模型用于应用程序的主要 window:
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:
<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
将 return 您添加的字符串。
让 TreeViewItem
与 TreeView
中的任意项目相关联实际上相当困难,因为您必须在根级别获取每个项目的容器,然后深入研究每个项目并为他们的物品获取容器,依此类推,直到找到您正在寻找的物品。
您可以在 TreeView
中找到有关如何查找项目容器 here. Note that you cannot use virtualization 的示例,以便它可靠地工作。另外,我建议不要以这种方式工作,因为你会让自己变得更难。
我想在之前添加的 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 异常是因为项目(在另一个 Whosebug 问题中)或 tempTVI 是字符串而不是 TreeViewItem
我不知道这是为什么,我 运行 没主意了。
如果有帮助,我正在使用 Visual Studio 2015 社区预览版。
感谢您的帮助。
也许您忘记了标签? 如果您的 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 将为 null,而 b 将为 TreeViewItem,而 c 将为字符串。
您得到的是 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 是否为 null。这是一个很好的做法,因为它可以防止空引用异常在出现问题时使您的应用程序崩溃。
TreeView in WPF is an extension of ItemsControl。基本上有两种方法可以使用这些控件,一种使动态变化的树易于管理,另一种使完全静态的树易于设置。
动态树
TreeView
的设计使用方式遵循 MVVM 设计模式。这是一个简单的例子。
首先,在使用 MVVM 时,您总是希望 implements property change notification 的视图模型有某种基础 class。这是最基本的例子:
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));
}
}
}
然后,您需要一个 class 来表示树中单个节点的数据。例如:
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;
}
}
现在,为您的控件创建一个视图模型,其中包含这些节点的集合。在此示例中,视图模型用于应用程序的主要 window:
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:
<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
将 return 您添加的字符串。
让 TreeViewItem
与 TreeView
中的任意项目相关联实际上相当困难,因为您必须在根级别获取每个项目的容器,然后深入研究每个项目并为他们的物品获取容器,依此类推,直到找到您正在寻找的物品。
您可以在 TreeView
中找到有关如何查找项目容器 here. Note that you cannot use virtualization 的示例,以便它可靠地工作。另外,我建议不要以这种方式工作,因为你会让自己变得更难。