Bind Object to WPF TreeView
Solution 1
Ok this is where the HierarchicalDataTemplate
will save you. The trick is you will need to use two different hierarchical templates, since you have a three-level hierarchy here. I have constructed a simple UserControl
to illustrate. First, here is some code-behind creating model data similar to what you have:
public partial class ThreeLevelTreeView : UserControl
{
public ArrayList DeviceGroups { get; private set; }
public ThreeLevelTreeView()
{
DeviceInstance inst1 = new DeviceInstance() { Name = "Instance1" };
DeviceInstance inst2 = new DeviceInstance() { Name = "Instance2" };
DeviceInstance inst3 = new DeviceInstance() { Name = "Instance3" };
DeviceInstance inst4 = new DeviceInstance() { Name = "Instance4" };
DeviceType type1 = new DeviceType() { Name = "Type1", DeviceInstances = new ArrayList() { inst1, inst2 } };
DeviceType type2 = new DeviceType() { Name = "Type2", DeviceInstances = new ArrayList() { inst3 } };
DeviceType type3 = new DeviceType() { Name = "Type3", DeviceInstances = new ArrayList() { inst4 } };
DeviceType type4 = new DeviceType() { Name = "Type4" };
DeviceGroup group1 = new DeviceGroup() { Name = "Group1", DeviceTypes = new ArrayList() { type1, type2 } };
DeviceGroup group2 = new DeviceGroup() { Name = "Group2", DeviceTypes = new ArrayList() { type3, type4 } };
DeviceGroups = new ArrayList() { group1, group2 };
InitializeComponent();
}
}
public class DeviceGroup
{
public string Name { get; set; }
public ArrayList DeviceTypes { get; set; }
}
public class DeviceType
{
public string Name { get; set; }
public ArrayList DeviceInstances { get; set; }
}
public class DeviceInstance
{
public string Name { get; set; }
}
Nothing difficult here, but note that you should use ObservableCollection
instead of ArrayList
if you want to add and remove from your collections dynamically. Now let's look at the XAML for this control:
<UserControl x:Class="TestWpfApplication.ThreeLevelTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApplication"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TreeView ItemsSource="{Binding DeviceGroups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DeviceTypes}">
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DeviceInstances}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And here is the result:
alt text http://img684.imageshack.us/img684/6281/threeleveltreeview.png
Solution 2
I lately had to deal with a similar issue and after much research was able to get to a good generic solution. My problem was a bit more generic: Visualize a .NET object's properties in a tree view. So given this class ```
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Person> Children { get; set; }
}
I should see a tree like for an example instance:
- root
- FirstName: John
- LastName: Smith
- Children:
- [0]
- FirstName: Ann
- LastName: Smith
``` It's rather difficult to use reflection and go over an object's properties and such. Luckily, we already have a library that does that - Newtonsoft.Json. My trick is to serialize the object with Newtonsoft.Json, then deserialize with System.Web.Script.Serialization.JavaScriptSerializer.
JavaScriptSerializer returns ArrayLists and Dictionaries, which are pretty simple to go over and add to the tree.
Thanks to this article which gave me some of the concept ideas.
Anyway, here's the entire code:
In XAML:
<TreeView ItemsSource="{Binding TreeItemsSource}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:TreeNode}" ItemsSource="{Binding Path=Children}">
<TreeViewItem>
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal" Margin="-10,0,0,0">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text=" : "/>
<TextBox Text="{Binding Path=Value}" IsReadOnly="True"/>
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
In ViewModel:
public IEnumerable<TreeNode> TreeItemsSource
{
get
{
TreeNode tree = TreeNode.CreateTree(SelectedSession);
return new List<TreeNode>() { tree };
}
}
And the TreeNode class
public class TreeNode
{
public string Name { get; set; }
public string Value { get; set; }
public List<TreeNode> Children { get; set; } = new List<TreeNode>();
public static TreeNode CreateTree(object obj)
{
JavaScriptSerializer jss = new JavaScriptSerializer();
var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
Dictionary<string, object> dic = jss.Deserialize<Dictionary<string, object>>(serialized);
var root = new TreeNode();
root.Name = "session";
BuildTree2(dic, root);
return root;
}
private static void BuildTree2(object item, TreeNode node)
{
if (item is KeyValuePair<string, object>)
{
KeyValuePair<string, object> kv = (KeyValuePair<string, object>)item;
TreeNode keyValueNode = new TreeNode();
keyValueNode.Name = kv.Key;
keyValueNode.Value = GetValueAsString(kv.Value);
node.Children.Add(keyValueNode);
BuildTree2(kv.Value, keyValueNode);
}
else if (item is ArrayList)
{
ArrayList list = (ArrayList)item;
int index = 0;
foreach (object value in list)
{
TreeNode arrayItem = new TreeNode();
arrayItem.Name = $"[{index}]";
arrayItem.Value = "";
node.Children.Add(arrayItem);
BuildTree2(value, arrayItem);
index++;
}
}
else if (item is Dictionary<string, object>)
{
Dictionary<string, object> dictionary = (Dictionary<string, object>)item;
foreach (KeyValuePair<string, object> d in dictionary)
{
BuildTree2(d, node);
}
}
}
private static string GetValueAsString(object value)
{
if (value == null)
return "null";
var type = value.GetType();
if (type.IsArray)
{
return "[]";
}
if (value is ArrayList)
{
var arr = value as ArrayList;
return $"[{arr.Count}]";
}
if (type.IsGenericType)
{
return "{}";
}
return value.ToString();
}
}
Robert
Updated on June 04, 2022Comments
-
Robert almost 2 years
I would like to know how to bind a custom data type to a
TreeView
.The data type is basically an arraylist of objects that contain other arraylists. Access would look something like this:
foreach (DeviceGroup dg in system.deviceGroups) { foreach (DeviceType dt in dg.deviceTypes) { foreach (DeviceInstance di in dt.deviceInstances) { } } }
I would like the
TreeView
to look something like this:DeviceGroup1
--> DeviceType1 --DeviceInstance1 --DeviceInstance2 --> DeviceType2 --DeviceInstance1
DeviceGroup2
--> DeviceType1 --DeviceInstance1 --> DeviceType2
-
Robert about 14 yearsThank you so much for your response. Your example works great when the class is contained within ThreeLevelTreeView, but I'm having some difficulty understanding how to bind an external class to the datacontext of the ThreeLevelTreeView. How would I go about doing that?
-
Charlie about 14 yearsYou don't have to bind the DataContext. You could simply set it in code like this: yourTreeView.DataContext = yourClass.
-
Robert about 14 yearsI was wondering how I would bind data to the treeview without using the user control. I have to reroute all the event handlers through the user control and I find that kind of tedious. I have copied out the TreeView part of the user control and placed it in my MainWindow.xaml. I bind the data in the Window_Loaded event like this: _systemTreeView.DataContext = DeviceGroups; But nothing appears in the TreeView.
-
Charlie about 14 yearsSwitch from using ArrayList to ObservableCollection. ArrayList does not support automatic change notifications so your TreeView doesn't know any items are being added. This worked in my example because I loaded the items and set the DataContext before InitializeComponent was called in the constructor. Since you are binding in the Loaded event the TreeView is never getting the notification. But ObservableCollection supports automatic change notifications so any items you add will update the binding.
-
Robert about 14 yearsI have switched to ObservableCollection, but it still does not work. Adding/Removing objects changes dynamically which is great, but when I try to add a brand new datacontext, the whole treeview disappears. I think it may have something to do with how the binding works in the xaml for ThreeLevelTreeView, but I'm not sure.
-
Robert about 14 yearsI guess what I'm really asking is can you do this without making it a User Control?
-
Charlie about 14 yearsYes of course. If you wanted to put it in a Window you could. Just make sure the Window's DataContext is set to itself and the top level items collection is located on the Window. Either way it will be no different than nesting the UserControl inside the Window, just less encapsulated.
-
Robert almost 14 yearsHow do I change the text color of the instance names to red?
-
Charlie almost 14 yearsChange the innermost data-template's TextBlock to this: <TextBlock Text="{Binding Name}" Foreground="Red"/>