I'm presenting a tree structure via a TreeView. As it is unknown how deep the tree will be, I'm using a recursive HierarchicalDataTemplate.
In the following code I'm Databinding to an external Object Tree
public class Tree : ObservableCollection<Node> {}
This works fine:
<TreeView ItemsSource="{Binding Source={StaticResource Tree}}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type src:Node}" ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
But when I try to bind to a Property
public ObservableCollection<Node> LocalTree{
get;
set;
}
Changing the first line of the XAML to
<TreeView ItemsSource="{Binding LocalTree}">
The TreeView stays empty.
What am I doing horribly wrong?
Related
I want to display some objects in a treeview, but so far unfortunately without success.
I've a ObservableCollection <ICustom> of objects: Settings.ListOfCustomers
The interface of the object ICustom:
int Id { get; }
int age { get; }
CustomerType CustomerType { get; }
ObservableCollection<IValue> ListOfValues { get; }
The ObservableCollection<IValue> ListOfValues has also some properties like:
String Name { get; }
My View:
<TreeView ItemsSource="{Binding ListOfCustomers, UpdateSourceTrigger=PropertyChanged}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type customerConfig:ICustomer}">
<TextBlock Text="{Binding Id}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type valueConfig:IValue}" ItemsSource="{Binding ListOfValues}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Question:
How can I display display these objects in a TreeView? My approach (see "My View") does not work.
To Question 1: ObservableCollections have the advantage, that all notifications to the view are done automatically. So you do not have to worry about NotifyPropertyChanged events for adding/removing objects from the collection.
To Question 2: Your ViewModel is your DataContext? I do not see where there is a property ObservableCollection<ICustomer>? Can you provide more detail about relationships in this classes?
EDIT:
Based on the answer of mm8, the x:Type attribute should be of a concrete type. So the code should look something like this:
I suggest that you have an ObservableCollection<ICustomer> ListOfCustomers in your ViewModel, then you can bind in the view:
<TreeView ItemsSource="{Binding ListOfCustomers}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type Customer}">
<TextBlock Text="{Binding Id}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type modSettings:Value}"
ItemsSource="{Binding ListOfValues}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I think this thread provides a more detailed solution for your problem: Implement WPF treeview with different Parent Nodes a well as different child nodes?
EDIT2:
I changed my code a bit, to match your requirements. This should display all nodes:
<TreeView ItemsSource="{Binding ListOfCustomers}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ListOfValues}" DataType="{x:Type Customer}">
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type modSettings:Value}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Path=Id}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I have converted all Lists to ObservableCollection, also the nested Lists. Is this necessary and correct?
It is not necessary unless you intend to add items to the collection dynamically at runtime. Then you need to use an ObservableCollection<T> for these to automatically show up in the TreeView. Othwerwise you might as well use a List<T>.
How can I display display these objects in a TreeView?
You get the error message because you add the templates to the Items property of the TreeView. They should be added to the Resources property, i.e. you need to add a <TreeView.Resources> element.
You should also read this:
Why can't a DataTemplate bind to an interface when that DataTemplate was explicitly returned from a DataTemplateSelector?
Templates with the DataType property set to an interface type are not applied. So you should either define a HierarchicalDataTemplate per concrete type or use a DataTemplateSelector.
In my program I have a treeView for which I want to allow child nodes that are comboBoxes and textBlocks. My treeView along with it's nodes are created in an MVVM style. I know how to create textBlock child nodes, but have never created child nodes of any other UI tool.
My treeview's xaml:
TreeView ItemsSource="{Binding UserControl_DataModel.TreeViewViewModel.ObservableCollection<TreeViewDataModel>}" DisplayMemberPath="DisplayName.Value".../>
This is where new nodes are created and added to the TreeView (TreeViewViewModel):
private TreeViewDataModel createNewNode(string nodeName)
{
var newNode = new TreeViewDataModel ()
{
DisplayName = nodeName
};
newNode.Children.Add(new TreeViewDataModel () { DisplayName = nodeName});
return newNode;
}
public void addNewLocNode(string nodeName)
{
TreeObservableCollection.Add(createNewNode(nodeName));
}
How do I create child nodes that are ComboBoxes, while still allowing the textblock children? This confuses me, because I don't see any part of the code that specifies what UI tool the children become. Please let me know if you need to see more code.
(Previous question about this same treeView if you need to reference it.)
Update:
Just for clarity, this is how my treeView nodes should look:
When I try something like this:
<HierarchicalDataTemplate DataType="{x:Type Data:Node}" ItemsSource="{Binding Teams}">
<StackPanel>
<TextBlock Text="{Binding IndividualProperty}" />
<ComboBox ItemsSource="{Binding CollectionProperty}" />
</StackPanel>
</HierarchicalDataTemplate>
I instead get this setup (it's like each item of the tree comes with a TextBlock and a ComboBox. Maybe there is another option besides StackPanel?):
Note that the comboBox is not a child of the textBlock node, but it's more like it's on the same level as the textBlock node.
A ComboBox is a collection control. Therefore, you need a collection property in your 'node' object to bind to the ComboBox.ItemsSource. While an individual property in the 'node' object might need a HierarchicalDataTemplate somewhat like this:
<HierarchicalDataTemplate DataType="{x:Type Data:Node}" ItemsSource="{Binding Teams}">
<TextBlock Text="{Binding IndividualProperty}" />
</HierarchicalDataTemplate>
You would need one more like this:
<HierarchicalDataTemplate DataType="{x:Type Data:Node}" ItemsSource="{Binding Teams}">
<ComboBox ItemsSource="{Binding CollectionProperty}" />
</HierarchicalDataTemplate>
Or perhaps like this:
<HierarchicalDataTemplate DataType="{x:Type Data:Node}" ItemsSource="{Binding Teams}">
<StackPanel>
<TextBlock Text="{Binding IndividualProperty}" />
<ComboBox ItemsSource="{Binding CollectionProperty}" />
</StackPanel>
</HierarchicalDataTemplate>
Pun intended.
I want to create a simple TreeView using the HierarchicalDataTemplate class.
Here's my problem XAML:
<Window.Resources>
<ObjectDataProvider
x:Key="myDataProvider"
ObjectType="vm:ContractViewModel" />
</Window.Resources>
<Window.DataContext>
<Binding Source="{StaticResource myDataProvider}" Path="Contract" />
</Window.DataContext>
<StackPanel
Orientation="Vertical"
VerticalAlignment="Top">
<ListBox MinWidth="400" Margin="10"
ItemsSource="{Binding Commissions}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Id}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TreeView>
<HierarchicalDataTemplate DataType="{x:Type m:Contract}"
ItemsSource="{Binding Commissions}">
<TextBlock Text="{Binding Id}" />
</HierarchicalDataTemplate>
</TreeView>
</StackPanel>
I'm using the MVVM pattern. The StaticResource "myDataProvider" returns an instance of a Contract (custom) class. Here's my model:
internal class Contract
{
public string Name { get; set; }
public ObservableCollection<Commission> Commissions { get; set; }
}
internal class Commission
{
public string Id { get; set; }
}
FYI - my model is actually more complex; my classes contain more members than shown, they have constructors, and they implement INotifyPropertyChanged.
In my test, I load two Commission objects into a Contract object. The listbox works as expected: I can see the Id of each Commission object w/in Contract. The TreeView doesn't work: it returns a "System.Windows.HierarchicalDataTemplate" string in the TreeView control where I'd expect each Commission Id to be listed.
I've referred to other posts and MSDN to no avail. I'd be appreciative of your help!
From what I can tell is that you're not using the TreeView correctly in XAML. You need to put your HierarchicalDataTemplate at the TreeView.Resources level and assign a ItemsSource
As shown here you want to set the Template of the item.
Try to do something like this instead:
<TreeView ItemsSource="{Binding Contracts}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type m:Contract}"
ItemsSource="{Binding Commissions}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type m:Commission}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Id}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I personally do it like this--using RadTreeView:
<telerik:RadTreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:BaseType}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Title}" />
</HierarchicalDataTemplate>
</telerik:RadTreeView.ItemTemplate>
I've not used the TreeView before other than in a few tutorials to get the hang of them. I thought I had, it turns out I haven't.
I'm trying to bind my TreeView to an object.
The Object is
public List<MyGrandparents> MyGrandParents {get;set;}
Within the MyGrandParent class, there is a property
public List<MyParents> MyParents{get;set;}
and lastly, within the MyParent class there is a property
public List<MyChildren> MyChildren {get;set;}
Within each class, there are other properties such as Name (no base classes though so no shared code at this stage)
I want to bind the lot to a tree view, so at 'root' level I only see grandparents. I can expand grandparents to only see parents, whom I can also expand to see children.
The issue I have is only the highest level is binding (grandparent level). My XAML is
<TreeView ItemsSource="{Binding MyGrandParents}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type local:MyGrandParent}">
<TextBlock Text="{Binding Name}" Margin="0,0,10,0" />
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MyParent}" ItemsSource="{Binding MyGrandParents}">
<TextBlock Text="Don't bind to test"></TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I am lost as to why this isn't binding to give me a nice Tree.
You are not following correct pattern to use HierarchicalDataTemplate.
Item which might contain children should be declare as HierarchicalDataTemplate with ItemsSource set to child collection you want to show beneath it.
And item not containing any child should be declare as DataTemplate instead.
It should be like this -
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyGrandParent}"
ItemsSource="{Binding MyParents}">
<TextBlock Text="{Binding Name}" Margin="0,0,10,0" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MyParent}"
ItemsSource="{Binding MyChildren}">
<TextBlock Text="Don't bind to test"></TextBlock>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:MyChildren}">
<TextBlock Text="Don't bind to test"></TextBlock>
</DataTemplate>
</TreeView.Resources>
I have the following markup in a ServiceTree control, which should present a TreeView of services in a ServiceGroup:
<Grid>
<TreeView ItemsSource="{Binding Services}" DataContext="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type businessService:BusinessService}">
<TreeViewItem Header="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
In the top level binding, Services is the collection of services that belongs to a ServiceGroup.
In the main window, I use the following data binding to create an OdcExpander from the Odyssey project:
<ItemsControl.ItemTemplate>
<DataTemplate DataType="groups:ServiceGroup">
<odc:OdcExpander Header="{Binding UIMetadata.MenuText}" HeaderBackground="{Binding UIMetadata.MenuTabBackColor}">
<XTime900Shell:ServiceTree />
</odc:OdcExpander>
</DataTemplate>
</ItemsControl.ItemTemplate>
This works. I get one expander correctly bound to each ServiceGroup, but on the groups with services, i.e. the "Employees" group, there is a space where the treeview should be, proportional in height to the number of services it should show, so it is binding to the Services collection property, and creating an item for each service, but not displaying anything for the service 'Name` property, which I know is correctly populated.
I think the error is that your are using a TreeViewItem, you should not. If you are creating a data template you can show any visual item, for instance a text block. The tree view item will be the container item, that you will show as a text box (for instace). you should do like this:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type businessService:BusinessService}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
Hope works...