Having trouble binding to a treeview in WPF - c#

I am trying to make a treeview that represents data from a file. The file data has data packets organized into three different sections: Header Info, Operation Header, and Operation Data. The packets in Header Info and Operation Header are one level deep, while the packets in Operation Data are grouped into lists so I thought it'd be easier to organize by making it go an extra level. That way you could pop open Data, see the lists, and pop those open to see individual data packets.
This is my xaml:
<TreeView ItemsSource="{Binding TreeViewItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding TreeNodes}">
<TextBlock Text="{Binding Name}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding DataPackets.ItemName}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And here is the code we are trying to bind to the hierarchical datatemplates:
var childData = new Children();
result.OperationData.ToList().ForEach(x => childData.DataPackets.Add(x));
if (result.HeaderInfo.Any())
{
TreeViewItems.Add(new TreePair() { TreeNodes = result.HeaderInfoProperties.ToList(), Name = "Header Info" });
}
TreeViewItems.Add(new TreePair() { TreeNodes = result.OperationHeaderProperties.ToList(), Name = "Operation Header" });
TreeViewItems.Add(new TreePair() { TreeNodes = result.OperationDataProperties.ToList(), Children = new List<Children> {childData}, Name = "Operation Data" });
and the classes
public class TreePair
{
public TreePair()
{
TreeNodes = new List<PropertyInfo>();
Children = new List<Children>();
}
public List<Children> Children { get; set; }
public List<PropertyInfo> TreeNodes { get; set; }
public string Name { get; set; }
}
public class Children
{
public Children()
{
DataPackets = new List<DataPacketBase>();
}
public string Name { get; set; }
public List<DataPacketBase> DataPackets { get; set; }
}
I have the data packets for Header info and operation header showing up, as well as the list names for Operation data but none of the child packets show up. They exist in the Children.DataPackets object.
This is throwing in the output window:
BindingExpression path error: 'Children' property not found on 'object'
The second TextBlock is correct, but the Children ItemSource Is not being found, but the list is being filled with items.
Children Name property Missing From Second Level

The binding won't find Children because it is looking for it on a PropertyInfo object. Children and TreeNodes are siblings in the tree, there is no Child/Parent relationship between the two so the HierarchicalDataTemplate is broken when it tries to find the Children object that belongs to PropertyInfo object.
If your object hierarchy is correct then you'll need a CompositeCollection that can combine the TreeNodes and Children objects into a single collection.
private CompositeCollection _treeNodeChildCollections;
public CompositeCollection TreeNodeChildCollections
{
get
{
if (_treeNodeChildCollections == null)
{
_treeNodeChildCollections = new CompositeCollection();
var cc1 = new CollectionContainer();
cc1.Collection = Children;
var cc2 = new CollectionContainer();
cc2.Collection = TreeNodes;
_treeNodeChildCollections.Add(cc1);
_treeNodeChildCollections.Add(cc2);
}
return _treeNodeChildCollections;
}
}
Then bind to that in the xaml.
<TreeView ItemsSource="{Binding TreeViewItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding TreeNodeChildCollections}">
<TextBlock Text="{Binding Name}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding DataPackets}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

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>

In a TreeView in WPF, is there a way to get the index of the child node that is selected and the index of the parent node of that selected child item

I am trying to populate a treeview using a three-level object.
Input for the treeview is ObservableCollection(GroupClass) where GroupClass contains a property called devices = ObservableCollection(DeviceDetails).
When I click on a DeviceDetails object in the UI, I would like to get the index of DeviceDetails item and the index of the GroupClass object it belongs to.
I have successfully populated the treeview using the collection of the GroupClass using a hierarchical template and data template.
I have looked at how to get the details of the selected item in a treeview, but most of the solution used TreeViewItem.Selected trigger.
In my case, as I am using a hierarchical template and data template I don't have any TreeView Item defined in my XAML.
Group Class:
public class GroupClass
{
public GroupClass()
{
this.devices = new ObservableCollection<DeviceDetails>();
}
public string groupName { get; set; }
public ObservableCollection<DeviceDetails> devices { get; set; }
}
public class DeviceDetails
{
public DeviceDetails()
{
this.status = "Online";
this.ipAddress = "192.168.1.1";
this.dateAccessed = DateTime.Now;
}
public string deviceName { get; set; }
public string status { get; set; }
public DateTime dateAccessed { get; set; }
public string ipAddress { get; set; }
}
XAML code for populating TreeView:
<Grid x:Name="ListPage" HorizontalAlignment="Stretch" Margin="0,80,0,0" >
<TreeView x:Name="HostTreeView" HorizontalAlignment="Stretch" >
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:GroupClass}" ItemsSource="{Binding devices}">
<Grid HorizontalAlignment="Stretch" Name="HeaderPanel5">
<Grid Margin="0,0,0,0" >
<TextBlock Text="{Binding groupName}" />
</Grid>
</Grid>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:DeviceDetails}">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="checkBox" />
<TextBlock Text="{Binding deviceName}" />
<TextBlock Text="{Binding status}" />
<TextBlock Text="{Binding dateAccessed}" />
<TextBlock Text="{Binding ipAddress}" />
<Button Content="Rename Device" Height="30"/>
<Button Content="Remove Device" Height="30"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
I create a new object of type ObservableCollection and load the values into it from XML file. And then bind the created object to TreeView ItemsSource.
public static ObservableCollection<GroupClass> groups = new ObservableCollection<GroupClass>();
StreamReader sr = new StreamReader("IPProfileData");
XmlSerializer srlzr = new XmlSerializer(typeof(ObservableCollection<GroupClass>));
groups = (ObservableCollection<GroupClass>)srlzr.Deserialize(sr);
HostTreeView.ItemsSource = groups;
When I click on the Rename Device or Remove Device button, I would like to change the groups Object that is created earlier. Eventually my UI gets changed as TreeView is binded with groups object.

WPF TreeView add custom Header to HierarchicalDataTemplates

I created a simple example that contains a list of classes and a list of students within the class. This gives a TreeView like this:
school
|-class1
|-student1
|-student1
|-class2
...
But what I want is a look like this:
school
|-CLASSES
|-class1
|-STUDENTS
|-student1
|-student1
|-class2
...
I would like to do this without altering the objects bound to the TreeView. It would be perfect if I could add a custom (CLASSES, STUDENTS, etc...) naming somehow to each HierarchicalDataTemplate.
How can I achieve this?
Here is my basis:
C# Classses needed
public class School
{
public string name { get; set; }
public List<Classi> classes { get; set; }
}
public class Classi
{
public string name { get; set; }
public List<Student> students { get; set; }
}
public class Student
{
public string name { get; set; }
}
C# List bound to TreeView
private List<object> _items = new List<object>();
public List<object> items
{
get
{
return _items;
}
set
{
_items = value;
NotifyOfPropertyChange(() => items);
}
}
C# Filling my school
var stud1 = new Student { name = "student1" };
var stud2 = new Student { name = "student2" };
var clas1 = new Classi { name = "class1" };
clas1.students = new List<Student>();
clas1.students.Add(stud1);
var clas2 = new Classi { name = "class2" };
clas2.students = new List<Student>();
clas2.students.Add(stud2);
var school = new School();
school.name = "school";
school.classes = new List<Classi>();
school.classes.Add(clas1);
school.classes.Add(clas2);
items.Add(school);
XAML The TreeView containing the HierarchicalDataTemplates
<TreeView ItemsSource="{Binding items}">
<TreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding classes}" DataType="{x:Type src:School}">
<TextBlock Text="{Binding name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding students}" DataType="{x:Type src:Classi}">
<TextBlock Text="{Binding name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type src:Student}">
<TextBlock Text="{Binding name}" />
</DataTemplate >
</TreeView.Resources>
</TreeView>
It's an old question, but since I was searching for an answer for a very similar problem, for the reference here's one possible (not very general) solution.
<Window.Resources>
<HierarchicalDataTemplate x:Key="studentTemplate">
<TextBlock Text="{Binding Path=name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="classesTemplate">
<TreeViewItem Header="{Binding Path=name}" >
<TreeViewItem Header="STUDENTS" ItemsSource="{Binding Path=students}"
ItemTemplate="{StaticResource studentTemplate}"/>
</TreeViewItem>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="schoolTemplate">
<TreeViewItem Header="{Binding Path=name}" >
<TreeViewItem Header="CLASSES" ItemsSource="{Binding Path=classes}"
ItemTemplate="{StaticResource classesTemplate}"/>
</TreeViewItem>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView Name="thisTree" ItemTemplate="{StaticResource schoolTemplate}" />
The solution is "inspired" by this and this

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.

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.

Categories