Hierarchical-DataBinding in TreeView - c#

I have a class called ClassWithPupils which looks like:
public class ClassWithPupils : ViewModelBase
{
public ClassWithPupils(IClass #class)
{
Class = #class;
Pupils = new ObservableCollection<IPupil>();
}
public IClass Class
{
get { return Get<IClass>(); }
set { Set(value); }
}
public ObservableCollection<IPupil> Pupils
{
get { return Get<ObservableCollection<IPupil>>(); }
set { Set(value); }
}
}
And I have a ViewModel which contains an ObservableCollection<ClassWithPupils>.
private ObservableCollection<ClassWithPupils> classesWithPupils;
public ObservableCollection<ClassWithPupils> ClassesWithPupils
{
get { return classesWithPupils ?? (classesWithPupils = new ObservableCollection<ClassWithPupils>()); }
}
This collection is filled correct with items from a Database.
Now I want to display all items from ClassesWithPupils hierarchical in a TreeView.
My View so far looks like:
<TreeView ItemsSource="{Binding ClassesWithPupils, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding}">
<Label Content="{Binding Class.Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So the Class-Names are displayed correct.
Unfortunately I have no clue how to bind the Pupils-Collection of each ClassWithPupils-Entry to the correct item in the TreeView as children.
I tried something like:
<TreeView ItemsSource="{Binding ClassesWithPupils, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding}">
<Label Content="{Binding Class.Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Pupils}">
<Label Content="{Binding Name}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
With no success...
So my question is: How can I display the Name of the Pupils as children of the classes in the TreeView?

After looking at your code I have spotted only one error in binding, that could cause the described problem, you should bind to Pupils inside the hierarchical template like this:
<TreeView ItemsSource="{Binding ClassesWithPupils, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Pupils}">
<Label Content="{Binding Class.Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Related

Display multiple TreeViews as an ObservableCollection of List of TreeViewItems in WPF

Since I can only ask one question per post, I will try again here.
I try to explain my problem as simple as I can. I would like to load a KML file and show its structure as a TreeView. The asynchronous load method populates the whole TreeView with root nodes and children nodes.
It works fine if I only load 1 KML file. But I would like to load multiple KML files and show the whole structure as a list of TreeViews if possible.
My View.xaml looks like that:
<ListView ItemsSource="{Binding TreeViews}">
<ListView.ItemTemplate>
<HierarchicalDataTemplate>
<TreeView Name="TreeView" MaxHeight="300" SelectedItemChanged="TreeView_SelectedItemChanged" ItemsSource="{Binding TreeViewItems}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:TreeViewItem}" ItemsSource="{Binding ChildrenItems}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False" IsChecked="{Binding IsChecked}"/>
<ContentPresenter Margin="2,0,0,0" Content="{Binding NodeName, Mode=OneTime}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</HierarchicalDataTemplate>
</ListView.ItemTemplate>
</ListView>
In my ViewModel I have two properties:
public ObservableCollection<List<TreeViewItem>> TreeViews { get; set; }
private List<TreeViewItem> treeViewItems;
public List<TreeViewItem> TreeViewItems
{
get { return treeViewItems; }
set
{
treeViewItems = value;
OnPropertyChanged();
}
}
For testing purposes I just added a few items in the load method:
List<TreeViewItem> temp = new List<TreeViewItem>();
TreeViewItem rootItem = new TreeViewItem(null, null);
TreeViewItem ChildItem1 = new TreeViewItem(null, null);
TreeViewItem ChildItem2 = new TreeViewItem(null, null);
rootItem.ChildrenItems.Add(ChildItem1);
rootItem.ChildrenItems.Add(ChildItem2);
temp.Add(rootItem);
TreeViewItems = temp;
TreeViews.Add(TreeViewItems);
TreeViews.Add(TreeViewItems);
Now it should show 2 identical TreeViews in the View. But unfortunately it only shows 2 empty list items with a CheckBox.
Does anybody have a hint for me how I can show multiple TreeViews?
Your question doesn't make much sense at present - why do you think you need TreeViews within TreeViews to handle this hierarchical data structure? What is local:TreeViewItem and how is it different to System.Windows.Controls.TreeViewItem?
You'd be much better off separating out the data structure from the visual controls. Something like this, with distinct classes for each level of items to be displayed in the TreeView.
public class KLMFile
{
public string FileName {get; set;}
...
public List<KLMItem> Children {get;} = new List<KLMItem>();
}
public class KLMItem
{
public string Description {get;set;}
...
public List<KLMSubItem> Children {get;} = new List<KLMSubItem>();
}
public class KLMSubItem
{
public string Description {get;set;}
...
// top level items - no children
}
Assign (or bind) a collection of KLMFile items as the ItemsSource property of your TreeView control.
Create a HierarchicalDataTemplate (or plain DataTemplate for the highest level items that don't have more children) for each item type within the TreeView.
<TreeView ItemsSource="{Binding KLMFileList}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:KLMFile}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding FileName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:KLMItem}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Description}" />
</HierarchicalDataTemplate>
<DataTemplate
DataType="{x:Type local:KLMSubItem}">
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
More details on WPF TreeViews (but focussing more on using them within the MVVM design pattern) on my blog post.
The ListView is bound to an ObservableCollection<List<TreeViewItem>> but you are trying to bind to a non-existing TreeViewItems property of the List<TreeViewItem> in your ItemTemplate:
<TreeView Name="TreeView" MaxHeight="300" ItemsSource="{Binding TreeViewItems}">
This won't work. You should bind directly to the List<TreeViewItem>:
<TreeView Name="TreeView" MaxHeight="300" ItemsSource="{Binding}">

WPF listbox hierarchial item template

I have a list box that is bound to hierarchial collection. The data has list menu items. There are two types of menus. First one is grouping menu, which will have child items. Second one is menu items which can be selected and this one does not have any children.
I have a property names HasChildren to distinguish between different type of menu. I want to apply different type of template for both types of menu. I want to show buttons for selectable(second) menus and tree view for menu with chidlren.
<ListBox ItemsSource="{Binding UserMenus}">
<ListBox.ItemTemplate>
<DataTemplate>
<TreeViewItem ItemsSource="{Binding Children}" Header="{Binding Name}">
<TreeViewItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel>
<Button Width="250" Content="{Binding Name}" ToolTip="{Binding Name}" Style="{StaticResource MaterialDesignRaisedButton}"
Command="{Binding DataContext.TemplateCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding Id}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I tried this approach but it is not working for top level menu items, it is displaying tree view item for both types of menus.
My hierarchial data class.
public class MenuModel : ObservableObject, IMenuModel
{
string _name, _description, _id;
public MenuModel()
{
Children = new ObservableCollection<IMenuModel>();
}
public string Name { get => _name; set => SetProperty(ref _name, value); }
public string Description { get => _description; set => SetProperty(ref _description, value); }
public string Id { get => _id; set => SetProperty(ref _id, value); }
public bool HasChildren
{
get
{
bool returnValue = false;
if (Children != null && Children.Count > 0)
{
returnValue = true;
}
return returnValue;
}
}
public ObservableCollection<IMenuModel> Children { get; set; }
}
Help me to apply template properly to achieve desired result.
You can try this:
Xaml
<ListBox ItemsSource="{Binding UserMenus}">
<ListBox.Resources>
<HierarchicalDataTemplate x:Key="DataTemplate2" ItemsSource="{Binding Children}">
<StackPanel>
<Button Width="250" Content="{Binding Name}" ToolTip="{Binding Name}" Style="{StaticResource MaterialDesignRaisedButton}"
Command="{Binding DataContext.TemplateCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding Id}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate x:Key="DataTemplate1">
<TreeViewItem ItemsSource="{Binding Children}" Header="{Binding Name}"/>
</DataTemplate>
</ListBox.Resources>
</ListBox>
DataTemplateSelector
public sealed class MenuModelSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is MenuModel menuModel)
{
var resourceKey = menuModel.HasChildren ? "DataTemplate2" : "DataTemplate1";
if (container is FrameworkElement element && element.FindResource(resourceKey) is DataTemplate dataTemplate)
{
return dataTemplate;
}
}
return base.SelectTemplate(item, container);
}
}

Binding Dictionary<string,List<Class>> to multiple TreeViewItem

So I've got some data that I want to store in a treeview and I have it working with 2 branches but I'm stuck on getting more than that. I think a dictionary would be best for this cause it'll be easiest to store the IDs in a key when I come to add in the 'sanity checks'
How I want my data to be presented:
--Internet Checks
----- 'Date, message, status'
--------- 'Details'
--Server Checks
----- 'Date, message, status'
--------- 'Details'
--Sanity Checks
----- 'Date, message, status'
--------- 'Details'
--------- 'Error1'
--------- 'Error2'
--------- 'Error3'
--------- 'etc'
--etc
And here's how it looks at the moment
--Internet Checks
----- 'Date, message, status'
--Server Checks
----- 'Date, message, status'
--Sanity Checks
----- 'Date, message, status'
--etc
So here's how I have my xaml at the moment:
<TreeView Name="treeView1" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.SubItems}">
<TextBlock Text="{Binding Value.Text}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Value.Text}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Here is my MenuItem class:
public class MenuItem
{
public MenuItem()
{
this.SubItems = new Dictionary<int, MenuItem>();
}
public Dictionary<int, MenuItem> SubItems { get; set; }
public string Text { get; set; }
}
And here is how I'm currently filling in the dictionary:
public Dictionary<int, MenuItem> Fill(string name, Dictionary<int, MenuItem> root, List<HealthCheckHourModel> list)
{
root.Add(nextNum, new MenuItem { Text = name + " Checks"});
foreach (HealthCheckHourModel hchm in list)
{
if (hchm.Message.Contains(name))
{
root[nextNum].SubItems.Add(hchm.ID, new MenuItem { Text = hchm.StartTime + " " + hchm.Message });
root[nextNum].SubItems[hchm.ID].SubItems.Add(-111, new MenuItem { Text = hchm.Details });
}
}
nextNum--;
return root;
}
There are two possibilities:
Delete ItemTemplate for HierarchicalDataTemplate.
<TreeView Name="treeView1" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.SubItems}">
<TextBlock Text="{Binding Value.Text}" />
<!--<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Value.Text}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>-->
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Leave ItemTemplate for HierarchicalDataTemplate, but replace DataTemplate with HierarchicalDataTemplate
<TreeView Name="treeView1" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.SubItems}">
<TextBlock Text="{Binding Value.Text}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.SubItems}">
<TextBlock Text="{Binding Value.Text}" />
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Binding a WPF TreeView to multiple Lists

I'd like to display the following structure in a WPF Treeview:
public class Group{
public string Groupname;
public IEnumerable<Group> Groups;
public Ienumerable<User> Member;
}
My ViewModel looks like this:
public class ViewModel{
public Group RootGroup;
}
I think the XAML Code should look like this:
<TreeView>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource={Binding ViewModel.RootGroup}>
<TextBlock Text={Binding Groupname}/>
<HierarchicalDataTemplate ItemsSource={Binding Member}>
<TextBlock Text={Binding Displayname}/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
What I want it to look like:
RootGroup-Name
-Member1
-Member2
-Member3
-Member4
-SubGroup1
-Member1
-Sub-SubGroup1
-Member1
-SubGroup2
-Sub-SubGroup2
-Sub-Sub-SubGroup1
-Member1
I've bound the DataContext to itself so this shouldn't be the reason why my TreeView wont show anything.
Finally I just found the solution by myself:
<TreeView Grid.Row="1" ItemsSource="{Binding MVM.RootGroup}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Groups}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Members}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Displayname}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
But the Users from the first level get lost.
To achieve that behavior you should change ViewModel and ItemTemplate. Here some code that I've used in my project.
<HierarchicalDataTemplate DataType="local:GItemViewModel" ItemsSource="{Binding Path=Nodes}">
<DockPanel>
<TextBlock Margin="2" VerticalAlignment="Center" Text="{Binding Path=Name}" FontSize="12" />
</DockPanel>
</HierarchicalDataTemplate>
ViewModel for this. Here Name is name for group and name for member depends on the object. Member is the node with Nodes is set to null.
public sealed class GItemViewModel : INotifyPropertyChanged
{
private string _name = string.Empty;
public string Name
{
get { return _name; }
set { _name = value; NotifyPropertyChanged( "Name" ); }
}
public ObservableCollection<GItemViewModel> Nodes { get; set; }
...
}
I hope this helps.
Update
To set root element Name you can add into the ViewModel elemet like this. And bind the root collection to the treeView like this.
GItemViewModel vm = new GItemViewModel();
GItemViewModel root = new GItemVieModel() { Name = "Root" };
vm.Nodes.Add( root );
treeView.ItemsSource = vm.Nodes;

Simple Nested TreeView Xaml structure?

I am trying to build a WPF TreeView with three layers. CountryReportTitle is a string property and ArticleCategoryTitlesList is a collection, both exposed from my ViewModel. There is no class hierarchy defined. This is the structure I'm looking for:
This is my attempted Xaml but I'm getting an exception in the Xaml at runtime:
{"Item has already been added. Key in dictionary: 'DataTemplateKey(ISESApp.ViewModels.ReportViewModel)' Key being added: 'DataTemplateKey(ISESApp.ViewModels.ReportViewModel)'"}
Xaml:
<TreeView ItemsSource="{Binding CountryReportTitle}">
<TreeView ItemsSource="{Binding CountryReportTitle}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ReportViewModel}"
ItemsSource="{Binding ArticleCategoryTitlesList}">
<TextBlock Text="{Binding CategoryTitle}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ReportViewModel}"
ItemsSource="{Binding ArticleCatagoryTypesList}">
<TextBlock Text="{Binding ArticleTitle}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:ReportViewModel}">
<TextBlock Text="{Binding ArticleTitle}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
</TreeView>
Local: is a namespace to my ViewModel:
xmlns:local="clr-namespace:MyApp.ViewModels"
What am I doing wrong, what is the best approach for this problem?
Here's my go-to example for treeviews.
Use a HierarchicalDataTemplate for elements in the tree. Note that there are three layers, and each layer is its own type. This is for convenience, but you could define one type and use one template or any mix of types for your tree. Having different types represent different things in the tree makes using templates extremely convenient.
The data classes
public class ViewModel
{
public ObservableCollection<ItemA> ItemsA { get; set; }
public ViewModel()
{
ItemsA = new ObservableCollection<ItemA>(new[]{
new ItemA{Name = "A one"},
new ItemA{Name = "A Two"},
new ItemA{Name = "A Three"},
});
}
}
public class ItemA
{
public ObservableCollection<ItemB> ItemsB { get; set; }
public string Name { get; set; }
public ItemA()
{
ItemsB = new ObservableCollection<ItemB>(new[]{
new ItemB{Name = "B one"},
new ItemB{Name = "B Two"},
new ItemB{Name = "B Three"},
});
}
}
public class ItemB
{
public ObservableCollection<ItemC> ItemsC { get; set; }
public string Name { get; set; }
public ItemB()
{
ItemsC = new ObservableCollection<ItemC>(new[]{
new ItemC{Name = "C one"},
new ItemC{Name = "C Two"},
new ItemC{Name = "C Three"},
});
}
}
public class ItemC
{
public string Name { get; set; }
}
And the UI
<TreeView ItemsSource="{Binding ItemsA}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type t:ItemA}"
ItemsSource="{Binding ItemsB}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type t:ItemB}"
ItemsSource="{Binding ItemsC}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type t:ItemC}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
gives you a simple treeview
You need to bind the TreeView Resources inside your TreeView:
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:FirstLayer}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding ChildrenName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:SecondLayer}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock Text="{Binding ChildrenName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
I think you'll need to modify your project to bind your Treeview with your ViewModel instead of a list of string
Here is a good Example

Categories

Resources