Treeview binding problem - wpf

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>

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 ListView grouping by 2 columns but display only 1 group header

A ListView displays a collection of the following class:
public class Employee
{
private string _department;
private string _manager;
private string _name;
private string _address;
public string Department
{
get { return _department; }
}
public string Manager
{
get { return _manager; }
}
public string Name
{
get { return _name; }
}
public string Address
{
get { return _address; }
}
}
There is a 1-to-1 relation between Department and Manager, so any 2 rows with the same department will also have the same manager.
I want to group by Department/Manager, with the group header showing "Department (Manager)".
My CollectionViewSource looks like
<CollectionViewSource x:Key="cvsEmployees" Source="{Binding Employees}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Department" />
<PropertyGroupDescription PropertyName="Manager" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
The plan is to not display the first level header (Department) and to somehow bind to both the Department (1st level) and the Manager (2nd level) from the 2nd level header.
3 questions:
To avoid displaying the 1st level header, I have an empty data template in the groupstyle:
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
This seems very clunky. Is there a more elegant way to skip a group header?
How do I bind to the 1st grouping level property (Department) from the 2nd level header (Manager) to achieve the required "Department (Manager)" ?
Is there a better way to do this than creating 2 grouping level?
Thanks
Solved the main stumbling block, question 2 above: how to bind from the group header to a property that is not the grouping property.
The solution is to change the data context to:{Binding Items}. The ItemSource properties are then available
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,10,0,3" DataContext="{Binding Items}" >
<TextBlock Text="{Binding Path=Department}" FontWeight="Bold" Margin="3"/>
<TextBlock Text="{Binding Path=Manager, StringFormat='({0})'}" Margin="3"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
I would create another model part, which represents the dual grouping that you need to have happen:
Model Classes:
public class EmployeeModel {
private readonly Employee _Employee;
public DepartmentManager ManagementInfo { get; private set; }
public string Name {
get { return _Employee.Name; }
}
public string Address {
get { return _Employee.Address; }
}
public EmployeeModel(Employee employee) {
this._Employee = employee;
this.ManagementInfo = new DepartmentManager(employee.Department, employee.Manager);
}
}
public class DepartmentManager {
public string Department { get; private set; }
public string Manager { get; private set; }
public DepartmentManager(string dept, string manager) {
this.Department = dept;
this.Manager= manager;
}
public override bool Equals(object obj) {
var model = obj as DepartmentManager;
if(null == model)
return false;
return Department.Equals(model.Department, StringComparison.InvariantCultureIgnoreCase) &&
Manager.Equals(model.Manager, StringComparison.InvariantCultureIgnoreCase);
}
}
XAML:
<CollectionViewSource x:Key="cvsEmpsModel" Source="{Binding EmployeesModel}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ManagementInfo" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate DataType="{x:Type models:EmployeeModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Address}" />
</StackPanel>
</DataTemplate>
...
<ListView ItemsSource="{Binding Source={StaticResource cvsEmpsModel}}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,10,0,3" DataContext="{Binding Items}">
<TextBlock Text="{Binding Path=ManagementInfo.Manager}" FontWeight="Bold" Margin="3" />
<TextBlock Text="{Binding Path=ManagementInfo.Department, StringFormat='({0})'}" Margin="3" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
Then in your Window/ViewModel:
this.EmployeesModel = new ObservableCollection<EmployeeModel>(MyListOfEmployersFromDB.Select(e => new EmployeeModel(e)));
Note, I've overriden Equals in the DepartmentManager class, but not GetHashCode, ideally you should do a custom implementation of that. I had to override equals so the grouping view source would correctly group the same entries. You could get rid of this need, buy constructing the DepartmentManager for the same Employees outside of the collection, and pass them into the EmployeeModel ctr.

Using Commands in WPF TreeView with HierarchicalDataTemplate

is there a way to use commands in a treeview with a HierarchicalDataTemplate, so that i can react on the click on an item in the treeview?
i would prefer a solution without code-behind if there is one.
Here is my TreeView:
<TreeView ItemsSource="{Binding Main.TreeItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Header}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
provide an property of type ICommand on your Item (ViewModel) and Bind to that. So your item class would be something like:
class MyTreeItem
{
public MyTreeItem()
{
this.SomeCommand = /* create command here */ null;
this.Children = new ObservableCollection<MyTreeItem>();
}
public ICommand SomeCommand { get; private set; }
public ObservableCollection<MyTreeItem> Children { get; private set; }
}
In xaml you can then write:
<TreeView ItemsSource="{Binding Main.TreeItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{TemplateBinding Header}" />
<Button Text="My Command" Command="{TemplateBinding SomeCommand}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I think that should work, at least it did similarily last time i used that :)

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.

WPF TreeView Never Populates Using HierarchicalDataTemplate

With the code below my treeview never populates.
Can anyone see waht I'm doing wrong?
thanks
public class FouList
{
public string Source { get; set; }
public List<FouData> ListOfFou { get; set; }
}
public struct FouData
{
public string Name { get; set; }
}
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:FouList}"
ItemsSource="{Binding Path=ListOfFou}">
<Border BorderBrush="Black"
BorderThickness="2"
CornerRadius="10">
<TextBlock Margin="10,0,0,0"
Text="{Binding Source}"></TextBlock>
</Border>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:FouData}">
<Border BorderBrush="Black"
BorderThickness="2"
CornerRadius="10">
<TextBlock Margin="10,0,0,0"
Text="{Binding Name}"></TextBlock>
</Border>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView Margin="26,0,35,12" Name="treeView1" Height="298"
ItemsSource="{Binding Path=FouList}" VerticalAlignment="Top"/>
FouList FL = new FouList();
//code to populate FL
//I've debugged and can see it populating correctly
treeView1.DataContext = FL;
ItemsSource binding of treeView1 is incorrect. I suppose you intend to bind to the property ListOfFou, not to FouList.
<TreeView Margin="26,0,35,12" Name="treeView1" Height="298"
ItemsSource="{Binding Path=ListOfFou}" VerticalAlignment="Top"/>
I would try either changing your
List<FouData> ListOfFou { get; set; }
to
ObservableCollection<FouData> ListOfFou { get; set; }
or propagating the change with NotifyPropertyChanged("ListOfFou");

Resources