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
Related
I have a Projects collection that contains two collections (either of which can be empty):
class Project {
public string Name { get; set; }
public int Priority { get; set; }
public List<Project> Projects { get; set; }
public List<Task> Tasks { get; set; }
}
I can get the nested Projects to display:
<TreeView x:Name="ProjectsTree" >
<TreeViewItem Header="Projects"
ItemsSource="{Binding ProjectsCollection, Mode=TwoWay}"
IsExpanded="True" >
<TreeViewItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Project}" ItemsSource="{Binding Projects}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
</TreeViewItem.Resources>
</TreeViewItem>
How do I add a 2nd template for Tasks? If I add:
<HierarchicalDataTemplate DataType="{x:Type local:Project}" ItemsSource="{Binding Tasks}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
I get an error about there already being an entry in the resource dictionary for datatype 'Project' (or something like that).
All help would be appreciated...
Assumption: the Project is given and cannot be changed. That means in the context of MVVM Project is the model. You can now create a view model that connects the view and model. It could look like this:
public class ProjectViewModel
{
public Project Project { get; set; }
public string Name
{
get
{
return Project.Name;
}
}
public int Priority
{
get
{
return Project.Priority;
}
}
public IList Children
{
get
{
if (Project.Projects.Count > 0)
{
return Project.Projects;
}
return Project.Tasks;
}
}
}
Then you adapt the template to the view model and you are done:
<TreeView x:Name="ProjectsTree" >
<TreeViewItem Header="Projects"
ItemsSource="{Binding ProjectsViewModelCollection, Mode=TwoWay}"
IsExpanded="True" >
<TreeViewItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Project}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"></TextBlock>
</HierarchicalDataTemplate>
</TreeViewItem.Resources>
</TreeViewItem>
I tried everything but no matter how I configure HierarchicalDataTemplate it always shows only the top level of the collection (StarSystem items and StarSystemName properties)
This is how my TreeView is configured right now:
<TreeView x:Name="StarSystemTreeView" ItemsSource="{Binding StarSystems, ElementName=window}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding}">
<TextBlock Text="{Binding Path=StarSystemName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Planets}">
<TextBlock Text="{Binding Path=PlanetNumber}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Moons}">
<TextBlock Text="{Binding Path=MoonNumber}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MoonMinerals}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
window is my MainWindow class, in which StarSystems is initialized:
public partial class MainWindow
{
private ObservableCollection<StarSystem> _starSystems = new ObservableCollection<StarSystem>
{
new StarSystem("FooSystem",
new ObservableCollection<Planet>
{
new Planet(1, new ObservableCollection<Moon>
{
new Moon(1, new ObservableCollection<string>
{
"FooMineral"
})
})
})
};
public ObservableCollection<StarSystem> StarSystems
{
get { return _starSystems; }
set { _starSystems = value; }
}
}
The 3 classes I want to bind to the TreeView are defined outside of MainWindow class, in the same namespace:
[NotifyPropertyChanged]
public class StarSystem
{
public StarSystem()
: this("", new ObservableCollection<Planet>())
{
}
public StarSystem(string starSystemName, ObservableCollection<Planet> planets)
{
StarSystemName = starSystemName;
Planets = planets;
}
public string StarSystemName { get; set; }
public ObservableCollection<Planet> Planets { get; set; }
}
[NotifyPropertyChanged]
public class Planet
{
public Planet()
: this(0, new ObservableCollection<Moon>())
{
}
public Planet(int planetNumber, ObservableCollection<Moon> moons)
{
PlanetNumber = planetNumber;
Moons = moons;
}
public int PlanetNumber { get; set; }
public ObservableCollection<Moon> Moons { get; set; }
}
[NotifyPropertyChanged]
public class Moon
{
public Moon()
: this(0, new ObservableCollection<string>())
{
}
public Moon(int moonNumber, ObservableCollection<string> moonMinerals)
{
MoonNumber = moonNumber;
MoonMinerals = moonMinerals;
}
public int MoonNumber { get; set; }
public ObservableCollection<string> MoonMinerals { get; set; }
}
This is how it look after starting the application:
I've been trying to figure it out for the past 3 days and I think I've tried every way possible to configure the HierarchicalDataTemplate so I'm probably just doing something wrong...
ItemTemplate is bit messed up. You have set ItemSource one hierarchy level down. It should be:
<TreeView x:Name="StarSystemTreeView"
ItemsSource="{Binding StarSystems, ElementName=window}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Planets}">
<TextBlock Text="{Binding Path=StarSystemName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Moons}">
<TextBlock Text="{Binding Path=PlanetNumber}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding Path=MoonMinerals}">
<TextBlock Text="{Binding Path=MoonNumber}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I'd like to visualize the following data structure using TreeViews in WPF:
class MyDataContext
{
ICollectionView Outers {get;set;}
//...
}
class Outer
{
string Name {get;set;}
IEnumberable<Inner> Actions {get;set;}
}
class Inner
{
string Description {get;set;}
Command OnClick {get;set;}
}
This is my attempt so far:
<!-- DataContext is MyDataContext at this point -->
<TreeView ItemsSource="{Binding Path=Outers}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type myns:Outer}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TreeView ItemsSource="{Binding Path=Actions}" >
<DataTemplate DataType="{x:Type myns:Inner}">
<Button Command={Binding Path=OnClick}>
<TextBlock Text="{Binding Path=Description}"/>
</Button>
</DataTemplate>
</TreeView>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
It seams like there's something wrong with this access since I get the following InvalidOperationException:
Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
If I drop the inner TreeView there's no exception (but also no buttons of course).
I used the page Mateusz mentioned (HierarchicalDataTemplate) and after reading the answer to this question: Bind Collection to StackPanel I found a solution that did what I wanted:
Here the players (level 3) are on the same row as the team (level 2):
<TreeView ItemsSource="{Binding League}">
<!-- Conference template -->
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Teams}">
<TextBlock Foreground="Red" Text="{Binding Name}" />
<!-- Team template -->
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}"/>
<ItemsControl ItemsSource="{Binding Players}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Please try to use HierarchicalDataTemplate with TreeView.
HierarchicalDataTemplate
Maybe this helps you a little bit ;)
The xaml:
<TreeView ItemsSource="{Binding Outers}">
<TreeView.ItemTemplate>
<DataTemplate>
<TreeViewItem ItemsSource="{Binding Actions}" Header="{Binding Name}">
<TreeViewItem.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Click}" Content="{Binding Name}" />
</DataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The data:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyDataContext();
}
}
class MyDataContext
{
public ObservableCollection<Outer> Outers { get; set; }
public MyDataContext()
{
Outers = new ObservableCollection<Outer>();
Outers.Add(new Outer() { Name = "Herp" });
Outers.Add(new Outer() { Name = "Derp" });
}
}
class Outer
{
public string Name { get; set; }
public ObservableCollection<Inner> Actions { get; set; }
public Outer()
{
Actions = new ObservableCollection<Inner>();
Actions.Add(new Inner { Name = "Test1" });
Actions.Add(new Inner { Name = "Test2" });
Actions.Add(new Inner { Name = "Test3" });
Actions.Add(new Inner { Name = "Test4" });
Actions.Add(new Inner { Name = "Test5" });
Actions.Add(new Inner { Name = "Test6" });
Actions.Add(new Inner { Name = "Test7" });
}
}
class Inner
{
public string Name { get; set; }
public ICommand OnClick { get; set; }
}
And if you are using Commands...
Try it with this example:
ICommand
I have a hierarchy of classes that I want to display in a WPF TreeView.
Task
Person
Items
Days
The classes include string properties and collections properties. It looks like this:
Laundry
John
Items Collection
Clothes
Washing Powder
Days Collection
Sunday
Thursday
Shopping
Millie
Items
Money
List
Bags
Car
Days
Saturday
I would like to display the strings (Person) as leaves and be able to drill into the collections (Items, Days) to reach the strings inside.
I found this explanation http://social.msdn.microsoft.com/forums/en-US/wpf/thread/e40e0a8f-7758-4b69-80f6-1c657294d019/ that works well for this layout:
Task
Person
Items
Days
But I can't work out how to adjust it to represent different classes at the same hierarchical level. All help appreciated, especially an example.
I've worked it out, I think. Let me know if there's a more sensible approach than this.
This blog post helped a lot, but unfortunately the code is riddled with typos:
David Sackstein's - HierarchicalDataTemplate and TreeView
The key is that every class that is bound to the TreeView is derived from a baseclass. This allows you to get the different types into the TreeView by binding it to a collection of their base class. Then in the XAML you can create a HierarchicalDataTemplate for each DataType and everything just works.
<Window x:Class="HierarchicalDataTemplateAndTreeView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:HierarchicalDataTemplateAndTreeView"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Task}" ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal" Margin="3">
<TextBlock Text="{Binding Path=Name}" FontSize="16" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Person}" ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Foreground="Purple" FontSize="14" FontWeight="Bold" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ItemCollection}" ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Foreground="Blue" FontSize="12" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Item}" ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Foreground="Blue" FontSize="11" FontStyle="Italic" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:DayCollection}" ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Foreground="RosyBrown" FontSize="12" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Day}" ItemsSource="{Binding Path=Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Foreground="RosyBrown" FontSize="12" FontStyle="Italic" />
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<StackPanel>
<TreeView Name="treeView"/>
</StackPanel>
</Window>
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
namespace HierarchicalDataTemplateAndTreeView
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
treeView.ItemsSource = GetData();
}
public List<Composite> GetData()
{
List<Composite> list = new List<Composite>()
{
new Task
{
Name = "Laundry", Children = new List<Composite>()
{
new Person { Name = "John" },
new ItemCollection
{
Name = "Items", Children = new List<Composite>()
{
new Item { Name = "Clothes" },
new Item { Name = "Washing Powder" }
}
},
new DayCollection
{
Name = "Days", Children = new List<Composite>()
{
new Day { Name = "Sunday" },
new Day { Name = "Thursday" }
}
}
}
},
new Task
{
Name = "Shopping", Children = new List<Composite>()
{
new Person { Name = "Millie" },
new ItemCollection
{
Name = "Items", Children = new List<Composite>()
{
new Item { Name = "Money" },
new Item { Name = "List" },
new Item { Name = "Bags" }
}
},
new DayCollection
{
Name = "Days", Children = new List<Composite>()
{
new Day { Name = "Saturday" }
}
}
}
}
};
return list;
}
}
}
using System.Collections.Generic;
namespace HierarchicalDataTemplateAndTreeView
{
public class Composite
{
public string Name { get; set; }
public List<Composite> Children { get; set; }
}
public class Task: Composite
{
}
public class Person : Composite
{
}
public class ItemCollection : Composite
{
}
public class Item : Composite
{
}
public class DayCollection : Composite
{
}
public class Day : Composite
{
}
}
When I set the treeviewitem's header via a DataContext, it adds a few pixels of padding that are clickable, and then puts the text with isn't clickable. I shall post an image; blue: clickable, red: unclickable.
The classes that store the data:
public class TagClass
{
public string TagClassMagic { get; set; }
public ITagClass RawClass { get; set; }
public List<TagEntry> TagEntries = new List<TagEntry>();
public IList Children
{
get
{
return new CompositeCollection()
{
new CollectionContainer() { Collection = TagEntries }
};
}
}
}
public class TagEntry
{
public string TagFileName { get; set; }
public ITagEntry RawTag { get; set; }
}
The XAML for displaying the Data:
<TreeView x:Name="tvTagList" Margin="15, 40, 15, 50" ItemsSource="{Binding}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="{x:Null}" BorderBrush="{DynamicResource ExtryzeAccentBrushSecondary}" BorderThickness="2" ScrollViewer.CanContentScroll="True" Foreground="White">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type DataBind:TagClass}" ItemsSource="{Binding Children}" >
<TreeViewItem Header="{Binding TagClassMagic}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type DataBind:TagEntry}" >
<TreeViewItem Header="{Binding TagFileName}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Try to remove this ones:
<TreeViewItem Header="{Binding TagClassMagic}" />
<TreeViewItem Header="{Binding TagFileName}" />
and instead add a data templates for TagEntry accordingly - put just simple textblocks in those data templates