I made a very simple mistake binding to a treeview that took me forever to figure out what was going wrong.
I realized that most the samples of binding to a treeview miss the core case of binding to a tree of objects with a single root. (I'm sure there's a good pun here about seeing the forest for the trees).
Let's consider a class called TreeNode that's structured like so:
public class TreeNode {
public object Value { get; set; }
public ObservableCollection<TreeNode> Children { get; }
}
Assume your tree is defined in code like so:
// Window.xaml.cs constructor
TreeNode tree = new TreeNode("root",
new TreeNode("Child 1",
new TreeNode("Child 1.1"),
new TreeNode("Child 1.2")),
new TreeNode("Child 2",
new TreeNode("Child 2.1"),
new TreeNode("Child 2.2")));
We want the TreeView itself to create a TreeViewItem for the Root, and then the subsequent TreeViewItems to generate TreeViewItems for the Root.Children and the Root.Children.Children ... and so on.
Lets break it down into two issues: showing the Root and showing the Children.
Like any other items control, you use the TreeView.ItemsSource to databind to the Root object.
Unfortunately, our object Is not a list, so it doesn't work with ItemsSource. If you bind an object that is not IEnumerable to the ItemsSource property, it will silently fail. This is easily solved one of two ways - create a one element list that contains the root object or implement IEnumerable on TreeNode.
IEnumerable solution - Option A: Assign the ItemsSource a one element array containing our Root tree node object
// Window.xaml.cs constructor
this.DataContext = new TreeNode[] { tree };
<Window ...>
<Grid>
<!-- ommiting Source on the Binding means
pick up the data from the DataContext
property of the Grid or Window -->
<TreeView Name="_treeView"
ItemsSource="{Binding}"/>
</Grid>
</Window>
IEnumerable solution - Option B: Implement IEnumerable on our TreeNode class so that it can be directly assigned to TreeView.ItemsSource.
public class TreeNode : IEnumerable {
public IEnumerator GetEnumerator() {
TreeNode[] treeNode =
new TreeNode[] { this };
return treeNode.GetEnumerator();
}
}
// Window.xaml.cs constructor
this.DataContext = tree;
<Window ...>
<Grid>
<TreeView Name="_treeView"
ItemsSource="{Binding}"/>
</Grid>
</Window>
Step 2: Showing the children.
If you're at this point you should see the picture in step one with just the root displayed. Now it's time to show the children.
Using a HierarchicalDataTemplate, you can say:
- "whenever you see a TreeNode"
- "use a TextBlock to display the TreeNode.Value of the current item"
- "for the next level down, display items in the TreeNode.Children collection"
In technicolor XAML, this would be:
<HierarchicalDataTemplate
DataType="{x:Type local:TreeNode}"
ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Value}"/>
</HierarchicalDataTemplate>
You can either directly assign this to the TreeView.ItemTemplate property, or put this in a ResourceDictionary in a parent element of the TreeView.
Window.xaml
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Grid>
<TreeView Name="_treeView" ItemsSource="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:TreeNode}"
ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Value}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</Window>
Window.xaml.cs
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
TreeNode tree = new TreeNode("root",
new TreeNode("Child 1",
new TreeNode("Child 1.1"),
new TreeNode("Child 1.2")),
new TreeNode("Child 2",
new TreeNode("Child 2.1"),
new TreeNode("Child 2.2")));
this.DataContext = tree;
// alternately, you can directly assign
// _treeView.ItemsSource = tree;
}
}
No comments:
Post a Comment