I have a collection of Database objects, each containing collections of Schema objects and User objects. I want to bind them to a TreeView, but adding additional static levels in the hierarchy, so that the resulting TreeView looks more or less like this:
<TreeView>
<TreeViewItem Header="All the databases:">
<TreeViewItem Header="Db1">
<TreeViewItem Header="Here's all the schemas:">
<TreeViewItem Header="Schema1"/>
<TreeViewItem Header="Schema2"/>
</TreeViewItem>
<TreeViewItem Header="Here's all the users:">
<TreeViewItem Header="User1"/>
<TreeViewItem Header="User2"/>
</TreeViewItem>
</TreeViewItem>
<TreeViewItem Header="Db2">
<TreeViewItem Header="Here's all the schemas:">
<TreeViewItem Header="Schema1"/>
<TreeViewItem Header="Schema2"/>
</TreeViewItem>
<TreeViewItem Header="Here's all the users:">
<TreeViewItem Header="User1"/>
<TreeViewItem Header="User2"/>
</TreeViewItem>
</TreeViewItem>
</TreeViewItem>
</TreeView>
I was able to get pretty close to what I want by using the following templates:
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type smo:Database}">
<TreeViewItem Header="{Binding Path=Name}">
<TreeViewItem Header="Here's all the schemas:" ItemsSource="{Binding Path=Schemas}"/>
<TreeViewItem Header="Here's all the users:" ItemsSource="{Binding Path=Users}"/>
</TreeViewItem>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type smo:Schema}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type smo:User}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</Window.Resources>
Then in the code I set the binding like this:
TreeViewItem treeViewItem = new TreeViewItem();
treeViewItem.Header = "All the databases:";
treeViewItem.ItemsSource = server.Databases;
treeView.Items.Add(treeViewItem);
The resulting TreeView looks like I want it to, but it's not possible to select a particular schema or user. Apparently WPF sees the whole subtree rooted at a database node as a single item, and it only selects the whole thing. I need to be able to select a particular schema, user or database. How do I set the templates and bindings so that it works the way I need?
You need to fill the properties you're using in your binding with data from your database. Currently you're using a new
TreeViewItem
, and using it as a datasource, so what you're saying about it seeing everything as a single node makes sense, as you've placed it in a single node.You need to load your database data and attach it to the properties you've used in your WPF template as binding items.
Oh man this is an incredibly frustrating task. I've tried doing it myself many times. I had a very similar requirement where I've got something like a Customer class that has both a Locations collection and a Orders collection. I wanted Locations and Orders to be "folders" in the tree view. As you've discovered, all the TreeView examples that show you how to bind to self-referencing types are pretty much useless.
First I resorted to manually building a tree of FolderItemNode and ItemNode objects that I would generate in the ViewModel but this defeated the purpose of binding because it would not respond to underlying collection changes.
Then I came up with an approach which seems to work pretty well.
The resulting XAML looks similar to the code below and you can grab a zip file which has all the classes and XAML in a working example.
Here's a modification of Josh's solution to work with SMO (my original problem statement):
and the modified converter:
The problem is that a TreeView is not very well suited to what you want to acomplish: It expects all the subnodes to be of the same type. As your database node has a node of type Collection
<Schemas
> and of type Collection<Users
> you cannot use a HierarchicalDataTemplate. A Better approach is to use nested expanders that contain ListBoxes.The code below does what you want I think,while being as close as possible to your original intent: