WPF: TreeView does not show any children - c#

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

Related

WPF recursive treeview with MVVM pattern

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.

Not able to make bindings in treeview

Im trying to make a custom treeview with an itemtemplate, so I can show the headertext + a type of the item in the treeview.
My inspiration comes from this answer;
https://stackoverflow.com/a/33119107/9156219
So, my problem is that I cant make the Itembindings work.
Here's my code;
XAML:
<TreeView x:Name="treeView" ItemsSource="{Binding treeList}" Grid.Column="0" IsVisibleChanged="treeView_IsVisibleChanged" SelectedItemChanged="treeView_SelectedItemChanged">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Type}" Margin="2,2,2,2" Background="LightBlue" FontSize='8'/>
<TextBlock Text="{Binding SystemName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
C#
public class CustomTreeViewItem : TreeViewItem
{
public String SystemName { get; set; }
public String Type { get; set; }
public String ParentItem { get; set; }
public String Path { get; set; }
}
public List<CustomTreeViewItem> treeList = new List<CustomTreeViewItem>();
public void SetRootNode()
{
int itmNumber = datSet.Rows.Count;
for (int i = 0; i < itmNumber; i++)
{
treeList.Add(new CustomTreeViewItem
{
SystemName = (string)datSet.Rows[i].ItemArray[1].ToString(),
Type = (string)datSet.Rows[i].ItemArray[2].ToString(),
ParentItem = (string)datSet.Rows[i].ItemArray[3].ToString(),
Path = (string)datSet.Rows[i].ItemArray[4].ToString(),
});
treeList[i].Header = treeList[i].SystemName;
}
foreach (CustomTreeViewItem item in treeList.Where(treeList => treeList.ParentItem == ""))
{
treeView.Items.Add(item);
}
foreach (CustomTreeViewItem item in treeList.Where(treeList => treeList.ParentItem != "").Where(treeList => treeList.Type != "Signal"))
{
var test = treeList.Find(treeList => treeList.SystemName == item.ParentItem);
test.Items.Add(item);
}
}
SetRootNode() is being called in the beginning of the program. datSet is being filled with a OleDBDataAdapter.
In the treeview, only the SystemName is being showed and not the type. What am I doing wrong?
Thank you in advance!
You are trying to create a TreeView whose TreeViewItems contain yet again TreeViewItems, if you remove the inheritance for your model, it works:
do this:
public class CustomTreeViewItem
instead of
public class CustomTreeViewItem : TreeViewItem
is this good enough for your needs?

WPF - Bind Teeview to List

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:

Binding list of objects to ItemsControl with custom ItemTemplate in MVVM

Current Setup
I have a custom class representing an installer file and some properties about that file, conforming to the following interface
public interface IInstallerObject
{
string FileName { get; set; }
string FileExtension { get; set; }
string Path { get; set; }
int Build { get; set; }
ProductType ProductType { get; set; }
Architecture ArchType { get; set; }
bool Configurable { get; set; }
int AverageInstallTime { get; set; }
bool IsSelected { get; set; }
}
My ViewModel has a ReadOnlyObservableCollection<IInstallerObject> property named AvailableInstallerObjects.
My View has a GroupBox containing the ItemsControl which binds to the aforementioned property.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
The binding works correctly, except it's not user friendly. 100+ items are shown.
Need Help Here
I'd like to be able to use my collection of IInstallerObjects but have the View present them with the following ItemTemplate structure.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=ProductType}" Margin="5" />
<ComboBox ItemsSource="{Binding Path=Build}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
Basically I want to be able to group by the ProductType property, showing a list of the available products, with the ComboBox representing the available Build property values for IInstallerObjects of the ProductType.
I can use LINQ in the ViewModel to extract the groupings, but I have no idea how I'd bind to what I've extracted.
My research also turned up the possibility of using a CollectionViewSource but I'm not certain on how I can apply that to my current setup.
I appreciate your help in advance. I'm willing to learn so if I've overlooked something obvious please direct me to the information and I'll gladly educate myself.
If Build should be a collection type.
so your class should be structured like this as an example.
Public Class Customer
Public Property FirstName as string
Public Property LastName as string
Public Property CustomerOrders as observableCollection(OF Orders)
End Class
This should give you the expected results. Each item in the main items presenter will show first name last name and combobox bound to that customers orders.
I know it's simple but this should do.
All you have to do is declare a CollectionViewSource in your view and bind it to the ObservableCollection. Within this object you declare one or more GroupDescriptions which will split up the source into several groups.
Bind this source to the listbox, create a Template for the group description and you are done.
An example can be found here: WPF Sample Series – ListBox Grouping, Sorting, Subtotals and Collapsible Regions. More about CollectionViewSource can be found here: WPF’s CollectionViewSource
The description of your problem lead me to believe you are looking for some kind of colapsing / expanding / grouped / tree-view sort of thing.
XAML for the tree-view
<Window x:Class="WPFLab12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WPFLab12"
Title="MainWindow" Height="350" Width="525">
<Grid>
<GroupBox Header="Products">
<TreeView ItemsSource="{Binding Path=ProductTypes}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type loc:ProductType}"
ItemsSource="{Binding AvailableInstallerObjects}">
<TextBlock Text="{Binding Description}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:InstallerObject}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</GroupBox>
</Grid>
</Window>
What does that do? Well, it establishes a hierarchy of controls in the tree based on the type of data found. The first HierarchicalDataTemplate handles how to display the data for each class, and how they are related in the hierarchy. The second HierarchicalDataTemplate handles how to display each InstallerObject.
Code behind for the Main Window:
public partial class MainWindow : Window
{
public ReadOnlyObservableCollection<ProductType> ProductTypes
{
get { return (ReadOnlyObservableCollection<ProductType>)GetValue(ProductTypesProperty); }
set { SetValue(ProductTypesProperty, value); }
}
// Using a DependencyProperty as the backing store for ProductTypes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ProductTypesProperty =
DependencyProperty.Register("ProductTypes", typeof(ReadOnlyObservableCollection<ProductType>), typeof(MainWindow), new UIPropertyMetadata(null));
public MainWindow()
{
this.InitializeComponent();
this.ProductTypes = new ReadOnlyObservableCollection<ProductType>(
new ObservableCollection<ProductType>()
{
new ProductType()
{
Description = "Type A",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "B" },
new InstallerObject() { FileName = "C" },
})
},
new ProductType()
{
Description = "Type B",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "D" },
})
}
});
this.DataContext = this;
}
}
This is totally cheating, though - normally the MainWindow.cs would not serve as the DataContext and have all this stuff. But for this example I just had it make a list of ProductTypes and populate each ProductType class with the InstallerObject instances.
Classes I used, note I made some assumptions and modified your class to suit this View Model better:
public class InstallerObject
{
public string FileName { get; set; }
public string FileExtension { get; set; }
public string Path { get; set; }
public int Build { get; set; }
public bool Configurable { get; set; }
public int AverageInstallTime { get; set; }
public bool IsSelected { get; set; }
}
public class ProductType
{
public string Description { get; set; }
public ReadOnlyObservableCollection<InstallerObject> AvailableInstallerObjects
{
get;
set;
}
public override string ToString()
{
return this.Description;
}
}
So, in MVVM, it seems to me that your current InstallerObject class is more of a Model layer sort of thing. You might consider transforming it in your ViewModel to a set of collection classes that are easier to manage in your View. The idea in the ViewModel is to model things similarly to how they are going to be viewed and interracted with. Transform your flat list of InstallerObjects to a new collection of hierarchical data for easier binding to the View.
More info on various ways to use and customize your TreeView: http://www.codeproject.com/Articles/124644/Basic-Understanding-of-Tree-View-in-WPF

WPF - Binding to collection in object

I was trying to get it working for few days.
What is wrong in this code?
This is my window XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Rapideo_Client"
x:Class="Rapideo_Client.MainWindow"
Title="NVM" SnapsToDevicePixels="True" Height="400" Width="625">
<Window.Resources>
<DataTemplate x:Key="linksTemplate" DataType="DownloadLink">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"></TextBlock>
<Label Content="{Binding Path=SizeInMB}"/>
<Label Content="{Binding Path=Url}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
x:Name="MainListBox"
ItemTemplate="{DynamicResource linksTemplate}">
</ListView>
</Window>
This is my class:
class Rapideo
{
(...)
public List<DownloadLink> Links { get; private set; }
(...)
}
This is my item:
class DownloadLink
{
public string Name { get; private set; }
public string Url { get; private set; }
public DateTime ExpiryDate { get; private set; }
public float SizeInMB { get; private set; }
public int Path { get; private set; }
public string Value { get; private set; }
public LinkState State { get; set; }
public enum LinkState
{
Ready, Downloading, Prepering, Downloaded
}
public DownloadLink(string name, string url, DateTime expiryDate, float sizeInMB, int path, string value, LinkState state)
{
Name = name;
Url = url;
ExpiryDate = expiryDate;
SizeInMB = sizeInMB;
Path = path;
Value = value;
State = state;
}
}
This is my binding:
RapideoAccount = new Rapideo();
MainListBox.ItemsSource = RapideoAccount.Links;
Later in the code I populate that list in RapideoAccount.Links.
But nothing is showing in ListView.
List View is always empty.
Where is mistake in that code?
Yes, it should be an ObservableCollection<DownloadLink> if you're planning on adding to it AFTER you have setup the ItemsSource. If the list is preloaded and you won't be changing it, List<T> would have worked.
Now I do think that
MainListBox.ItemsSource = RapideoAccount.Links;
is still technically a binding. But what you are probably thinking of is binding via the DataContext rather than directly (al la MVVM style). So that'd be:
RapideoAccount = new Rapideo();
this.DataContext = RapideoAccount;
Then in your window, you'd bind your ItemSource like this:
<Window
...
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
x:Name="MainListBox"
ItemsSource="{Binding Links}"
ItemTemplate="{DynamicResource linksTemplate}">
</ListView>
</Window>
First off, you should use an ObservableCollection<DownloadLink> rather than a List<DownloadLink> if you're planning on making changes to the list after setting up the binding.
Second of all, just to be clear:
MainListBox.ItemsSource = RapideoAccount.Links;
is not a binding. You're just setting the property. That will work for certain scenarios, but its not really a binding like we normally talk about in WPF.
I think that Links needs to be an ObservableCollection, not a List.

Categories

Resources