Set TreeView HierarchicalDataTemplate with 2 levels in WPF - wpf

I have a View Model that is represented in the DataContext of the window.
public class SchoolViewModel:ViewModelBase
{
public ObservableCollection<SchoolClassGroup> ClassesCollection { get; set; }
....
}
public class SchooleClassGroup:ViewModelBase
{
public string ClassName {get;set;}
public string TeacherName {get;set;}
public ObservableCollection<Students> StudentCollection{ get; set; }
}
public class Student
{
public string Name {get;set;}
public int Age {get;set;}
public DateTime BirthDate {get;set;}
...
}
I want to represent a TreeView that will show me schools,class and students.
How can I do it?
Thanks!

You need to create HeirarchicalDataTemplates for each level of your object tree that has a Parent-Child relationship, plus a simple DataTemplate for the leaf node.
Your object tree should be School -> Class -> Student
class School has a
List<Class>
class Class has a
List<Student>
Then it's simple
<Window.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Classes}" DataType="{x:Type School}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Students}" DataType="{x:Type Class}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type Student}">
<TextBlock Text="{Binding Name}" />
</DataTemplate >
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Schools}" >
</Grid>

Related

XAML Binding to child collection

In a Windows UWP project I'm trying to bind to the following properties in this class
using System;
using System.Collections.ObjectModel;
namespace IAmOkShared.Models
{
public class Client
{
public Guid clientId { get; set; }
public string lastname { get; set; }
public DateTime timestamp { get; set; }
//- List af addresses of this client
public ObservableCollection<Address> clientaddresses;
public Client ()
{
clientId = Guid.Empty;
lastname = string.Empty;
timestamp = DateTime.Today;
clientaddresses = new ObservableCollection<Address>();
}
}
}
Binding to clientId and lastname is no problem, but can't get it right to bind to one or more of the properties of clientaddresses (e.g city, country)
My XAML:
<DataTemplate x:Name="DetailTemplate" x:DataType="models:Client">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="LastNameTextBlock" Text="{Binding lastname}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock x:Name="AddressTextBlock" Text="{Binding clientaddresses[0].city}" />
</StackPanel>
</StackPanel>
</DataTemplate>
Any idea how to solve this?
Steven
You are binding to a field instead of a property.
public ObservableCollection<Address> clientaddresses;
Change this to
public ObservableCollection<Address> Clientaddresses { get; private set; }
So it cannot be instantiated outside the viewmodel then the binding should work.
Also you could create additional data template for the Address and just use the entire collection in your datatemplate of the Client, because then you would not get possible Index out of bounds exception if your ClientAddresses collection is empty.
<DataTemplate x:DataType="models:Address">
<TextBlock x:Name="AddressTextBlock" Text="{Binding city}" />
<DataTemplate>
<DataTemplate x:Name="DetailTemplate" x:DataType="models:Client">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="LastNameTextBlock" Text="{Binding lastname}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding ClientAddresses}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
Also note your model is not implementing the INotifyPropertyChanged so your UI will not be updated when the model properties change.
Also the convention for back-end private fields is to start with lower case character and for the properties that utilize the INotifyPropertyChanged you should start the property with upper case.
private int myProperty;
public int MyProperty { get { ... } set { ... }}
that's why we create ViewModel and additional property in it
public Address ClientFirstAddress
{
get {return clientaddresses[0].city;}
}
and then Bind it to View,
remember to call NofityPropertyChanged for this property when you set clientaddresses collection

Wpf adding stack panels from view

I'm not sure my title is clear (poor wpf skills).
What i'm trying to do is to create a smart data entry form. My goal is to have a hard coded data that the user should enter, and on demand (a plus button) he can enter another set of data, every time the user will click the plus button another set will appear in the window (endless)
Edit:
For more details, for a very simple example of what i'm trying to achieve, lets say that this is the window:
And after the user will click the plus button the window will look like this:
And the plus button will always let the user adding more peoples.
Seems like all you need is a List and a ItemControl:
Your Model:
public class User
{
public String Name { get; set; }
public int Age { get; set; }
}
In your ViewModel:
public List<User> Users { get; set; }
//In your constructor
Users = new List<User>();
In your View:
<ItemsControl ItemsSource={Binding Users}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Name:" />
<TextBox Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Age:" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And then below this wire up your add button to a command to point to a method that would do someething like:
private void AddUser()
{
Users.Add(new User());
NotifyPropertyChange("Users");
}
Use an ItemsControl with its ItemsSource property bound to a ReadOnlyObservableCollection<Person>, where Person is a class holding the name and age as strings.
(1) Create Person
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
(2) Create PeopleViewModel, holding your collection.
public class PeopleViewModel
{
private ObservableCollection<Person> _people;
public ReadOnlyObservableCollection<Person> People { get; private set; }
public PeopleViewModel()
{
_people = new ObservableCollection<Person>();
People = new ReadOnlyObservableCollection<Person>(_people);
addPerson(); // adding the 1st person
}
// You also need to hook this up to the button press somehow
private void addPerson()
{
_people.Add(new Person());
}
}
(3) Set the DataContext of your window to be a PersonViewModel in the code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new PeopleViewModel();
}
}
(4) Create an ItemsControl along with a DataTemplate for Person
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="name:" />
<TextBox Text="{Binding Name}" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="age:" />
<TextBox Text="{Binding Age}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Don't forget to hook up your button either through a Command or through the Button.Click event.

Rendering Collection of collections - ItemsControl

I have an object model like below:
public class ViewModel
{
public List<Group> Groups{ get; set; }
}
public class Group
{
public string Name { get; set; }
public List<Contact> Contacts { get; set; }
}
public class Contact
{
public string Name { get; set; }
public bool IsOnline { get; set; }
}
and I'm binding the groups to an itemscontrol like this:
<ItemsControl ItemsSource="{Binding Path=Groups}"
ItemTemplate="{StaticResource GroupTemplate}" >
</ItemsControl>
and I have datatemplate for rendering them.
<DataTemplate x:Key="GroupTemplate" DataType="{x:Type Group}">
</DataTemplate>
<DataTemplate x:Key="ContactTemplate" DataType="{x:Type Contact}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanle>
</DataTemplate>
How can I get the contacts displayed inside the items control? The contacts is a collection inside each group and my viewmodel has a collection of groups. To complicate it a bit further, I have different datatemplates for different contacts, and I should be using a datatemplateselector for choosing the appropriate contact template. Also please note, I have nothing to display in the group template, and I only need to show Contacts.
Thanks,
-Mike
Use another ItemsControl in the first template:
<DataTemplate x:Key="GroupTemplate" DataType="{x:Type my:Group}">
<ItemsControl ItemsSource="{Binding Contacts}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type my:Contact}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
And with a template selector:
<DataTemplate x:Key="GroupTemplate" DataType="{x:Type my:Group}">
<ItemsControl ItemsSource="{Binding Contacts}"
ItemTemplateSelector="{StaticResource yourContactItemSelector}"/>
</DataTemplate>

Multi level Nested TreeView with Dynamic Binding in WPF

I am trying to create an application in which i require to display employees and their departments in the treeview kind of structure as below :
Employee1
Department
Dept1
Dept2
Employee2
Department
Dept3
Dept4
how could i do this with WPF ?
The correct way to do this is to use a HierarchicalDataTemplate. The most basic one I can imagine is the following:
<UserControl.Resources>
<HierarchicalDataTemplate
x:Key="RecursiveData" DataType="TreeViewItem" ItemsSource="{Binding Items}">
</HierarchicalDataTemplate>
</UserControl.Resources>
Which can be used in the XAML as follows:
<TreeView ItemTemplate="{StaticResource RecursiveData}" />
Of course you can customize the template at will with styles and subcomponents.
Note that the ItemSource of your TreeView needs to actually provide nested TreeViewItems where each TreeViewItem contains it's subitems in Items.
If you've structure like this:
public ObservableCollection<ChartOfAccount> ChartOfAccounts { get; set; }
public class ChartOfAccount
{
public Book Book { get; set; }
public List<LedgerHierarchy> ControlLedgers { get; set; }
}
public class LedgerHierarchy
{
public ControlLedger ControlLedger { get; set; }
public ObservableCollection<Ledger> Ledgers { get; set; }
}
you could bind directly in TreeView like this:
<TreeView ItemsSource="{Binding ChartOfAccounts}"
BorderThickness="0"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemContainerStyle="{StaticResource treeStyle}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ControlLedgers}">
<TextBlock Text="{Binding Book.Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Ledgers}">
<TextBlock Text="{Binding ControlLedger.Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
instead of creating HierarchicalDataTemplate in Control.Resource.

Treeview binding problem

I have a window MainWindow.xaml and
private static Tutorial tutorial; there.
Also I have class Structure.cs where I describe child types
public class Tutorial
{
public string Name { get; set; }
public IList<Chapter> Chapters = new List<Chapter>();
}
public class Chapter
{
public string Name { get; set; }
public IList<Unit> Units = new List<Unit>();
}
public class Unit
{
public string Name { get; set; }
public IList<Frame> Frames = new List<Frame>();
...
}
I want to bind tutorial structure to treeview. How can I do this?
I tried this way.
<TreeView Grid.Row="2" x:Name="treeViewStruct" Margin="5,0,5,0" Background="LemonChiffon" BorderBrush="Bisque" BorderThickness="1" ScrollViewer.VerticalScrollBarVisibility="Auto" IsTextSearchEnabled="True" Cursor="Hand">
<TreeView.Resources>
<HierarchicalDataTemplate DataType = "{x:Type Structure:Chapter}"
ItemsSource = "{Binding Path=Units}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type Structure:Unit}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
It doesn't work.
Please, help! I'm a newbie in WPF. I need dynamic tree
so that when I add a chapter or a unit in the object tutorial, tree is updated.
And for this way of binding please throw the idea how can I get a collection item, when I selected some tree node.
This may help :
<HierarchicalDateTemplate DataType = "{x:Type local:Tutorial}"
ItemsSource="{Binding Chapters}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDateTemplate>
<HierarchicalDateTemplate DataType = "{x:Type local:Chapter}"
ItemsSource="{Binding Units}"
<TextBlock Text="{Binding Name}"/>
</HierarchicalDateTemplate>
<DateTemplate DataType = "{x:Type local:Unit}"
<TextBlock Text="{Binding Name}"/>
</DateTemplate>

Resources