WPF TreeView does not display items from observable collection - c#

I have a TreeView that I'm binding to an observable collection, which has one observable collections inside of it.
I have the following classes:
the base class:
public class BaseObject : BindableBase, IBaseObject
{
public string Name { get; set; }
}
and 2 other class for directory and files which inherit from this BaseObject. Right now the difference between File and Directory class is the collection:
public ObservableCollection<BaseObject> DirectoryItems
{
get => _directoryItems;
set
{
_directoryItems = value;
RaisePropertyChanged();
}
}
In my view model I add files and directories to fill the observable collection, but the problem is that the TreeView is empty.
The following is the xaml:
<UserControl.Resources>
<DataTemplate
x:Key="ProjectExplorerFileTemplate">
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
<HierarchicalDataTemplate
x:Key="ProjectExplorerFolderTemplate"
ItemTemplate="{StaticResource ProjectExplorerFileTemplate}"
ItemsSource="{Binding Path=DirectoryItems}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</UserControl.Resources>
<Grid Margin="10">
<TreeView
Name="ProjectExplorerTreeView"
ItemTemplate="{StaticResource ProjectExplorerFolderTemplate}"
ItemsSource="{Binding Path=ProjectExplorerNodes}" />
</Grid>
I am not sure where I am doing something wrong.
In the observable collection I have all data that I need.
The problem it is in the xaml?
EDIT
ProjectExplorerNodes is the observable collections where I add the files and directories from root path, in view model.
public ObservableCollection<BaseObject> ProjectExplorerNodes
{
get => _projectExplorerNodes;
set
{
_projectExplorerNodes = value;
RaisePropertyChanged();
}
}

Related

C# ObservableCollection dosen't appear in TreeView by MVVM

So I have a collection with Name, Code, Id and List with node type ServiceTypeDto like this model:
public class ServiceTypeDto
{
public long Id
public string Code
public string Name
public List<ServiceTypeDto> ChildrenList
}
I have a method which returns a list of ServiceTypeDtos, like this:
I have a ChildernList that exposes the ServiceTypeDtos.
This is how I try to do this in the ViewModel:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ServiceTypeService.Dto;
using ServiceTypeService.Interface;
using ShowServiceType.Interfaces;
using ShowServiceType.Utils;
namespace ShowServiceType.ViewModel
{
class MainWindowViewModel : ViewModelBase
{
public string _name, _code;
public long _id;
public List<ServiceTypeDto> _childrenList = new List<ServiceTypeDto>();
/// <summary>
/// Create Services for work
/// </summary>
ILogService Log => Service.CreateLog();
IExceptionHandler ExceptionHandler => Service.CreateExeptionHandler();
IServiceType ServiceType => Service.CreateGetServiceType();
public ObservableCollection<ServiceTypeDto> _servicesCollection;
public MainWindowViewModel()
{
ServiceConfig.Initialization();
var _services = ServiceType.GetServiceTypesTree();
_servicesCollection = new ObservableCollection<ServiceTypeDto>();
//This is convert to ObservableCollection my List<> =)
foreach (var item in _services)
_servicesCollection.Add(item);
}
public long ID
{
get => _id;
set
{
_id = value;
OnPropertyChanged("ID");
}
}
public string Code
{
get => _code;
set
{
_code = value;
OnPropertyChanged("Code");
}
}
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public List<ServiceTypeDto> Children
{
get => _childrenList;
set
{
_childrenList = value;
OnPropertyChanged("Children");
}
}
}
}
My ViewModelBase type:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged( string propname ) => PropertyChanged?.Invoke(this , new PropertyChangedEventArgs(propname));
}
Code-behind of my main window:
public MainWindow()
{
InitializeComponent();
ServiceConfig.Initialization();
DataContext = new MainWindowViewModel();
}
Finally this is the XAML of the view.
<Grid Grid.Row="1">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Visible">
<TreeView>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<StackPanel FlowDirection="LeftToRight" Orientation="Horizontal">
<TextBlock Text="{Binding ID}" />
<TextBlock Text="{Binding Code}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</ScrollViewer>
</Grid>
The ObservableCollection dosen't appear in the TreeView. What is wrong?
There are multiple issues in your view and view model:
The TreeView is not bound to anything, bind its ItemSource to Children, otherwise it will not show any items
The ItemsSource of the HierarchicalDataTemplate must be bound to the child collection within your ServiceTypeDto, which is ChildrenList, not Children
You do not populate the Children collection (or its backing collection _childrenList ), so it is empty.
You add items to _servicesCollection, but it is not used either
The ServiceTypeDto does not implement INotifyPropertyChanged, so changes of properties will not be reflected in the user interface
ChildrenList in the ServiceTypeDto is not an ObservableCollection, so adding or removing items will also not be reflected in the user interface
You should consider using a naming convention like Children for your property and _children for the backing field to improve readability of your code, look here for reference on naming in C#.
1) Ok I bound on View my TreeView.ItemSource to ChildrenList
2) I don't understand how I bound ChildList in collection ServiceTypeDto to my ObserveCollection, How I understand wich index I select on UI ?
public List<ServiceTypeDto> ChildrenList
{
get => _servicesCollection[0].ChildrenList; // ? index ?
set
{
_servicesCollection[0].ChildrenList = value; // ? 0 ?
OnPropertyChanged("ChildrenList");
}
}
public string Name
{
get => _servicesCollection[0].Name; // ? 0 ?
set
{
_servicesCollection[0].Name = value; // ?
OnPropertyChanged("Name");
}
}
And other properties must know index to request from _servicesCollection[index].Name correct data?
3) I cant change ChildrenList in ServiceTypeDto to ObserveCollection out off dll, by condition of task.
This is just Reference.
After this, I dont know how but its work with 0 index !
But work only first Nodes don't appear other list in subList
<Grid Grid.Row="1">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Visible">
<TreeView ItemsSource="{Binding Path=ChildrenList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ChildrenList}">
<StackPanel FlowDirection="LeftToRight" Orientation="Horizontal">
<TextBlock Text="{Binding ID}" />
<TextBlock Text="{Binding Code}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</ScrollViewer>
</Grid>

C# wpf listbox not updating from ObservableCollection

I'm trying to get the databinding I need to work with a ListBox.
I've parsed some data from a text file to a ObservableCollection<ViewModel> but the data isn't updating in the ListBox.
Here's some information:
The data which is written to from the parser:
class MainData
{
private static ObservableCollection<GroupViewModel> groupModelList = new ObservableCollection<GroupViewModel>();
public static ObservableCollection<GroupViewModel> GroupModelList
{
get { return groupModelList; }
}
}
What GroupViewModel holds (not everything but it's all the same):
class GroupViewModel : INotifyPropertyChanged
{
private GroupModel groupModel;
public event PropertyChangedEventHandler PropertyChanged;
public GroupViewModel()
{
groupModel = new GroupModel();
}
public string Name
{
get { return groupModel.name; }
set
{
if (groupModel.name != value)
{
groupModel.name = value;
InvokePropertyChanged("Name");
}
}
}
...
}
And what GroupModel Holds:
class GroupModel
{
public string name { get; set; }
}
This is how the parser adds new items to the GroupModelView:
if (split[0] == "group")
{
currentGroup = new GroupViewModel();
currentGroup.Name = split[1];
MainData.GroupModelList.Add(currentGroup);
}
I created a ListBox in my WPF application with these XAML options:
<Window x:Class="SoundManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SoundManager.ViewModels"
xmlns:vm2="clr-namespace:SoundManager.Code"
Title="MainWindow" Height="720" Width="1280">
<Window.Resources>
<vm:MainViewModel x:Key="MainViewModel" />
<vm2:MainData x:Key="MainData" />
</Window.Resources>
<ListBox Grid.Row="2" Height="484" HorizontalAlignment="Left" Margin="12,0,0,0" Name="lbFoundItems" VerticalAlignment="Top" Width="201" ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList/Name}" />
but for some reason the data isn't updating in the UI (new items aren't added visibly in the UI).
I've been just getting started with the MVVM pattern and databinding and I can't figure out what I'm doing wrong.
Thanks in advance!
GroupModelList/Name is not a valid property path here. Setting it like that does not make the ListBox show the Name property of the data items in the GroupModelList collection.
You would instead have to set the ListBox's DisplayMemberPath property:
<ListBox ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList}"
DisplayMemberPath="Name"/>
or set the ItemTemplate property:
<ListBox ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Moreover, the GroupModelList property should not be static:
class MainData
{
private ObservableCollection<GroupViewModel> groupModelList =
new ObservableCollection<GroupViewModel>();
public ObservableCollection<GroupViewModel> GroupModelList
{
get { return groupModelList; }
}
}
Then you might have MainData as a property in your view model, and bind the ListBox like this:
<ListBox ItemsSource="{Binding Source={StaticResource MainViewModel},
Path=MainData.GroupModelList}" .../>

Databinding a nested List property in WPF

I am using the following XAML code to display a list of checked list boxes.
<ListBox x:Name="lbxProjects" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ListBox x:Name="lbxUnits" ItemsSource="{Binding Units}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="{Binding unit.Name}" IsChecked="{Binding isSelected}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The data model is as follows
public class ProjectsListBox
{
public Project project { get; set; }
public List<UnitsCheckBox> Units = new List<UnitsCheckBox>();
public ProjectsListBox(Project project)
{
this.project = project;
foreach(var d in project.Documents)
{
Units.Add(new UnitsCheckBox(d));
}
}
}
public class UnitsCheckBox : INotifyPropertyChanged
{
public Document unit { get; set; }
private bool isselected = true;
public bool isSelected
{
get { return isselected; }
set
{
isselected = value;
NotifyPropertyChanged("isSelected");
}
}
public UnitsCheckBox(Document d)
{
unit = d;
}
}
I am assigning the data source for the parent listbox like
lbxProjects.DataContext = projectsList;
The code creates the child list boxes but not the checkboxes inside the child list boxes. What am i missing?
How should WPF resolve unit.Name?
If the type UnitsCheckBox contains a Name property, then the CheckBox's Content should be bound to Name:
Content="{Binding Name}"
You should always specify the type of your DataTemplate:
<DataTemplate DataType="{x:Type local:UnitsCheckBox}" ...>
Those are the probable problems but I can't be sure unless you give us the UnitsCheckBox code.

Reference Implementation for MVVM with Hierarchical Objects

I read a couple of MVVM Tutorials (1,2,3,4) but I can't find a proper answer to my problem.
My model is a hierarchical tree like this:
public class MyModel {
public string Name { get; set; }
public Guid Id { get; set; }
public List<MyModel> Children { get; set; }
}
Now I want to display this in a TreeView like this (each entry if from the type MyModel):
Root
|-SubElement
|-SubElement2
| |-SubSubElement1
|-SubElement3
Now my questions:
How would the corresponding View-Model look like? Is there a reference implementation for Collections?
Should the Model or the View-Model or both implement INotifyPropertyChanged, INotifyCollectionChanged or should the List of Children be of the Type ObservableCollection<MyModel>? If so, when to call OnPropertyChanged()?
Now for the displaying:
I would like to display the Name of the object in the TreeView, but when selecting the element, I want to be notified of that event (and get the corresponding element). The ViewModel must somehow support this. Something like holding a list (say MyModelList) to which I can bind in XAML, like:
<TreeView ...
ItemsSource="{Binding ElementName=MyModelList, Path=SelectedItem.Name}"
... >
and where I can use InputBindings or EventTriggers.
Try using Hierarchical data template for showing tree structure within TreeView
<Grid>
<Grid.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Children}"
DataType="{x:Type local:MyModel}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</Grid .Resources>
<TreeView ItemsSource="{Binding MyModelList}"/>
</Grid>
Go through this MSDN Blog to get a better understanding - TreeView and HierarchicalDataTemplate, Step-by-Step
How would the corresponding View-Model look like?
In this situation will be Model and the collection, which will be in the ViewModel.
Should the Model or the View-Model or both implement INotifyPropertyChanged
I advise you to use ObservableCollection<T>, because all changes for Collection will automatically (add, remove, etc) appear in it. Properties that will be in Model must implement INotifyPropertyChanged interface. I personally do it on the side of the Model, but there are opponents of this idea, so where to implement it - your personal desire.
Example of this idea:
Model
public class MyModel : NotificationObject // he implement INotifyPropertyChanged
{
...
}
ViewModel
public ObservableCollection<MyModel> MyObjects
{
get;
set;
}
// in Constructor of ViewModel
MyLogObjects = new ObservableCollection<MyModel>();
When selecting the element, I want to be notified of that event
In WPF and Silverlight TreeView.SelectedItem is a readonly property. In this case you can see this example:
<StackPanel x:Name="LayoutRoot">
<StackPanel.Resources>
<sdk:HierarchicalDataTemplate x:Key="ChildTemplate" ItemsSource="{Binding Path=Children}" >
<TextBlock Text="{Binding Path=Name}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="NameTemplate"
ItemsSource="{Binding Path=Children}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" />
</sdk:HierarchicalDataTemplate>
</StackPanel.Resources>
<sdk:TreeView x:Name="myTreeView"
Width="400"
Height="300"
ItemsSource="{Binding HierarchicalAreas}"
ItemTemplate="{StaticResource NameTemplate}"
local:Attached.TreeViewSelectedItem="{Binding SelectedArea, Mode=TwoWay}" />
</StackPanel>
Prefix sdk: needed for Silverlight
For notified event you can create an PropertyChangedEventHandler event handler for ViewModel in constructor, to know that the SelectedItem changed:
public MyViewModel()
{
MyModel = new MyModel();
MyModel.PropertyChanged += new PropertyChangedEventHandler(MyModel_PropertyChanged);
}
private void MyModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals("SelectedItem"))
{
System.Diagnostics.Debug.WriteLine("SelectedItem changed");
}
}

Binding to collections in WPF

So I finally decided to move from WinForms to WPF, and I'm having quite an interesting journey. I have a simple application in which I bind an ObservableCollection to a ListBox.
I have an Animal entity:
namespace MyTestApp
{
public class Animal
{
public string animalName;
public string species;
public Animal()
{
}
public string AnimalName { get { return animalName; } set { animalName = value; } }
public string Species { get { return species; } set { species = value; } }
}
}
And an AnimalList entity:
namespace MyTestApp
{
public class AnimalList : ObservableCollection<Animal>
{
public AnimalList() : base()
{
}
}
}
And finally here's my main window:
<Window x:Class="MyTestApp.Window3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyTestApp"
Title="Window3" Height="478" Width="563">
<Window.Resources>
<local:AnimalList x:Key="animalList">
<local:Animal AnimalName="Dog" Species="Dog"/>
<local:Animal AnimalName="Wolf" Species="Dog"/>
<local:Animal AnimalName="Cat" Species="Cat"/>
</local:AnimalList>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical" Margin="10,0,0,0">
<TextBlock FontWeight="ExtraBold">List of Animals</TextBlock>
<ListBox ItemsSource="{Binding Source={StaticResource animalList}, Path=AnimalName}"></ListBox>
</StackPanel>
</Grid>
Now when I run the application, I see the listbox populated with three items: "D", "o", and "g" instead of "Dog", "Wolf", and "Cat":
I have a strong feeling that I'm doing something stupid somewhere (AnimalList constructor maybe?) but I can't figure out what it is. Any help is appreciated.
You need to set the DisplayMemberPath (as opposed to the Path property in the binding).
<Grid>
<StackPanel Orientation="Vertical" Margin="10,0,0,0">
<TextBlock FontWeight="ExtraBold">List of Animals</TextBlock>
<ListBox ItemsSource="{Binding Source={StaticResource animalList}}" DisplayMemberPath="AnimalName"></ListBox>
</StackPanel>
</Grid>
Since you are binding to a list of Animal objects, DisplayMemberPath specifies the name of the property in the Animal class that you want to show up as a list item.
If the property is itself an object, you can use dot notation to specify the full path to the property you want displayed ie..
<ListBox ItemsSource="{Binding Source={StaticResource animalList}}" DisplayMemberPath="PropertyInAnimalClass.PropertyInTheChildObject.PropertyToDisplay" />
You're binding your listbox to the animalname. Instead you should bind your listbox to your collection:
<ListBox ItemsSource="{Binding Source={StaticResource animalList}}"></ListBox>
Notice that I've removed the path=AnimalName from the binding.
Now you will see the class name, since the ListBox doesn't know how to display an Animal and therefore it calls its ToString-method.
You can solve this by giving it an ItemTemplate like so:
<ListBox ItemsSource="{Binding Source={StaticResource animalList}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding AnimalName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Inside the itemtemplate your DataContext is an instance of Animaland you can then bind to the properties on that instance. In my example I have bound the AnimalName, but you basically construct any template you want using normal XAML-controls and binding to the different properties of your bound object.

Categories