Though many solutions exist, but I want to hit a particular problem statement that I come across frequently, that is similar to:
In a WPF DataGrid, bind through MVVM approach,
- If I check "Select All" checkbox in the header, all the checkboxes in that column should be checked.
- If I uncheck "Select All" checkbox in the header, all the checkboxes in that column should be unchecked
- If I uncheck any of the checkbox in that column of any row, then the "Select All" checkbox in the header should be unchecked.
- If all checkboxes in that column are checked(one each row), then the "Select All" checkbox in the header should be checked (left to your exploration ;))
Model:
public class Employee : INotifyPropertyChanged
{
private int _id;
private string _name;
private double _salary;
private bool _isSelected;
public int Id { get => _id; set => _id = value; }
public string Name { get => _name; set => _name = value; }
public double Salary { get => _salary; set => _salary = value; }
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
}
}
ViewModel:
public class EmployeeDetailsViewModel
{
public EmployeeDetailsViewModel()
{
SelectAllCommand = new DelegateCommand<bool?>(HandleSelectAllCommand);
}
ObservableCollection<Employee> _employees = null;
/// <summary>
/// Collection bound to DataGrid
/// </summary>
public ObservableCollection<Employee> Employees { get => _employees; set { _employees = value; } }
/// <summary>
/// Command bound to SelectAll checkbox in XAML
/// </summary>
public ICommand SelectAllCommand { get; set; }
private void HandleSelectAllCommand(bool? isChecked)
{
if (isChecked.HasValue)
{
foreach (var employee in Employees)
{
employee.IsSelected = isChecked.Value;
}
}
}
void PrepareData()
{
this.Employees = new ObservableCollection<Employee>()
{
new Employee{Id=1, Name="abc", Salary=100000.00d},
new Employee{Id=2, Name="def", Salary=200000.00d},
new Employee{Id=3, Name="ghi", Salary=300000.00d},
new Employee{Id=4, Name="jkl", Salary=400000.00d}
};
}
}
View(XAML):
<DataGrid ItemsSource="{Binding Employees}" AutoGenerateColumns="False" CanUserAddRows="False" >
<DataGrid.Columns>
<DataGridTemplateColumn >
<DataGridTemplateColumn.Header>
<CheckBox x:Name="chkSelectAll">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path = DataContext.SelectAllCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid,AncestorLevel=1}}"
CommandParameter="{Binding ElementName=chkSelectAll, Path=IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Unchecked">
<local:EventTriggerPropertySetAction TargetObject="{Binding ElementName=chkSelectAll}" PropertyName="IsChecked" PropertyValue="False"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Id}" Header="Id"/>
<DataGridTextColumn Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Header="Name"/>
<DataGridTextColumn Binding="{Binding Salary, UpdateSourceTrigger=PropertyChanged}" Header="Salary"/>
</DataGrid.Columns>
</DataGrid>
To access the interaction triggers, you need to add following name space to Window/UserControl markup tags:
<Window x:Class="TestApp.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:i="http://schemas.microsoft.com/expression/2010/interactivity">
The important part of simplification is due to <local:SetPropertyAction ...
based on this SO answer or this SO answer.
Don't forget to up-vote and encourage, when you visit those link ;)
Hope this help some one!