I\'m trying to select all CheckBox in a DataGrid but I didn\'t get any result using this code bellow
This is the function that I\'m calling when the main CheckBox is
What you do in your example is iterating through data item not through the controls(I suppose you have no controls as ItemsSource).
In the link you have posted YourClass
is the class from ViewModel, data object for grid's row.
This one should work with minimal code changes on your side(but I would prefer to handle it in the ViewModel with something like CheckUncheckCommand + binding of IsChecked
to the CommandParameter
):
<DataGridCheckBoxColumn x:Name="col0" HeaderStyle="{StaticResource ColumnHeaderGripperStyle}" DisplayIndex="0">
private void CheckUnCheckAll(object sender, RoutedEventArgs e)
{
var chkSelectAll = sender as CheckBox;
var firstCol = dgUsers.Columns.OfType<DataGridCheckBoxColumn>().FirstOrDefault(c => c.DisplayIndex == 0);
if (chkSelectAll == null || firstCol == null || dgUsers?.Items == null)
{
return;
}
foreach (var item in dgUsers.Items)
{
var chBx = firstCol.GetCellContent(item) as CheckBox;
if (chBx == null)
{
continue;
}
chBx.IsChecked = chkSelectAll.IsChecked;
}
}
This is a modification based on @Manfred's solution. I use Command
instead of event
.
XAML:
<DataGrid ItemsSource="{Binding Students}" AutoGenerateColumns="True" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding DataContext.IsAllSelected, RelativeSource={RelativeSource AncestorType=DataGrid}}" Command="{Binding DataContext.CheckAllStudentsCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}" Command="{Binding DataContext.CheckStudentCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private List<Student> students;
public List<Student> Students
{
get { return students; }
set { students = value; OnPropertyChanged(); }
}
private bool? isAllSelected;
public bool? IsAllSelected
{
get { return isAllSelected; }
set { isAllSelected = value; OnPropertyChanged(); }
}
public RelayCommand CheckStudentCommand { get; private set; }
public RelayCommand CheckAllStudentsCommand { get; private set; }
public MainWindowViewModel()
{
Students = new List<Student>() { new Student { Name = "Walter" }, new Student { Name = "Jenny" }, new Student { Name = "Joe" } };
CheckStudentCommand = new RelayCommand(OnCheckStudent);
CheckAllStudentsCommand = new RelayCommand(OnCheckAllStudents);
IsAllSelected = false;
}
private void OnCheckAllStudents()
{
if (IsAllSelected == true)
Students.ForEach(x => x.IsChecked = true);
else
Students.ForEach(x => x.IsChecked = false);
}
private void OnCheckStudent()
{
if (Students.All(x => x.IsChecked))
IsAllSelected = true;
else if (Students.All(x => !x.IsChecked))
IsAllSelected = false;
else
IsAllSelected = null;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Source code is available here
TLDR; This is what you want, code below:
The proper place to do this would be in your ViewModel. Your CheckBox can have three states, all of which you want to make use of:
You will want to update the CheckBox whenever an item is checked/unchecked and update all items whenever the CheckBox was changed - implementing this only one way will leave the CheckBox in an invalid state which might have a negative impact on user experience. My suggestion: go all the way and implement it properly. To do this you need to be aware of which caused the change - the CheckBox of an entry or the CheckBox in the header.
Here is how I would do it:
First you need a ViewModel for your items, I've used a very simplified one here that only contains the IsChecked
property.
public class Entry : INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set
{
if (value == _isChecked) return;
_isChecked = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Your main ViewModel will have a collection of all items. Whenever an item's IsChecked
property changes, you'll have to check if all items are checked/unchecked and update the CheckBox in the header (or rather the value of its datasource).
public class ViewModel : INotifyPropertyChanged
{
public List<Entry> Entries
{
get => _entries;
set
{
if (Equals(value, _entries)) return;
_entries = value;
OnPropertyChanged();
}
}
public ViewModel()
{
// Just some demo data
Entries = new List<Entry>
{
new Entry(),
new Entry(),
new Entry(),
new Entry()
};
// Make sure to listen to changes.
// If you add/remove items, don't forgat to add/remove the event handlers too
foreach (Entry entry in Entries)
{
entry.PropertyChanged += EntryOnPropertyChanged;
}
}
private void EntryOnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
// Only re-check if the IsChecked property changed
if(args.PropertyName == nameof(Entry.IsChecked))
RecheckAllSelected();
}
private void AllSelectedChanged()
{
// Has this change been caused by some other change?
// return so we don't mess things up
if (_allSelectedChanging) return;
try
{
_allSelectedChanging = true;
// this can of course be simplified
if (AllSelected == true)
{
foreach (Entry kommune in Entries)
kommune.IsChecked = true;
}
else if (AllSelected == false)
{
foreach (Entry kommune in Entries)
kommune.IsChecked = false;
}
}
finally
{
_allSelectedChanging = false;
}
}
private void RecheckAllSelected()
{
// Has this change been caused by some other change?
// return so we don't mess things up
if (_allSelectedChanging) return;
try
{
_allSelectedChanging = true;
if (Entries.All(e => e.IsChecked))
AllSelected = true;
else if (Entries.All(e => !e.IsChecked))
AllSelected = false;
else
AllSelected = null;
}
finally
{
_allSelectedChanging = false;
}
}
public bool? AllSelected
{
get => _allSelected;
set
{
if (value == _allSelected) return;
_allSelected = value;
// Set all other CheckBoxes
AllSelectedChanged();
OnPropertyChanged();
}
}
private bool _allSelectedChanging;
private List<Entry> _entries;
private bool? _allSelected;
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Demo XAML:
<DataGrid ItemsSource="{Binding Entries}" AutoGenerateColumns="False" IsReadOnly="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}">
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MainWindow}, Path=ViewModel.AllSelected}">Select All</CheckBox>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>