I am trying to bind a collection of objects held by a Model to a treeview in WPF. My XML to do this was based on WPF Treeview Databinding Hierarchal Data with mixed types but I am not having any luck.
My current XAML looks like this for the treeview.
<TreeView Name="ConfigurationFilter">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:MyModel}" ItemsSource="{Binding Path=Filters.FilterType1}">
<StackPanel Orientation="Horizontal">
<CheckBox></CheckBox>
<Label Content="{Binding Path=Name}"></Label>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I have a model that looks like this
public class MyModel
{
public Observable<MyFilter> FilterType1 {get;set;}
public Observable<MyFilter> FilterType2 {get;set;}
}
public class MyFilter
{
public string Name {get;set;}
public bool IsSelected {get;set;}
}
Within my MainWindow.Xaml.cs I have the following:
public partial class MainWindow : Window
{
public MyModel Filters { get; set; }
}
The FilterType1 property has 331 items in it. Yet when I run the app, the binding never happens. I do not see any items in my Treeview. What am I doing wrong?
Update 1
I have added my main window as the data context for the treeview and the binding as suggested but i still do not have any items in the tree
<TreeView Name="ConfigurationFilter" ItemsSource="{Binding Filters}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:MyModel}" ItemsSource="{Binding Path=Filters.FilterType1}">
<StackPanel Orientation="Horizontal">
<CheckBox></CheckBox>
<Label Content="{Binding Path=Name}"></Label>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
and my MainWindow.cs
public MainWindow()
{
InitializeComponent();
ConfigurationFilter.DataContext = this;
}
I was able to get this working by modifying my treeview to bind to a composite view model as demonstrated on CodeProject.
<TreeView Grid.ColumnSpan="2" Grid.RowSpan="2">
<TreeViewItem Header="Routes" Name="RouteView" ItemsSource="{Binding Routes}">
<TreeViewItem.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:RouteViewModel}" ItemsSource="{Binding}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Name}" VerticalAlignment="Center"></Label>
</StackPanel>
</HierarchicalDataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</TreeView>
Since my treeview will have multiple root nodes that are pre-determined, I had to bind the datasource to each root TreeViewItem. Then I placed my objects into a composite viewmodel.
Composite View Model
public class DomainViewModel
{
public ReadOnlyCollection<RouteViewModel> Routes { get; set; }
public ReadOnlyCollection<SkyViewModel> SkyLines { get; set; }
public DomainViewModel(List<Route> routes)
{
Routes = new ReadOnlyCollection<RouteViewModel>(
(from route in routes
select new RouteViewModel(route))
.ToList<RouteViewModel>());
}
Skylines = new ReadOnlyCollection<SkyViewModel>(
(from route in routes
select new SkyViewModel(route))
.ToList<SkyViewModel>());
}
}
ViewModel - Only 1 shown to help readability.
public class RouteViewModel : INotifyPropertyChanged
{
private Route _route; // My actual database model.
public string Name
{
get { return _route.RouteName; }
}
public RouteViewModel(Route route)
{
_route = route;
}
}
MainWindow
public MainWindow()
{
InitializeComponent();
this.DomainFilterTreeView.DataContext = new this.Domains;
}
TreeView doesn't have an ItemsSource bound to. Assuming the DataContext of the TreeView is your MainWindow, then you can add the ItemsSource binding.
<TreeView Name="ConfigurationFilter" ItemsSource={Binding Filters}>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:MyModel}" ItemsSource="{Binding Path=Filters.FilterType1}">
<StackPanel Orientation="Horizontal">
<CheckBox></CheckBox>
<Label Content="{Binding Path=Name}"></Label>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
ConfigurationFilter.DataContext = this;
}
public MyModel Filters { get; set; }
}
Related
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>
I am new to WPF and MVVM. What I am trying to do is to bind two different DataTemplates to two different kinds of objects in one ContentControl. Each kind of object corresponds to one DataTemplate.
The two kinds of objects are called Unit and Component respectively. They contain different properties. For example a Unit has 3 properties: Id, Name and Manufacture. A Component has 3 properties Id, Type and Materials. The example code is as below:
public class Unit : INotifyPropertyChanged
{
private int _id;
private string _name;
private string _manufacture;
public int Id
{
get {return this._id}
set
{
this._id = value;
OnPropertyChanged("Id")
}
{
public string Name
{
get {return this._name}
set
{
this._id = value;
OnPropertyChanged("Name")
}
{
public string Manufacture
{
get {return this._manufacture}
set
{
this._id = value;
OnPropertyChanged("Manufacture")
}
{
public event PropertyChangedEventHandler PropertyChanged;
...
}
The Component class has the similar structure.
In the MainWindow, I have a ListBox listing names of objects (I will change it to a TreeView in the future) on the left, and a ContentControl on the right. I want that when I select the name of an object, the details of the object will be shown on the right. The code of the MainWindow is as below:
<Windows.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=UnitItems}"
x:Key="UnitDataView">
</CollectionViewSource>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=ComponentItems}"
x:Key="ComponentDataView">
</CollectionViewSource>
<CompositeCollection x:Key="AllDataView
<CollectionContainer Collection="{Binding Source={StaticResource UnitDataView}}" />
<CollectionContainer Collection="{Binding Source={StaticResource ComponentDataView}}" />
</CompositeCollection>
<local: PartDataTemplateSelector x:Key="MyDataTemplateSelector"
UnitTemplate="{StaticResource unitTemplate}"
ComponentTemplate="{StaticResource componentTemplate}" />
</Windows.Resources>
<Grid>
<Grid.ColumnDefinition>
<ColumnDefinition>
<ColumnDefinition>
</Grid.ColumnDefinition>
<ListBox x:Name="ComponentListView" Grid.Column="0"
ItemsSource="{Binding Source={StaticResource AllDataView}}" />
<TabControl Grid.Column="1"
<TabItem Header="Basic Info">
<ContentControl x:Name="BasicInfoContent"
ContentTemplateSelector="{StaticResource MyDataTemplateSelector}"
Content="{Binding Source={StaticResource AllDataView}}">
</ContentControl>
</TabItem>
</TabControl>
</Grid>
The UnitItems and ComponentItems are two ObservableCollection<T> objects defined in App.xaml.cs. And I have defined some DataTemplates in App.xaml. The example code is as below:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="..."
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type src:Unit}">
<!-- This template is to show the name of a unit object in the ListBox -->
</DataTemplate>
<DataTemplate DataType="{x:Type src:Component}">
<!-- This template is to show the name of a component object in the ListBox -->
</DataTemplate>
<DataTemplate x:Key="unitTemplate" DataType="{x:Type src:Unit}">
<!-- This template is to show the details of a unit object in the ContentControl -->
</DataTemplate>
<DataTemplate x:Key="componentTemplate" DataType="{x:Type src:Component}">
<!-- This template is to show the details of a component object in the ContentControl -->
</DataTemplate>
</Application.Resources>
And my custom DataTemplateSelector is as below:
class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate UnitTemplate { get; set; }
public DataTemplate ComponentTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
swith (item)
{
case Unit _:
return UnitTemplate;
case Component _:
return ComponentTemplate;
}
return null;
}
}
I have read this article ContentTemplateSelector and tried the ContentTemplateSelector, but since I use a CompositeCollection and CollectionContainer to bind these two kinds of objects in the ContentControl, the item object in my DataTemplateSelector class receives the CompositeCollection type, not a Unit type nor a Component type, so there is no proper template being returned. Also I tried the method mentioned in this article DataType Property, which is to set a DataType property for each of the DataTemplate and set the Path to "/". Maybe I misunderstood it, but it did not work either, where I think it has the same issue with the ContentTemplateSelector one. So anybody can help me on this problem?
It is my very first time to ask a question on Stack Overflow. I know some of my description and codes are trivial to this question, but I just don't want to miss any details that may be related to my problem. I apologise for that. Also if there are any problem with my coding style and data structure, please feel free to point it out. I really appreciate it. Thank you for your reading and help!
You do not need a DataTemplateSelector. Just make sure that the detail DataTemplates can be automatically selected, by not assining a key to them.
It also seems that you don't need two collections for your objects. You might as well derive both Unit and Component from a common base class and have a single collection of base class references.
Finally there should be a view model, which besides the objects collection also has a property for the currently selected object.
Take this simplified example view model:
public class Base
{
public int Id { get; set; }
}
public class Unit : Base
{
public string UnitData { get; set; }
}
public class Component : Base
{
public string ComponentData { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Base> Objects { get; }
= new ObservableCollection<Base>();
private Base selectedObject;
public Base SelectedObject
{
get { return selectedObject; }
set
{
selectedObject = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(SelectedObject)));
}
}
}
An instance of it should be assigned to the window's DataContext:
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
vm.Objects.Add(new Unit { Id = 1, UnitData = "Unit Data" });
vm.Objects.Add(new Component { Id = 2, ComponentData = "Component Data" });
DataContext = vm;
}
Finally, the XAML would be this:
<ListBox ItemsSource="{Binding Objects}"
SelectedItem="{Binding SelectedObject}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:Unit}">
<TextBlock>
<Run Text="Unit, Id:"/>
<Run Text="{Binding Id}"/>
</TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Component}">
<TextBlock>
<Run Text="Component, Id:"/>
<Run Text="{Binding Id}"/>
</TextBlock>
</DataTemplate>
</ListBox.Resources>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding SelectedObject}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:Unit}">
<StackPanel>
<TextBlock Text="{Binding Id}"/>
<TextBlock Text="{Binding UnitData}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Component}">
<StackPanel>
<TextBlock Text="{Binding Id}"/>
<TextBlock Text="{Binding ComponentData}"/>
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
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