Why does my wpf treeview not display the content when i've set the itemsource in the xaml? It only appears to show the data when i set this property in the CS file using this
trvFamilies.ItemsSource = families;
Is it maybe not updating because im not triggering and updating somehow in the xaml? I'm not sure what to change to get this to work correctly.
ViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public class ViewModel : ObservableObject
{
private ObservableCollection<Family> families;
public ObservableCollection<Family> Families
{
get { return families; }
set
{
families = value;
NotifyPropertyChanged("Families");
}
}
public ViewModel()
{
// FAMILIES
Family family1 = new Family() { Name = "The Doe's" };
family1.Members.Add(new FamilyMember() { Name = "John Doe", Age = 42 });
family1.Members.Add(new FamilyMember() { Name = "Jane Doe", Age = 39 });
family1.Members.Add(new FamilyMember() { Name = "Sammy Doe", Age = 13 });
Families.Add(family1);
Family family2 = new Family() { Name = "The Moe's" };
family2.Members.Add(new FamilyMember() { Name = "Mark Moe", Age = 31 });
family2.Members.Add(new FamilyMember() { Name = "Norma Moe", Age = 28 });
Families.Add(family2);
}
}
public class Family
{
public Family()
{
this.Members = new ObservableCollection<FamilyMember>();
}
public string Name { get; set; }
public ObservableCollection<FamilyMember> Members { get; set; }
}
public class FamilyMember
{
public string Name { get; set; }
public int Age { get; set; }
}
}
ObservableObject.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="300"
WindowStartupLocation="CenterScreen">
<Grid Margin="5">
<TreeView Name="trvFamilies" ItemsSource="{Binding self:families}" Grid.Row="1" Grid.ColumnSpan="2">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:Family}" ItemsSource="{Binding Members}">
<StackPanel Orientation="Horizontal">
<Label VerticalAlignment="Center" FontFamily="WingDings" Content="1"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type self:FamilyMember}">
<StackPanel Orientation="Horizontal">
<Label VerticalAlignment="Center" FontFamily="WingDings" Content="2"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
Currently your are binding to a public field:
public List<Family> families
But a binding source (the data) needs to be a public property.
For collection types, in WPF you usually use a ObservableCollection
So change your property to:
public ObservableCollection<Family> Families { get; set; }
Additional info:
Usually the property you bind to does not reside in the view class (here: not in your window), but in a separate class called "view model" which should implement the INotifyPropertyChanged interface.
Take a look here for more info: Model-View-ViewModel-MVVM-Explained
In the end, your view model should look like this:
public ObservableCollection<Family> Families
{
get { return _families; }
set
{
_families= value;
RaisePropertyChanged("Families");
}
}
public ObservableCollection<Family> _families;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
With the new view model class in place, the binding can be changed to
ItemsSource="{Binding Families}"
Finally you need to set you new view model class as DataContext in your window, see here.
Related
I am trying to implement a countries list as per this link. Basically it has a id:country with 3 levels of data.
I have the tree view displaying as required using this class:
using System.Collections.ObjectModel;
namespace ckd.Library
{
/// <summary>
/// implementation of the hierarchical data from the ABS SACC 2016
/// #link https://www.abs.gov.au/statistics/classifications/standard-australian-classification-countries-sacc/latest-release
/// </summary>
public static class Sacc2016
{
public static ObservableCollection<MajorGroup> Countries { get; set; }
static Sacc2016()
{
Countries = new ObservableCollection<MajorGroup>();
var majorGroup1 = new MajorGroup(1, "OCEANIA AND ANTARCTICA");
var minorGroup11 = new MinorGroup(11, "Australia (includes External Territories)");
var minorGroup12 = new MinorGroup(12, "New Zealand");
var minorGroup13 = new MinorGroup(13, "Melanesia");
minorGroup11.Countries.Add(new Country(1101, "Australia"));
minorGroup11.Countries.Add(new Country(1102, "Norfolk Island"));
minorGroup11.Countries.Add(new Country(1199, "Australian External Territories, nec"));
minorGroup12.Countries.Add(new Country(1201, "New Zealand"));
minorGroup13.Countries.Add(new Country(1301, "New Caledonia"));
Countries.Add(majorGroup1);
}
}
public class MajorGroup
{
public MajorGroup(int id, string name)
{
Id = id;
Name = name;
MinorGroups = new ObservableCollection<MinorGroup>();
}
public int Id { get; set; }
public string Name { get; set; }
public ObservableCollection<MinorGroup> MinorGroups { get; set; }
}
public class MinorGroup
{
public MinorGroup(int id, string name)
{
Id = id;
Name = name;
Countries = new ObservableCollection<Country>();
}
public int Id { get; set; }
public string Name { get; set; }
public ObservableCollection<Country> Countries { get; set; }
}
public class Country
{
public Country(int id, string name)
{
Id = id;
Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
}
}
My ViewModel implements INotifyPropertyChanged and in part is:
private int? _CountryOfBirth;
public int? CountryOfBirth
{
get => _CountryOfBirth;
set => SetProperty(ref _CountryOfBirth, value);
}
public ObservableCollection<MajorGroup> CountriesObservableCollection { get; private set; }
void ViewModelConstructor(){
...
CountriesObservableCollection = Sacc2016.Countries;
...
}
Finally, the xaml section is:
<TreeView x:Name="CountriesTreeView" HorizontalAlignment="Stretch" Margin="10" VerticalAlignment="Stretch"
ItemsSource="{Binding CountriesObservableCollection}"
SelectedValue="{Binding CountryOfBirth, Mode=OneWayToSource }"
SelectedValuePath="Id"
>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding MinorGroups}" DataType="{x:Type library:MajorGroup}">
<Label Content="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Countries}" DataType="{x:Type library:MinorGroup}">
<Label Content="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type library:Country}">
<Label Content="{Binding Name}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
this xaml gives the error: View.xaml(260, 23): [MC3065] 'SelectedValue' property is read-only and cannot be set from markup. Line 260 Position 23.
removing the selectedValue shows:
so my question is, how can I link the Id field (from MajorGroup, MinorGroup and Country) to the CountryOfBirth property in my viewmodel?
There exist many solutions. One simple MVVM ready solution is to handle the TreeView.SelectedItemChanged event to set a local dependency property which you can bind to the view model class:
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static readonly DependencyProperty SelectedTreeItemProperty = DependencyProperty.Register(
"SelectedTreeItem",
typeof(object),
typeof(MainWindow),
new PropertyMetadata(default));
public object SelectedTreeItem
{
get => (object)GetValue(SelectedTreeItemProperty);
set => SetValue(SelectedTreeItemProperty, value);
}
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var treeView = sender as TreeView;
this.SelectedTreeItem = treeView.SelectedItem;
}
}
MainWindow.xaml
<Window>
<Window.Resources>
<Style TargetType="local:MainWindow">
<Setter Property="SelectedTreeItem"
Value="{Binding SelectedDataModel, Mode=OneWayToSource}" />
</Style>
</Window.Resources>
<TreeView SelectedItemChanged="TreeView_SelectedItemChanged" />
</Window>
MainViewModel.cs
class MainViewModel : INotifyPropertyChanged
{
public object SelectedDataModel { get; set; }
}
Alternatively, you can also move the logic from the MainWindow.xaml.cs to an attached behavior.
I am working around the MVVM pattern and a TreeView.
I failed to bind the data models with the view. Here's my currently code:
MainWindow.xaml:
xmlns:reptile="clr-namespace:Terrario.Models.Reptile"
...
<TreeView x:Name="TrvFamily" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding }">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type reptile:Family}" ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
LoadFamily();
}
MainWindow.xaml.cs:
private void LoadFamily()
{
FamilyVM familiesVM = new FamilyVM();
familiesVM.Load();
TrvFamily.DataContext = familiesVM;
}
}
ViewModels\FamilyVM:
class FamilyVM
{
public ObservableCollection<Family> Families { get; set; }
public void Load()
{
ObservableCollection<Family> families = new ObservableCollection<Family>();
families.Add(new Family { ID = 1, Name = "Amphibian" });
families.Add(new Family { ID = 2, Name = "Viperidae" });
families.Add(new Family { ID = 3, Name = "Aranae" });
Families = families;
}
}
Models\Family.cs
class Family
{
public int ID { get; set; }
public string Name { get; set; }
}
The TreeView still white, like without data.
Hope you have a issue ;)
Thanks per advance
You're binding to the instance of FamilyVM rather than Families.
That has no Name property, so you get nothing.
You should also always implement inotifypropertychanged on any viewmodel.
You get a memory leak otherwise.
And you have no child collection on Family.
<TreeView x:Name="TrvFamily" Grid.Row="1"
ItemsSource="{Binding Families}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource="{Binding SomeCollectionYouDoNotHaveYet}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
It's usual to put the inotifypropertychanged stuff in a base class you inherit viewmodels from.
class FamilyVM : INotifyPropertyChanged
{
private ObservableCollection<Family> families = new ObservableCollection<Family>();
public ObservableCollection<Family> Families
{
get { return families; }
set { families = value; NotifyPropertyChanged(); }
}
public void Load()
{
ObservableCollection<Family> families = new ObservableCollection<Family>();
families.Add(new Family { ID = 1, Name = "Amphibian" });
families.Add(new Family { ID = 2, Name = "Viperidae" });
families.Add(new Family { ID = 3, Name = "Aranae" });
Families = families;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Models
public class EmployeeDetails
{
public string Name { get; set; }
public int Age {get;set;}
}
public class AddressDetails
{
public EmployeeDetails EmployeeName { get; set; }
public string City { get; set; }
}
View
<Window x:Class="ClassCollection.MainWindow"
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:local="clr-namespace:ClassCollection"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Source={StaticResource loc},Path=ViewModel}"
>
<Grid>
<StackPanel Margin="0 20 0 0">
<TextBox x:Name="txt1" Width="90" Height="20" Text="{Binding Details.EmployeeName}"/>
<TextBox x:Name="txt2" Width="90" Height="20" Text="{Binding Details.City}" Margin="0 20 0 0"/>
</StackPanel>
<Button x:Name="btn" Width="90" Height="25" Content="Add" Command=" {Binding AddCommand}"/>
</Grid>
</Window>
ViewModel
public class Viewmodel
{
public ObservableCollection<AddressDetails> EmployeeList;
public Viewmodel()
{
EmployeeList = new ObservableCollection<AddressDetails>();
LoadCommand();
}
private AddressDetails _details;
public AddressDetails Details
{
get { return _details; }
set
{
_details = value;
}
}
// Commands
public ICommand AddCommand { get; set; }
private void LoadCommand()
{
AddCommand = new CustomCommand(Add, CanAdd);
}
private bool CanAdd(object obj)
{
return true;
}
private void Add(object obj)
{
EmployeeList.Add(new AddressDetails { EmployeeName = Details.EmployeeName, City = Details.City });
}
}
Locator
public class Locator
{
private static Viewmodel viewmodel = new Viewmodel();
public static Viewmodel ViewModel
{
get { return viewmodel; }
}
}
How to add TextBox value to collection list using MVVM?
The Above is my code that I have tried. It shows null reference exception if I do like above. What would be the problem?
Update
I have two fields in EmployeeDetails class. So I must give input for these two field when add to collection. But I need only one field Name to insert to the collection. How to do it?
Analysis
It seems the _details field is not «initialized».
Solution
Please consider introducing the appropriate field initialization, for example:
private readonly AddressDetails _details = new AddressDetails
{
EmployeeName = new EmployeeDetails()
};
I'm binding a List of a custom class (which implements the INotifyPropertyChanged) into a ListBox.
When I add any item to the List, the ListBox doesn't update, but if I scroll, the ListBox gets updated.
The class
class MyClass : INotifyPropertyChanged
{
private string name = string.Empty;
public string Name { /* INotifyPropertyChanged */ }
}
The property
private List<MyClass> loaded;
public List<MyClass> Loaded { /* INorutyPropertyChanged */ }
The ListBox
<ListBox ItemsSource={Binding Loaded} />
If I force override the List property, it works fine:
Loaded = new List<MyClass>() { new MyClass { Name = "test"; } }
Update to:
public ObservableCollection<MyClass> Loaded { get; private set; }
and
<ListBox ItemsSource={Binding Loaded, UpdateSourceTrigger=PropertyChanged} />
Also, you do not need to use INotiftyPropertyChanged for your Loaded property. If the binding happens once, and the data-source does not change, there's no need.
Edit:
Here's a working example.
MainWindow.xaml
<Window x:Class="WpfApplication3.MainWindow"
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"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Width="200" ItemsSource="{Binding Items, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Width="100" Height="75" HorizontalAlignment="Right" Content="Add" Command="{Binding AddItem}"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new DataContext();
}
}
DataContext.cs
namespace WpfApplication3
{
public class DataContext
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public DataContext()
{
Items = new ObservableCollection<Item>
{
new Item
{
Value = "test"
}
};
AddItem = new RelayCommand(() =>
{
Items.Add(new Item
{
Value = "new item"
});
}, () => true);
}
}
public class Item
{
public string Value { get; set; }
}
}
RelayCommand.cs
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
Let me know if you have any questions.
I am making a WPF application following MVVM pattern. In this i am using entity framework,
my entity structure is simple, it has 3 entities: department, course, books,
a department can have many courses, and a course can have many books,
now i want to show this in a treeview, so my output in wpf should look like this,
Department1
Course1
Book1
Book2
Course2
Book3
Department2
Course
Book
Department3
in my ViewModel i have EntityContext object. But i dont know how to show this in a treeview.
how i can do this.
I prepared the small sample to replicate this..
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:TestApp"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<this:TreeViewModel />
</Window.DataContext>
<Window.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Courses}" DataType="{x:Type this:Department}">
<Label Content="{Binding DepartmentName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Books}" DataType="{x:Type this:Course}">
<Label Content="{Binding CourseName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type this:Book}">
<Label Content="{Binding BookName}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Departments}">
</TreeView>
</Grid>
</Window>
Model and ViewModel classes.
public class Book :ViewModelBase
{
private string bookname = string.Empty;
public string BookName
{
get
{
return bookname;
}
set
{
bookname = value;
OnPropertyChanged("BookName");
}
}
public Book(string bookname)
{
BookName = bookname;
}
}
Department class
public class Department : ViewModelBase
{
private List<Course> courses;
public Department(string depname)
{
DepartmentName = depname;
Courses = new List<Course>()
{
new Course("Course1"),
new Course("Course2")
};
}
public List<Course> Courses
{
get
{
return courses;
}
set
{
courses = value;
OnPropertyChanged("Courses");
}
}
public string DepartmentName
{
get;
set;
}
}
Course class
public class Course :ViewModelBase
{
private List<Book> books;
public Course(string coursename)
{
CourseName = coursename;
Books = new List<Book>()
{
new Book("JJJJ"),
new Book("KKKK"),
new Book("OOOOO")
};
}
public List<Book> Books
{
get
{
return books;
}
set
{
books = value;
OnPropertyChanged("Books");
}
}
public string CourseName
{
get;
set;
}
}
TreeViewModel class.
public class TreeViewModel :ViewModelBase
{
private List<Department> departments;
public TreeViewModel()
{
Departments = new List<Department>()
{
new Department("Department1"),
new Department("Department2")
};
}
public List<Department> Departments
{
get
{
return departments;
}
set
{
departments = value;
OnPropertyChanged("Departments");
}
}
}
ViewModelBase class.
public class ViewModelBase :INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propname)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propname));
}
}
}
Finally it displays the data in the hierarchical format.. I hope this would satisfy you...
You have to define hierarchy data template template for this Here is the sample how to use this.
Based on 'David Bekham' answer i created an more generic way to display the items
hops it helps:
define a generic HierarchicalDataTemplate
<HierarchicalDataTemplate
ItemsSource="{Binding ChildItems}"
DataType="{x:Type viewModels:TVItemViewModel}">
<Label Content="{Binding Name}" />
</HierarchicalDataTemplate>
here the viewmodel (if your content is non static you need to implement the viewModelbase class )
public class TVItemViewModel
{
private bool isSelected;
private string name;
public TVItemViewModel(string name)
{
this.Name = name;
}
public string Name
{
get => name;
set => name= value;
}
public bool IsSelected
{
get => isSelected;
set => isSelected= value;
}
public ObservableCollection<TVItemViewModel> ChildItems { get; set; }
}
and in the MainViewModel i create a root collection like
TvItems = new ObservableCollection<TVItemViewModel>()
{ new TVItemViewModel("RootItem1")
{ ChildItems = new ObservableCollection<TVItemViewModel>()
{ new TVItemViewModel("Child1"),
new TVItemViewModel("Child2"),
new TVItemViewModel("Child3)
}
}
};
I hope someone finds this usefull
We need to define the 'n' level of HierachialDataTemplate for the nested level we want.. We will have ItemsSource property for the HierarchicalDataTemplate class to define this.. We can do the same for the MenuControl also..