Bind WPF TreeView on button click with MVVM - wpf

I am building WPF application where I am using MVVM pattern for data binding and command binding.
When I bind the tree view in a constructor(LoadTreeViewViewModel) it works without any issue but when I do the same in Button Click it is not working. I did a little bit of research where I am also binding to listview and binding is working without issue for listview on button click. So the problem is only with TreeView binding
Below is the complete code for the sample application.
XAML
<Window x:Class="WpfApp1.LoadTreeView"
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:WpfApp1"
mc:Ignorable="d"
Title="LoadTreeView" Height="300" Width="300">
<Window.DataContext>
<local:LoadTreeViewViewModel></local:LoadTreeViewViewModel>
</Window.DataContext>
<Window.Resources>
<local:LoadTreeViewViewModel x:Key="viewModel"></local:LoadTreeViewViewModel>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="5*"></RowDefinition>
<RowDefinition Height="5*"></RowDefinition>
<RowDefinition Height="1*"></RowDefinition>
</Grid.RowDefinitions>
<TreeView ItemsSource="{Binding Folders}" Grid.Column="0" Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Folders}" DataType="{x:Type local:IFolder}">
<TreeViewItem Header="{Binding FolderLabel}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<ListView VerticalAlignment="Top" Grid.Column="0" Grid.Row="1"
ItemsSource="{Binding Lists, Source={StaticResource viewModel}}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"
Grid.Column="0" Grid.Row="2"
Command="{Binding LoadSourceCommand, Source={StaticResource viewModel}}"/>
</Grid>
</Window>
LoadTreeView
public partial class LoadTreeView : Window
{
public LoadTreeView()
{
InitializeComponent();
//this.DataContext = new LoadTreeViewViewModel();
}
}
IFolder
public interface IFolder
{
List<IFolder> Folders { get; set; }
string FolderLabel { get; set; }
string FullPath { get; set; }
}
Folder
public class Folder : IFolder
{
public List<IFolder> Folders { get; set; }
public string FolderLabel { get; set; }
public string FullPath { get; set; }
public Folder()
{
Folders = new List<IFolder>();
}
}
LoadTreeViewViewModel
class LoadTreeViewViewModel : INotifyPropertyChanged
{
public LoadTreeViewViewModel()
{
this.LoadSourceCommand = new ViewModel.btnClick(this.LoadData, this.IsValid);
//this.Folders = await LoadDataAsync();
//LoadData();
}
private async void LoadData()
{
this.Folders = await LoadTreeAsync();
this.Lists = await LoadListAsync();
}
private async Task<string[]> LoadListAsync()
{
List<string> temp = new List<string>();
await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
//add Root items
temp.Add("Dummy1");
temp.Add("Dummy2");
temp.Add("Dummy3");
temp.Add("Dummy4");
return temp.ToArray();
}
private async Task<List<IFolder>> LoadTreeAsync()
{
List<IFolder> temp = new List<IFolder>();
await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
//add Root items
temp.Add(new Folder { FolderLabel = "Dummy1", FullPath = #"C:\dummy1" });
temp.Add(new Folder { FolderLabel = "Dummy2", FullPath = #"C:\dummy2" });
temp.Add(new Folder { FolderLabel = "Dummy3", FullPath = #"C:\dummy3" });
temp.Add(new Folder { FolderLabel = "Dummy4", FullPath = #"C:\dummy4" });
//add sub items
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy11", FullPath = #"C:\dummy11" });
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy12", FullPath = #"C:\dummy12" });
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy13", FullPath = #"C:\dummy13" });
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy14", FullPath = #"C:\dummy14" });
return temp;
}
private bool IsValid()
{
return true;
}
#region Members
private ViewModel.btnClick loadSourceCommand;
public btnClick LoadSourceCommand
{
get { return loadSourceCommand; }
set { loadSourceCommand = value; }
}
private List<IFolder> m_folders;
public List<IFolder> Folders
{
get { return m_folders; }
set
{
m_folders = value;
NotifiyPropertyChanged("Folders");
}
}
private string[] lists;
public string[] Lists
{
get { return lists; }
set { lists = value; NotifiyPropertyChanged("Lists"); }
}
#endregion
void NotifiyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
btnClick
public class btnClick : System.Windows.Input.ICommand
{
private Action WhatToExecute;
private Func<bool> WhenToExecute;
public btnClick(Action what, Func<bool> when)
{
WhatToExecute = what;
WhenToExecute = when;
}
public void Refresh()
{
if (this.CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return WhenToExecute();
}
public void Execute(object parameter)
{
WhatToExecute();
}
}

You likely have a binding error.
Check the output log for and BindingExpression errors.
I suspect you want to use viewModel instance you've defined, rather than the window's datacontext, because they're going to be two different instances.
<TreeView ItemsSource="{Binding Folders, Source={StaticResource viewModel}}"
Grid.Column="0"
Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Folders}"
DataType="{x:Type local:IFolder}">
<TreeViewItem Header="{Binding FolderLabel}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

Related

Updating a collection member's property within another collection to show in a DataGrid

How can I make it so when I change a property of a collection member in a collection it updates in the datagrid display?
In my example I have an ObservableCollection of Employees. Each employee has a List property.
When I assign the Employee's Car value to a new List it will update successfully. When I assign the Employee's Car's Model property it doesn't update.
MainWindow.xaml
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="DataBindings.MainWindow" Title="MainWindow" Height="332" Width="474" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<CollectionViewSource x:Key="GridDataSource" Source="{Binding Employees}" />
</Window.Resources>
<Grid Margin="0,0,0,-1">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="1" ItemsSource="{Binding Source={StaticResource GridDataSource}}" AutoGenerateColumns="False" CanUserAddRows="False" Margin="0,0,0,114">
<DataGrid.Columns>
<DataGridTextColumn x:Name="Names" Width="Auto" Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn x:Name="Cars" Width="Auto" Header="Cars" Binding="{Binding CarsString}" />
</DataGrid.Columns>
</DataGrid>
<Button Content="Update" Click="Update" HorizontalAlignment="Left" Margin="170,202,0,-20" Grid.Row="1" VerticalAlignment="Top" Width="75" />
</Grid>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
namespace DataBindings
{
public partial class MainWindow : Window
{
DAL dataAccess = new DAL();
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<Employee> Employees { get { return dataAccess.EmployeesList; } }
private void Update(object sender, RoutedEventArgs e)
{
Employees[1].Name = "Mike"; //changing the name property on the collection of employees works
Debug.WriteLine($"Mike's Car is a {Employees[1].Cars[0].Model}");
Employees[1].Cars[0].Model = "Volvo"; //changing the model of car in a cars list does not
Debug.WriteLine($"Mike's Car is a {Employees[1].Cars[0].Model}");
}
}
}
DAL.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace DataBindings
{
public class DAL : ObservableCollection<Employee>
{
public ObservableCollection<Employee> EmployeesList = new ObservableCollection<Employee>();
public DAL()
{
List<Car> carList = new List<Car>();
carList.Add(new Car { Model = "Ford" });
carList.Add(new Car { Model = "Honda" });
EmployeesList.Add(new Employee { Name = "Bob", Cars = carList });
EmployeesList.Add(new Employee { Name = "John", Cars = carList });
//EmployeesList.CollectionChanged += EmployeesList_CollectionChanged;
}
}
public class Employee : INotifyPropertyChanged
{
private string empName;
public List<Car> empCars = new List<Car>();
private string carsString;
public string Name
{
get { return this.empName; }
set
{
if (this.empName != value)
{
empName = value;
this.NotifyPropertyChanged("Name");
};
}
}
public List<Car> Cars
{
get { return this.empCars; }
set
{
if (this.empCars != value)
{
empCars = value;
carsToString(empCars);
this.NotifyPropertyChanged("Cars");
};
}
}
public string CarsString
{
get { return this.carsString; }
set
{
if (this.carsString != value)
{
carsString = value;
this.NotifyPropertyChanged("CarsString");
};
}
}
public void carsToString(List<Car> Cars)
{
string carString = "";
foreach (Car car in Cars)
{
carString += car.Model + " ";
}
CarsString = carString;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class Car : INotifyPropertyChanged
{
private string carModel;
public string Model
{
get { return this.carModel; }
set
{
if (this.carModel != value)
{
carModel = value;
this.NotifyPropertyChanged("Model");
};
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
I modified your code a bit, which IMO can be improved more.
Here is my xaml:
<Window.DataContext>
<local:DAL />
</Window.DataContext>
<Grid Margin="0,0,0,-1">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DataGrid x:Name="grid" Grid.Row="1" ItemsSource="{Binding EmployeesList}" AutoGenerateColumns="False" CanUserAddRows="False" Margin="0,0,0,114">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Names">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Cars">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CarsString, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Update" Click="Update" HorizontalAlignment="Left" Margin="170,202,0,-20" Grid.Row="1" VerticalAlignment="Top" Width="75" />
</Grid>
Here's my code-behind:
You can have an ICommand to your UpdateButton so you can leave your code behind clean: https://www.c-sharpcorner.com/UploadFile/e06010/wpf-icommand-in-mvvm/
public DAL VM => (DAL) DataContext;
public MainWindow()
{
InitializeComponent();
}
private void Update(object sender, RoutedEventArgs e)
{
VM.EmployeesList[1].Name = "Mike"; //changing the name property on the collection of employees works
Debug.WriteLine($"Mike's Car is a {VM.EmployeesList[1].Cars[0].Model}");
VM.EmployeesList[1].Cars[0].Model = "Volvo"; //changing the model of car in a cars list does not
Debug.WriteLine($"Mike's Car is a {VM.EmployeesList[1].Cars[0].Model}");
}
Here's my DAL.cs
Notice that I changed your List to ObservableCollection then in accessors, you need to make sure that CarsString is updated, since you're not directly updating it in your view.
public class DAL : INotifyPropertyChanged
{
private ObservableCollection<Employee> _employeesList;
public ObservableCollection<Employee> EmployeesList
{
get => _employeesList;
set
{
_employeesList = value;
OnPropertyChanged();
}
}
public DAL()
{
EmployeesList = new ObservableCollection<Employee>();
ObservableCollection<Car> carList = new ObservableCollection<Car>();
carList.Add(new Car { Model = "Ford" });
carList.Add(new Car { Model = "Honda" });
EmployeesList.Add(new Employee { Name = "Bob", Cars = carList });
EmployeesList.Add(new Employee { Name = "John", Cars = carList });
//EmployeesList.CollectionChanged += EmployeesList_CollectionChanged;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Employee : INotifyPropertyChanged
{
private string empName;
public ObservableCollection<Car> empCars = new ObservableCollection<Car>();
private string carsString;
public string Name
{
get { return this.empName; }
set
{
if (this.empName != value)
{
empName = value;
this.NotifyPropertyChanged("Name");
};
}
}
public ObservableCollection<Car> Cars
{
get
{
carsToString(empCars);
this.NotifyPropertyChanged("CarsString");
return this.empCars;
}
set
{
if (this.empCars != value)
{
empCars = value;
carsToString(empCars);
this.NotifyPropertyChanged("Cars");
this.NotifyPropertyChanged("CarsString");
};
}
}
public string CarsString
{
get { return this.carsString; }
set
{
if (this.carsString != value)
{
carsString = value;
this.NotifyPropertyChanged("CarsString");
};
}
}
public void carsToString(ObservableCollection<Car> Cars)
{
string carString = "";
foreach (Car car in Cars)
{
carString += car.Model + " ";
}
CarsString = carString;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class Car : INotifyPropertyChanged
{
private string carModel;
public string Model
{
get { return this.carModel; }
set
{
if (this.carModel != value)
{
carModel = value;
this.NotifyPropertyChanged("Model");
};
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}

Databind in WPF

"EDIT"
What I want to achieve is something like -
MainWindow.xaml contains a Button, a ComboBox and a ContentControl.
UserControl-A and UserControl-C both contains a ContentControl.
UserControl-B and UserControl-D both contains a TextBlock.
UserControl-B will be Content of ContentControl Contained in
UserControl-A. UserControl-D will be Content of ContentControl
Contained in UserControl-C.
UserControl-A Or UserControl-C will be Content of ContentControl
Contained in MainWindow.xaml on click of the Button.
On change in SelectedValue of Combobox Update TextBlock of
UserControl-B Or UserControl-D.
"EDIT"
In addition to MainWindow.xaml, App.xaml and App.config I have MainContent1.xaml, MainContent2.xaml, ComboItems.cs and two subfolder Sub1 and Sub2, each of them contains a single file named S1.xaml, in my project.
All of my code is written in ComboItems.cs as -
namespace MultiBinding
{
public class Item
{
public string Name { get; set; }
public int Id { get; set; }
}
public class ComboItems
{
public ComboItems()
{
Items = new List<Item>(3);
for (int i = 1; i < 4; i++)
Items.Add(new Item { Id = i, Name = "Name " + i });
}
public List<Item> Items { get; set; }
}
public class ButtonContent : INotifyPropertyChanged
{
public ICommand MyCommand { get; set; }
private string _content;
public ButtonContent()
{
_content = "First";
MyCommand = new Command(Do, CanDo);
}
public string Content
{
get { return _content; }
set { _content = value; OnChange("Content"); }
}
private bool CanDo(object parameter) => true;
private void Do(object parameter) => Content = Content == "First" ? "Second" : "First";
public event PropertyChangedEventHandler PropertyChanged;
private void OnChange(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public class Command : ICommand
{
private Action<object> Do;
private Func<object, bool> CanDo;
public Command(Action<object> Do, Func<object, bool> CanDo)
{
this.Do = Do;
this.CanDo = CanDo;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => Do(parameter);
}
public class SomeText
{
public static string Text1 { get; set; }
public static string Text2 { get; set; }
}
public class Converter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
switch (values[1].ToString())
{
case "1": SomeText.Text1 = SomeText.Text2 = "Selected Item is 1"; break;
case "2": SomeText.Text1 = SomeText.Text2 = "Selected Item is 2"; break;
case "3": SomeText.Text1 = SomeText.Text2 = "Selected Item is 3"; break;
}
if (values[0].ToString() == "First") return new MainContent1();
else return new MainContent2();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Content of MainWindow.xaml is -
<Window x:Class="MultiBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiBinding">
<Window.Resources>
<local:ComboItems x:Key="cboItems" />
<local:Item x:Key="Item" />
<local:Converter x:Key="convert" />
<local:ButtonContent x:Key="content"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*"/>
<RowDefinition Height="90*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<ComboBox x:Name="combo"
DataContext="{Binding Source={StaticResource Item}}"
ItemsSource="{Binding Path=Items, Source={StaticResource cboItems}}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=Id}"/>
<Button x:Name="butt" Grid.Column="1"
DataContext="{Binding Source={StaticResource content}}"
Content="{Binding Path=Content}"
Command="{Binding Path=MyCommand}"/>
<ContentControl Grid.Row="1" Grid.ColumnSpan="2">
<ContentControl.Content>
<MultiBinding Converter="{StaticResource convert}">
<Binding ElementName="butt" Path="Content"/>
<Binding ElementName="combo" Path="SelectedValue"/>
</MultiBinding>
</ContentControl.Content>
</ContentControl>
</Grid>
Content of MainContent1.xaml is -
<UserControl x:Class="MultiBinding.MainContent1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sub="clr-namespace:MultiBinding.Sub1">
<UserControl.Resources>
<sub:S1 x:Key="s1"/>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock FontSize="20" Text="On Main ContentControl No. 1"/>
<ContentControl
Content="{Binding Source={StaticResource s1}}"
Grid.Row="1"/>
</Grid>
MainContent2.xaml contains exact same code as above except
xmlns:sub="clr-namespace:MultiBinding.Sub2"
Content of S1.xaml under folder Sub1 is -
<UserControl x:Class="MultiBinding.Sub1.S1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:main="clr-namespace:MultiBinding">
<UserControl.Resources>
<main:SomeText x:Key="MyText"/>
</UserControl.Resources>
<Grid Background="Bisque">
<TextBlock Text="{Binding Text1, Source={StaticResource MyText}}" />
</Grid>
S1.xaml under folder Sub2 is similar to that of Sub1 folder except
<TextBlock Text="{Binding Path=Text2, Source={StaticResource MyText}}" />
Everything works as expected in this way.
Is there any problem using static keyword infront of Text1 and Text2 properties of SomeText Class in ComboItems.cs?
How can I achieve same functionality without using static properties of SomeText class?
I have removed "ComboItems and Converter" classes from ComboItems.cs, renamed "ButtonContent" class as "MyCode" and implemented it as follows -
public class MyCode : INotifyPropertyChanged
{
//Fields
private Sub1.S1 _s1;
private Sub2.S1 _s2;
private MainContent1 _m1;
private MainContent2 _m2;
private object _mainContent;
private object _subContent;
private int _selectedIndex;
//Properties
public string Text { get; set; }
public List<Item> Items { get; set; }
public ICommand MyCommand { get; set; }
public string Content { get; set; }
public object MainContent
{
get { return _mainContent; }
set { _mainContent = value; OnChange("MainContent"); }
}
public object SubContent
{
get { return _subContent; }
set { _subContent = value; OnChange("SubContent"); }
}
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
//Action on selection change
switch (_selectedIndex)
{
case 0: Text = "Selected Item is 1"; break;
case 1: Text = "Selected Item is 2"; break;
case 2: Text = "Selected Item is 3"; break;
}
OnChange("Text");
}
}
//Constructor
public MyCode()
{
Content = "First";
MyCommand = new Command(Do, CanDo);
_m1 = new MainContent1();
_m2 = new MainContent2();
_s1 = new Sub1.S1();
_s2 = new Sub2.S1();
MainContent = _m1;
SubContent = _s1;
Items = new List<Item>(3);
for (int i = 1; i < 4; i++)
Items.Add(new Item { Id = i, Name = "Name " + i });
SelectedIndex = 0;
}
//Action on Button Click
private void Do(object parameter)
{
if(Content == "First")
{
MainContent = _m2;
SubContent = _s2;
Content = "Second";
}
else
{
MainContent = _m1;
SubContent = _s1;
Content = "First";
}
OnChange("Content");
}
private bool CanDo(object parameter) => true;
//Inotify
public event PropertyChangedEventHandler PropertyChanged;
private void OnChange(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Changed MainWindow.xaml to -
<Window.Resources>
<local:MyCode x:Key="Code"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource Code}}">
<Grid.RowDefinitions...>
<Grid.ColumnDefinitions...>
<ComboBox ItemsSource="{Binding Path=Items}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedIndex="{Binding Path=SelectedIndex}"/>
<Button x:Name="butt" Grid.Column="1"
Content="{Binding Path=Content}"
Command="{Binding Path=MyCommand}"/>
<ContentControl Grid.Row="1" Grid.ColumnSpan="2"
Content="{Binding Path=MainContent}"/>
</Grid>
Both of MainContent1.xaml and MainContent2.xaml now contains -
<ContentControl Content="{Binding Path=SubContent}"/>
And S1.xaml under folder Sub1 and Sub2 contains -
<TextBlock Text="{Binding Path=Text}" />
Done!

Hierarchial Data Template binding with ItemsControl does't work - WPF

I have an ItemsControl, which is bound to a collection, and I specify a HierarchicalDataTemplate to render the items. I got a template selector as well since the rendering for individual items are going to wary. For some reason this does not work.
Below is an extract of the code, can you please help? I'm looking to render items from the following hierarchy Parent->Child Collection->SubChildCollection (as in the below code).
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication2="clr-namespace:WpfApplication2" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="EntityItemTemplate" DataType="{x:Type WpfApplication2:EntityItem}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<WpfApplication2:TemplateSelector x:Key="ts" EntityItemTemplate="{StaticResource EntityItemTemplate}"/>
<HierarchicalDataTemplate x:Key="hdt" DataType="{x:Type WpfApplication2:EntityGroup}" ItemsSource="{Binding Path=EntityItems}" ItemTemplateSelector="{StaticResource ts}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" ItemsSource="{Binding Path=Entity.EntityGroups}" ItemTemplate="{StaticResource hdt}"></ItemsControl>
</Grid>
</Window>
public partial class MainWindow : Window
{
ViewModel vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = vm;
vm.Entity = new Entity()
{
Name = "abc",
EntityGroups = new ObservableCollection<EntityGroup>()
{
new EntityGroup()
{
EntityItems = new ObservableCollection<EntityItem>()
{
new EntityItem() {Name = "Entity1"},
new EntityItem() {Name = "Entity2"}
}
}
}
};
}
}
public class TemplateSelector:DataTemplateSelector
{
public DataTemplate EntityItemTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null || !(item is EntityItem))
return base.SelectTemplate(item, container);
return EntityItemTemplate;
}
}
public class ViewModel:NotificationObject
{
private Entity _entity;
public Entity Entity
{
get { return _entity; }
set
{
_entity = value;
RaisePropertyChanged(() => Entity);
}
}
}
public class Entity:NotificationObject
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
private ObservableCollection<EntityGroup> _entityGroups;
public ObservableCollection<EntityGroup> EntityGroups
{
get { return _entityGroups; }
set
{
_entityGroups = value;
RaisePropertyChanged(() => EntityGroups);
}
}
}
public class EntityGroup:NotificationObject
{
public string Name { get; set; }
private ObservableCollection<EntityItem> _entityItems;
public ObservableCollection<EntityItem> EntityItems
{
get { return _entityItems; }
set
{
_entityItems = value;
RaisePropertyChanged(() => EntityItems);
}
}
}
public class EntityItem:NotificationObject
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
}
Because you're using it in an ItemsControl, you shouldn't use a HierarchicalDataTemplate. As MSDN states, a HierarchicalDataTemplate:
Represents a DataTemplate that supports HeaderedItemsControl, such as
TreeViewItem or MenuItem.
An ItemsControl shows its data inside a ContentPresenter. A TreeView will generate TreeViewItems, and a Menu will generate MenuItems.
If you want to use a DataTemplateSelector to display different templates for items in the ItemsControl, just set it directly as the ItemTemplateSelector:
<ItemsControl ItemsSource="{Binding Path=Entity.EntityGroups}"
ItemTemplateSelector="{StaticResource ts}" />
If you want a hierarchical display of your data, use a TreeView:
<TreeView ItemsSource="{Binding Path=Entity.EntityGroups}"
ItemTemplate="{StaticResource hdt}" />

ItemsControl that contains bound ComboBox in ItemTemplate

I've just stuck in a problem to bind collection in ItemsControl with ItemTeplate that contains bounded ComboBox.
In my scenario I need to "generate" form that includes textbox and combobox for each item in collection and let user to update items. I could use DataGrid for that but I'd like to see all rows in edit mode, so I use ItemsControl with custom ItemTemplate.
It's ok to edit textboxes but when you try to change any ComboBox, all other ComboBoxes in other rows will change too.
Is it a bug or feature?
Thanks, Ondrej
Window.xaml
<Window x:Class="ComboInItemsControlSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="480" Width="640">
<Window.Resources>
<CollectionViewSource x:Key="cvsComboSource"
Source="{Binding Path=AvailableItemTypes}" />
<DataTemplate x:Key="ItemTemplate">
<Border BorderBrush="Black" BorderThickness="0.5" Margin="2">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=ItemValue}" />
<ComboBox Grid.Column="2"
SelectedValue="{Binding Path=ItemType}"
ItemsSource="{Binding Source={StaticResource cvsComboSource}}"
DisplayMemberPath="Name"
SelectedValuePath="Value" />
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Path=SampleItems}"
ItemTemplate="{StaticResource ItemTemplate}"
Margin="10" />
</Grid>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel
{
public ViewModel()
{
SampleItems = new List<SampleItem> {
new SampleItem { ItemValue = "Value 1" },
new SampleItem { ItemValue = "Value 2" },
new SampleItem { ItemValue = "Value 3" }
};
AvailableItemTypes = new List<SampleItemType> {
new SampleItemType { Name = "Type 1", Value = 1 },
new SampleItemType { Name = "Type 2", Value = 2 },
new SampleItemType { Name = "Type 3", Value = 3 },
new SampleItemType { Name = "Type 4", Value = 4 }
};
}
public IList<SampleItem> SampleItems { get; private set; }
public IList<SampleItemType> AvailableItemTypes { get; private set; }
}
public class SampleItem : ObservableObject
{
private string _itemValue;
private int _itemType;
public string ItemValue
{
get { return _itemValue; }
set { _itemValue = value; RaisePropertyChanged("ItemValue"); }
}
public int ItemType
{
get { return _itemType; }
set { _itemType = value; RaisePropertyChanged("ItemType"); }
}
}
public class SampleItemType : ObservableObject
{
private string _name;
private int _value;
public string Name
{
get { return _name; }
set { _name = value; RaisePropertyChanged("Name"); }
}
public int Value
{
get { return _value; }
set { _value = value; RaisePropertyChanged("Value"); }
}
}
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Picture
here you can see the result on picture
I believe it's because you're binding to a CollectionViewSource, which tracks the current item. Try binding directly to your list instead, which won't track the current item
<ComboBox Grid.Column="2"
SelectedValue="{Binding Path=ItemType}"
DisplayMemberPath="Name"
SelectedValuePath="Value"
ItemsSource="{Binding RelativeSource={
RelativeSource AncestorType={x:Type ItemsControl}},
Path=DataContext.AvailableItemTypes}" />
While you have a combobox in each row, it doesnt see these comboboxes as being seperate. i.e. They are all using the same collection, and the same selectedValue, so when a value changes in one box, it changes in all of them.
The best way to fix this is to add the SampleItemType collection as a property on your SampleItem model and to then bind the combo box to that property.

Problem showing selected value of combobox when it is bind to a List<T> using Linq to Entities

I have a ComboBox which is has an ItemTemplate applied on it and is bind to a List of entity return using linq. I'm using mvvm. It is bind to it successfully but when I set the selected value of it from code at runtime to show the selected value coming from db it doesn't select it. For reference here is my ComboBox xaml.
<DataTemplate x:Key="ManufacturerDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="imgManufacturer" Width="25" Height="25"
Source="{Binding Path=ManufacturerImage}" Grid.Column="0"/>
<TextBlock x:Name="txtManufacturer" Grid.Column="1" HorizontalAlignment="Left"
VerticalAlignment="Center" Text="{Binding Path=ManufacturerName}"
Tag="{Binding Path=ManufacturerID}"/>
</Grid>
</DataTemplate>
<ComboBox x:Name="cboManufacturer"
SelectionChanged="cboManufacturer_SelectionChanged"
ItemsSource = "{Binding Path=CurrentManufacturers}"
SelectedValue="{Binding Path=SelectedManufacturer}"
Grid.Column="3" Grid.Row="2" Margin="20,9.25,68,7.75"
ItemTemplate="{StaticResource ManufacturerDataTemplate}" TabIndex="6"/>
Here is my part from code behind from viewModel.
List<tblManufacturer> currentManufacturers
= new List<tblManufacturer>();
tblManufacturer selectedManufacturer = null;
public List<tblManufacturer> CurrentManufacturers
{
get
{
return currentManufacturers;
}
set
{
currentManufacturers = value;
NotifyPropertyChanged("CurrentManufacturers");
}
}
public tblManufacturer SelectedManufacturer
{
get
{
return selectedManufacturer;
}
set
{
selectedManufacturer = currentManufacturers.Where(mm => mm.ManufacturerID == Convert.ToInt32(selectedDevice.tblManufacturer.EntityKey.EntityKeyValues[0].Value)).First();
NotifyPropertyChanged("SelectedManufacturer");
}
}
Here is the sample code snippet:
Xaml for ComboBox:
<ComboBox ItemsSource="{Binding ManufacturerList}" DisplayMemberPath="Name" SelectedValuePath="ID"
SelectedItem="{Binding SelectedManufacturer}"/>
ViewModel code :
public class Manufacturer
{
public int ID { get; set; }
public string Name { get; set; }
}
private List<Manufacturer> _manufactuerlist;
private Manufacturer _selectedManufacturer;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public Manufacturer SelectedManufacturer
{
get
{
return _selectedManufacturer;
}
set
{
_selectedManufacturer = value;
NotifyPropertyChanged("SelectedManufacturer");
}
}
public List<Manufacturer> ManufacturerList
{
get
{
return _manufactuerlist;
}
set
{
_manufactuerlist = value;
NotifyPropertyChanged("ManufacturerList");
}
}
And finally Set the Selected Manufacturer in your view model like this:
SelectedManufacturer = _manufactuerlist.Find(m => m.ID == 2);

Resources