I want to create the hierarchical tree. I followed every step from here, but for some reason it shows me only one level deep.
Here's what I get:
What could be the reason?
XAML
<TreeView SelectedItemChanged="TreeView_SelectedItemChanged" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type local:MyClass}">
<TextBlock Foreground="Red" Text="{Binding Name}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" MouseLeftButtonUp="TreeViewItem_MouseLeftButtonUp"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
VIEWMODEL
public class MyClass
{
public string Name { get; set; }
public List<MyClass> Children { get; set; }
}
public class MyViewModel
{
private List<MyClass> _items;
public List<MyClass> Items
{
get
{
return _items;
}
}
public MyViewModel()
{
_items = new List<MyClass>();
_items.Add(new MyClass
{
Name = "1",
Children = new List<MyClass>
{
new MyClass
{
Name = "1_1",
Children = new List<MyClass>
{
new MyClass
{
Name = "1_1_1"
},
new MyClass
{
Name = "1_1_2"
}
}
},
new MyClass
{
Name = "1_2"
}
}
});
}
}
You are defining your template as exactly two levels deep, so you are going to get a two level tree view. This is because you explicitly state that the ItemTemplate of the HeirarchicalDataTemplate should be a DataTemplate with no children.
I am not sure what your intended template is supposed to be, but if you remove that inner template you should get all of the items to display. Alternatively, you could make the inner template also a HeirarchicalDataTemplate.
Related
i'm new to WPF and MVVM pattern. I'm trying to bind recursively a Treeview to ObservableCollections.
I have searched so many times on this site, but I found no answers to my problem.
Here it is my Model class:
public class CategoryCounter
{
public int ID { get; set; }
public string Supplier { get; set; }
public string Name { get; set; }
public Nullable<int> Parent { get; set; }
public ObservableCollection<CategoryCounter> Children => new ObservableCollection<CategoryCounter>(/*some linq code here*/);
And the ViewModel class:
public class CategoriesViewModel : BaseViewModel
{
private string supplier;
private ObservableCollection<CategoryCounter> categories;
public ObservableCollection<CategoryCounter> Categories
{
get { return categories; }
set
{
if (value != categories)
{
categories = value;
}
}
}
public void SetSupplier(string supplier)
{
this.supplier = supplier;
Categories = new ObservableCollection<CategoryCounter>(CategoryContatori.GetRootBySupplier(supplier));
NotifyPropertyChanged();
}
}
Now, when i call "SetSupplier()" the collection is filled and it is all ok, but the view does not show me anything.
This is the XAML code:
<StackPanel>
<TreeView ItemsSource="{Binding Categories}" HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="250">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type dbModel:CategoryCounter}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</StackPanel>
How can I bind the children items even if they are the same object? Is this the problem?
Thank you for your patience.
Try setting the template directly
<TreeView ItemsSource="{Binding Categories}" HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="250">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Resources generally need to be in the parent of the item that needs to access them.
EDIT: Although having just done a quick search this may not be the case for TreeViews, so it's likely that INPC is your problem as already noted in the comments.
Raise the PropertyChanged in the setter of your Categories property:
private ObservableCollection<CategoryCounter> categories;
public ObservableCollection<CategoryCounter> Categories
{
get { return categories; }
set
{
categories = value;
NotifyPropertyChanged(nameof(Categories));
}
}
Also make sure that the Categories property and the Children property return materialized collections of CategoryCounter objects.
I've been trying to get my first TreeView to work, at first without the ViewModel. But no matter what I do, it doesn't show any children, even though the binding is correct.
I'm using two Item templates, one for hierarchical and another for data. You can see the important parts for both below:
class GrupoTag
{
public string Nome { get; set; }
public GrupoTag Pai { get; set; }
public List<Tag> ListaFilhos { get; set; }
public List<GrupoTag> SubGrupos { get; set; }
public GrupoTag(string nome)
{
Nome = nome;
ListaFilhos = new List<Tag>();
SubGrupos = new List<GrupoTag>();
}
public List<object> Filhos
{
get
{
List<object> lista = new List<object>();
foreach (GrupoTag subGrupo in SubGrupos)
lista.Add(subGrupo);
foreach (Tag filho in ListaFilhos)
lista.Add(filho);
}
}
}
class Tag
{
public GrupoTag Pai { get; set; }
public string Nome { get; set; }
public Tag(string nome)
{
Nome = nome;
}
public Tag(GrupoTag pai, string nome)
{
Pai = pai;
Nome = nome;
}
}
The XAML binding to all of this:
<TreeView Name="MenuTags">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="xml2Excel2:GrupoTag" ItemsSource="{Binding Filhos}">
<TextBlock Text="{Binding Nome}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="xml2Excel2:Tag">
<TextBlock Text="{Binding Nome}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
But the property "Filhos" on the GrupoTag class is never accessed. I've tried putting a breakpoint in there, throwing an exception, but it's simply never called. And the TreeView only displays the names of the collection of GrupoTags I assigned to it as its ItemSource in code-behind.
MenuTags.ItemsSource = arvoreTeste.SubGrupos;
I've read all the related questions and corrected the code for the respective errors, but I'm still lost here.
EDIT 1: So I modified the code of the classes to conform to the simple interface below:
class ITag
{
public string Nome { get; set; }
public ObservableCollection<ITag> Filhos { get; set; }
}
As per Benin comment, GrupoTag now uses a single property stored in a ObservableCollection to represent its children. And, as per Adnan answer removed the DataType from the XAML TreeView. Now it looks like this:
<TreeView Name="ArvoreTags">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Nome}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
It works, the TreeViewis functional. But I don't know why.
You need to give your TreeView ItemSource and DataType. And I would agree to Berins comment , you should avoid making new list to each access to Filhos property. TreeView works very good with Polymorphis, I mean with data types.
<TreeView DockPanel.Dock="Top"
DataContext="{Binding ProjectNodes_DataContext}"
ItemsSource="{Binding Nodes}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ProjectNode}" ItemsSource="{Binding SubItems}">
<StackPanel>
<TextBlock Text="{Binding Header}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
My Property is:
private ProjectNodesVM mProjectNodes_DataContext;
public ProjectNodesVM ProjectNodes_DataContext
{
get { return mProjectNodes_DataContext; }
protected set
{
SetProperty(ref mProjectNodes_DataContext, value);
}
}
and class ProjectNodesVM has:
public ObservableCollection<ProjectNode> Nodes {
get { return mNodes; }
protected set { SetProperty(ref mNodes, value); }
}
inside ProjectNode class i have:
private string mHeader;
public string Header
{
get { return mHeader; }
set { SetProperty(ref mHeader, value); }
}
I found a simple answer to my specific case, not yours it seems
The children must be a "property".
So:
public ObservableCollection<Tag> ListaFilhos ; //doesn't work
public ObservableCollection<Tag> ListaFilhos {get; set;} //works
There seems to be a ton of information on HierarchicalDataTemplate, but I have had a hard time finding info specific enough to help me out with hierarchies containing different types.
Assuming the following class structure:
public class classA
{
public string name{get;set;}
}
public class classB
{
public string name{get;set;}
public List<classA> subItems{get;set;}
}
public class classC
{
public string name{get;set;}
public List<classB> subItems{get;set;}
}
Now taking into assumption the reason the classes aren't self referencing thus keeping one type throughout my hierarchy is that there are fundamental differences in properties contained theirin, is there a way to create a type sensitive HierarchicalDataTemplate?
HierarchicalDataTemplate has a DataType property, so you use that to specify the type just as you do for DataTemplate. Let's say you wrap your hierarchy in a view model:
public class MyViewModel
{
public List<classC> Items { get; set; }
}
And then create your hierarchy like so:
this.DataContext = new MyViewModel
{
Items = new List<classC>
{
new classC
{
name = "Class C",
subItems = new List<classB> {
new classB{
name = "Class B1",
subItems = new List<classA>{
new classA {name="Class A1a"},
new classA {name="Class A1b"},
new classA {name="Class A1c"},
}
},
new classB{
name = "Class B2",
subItems = new List<classA>{
new classA {name="Class A2a"},
new classA {name="Class A2b"},
new classA {name="Class A2c"},
}
}
}
}
}
};
Then in XAML all you need to do is add the relevant DataTemplates and HierarchicalDataTemplates to your TreeView's resources block:
<TreeView ItemsSource="{Binding Items}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type local:classA}" >
<TextBlock Text="{Binding name}" Foreground="Blue" />
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:classB}" ItemsSource="{Binding subItems}" >
<TextBlock Text="{Binding name}" Foreground="Green" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:classC}" ItemsSource="{Binding subItems}" >
<TextBlock Text="{Binding name}" Foreground="Red" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Result:
I have the following task:
create Tree which user can modify through app UI - add new Items, delete existing one. TreeView control should be binded to appropriate List in code behind.
Items in tree are CriteriaItem objects.
public class Subcriteria
{
public Subcriteria(string header)
{
Title = header;
subcriterias = new ObservableCollection<Subcriteria>();
}
public string Title { get; set; }
public ObservableCollection<Subcriteria> subcriterias { get; set; }
}
public class Criteria
{
public Criteria(string header)
{
Title = header;
criterias = new ObservableCollection<Subcriteria>();
}
public string Title { get; set; }
public ObservableCollection<Subcriteria> criterias { get; set; }
}
public MainWindow()
{
InitializeComponent();
public ObservableCollection<Alternative> _alt = new ObservableCollection<Alternative>();
Criteria root = new Criteria("root");
criteriaBundle.Add(root);
trvMenu.DataContext = _alt;
}
XAML:
<TreeView Name="trvMenu" Grid.Row="2" ItemsSource="{Binding criteriaBundle}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding criterias}">
<TextBlock Text="{Binding Title}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding subcriterias}">
<TextBlock Text="{Binding Title}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Title}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
But it doesn't work. Could you please assist me with binding?
You should change your code-behind like this:
1) You should set DataContext, if you use binding
2) You can use only Properties in binding, not fields
My personal advice that You should read about binding basic and MVVM
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
criteriaBundle = new ObservableCollection<CriteriaItem> {new CriteriaItem("root")};
}
public ObservableCollection<CriteriaItem> criteriaBundle { get; set; }
}
EDIT:
I'm trying to bind a collection to a treeview. My attempt so far have failed.
I miss something despite the articles I read about the matter.
So far I tried the something like, but the Treeview just plot the Id of class A and thats it, with no button to expand.
<Grid>
<TreeView HorizontalAlignment="Left" Height="270" VerticalAlignment="Top" Width="292" ItemsSource="{Binding ManagerObjects}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ManagerObject}" ItemsSource="{Binding MyManager}">
<TextBlock Text="{Binding Id}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Manager}" ItemsSource="{Binding ManagerClientServerProperty}">
<TextBlock Text="{Binding ManagerClientServerProperty}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local1:ManagerClientServer}">
<TextBlock Text="TEST"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local1:NetworkObject}" ItemsSource="{Binding Entities}">
<TextBlock Text="TEST"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local1:RemoteEntity}" ItemsSource="{Binding Fields}">
<TextBlock Text="TEST"/>
<!-- how classD should look like -->
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
EDIT: ADDING REAL CODE
This is in my Model :
public class ManagerObject
{
// PROPERTIES
public int Id { get; private set; }
public Manager MyManager { get; private set; }
}
public class Manager
{
// FIELDS
private readonly ManagerClientServer managerClientServer;
// PROPERTIES
public ManagerClientServer ManagerClientServerProperty { get { return managerClientServer;} }
/**** OTHER STUFF NON IMPORTANT ****/
}
public class ManagerClientServer
{
// FIELDS
private readonly ObservableCollection<NetworkObject> Clients = new ObservableCollection<NetworkObject>();
private readonly ObservableCollection<NetworkObject> Servers = new ObservableCollection<NetworkObject>();
// PROPERTIES
public ObservableCollection<NetworkObject> ClientsProperty
{
get { return Clients; }
}
public ObservableCollection<NetworkObject> ServersProperty
{
get { return Servers; }
}
/*** OTHER STUFF NON IMPORTANT HERE ***/
}
public class NetworkObject
{
// FIELDS
private readonly ObservableCollection<RemoteEntity> _entities=new ObservableCollection<RemoteEntity>();
public uint NetworkId { get; private set; }
// PROPERTIES
public ObservableCollection<RemoteEntity> Entities
{
get { return _entities; }
}
// CONSTRUCTOR
public NetworkObject(uint id)
{
NetworkId = id;
}
}
public class RemoteEntity
{
// FIELDS
private readonly ObservableCollection<int> _fields=new ObservableCollection<int>();
// PROPERTIES
public bool IsLost { get; set; }
public bool NeedUpdate { get; set; }
public uint SessionId { get; private set; }
public ObservableCollection<int> Fields
{
get { return _fields; }
}
// CONSTRUCTOR
public RemoteEntity(uint id)
{
SessionId = id;
}
}
The ViewModel just expose this property:
public ObservableCollection<ManagerObject> ManagerObjects
{
get { return managerObjects; }
set
{
managerObjects = value;
NotifyOfPropertyChange(()=>ManagerObjects);
}
}
private ObservableCollection<ManagerObject> managerObjects;
The initialization is just 2 ManagerObject, after this they all include a random number of NetworkObjects in both Clients and Servers collection and each of those has a random number of Entities.
All collections here are Observable, however, they are of another type in real but they expose a method which can make them Observable so lets consider it this way.
Many Thanks.
Ah, I see your problem now and it's a really simple one. You can't expand anything because there is nothing to expand. Your TreeView.ItemsSource is bound to the ManagerObjects collection and that's ok, because it is a collection. However, in your HierarchicalDataTemplate for your ManagerObject data type, you have this:
<HierarchicalDataTemplate DataType="{x:Type local:ManagerObject}"
ItemsSource="{Binding MyManager}"> <!-- Look here -->
<TextBlock Text="{Binding Id}"/>
</HierarchicalDataTemplate>
You are trying to data bind the MyManager property to the HierarchicalDataTemplate.ItemsSource property, but you can't because it is not a collection. Instead, try this:
<DataTemplate DataType="{x:Type local:ManagerObject}">
<StackPanel>
<TextBlock Text="{Binding Id}" />
<ContentControl Content="{Binding MyManager}" />
</StackPanel>
</DataTemplate>
You'll have other problems like this too, so you'll have to adjust a number of your templates. For example, this won't work because the ManagerClientServerProperty property is not a collection:
<HierarchicalDataTemplate DataType="{x:Type local:Manager}"
ItemsSource="{Binding ManagerClientServerProperty}">
...
</HierarchicalDataTemplate>
You could do this:
<HierarchicalDataTemplate DataType="{x:Type local:Manager}"
ItemsSource="{Binding ManagerClientServerProperty.Clients}">
...
</HierarchicalDataTemplate>
... but then that would only be one of the collections. When writing WPF, I've learned that it's always best to make your data the right shape to fit your UI. Usually, that just means adding a few extra properties here and there to make your job displaying it easier. For example, instead of using a CompositeCollection in the UI, you could just add an extra property to your ManagerClientServer class. Maybe something like this:
public ObservableCollection<NetworkObject> NetworkObjects
{
get
{
hhh networkObjects = new ObservableCollection<NetworkObject>(Clients);
networkObjects.Add(Servers);
return networkObjects;
}
}
Then you could do this:
<HierarchicalDataTemplate DataType="{x:Type local:Manager}"
ItemsSource="{Binding ManagerClientServerProperty.NetworkObjects}">
...
</HierarchicalDataTemplate>
Anyway, I guess you get the picture now, so I trust that you can finish the rest on your own. Oh, one last thing... don't be surprised if it won't work, because your data is not in the correct 'shape' that a TreeView expects. It might work, but if not, forget the HierarchicalDataTemplates and just define ListBoxes in DataTemplates to bind to the inner collections.