Binding Hierarchical TreeView To Dictionary - c#

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.

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>

Dynamically display TreeViewItem children

I have a WPF application with a TreeView. I drag and drop files and/or folders onto this TreeView. In the case that the dragged item is a folder, which I have set up to detect, I search the top level of this directory.
Where I am stuck is in displaying these inner files on the TreeView. I wish for these inner files to be displayed as children of the folder item.
This is the class to model an item in the TreeView:
public class FileList
{
public enum FileType { File, Folder };
public FileType Type { get; set; }
public string Name { get; set; }
private ObservableCollection<FileList> innerFiles;
public ObservableCollection<FileList> InnerFiles
{
get
{
return innerFiles;
}
set
{
innerFiles = value;
}
}
public FileList(string file)
{
Name = file;
Type = FolderOrFile(file);
}
}
This is the xaml code within TreeView.ItemTemplate
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Name="mnuExpand" Header="Expand" Click="mnuExpand_Click" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding InnerFiles}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
I assign an ObservableCollection of FileList objects to the inner collection in the TreeViewItem's FileList and then refresh the grid, but these children items do not appear.
I tried following: TreeView not showing my Children
However I want the potential to open/search as many levels down as possible.
Any help would be much appreciated.
You need to tell the HierarchicalDataTemplate what object the source for the child items is using the ItemsSource property. Try this instead:
<HierarchicalDataTemplate ItemsSource="{Binding InnerFiles}">
<TextBlock Text="{Binding Name}"> <!-- DataTemplate for parent -->
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Name="mnuExpand" Header="Expand" Click="mnuExpand_Click" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate> <!-- DataTemplate for children -->
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
See the HierarchicalDataTemplate Class on MSDN for more information.

Bounded element's ItemsSource is not updated in view

I have a simple ListView What is bound to an object's (TaxonDescription) list.
When I select another TaxonDescription, then the ListView's element are doesnt updated.
Maybe I need somewhere the NotifyPropertyChanged, but i tried everywhere.
There are my classes.
At the page's codebehind I check the ItemSource, and it is have the right list element, just dont updated to the View.
<ListView ItemsSource="{Binding Descriptions}"
SelectedItem="{Binding ActualSelectedDescription, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DescriptionName}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
// this not updated
<ListView ItemsSource="{Binding ActualSelectedDescription.Images}">
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding NormalUri}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Text="{Binding ActualSelectedDescription.Name}"/> //This is works well
The ActualSelectedDescription is changed by an event.
public TaxonDescription ActualSelectedDescription
{
get{return actualSelectedDescription;}
set { actualSelectedDescription = value;
RaisePropertyChanged("ActualSelectedDescription"); }
//In the setter the Images are in the list
}
And there is the list element in the Images list
There is the Description class, with the list.
class TaxonDescription
{
public List<BaseImage> Images { get; private set; }
public string Name { get; private set; }
public TaxonDescription(string taxonName, string descriptionName)
{
Name = taxonName;
Images = new List<BaseImage>();
//Adding some element
}
}
Any accurate idea can help, i will try them all ;)
ActualSelectedDescription.Images has no change notification.
Try adding notification for Images property.
If that's not possible, make Images an ObservableCollection<BaseImage>.
If that's an issue, try setting data context to a notifying property:
// this should start updating
<ListView DataContext="{Binding ActualSelectedDescription}"
ItemsSource="{Binding Images}">
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding NormalUri}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
EDIT:
Just noticed that TaxonDescription is not public - is that a typo here in SO or is the class private in your actual code?
Since if it's private, the ItemsSource="{Binding ActualSelectedDescription.Images}" will not work since there's no access to Images property.

WPF datatemplateselector not getting called

Hello I'm trying to dynamically change datatemplate but my method SelectTemplate in class TreeViewItemTemplateSelector never getting called (I've checked it by debugger) :( please help me :)
Code from xaml MainWindow:
Code in code behind:
Your problem seems to be that your TreeViewCustomItem is inheriting from TreeViewItem. (As seen in http://pastebin.com/jnP2nWMF)
Removing that inheritance (and the dependency property) causes the template selector to fire fine. What were/are you trying to achieve with the node item?
Looking at the OutputWindow, I get this message:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='TreeViewCustomItem'
You don't have to have items inherit from the TreeViewItem in order to bind them to a TreeView, TreeViewItem is something that the TreeView uses to hold the data, and then the DataTemplate is used to present the data.
Move DataTemplates from TreeView.Resources to Window.Resources
<Window.Resources><DataTemplate x:Key="DefaultTemplate">
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate><DataTemplate x:Key="Regulation">
<TextBlock Text="{Binding Path=Header}" FontWeight="Bold"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="Article">
<TextBlock Text="{Binding Path=Header}" Foreground="Green"></TextBlock>
</DataTemplate>
<local:TreeViewItemTemplateSelector x:Key="TemplateSelector" DefaultTemplate="{StaticResource DefaultTemplate}" ArticleTemplate="{StaticResource Article}" RegulationTemplate="{StaticResource Regulation}" />
and make change
<TreeView ItemTemplateSelector="{StaticResource TemplateSelector}" Height="409" HorizontalAlignment="Left" Margin="10,10,0,0" Name="treeView1" VerticalAlignment="Top" Width="277" ItemsSource="{Binding}"/>
Update code and we will see. I put similar code into VS and it works so we need to take a closer look. So i checked this and changed
public class TreeViewCustomItem
{
public string Header { get; set; }
}
and this
listmy = new ObservableCollection<TreeViewCustomItem> { new TreeViewCustomItem { Header = "xD" }, new TreeViewCustomItem { Header = "xxD" } };
//treeView1.ItemsSource = listmy;
this.DataContext = listmy;
public class selector : DataTemplateSelector
{
public DataTemplate RegulationTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
TreeViewCustomItem treeViewItem = item as TreeViewCustomItem;
if (treeViewItem.Header == "xD")
{
return RegulationTemplate;
}
else
{
return DefaultTemplate;
}
}
}
and in XAML looks like this
xmlns:local="clr-namespace:WpfApplication1.Views">
<Window.Resources>
<DataTemplate x:Key="DefaultTemplate">
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="Regulation">
<TextBlock Text="{Binding Path=Header}" FontWeight="Bold"></TextBlock>
</DataTemplate>
<local:selector x:Key="selector_" DefaultTemplate="{StaticResource DefaultTemplate}" RegulationTemplate="{StaticResource Regulation}"/>
</Window.Resources>
<Grid>
<TreeView Height="409" HorizontalAlignment="Left" Margin="10,10,0,0" Name="treeView1" VerticalAlignment="Top" Width="277"
ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource selector_}"/>
</Grid>
And it works so my presumption is that problem is inside TreeViewCustomItem.

WPF Treeview Bindings

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.)

Categories