WPF Treeview Bindings - c#

I'm trying to bind an ObservableCollection to a treeview in WPF. It does kind-of work but not quite the way I thought it would.
This is the binding I have setup
<TreeView Height="250" ItemsSource="{Binding Path=TheUsers}" VerticalAlignment="Bottom" Width="500" Margin="0,39,0,0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Permission}">
<TextBlock Text="{Binding}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
This is the collection it is binding to:
ObservableCollection<UserViewModel> theUsers;
public ObservableCollection<UserViewModel> TheUsers
{
get
{
return theUsers;
}
set
{
theUsers = value;
OnPropertyChanged("TheUsers");
}
}
This is the object in the collection:
public class UserViewModel
{
string userName = null;
public string UserName
{
get
{
return userName;
}
set
{
userName = value;
OnPropertyChanged("UserName");
}
}
int permCount = 0;
public int PermCount
{
get
{
return permCount;
}
set
{
permCount = value;
OnPropertyChanged("PermCount");
}
}
List<string> permission = null;
public List<string> Permission
{
get
{
return permission;
}
set
{
permission = value;
OnPropertyChanged("Permission");
}
}
This is what it displays
What I would like it to do is display the UserName for UserViewModel and the Permissions List<string> items as the children. What is the proper way to do this ?
Thanks in Advance!

Use a HierarchicalDataTemplate like
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:UserViewModel}" ItemsSource="{Binding Permission}" >
<TextBlock Text="{Binding UserName}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>

You should be able to do this with a HierarchicalDataTemplate in addition to a normal DataTemplate:
<TreeView Height="250" ItemsSource="{Binding Path=TheUsers}" VerticalAlignment="Bottom" Width="500" Margin="0,39,0,0">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:UserViewModel}" ItemsSource="{Binding Permission}">
<TextBlock Text="{Binding Path=UserName}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type sys:String}" >
<TextBlock Text="{Binding}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
Add the following namespaces to your control/window:
xmlns:local="clr-namespace:WhateverYourAssemblyNamespaceIs"
xmlns:sys="clr-namespace:System;assembly=mscorlib"

With a bit of help from the MSDN DataTemplating overview, I think the following will do what you want. Note that I've added a top-level 'All Users' item to the treeview:
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:UserViewModel}" ItemsSource="{Binding Path=Permission}">
<TextBlock Text="{Binding Path=UserName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding}"/>
</DataTemplate>
</Window.Resources>
...
<TreeView Height="250" VerticalAlignment="Bottom" Width="500" Margin="0,39,0,0">
<TreeViewItem ItemsSource="{Binding Path=TheUsers}" Header="All Users" />
</TreeView>
Replace the prefix local with a namespace prefix bound to the namespace your UserViewModel class lives in.
You can set IsExpanded="True" on the <TreeViewItem> if you want the treeview to appear with the All Users item expanded.
Note that I used an ordinary DataTemplate for the permissions, because I don't think you want the treeview to expand any further beyond a permission. (The tree could expand, because permissions are strings, and strings implement IEnumerable<char>. If you tried to expand a string, you'd get a list of the individual characters of the string.)

Related

How to bind hierarchical list<T> to WPF TreeView

I have a hierarchical type Category, that I want to put into TreeView. Nested level count is unlimited. Data is stored in DB with hierarchyid field.
Class Definition
public class Category
{
public Category()
{
NestedCategories = new List<Category>();
}
public string CategoryParentID { get; set; }
public string CategoryHID { get; set; }
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string CategoryValueType { get; set; }
public DateTime LastTimeUsed { get; set; }
public List<Category> NestedCategories
{
get; set;
}
public void AddChild(Category cat)
{
NestedCategories.Add(cat);
}
public void RemoveChild(Category cat)
{
NestedCategories.Remove(cat);
}
public List<Category> GetAllChild()
{
return NestedCategories;
}
}
Firstly I took all data from table and Put it to structured list. I checked result in debugger, and it's really contains all categories donwn by levels.
public CategorySelector()
{
InitializeComponent();
catctrl = new CategoryController();
Categories = CategoriesExpanded();
DataContext = this;
}
private readonly CategoryController catctrl;
public List<Category> Categories { get; set; }
private List<Category> CategoriesExpanded()
{
List <Category> list = catctrl.GetAllCategories();
foreach (Category cvo in GetAllCat(list))
{
foreach (Category newparent in GetAllCat(list))
{
if (newparent.CategoryHID.ToString().Equals(cvo.CategoryParentID.ToString()))
{
list.Remove(cvo);
newparent.AddChild(cvo);
break;
}
}
}
return list;
}
private List<Category> GetAllCat(List<Category> list)
{
List<Category> result = new List<Category>();
foreach (Category child in list)
{
result.AddRange(GetNestedCat(child));
}
return result;
}
private List<Category> GetNestedCat(Category cat)
{
List<Category> result = new List<Category>();
result.Add(cat);
foreach (Category child in cat.NestedCategories)
{
result.AddRange(GetNestedCat(child));
}
return result;
}
Then I add binding in XAML and there is the problem. I tried different combinations, but all I got is only first level displayed.
<mah:MetroWindow x:Class="appname.Views.CategorySelector"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:localmodels="clr-namespace:appname.Models"
mc:Ignorable="d"
.....
<TreeView Name="TV_Categories" Grid.Row="5" FontSize="16" ItemsSource="{Binding Categories}" DisplayMemberPath="CategoryName">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type localmodels:Category}" ItemsSource="{Binding NestedCategories}" >
<TextBlock Text="{Binding CategoryName}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</mah:MetroWindow>
So what did I do wrong? Thank you.
I think you are using the wrong XAML language version. There are multiple XAML langauge versions. WPF only fully supports the 2006 version. The 2009 version is only partially supported.
In WPF, you can use XAML 2009 features, but only for XAML that is not WPF markup-compiled. Markup-compiled XAML and the BAML form of XAML do not currently support the XAML 2009 language keywords and features.
These are the correct 2006 language XML namespaces:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
In your DataType definition, you use xmlns on a property element, which is a 2009 language feature.
XAML 2009 can support XAML namespace (xmlns) definitions on property elements; however, XAML 2006 only supports xmlns definitions on object elements.
You cannot use this in WPF, if your project does not meet the constraints above. Instead, you can declare your local XML namespace on an object element, e.g. your top level element (here Window) and use the x:Type markup extension or simply DataType="localmodels:Category", e.g.:
<Window x:Class="YourApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:localmodels="clr-namespace:YourApp"
<HierarchicalDataTemplate DataType="{x:Type localmodels:Category}" ItemsSource="{Binding NestedCategories}" >
<TextBlock Text="{Binding CategoryName}" />
</HierarchicalDataTemplate>
Update, I found the root cause. If you set the DisplayMemberPath this will cause the TreeView to apply a data template that just shows the ToString() text of the corresponding property value. It does not know about your nested collections of categories.
Now, if you assign your data template directly to TreeView.ItemTemplate in addition to setting DisplayMemberPath, you will get an exception stating that you cannot use both.
System.InvalidOperationException: 'Cannot set both "DisplayMemberPath" and "ItemTemplate".'
However, if you define the data template in the Resources, there is no exception, it fails silently and applies the DisplayMemberPath template and that is why only one level is displayed.
In order to solve the issue, just remove the DisplayMemberPath and all of these variants work.
<TreeView Name="TV_Categories" Grid.Row="5" FontSize="16" ItemsSource="{Binding Categories}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Category}" ItemsSource="{Binding NestedCategories}" >
<TextBlock Text="{Binding CategoryName}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<TreeView Name="TV_Categories" Grid.Row="5" FontSize="16" ItemsSource="{Binding Categories}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Category}" ItemsSource="{Binding NestedCategories}" >
<TextBlock Text="{Binding CategoryName}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<TreeView Name="TV_Categories" Grid.Row="5" FontSize="16" ItemsSource="{Binding Categories}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding NestedCategories}" >
<TextBlock Text="{Binding CategoryName}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Finally found a solution here - TreeView with multiple levels but same object type
Should've done this:
<TreeView Name="TV_Categories" Grid.Row="5" FontSize="16" ItemsSource="{Binding Categories}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding NestedCategories}">
<TextBlock Text="{Binding CategoryName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Create a treeview from a List<customClass> WPF

I would like to create an TreeView from an List<PhonesFromStudents>.
My custom class is PhonesFromStudents
public class PhonesFromStudents
{
public string Name { get; set; }
public List<string> Phones { get; set; }
}
XAML is :
<TreeView x:Name="tv_source" Grid.Row="2" Grid.Column="1" Margin="0,5" HorizontalAlignment="Stretch" ItemsSource="{Binding ListStudents}" Background="White" BorderBrush="{x:Null}">
<TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
MainWindows.cs
internal MainWindow(List<PhonesFromStudents> list)
{
InitializeComponent();
this.ListStudents = list;
this.DataContext = this;
}
Example :
2 students
Thomas - iPhone8,iPhone6
Lucas - iPhone4s, S8
i would like to have
Thomas
|_____iPhone8
|_____iPhone6
Lucas
|_____iPhone4s
|_____S8
But I get an empty list from UI.
Can someone help me?
Thanks
Use an HierarchicalDataTemplate:
<TreeView x:Name="tv_source" Grid.Row="2" Grid.Column="1" Margin="0,5"
HorizontalAlignment="Stretch" ItemsSource="{Binding ListStudents}" Background="White" BorderBrush="{x:Null}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:PhonesFromStudents}" ItemsSource="{Binding Phones}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
...and make sure that ListStudents is a public property:
public List<PhonesFromStudents> ListStudents { get; set; }

Binding Hierarchical TreeView To Dictionary

I'm trying to get my TreeView to bind to a Dictionary that will be updated. I've implemented INotifyProperyChangedHandler on the data type itself, but will this effect the treeview at all?
What I'm aiming for is:
-FolderName
--->Item1
--->Item2
-FolderName
--->Item1
This is my View:
<UserControl.Resources>
<HierarchicalDataTemplate x:Key="ChildTemplate" >
<TextBlock FontStyle="Italic" Text="{Binding Path=m_Items}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=Value}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Foreground="White" Text="{Binding m_Name}" FontWeight="Bold" />
</HierarchicalDataTemplate>
</UserControl.Resources>
<TreeView x:Name="NewTree" ItemsSource="{Binding m_FolderList, UpdateSourceTrigger=PropertyChanged}" ItemTemplate="{StaticResource NameTemplate}">
</TreeView>
My ViewModel:
public Dictionary<UInt16, Folder> m_FolderList
{
get { return Manager.Instance.GetFolderDirectory(); }
}
My Folder class:
public class Folder
{
public m_Name { get; set; }
public ObservableCollection<String> m_Items { get; set; }
}
What I'm getting is a blank treeview that never updates. Whenever I add a new item to the "FolderDirectory" in that singleton manager instance, I do an OnPropertyChanged call. Which works for anything else I've been binding to that dictionary or Folder item.
I was trying to follow this, but how the binding observes that it automatically childrens the "FamilyMembers" list escapes me because nowhere do you explicitly tell the XAML template that it should bind itemsource to that collection. http://www.wpf-tutorial.com/treeview-control/treeview-data-binding-multiple-templates/
EDIT:
The control is definitely getting all the values from my singleton. I just was playing around with the naming of the Binding Path and when I accidentally set the TreeView to bind to m_FolderList.Value (dictionaries have Values, not just Value), it gave me as many binding errors in the console as there were Items in the dictionary.
EDIT2:
public List<Folder> m_FolderList
{
get
{
List<Folder> list = new List<Folder>();
list.AddRange(Manager.Instace.GetFolderDirectory().Values);
return list;
}
}
If I do this, instead of Dictionary, the first Level of information appears... Which is a nuisance
Try this
<Window.Resources>
<DataTemplate x:Key="secondLevel">
<TextBlock Text="{Binding}"/>
</DataTemplate>
<HierarchicalDataTemplate x:Key="topLevel" ItemsSource="{Binding Value.m_Items}" ItemTemplate="{StaticResource secondLevel}">
<TextBlock Text="{Binding Value.m_Name}">
</TextBlock>
</HierarchicalDataTemplate>
</Window.Resources>
<StackPanel>
<TreeView x:Name="FolderTree" ItemsSource="{Binding m_FolderList}" ItemTemplate="{StaticResource topLevel}">
</TreeView>
</StackPanel>
output
public List<Folder> m_FolderList
{
get
{
List<Folder> list = new List<Process>();
list.AddRange(Manager.Instance.GetFolderDirectory().Values);
return list;
}
}
I hate doing this, but its the only method that worked here with the Heirarchical template. I've used a binding to dictionary Path=Value before numerous times but it just refused to work in this instance with many attempts. Its not the best answer, but its the only working one I have at the moment.

How to correctly databind collection to TabControl?

I've looked at several qustions/answers on SO and for some reason I'm not getting anything to work for binding a collection to the TabControl. I am trying to do this so I don't have to assign the DataContext in the code-behind.
Here is the view model:
public class DocumentsCollectionViewModel : IEnumerable<DocumentViewModel> {
private readonly ObservableCollection<DocumentViewModel> mDocsCollection = new ObservableCollection<DocumentViewModel>();
public ObservableCollection<DocumentViewModel> Documents {
get { return mDocsCollection; }
}
// initially excluded from question as I thought it was understood :)
public IEnumerator<DocumentViewModel> GetEnumerator() {
return mDocsCollection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return mDocsCollection.GetEnumerator();
}
}
...for completeness sake, the DocumentViewModel:
public class DocumentViewModel {
private readonly Document mDocument;
public string Name {
get { return mDocument.Name; }
}
}
In the XAML, I am a little confused about where to tell the tab control to use the Documents property in DocumentsCollectionViewModel:
<TabControl Name="DocumentsTab"
ItemsSource="{Binding localmodels:DocumentsCollectionViewModel}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Have you set the DataContext of your Window/UserControl which has this TabControl to the instance of your DocumentsCollectionViewModel?
Try doing this in the constructor of your Window containing the TabControl
public void MainWindow()
{
InitializeComponents();
this.DataContext = new DocumentsCollectionViewModel();
//Initialize the collection inside your VM
}
OR you can set DataContext in xaml like
<Window>
<Window.DataContext>
<localmodels:DocumentsCollectionViewModel/>
</Window.DataContext>
</Window>
then in your xaml just directly bind to Documents property
<TabControl Name="DocumentsTab"
ItemsSource="{Binding Documents}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

WPF TreeView parent node binding

I have the following classes -
public class A : INotifyPropertyChanged
{
public string Name { get; set; }
public ObservableCollection<Child> Children { get; set; }
...
}
public class Child : INotifyPropertyChanged
{
public string InstanceName { get; set; }
...
}
And here is my TreeView XAML -
<TreeView ItemsSource="{Binding ListOfClassA}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=InstanceName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
In this case, the parent class A name is not displayed. How can I show the Name property on class A as well as the InstanceName property on the Child class.
In your code
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=InstanceName}"/>
</HierarchicalDataTemplate>
Is actually the template for your A class. It should be:
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate>
<!-- this is how Child is visualised-->
<TextBlock Text="{Binding Path=InstanceName}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<!-- this is how A is visualised-->
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
For better code visibility you can write them in resource section as two separate templates:
<HierarchicalDataTemplate x:Key="Child_template">
<!-- this is how Child is visualised-->
<TextBlock Text="{Binding Path=InstanceName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="A_template" ItemsSource="{Binding Path=Children}"
ItemTemplate="{StaticResource Child_template}">
<!-- this is how A is visualised-->
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
Hope this helps.
For reference: http://msdn.microsoft.com/en-us/library/dd759035(v=vs.95).aspx

Categories