Reputation: 349
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 clicked
private void CheckUnCheckAll(object sender, RoutedEventArgs e)
{
CheckBox chkSelectAll = ((CheckBox)sender);
if (chkSelectAll.IsChecked == true)
{
dgUsers.Items.OfType<CheckBox>().ToList().ForEach(x => x.IsChecked = true);
}
else
{
dgUsers.Items.OfType<CheckBox>().ToList().ForEach(x => x.IsChecked = false);
}
}
dgUsers is the DataGrid but as I realize any checkbox is found.
This is the XAML that I'm using tho create the CheckBox in the datagrid
<DataGrid.Columns>
<DataGridCheckBoxColumn x:Name="col0" HeaderStyle="{StaticResource ColumnHeaderGripperStyle}">
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Click="CheckUnCheckAll" >
</CheckBox>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
<DataGrid.Columns>
And this is the picture of my DataGrid
Is there some way to select all checkbox programatically ?
Edit I already tried to follow this steps
that you can see that my code is the same there but didn't work to me
Upvotes: 12
Views: 22782
Reputation: 1167
This is a out-of-the-box DataGrid Column define work for check all, just use it as a DataGridCheckBoxColumn
, without any other code behind in the view, very use for pure style or simplify the modify checkable property.
The items changes can be fed back to the aggregate checkbox. Supports collection realtime changes, data virtualization and CollectionView
filtering results.
It behaves as designed:
It used System.Linq
so needs .NET Framework 3.5 or higher.
But you can modify the linq logical to loop and System.Linq.Expression
as reflection to be compatible with .NET Framework 3.0, please do it yourself.
XAML Code:
<DataGrid.Columns>
<!-- Binding to bool property for entity which means it checked -->
<fc:DataGridCheckAllColumn Binding="{Binding IsChecked}" />
<!-- else columns -->
<DataGridTextColumn Binding="{Binding EntityName}" Header="Name" />
</DataGrid.Columns>
Friendly reminder:
- If you set
VirtualizingPanel.IsVirtualizing="True"
on DataGrid, you should also setVirtualizingPanel.VirtualizationMode="Standard"
, otherwise it will caused unexpected binding behavior.- Don't set any not implement
INotifyCollectionChanged
collection to ItemsSource, and don't use not implementINotifyPropertyChanged
class as data source, it will caused memory leak.
(If leak, is not caused by my class, don't ask me.)
The class Code(C#):
// Code Auther: Flithor (Mr.Squirrel.Downy)
// License: MIT
// =======WARNING=======
// Use unknow code at your own risk
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
using Expression = System.Linq.Expressions.Expression;
namespace Flithor_Codes
{
/// <summary>
/// A DataGrid Column work for binding to Checked property for item element, and can Check All
/// </summary>
public class DataGridCheckAllColumn : DataGridBoundColumn
{
#region Private Fields
//CheckBox in header
private readonly CheckBox checkAllCheckBox;
//owner DataGrid control for this column
private DataGrid ownerDatagrid;
//owner DataGrid current delegate get current list version
//if version changed then change bindings
private Func<int> getInnerEnumeratorVersion;
//cached list version
private int cachedInnerVersion;
//default style for CheckBox
private static Style _defaultElementStyle;
#endregion
#region Initialize Control
public static Style DefaultElementStyle
{
get
{
if (_defaultElementStyle == null)
{
var style = new Style(typeof(CheckBox))
{
Setters =
{
new Setter(UIElement.FocusableProperty, false),
new Setter(CheckBox.HorizontalAlignmentProperty, HorizontalAlignment.Center),
new Setter(CheckBox.VerticalAlignmentProperty, VerticalAlignment.Center)
}
};
style.Seal();
_defaultElementStyle = style;
}
return _defaultElementStyle;
}
}
static DataGridCheckAllColumn()
{
//override default element style
ElementStyleProperty.OverrideMetadata(typeof(DataGridCheckAllColumn), new FrameworkPropertyMetadata(DefaultElementStyle));
//make column readonly by default
IsReadOnlyProperty.OverrideMetadata(typeof(DataGridCheckAllColumn), new FrameworkPropertyMetadata(true));
//not allows move column
CanUserReorderProperty.OverrideMetadata(typeof(DataGridCheckAllColumn), new FrameworkPropertyMetadata(false));
//not allows resize column
CanUserResizeProperty.OverrideMetadata(typeof(DataGridCheckAllColumn), new FrameworkPropertyMetadata(false));
//not allows order items by click header
CanUserSortProperty.OverrideMetadata(typeof(DataGridCheckAllColumn), new FrameworkPropertyMetadata(false));
}
public DataGridCheckAllColumn()
{
//override header
Header = checkAllCheckBox = new CheckBox();
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (ownerDatagrid == null)
{
ownerDatagrid = GetParentDataGrid();
if (ownerDatagrid != null)
{
InitInnerVersionDetect(ownerDatagrid.Items);
((INotifyPropertyChanged)ownerDatagrid.Items).PropertyChanged += OnPropertyChanged;
//if DataGrid has items now, init bindings
checkAllCheckBox.IsEnabled = ownerDatagrid.Items.Count > 0;
if (checkAllCheckBox.IsEnabled)
ResetCheckCurrentAllBinding();
}
}
base.OnPropertyChanged(e);
}
//find parent DataGrid(if not end initialize, may return null)
private DataGrid GetParentDataGrid()
{
DependencyObject elment = checkAllCheckBox;
do
{
elment = VisualTreeHelper.GetParent(elment);
}
while (elment != null && !(elment is DataGrid));
return elment as DataGrid;
}
#endregion
#region Generate Element
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
return GenerateCheckBox(false, cell, dataItem);
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
return GenerateCheckBox(true, cell, dataItem);
}
private CheckBox GenerateCheckBox(bool isEditing, DataGridCell cell, object dataItem)
{
var checkBox = new CheckBox();
ApplyStyle(isEditing, checkBox);
ApplyBinding(dataItem, checkBox);
return checkBox;
}
private void ApplyBinding(object dataItem, CheckBox checkBox)
{
var binding = CloneBinding(Binding, dataItem);
if (binding is Binding newBinding)
{
newBinding.Mode = BindingMode.TwoWay;
newBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
}
BindingOperations.ClearBinding(checkBox, CheckBox.IsCheckedProperty);
checkBox.SetBinding(CheckBox.IsCheckedProperty, binding);
}
internal void ApplyStyle(bool isEditing, FrameworkElement element)
{
Style style = PickStyle(isEditing);
if (style != null)
{
element.Style = style;
}
}
private Style PickStyle(bool isEditing)
{
Style style = isEditing ? EditingElementStyle : ElementStyle;
if (isEditing && (style == null))
{
style = ElementStyle;
}
return style;
}
#endregion
#region Update Binding
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (ownerDatagrid == null || e.PropertyName != nameof(ownerDatagrid.Items.Count))
return;
//if items count changed then means the collection may changed
if (ownerDatagrid.Items.Count == 0)
{
//if Items goes empty then clear the check binding and disable check all
BindingOperations.ClearBinding(checkAllCheckBox, CheckBox.IsCheckedProperty);
checkAllCheckBox.IsEnabled = false;
}
else
{
//else update the binding to current displayed items
ResetCheckCurrentAllBinding();
checkAllCheckBox.IsEnabled = true;
}
}
private void ResetCheckCurrentAllBinding()
{
//If version changed then update binding by current items
if (ownerDatagrid == null || !InnerVersionChanged()) return;
var checkAllBinding = new MultiBinding
{
Converter = AllBoolStatusConverter.Default,
Mode = BindingMode.TwoWay
};
//binding items by current displayed items
var currentItems = ownerDatagrid.Items.OfType<object>().ToList();
foreach (var item in currentItems)
{
checkAllBinding.Bindings.Add(CloneBinding((Binding)Binding, item));
}
//clear old binding if exists
BindingOperations.ClearBinding(checkAllCheckBox, CheckBox.IsCheckedProperty);
checkAllCheckBox.SetBinding(CheckBox.IsCheckedProperty, checkAllBinding);
}
//generate DataGrid.Items version get delegate
private void InitInnerVersionDetect(ItemCollection itemCollection)
{
//Timestamp property is the version mark of ItemCollection to tell us is it changed
var collectionTimestampProerty = itemCollection.GetType()
.GetProperty("Timestamp", BindingFlags.Instance | BindingFlags.NonPublic);
//use Linq Expression build a simple delegate to access Timestamp property
getInnerEnumeratorVersion = Expression.Lambda<Func<int>>(Expression.Property(
Expression.Constant(itemCollection),
collectionTimestampProerty)).Compile();
}
//get the inner collection version to detect is it changed
private bool InnerVersionChanged()
{
var currentInnerVersion = getInnerEnumeratorVersion.Invoke();
if (currentInnerVersion != cachedInnerVersion)
{
cachedInnerVersion = currentInnerVersion;
return true;
}
return false;
}
//create a new binding instance by existed binding
private static BindingBase CloneBinding(BindingBase bindingBase, object source)
{
switch (bindingBase)
{
case Binding binding:
var resultBinding = new Binding
{
Source = source,
AsyncState = binding.AsyncState,
BindingGroupName = binding.BindingGroupName,
BindsDirectlyToSource = binding.BindsDirectlyToSource,
Converter = binding.Converter,
ConverterCulture = binding.ConverterCulture,
ConverterParameter = binding.ConverterParameter,
//ElementName = binding.ElementName,
FallbackValue = binding.FallbackValue,
IsAsync = binding.IsAsync,
Mode = binding.Mode,
NotifyOnSourceUpdated = binding.NotifyOnSourceUpdated,
NotifyOnTargetUpdated = binding.NotifyOnTargetUpdated,
NotifyOnValidationError = binding.NotifyOnValidationError,
Path = binding.Path,
//RelativeSource = binding.RelativeSource,
StringFormat = binding.StringFormat,
TargetNullValue = binding.TargetNullValue,
UpdateSourceExceptionFilter = binding.UpdateSourceExceptionFilter,
UpdateSourceTrigger = binding.UpdateSourceTrigger,
ValidatesOnDataErrors = binding.ValidatesOnDataErrors,
ValidatesOnExceptions = binding.ValidatesOnExceptions,
XPath = binding.XPath,
};
foreach (var validationRule in binding.ValidationRules)
{
resultBinding.ValidationRules.Add(validationRule);
}
return resultBinding;
case MultiBinding multiBinding:
var resultMultiBinding = new MultiBinding
{
BindingGroupName = multiBinding.BindingGroupName,
Converter = multiBinding.Converter,
ConverterCulture = multiBinding.ConverterCulture,
ConverterParameter = multiBinding.ConverterParameter,
FallbackValue = multiBinding.FallbackValue,
Mode = multiBinding.Mode,
NotifyOnSourceUpdated = multiBinding.NotifyOnSourceUpdated,
NotifyOnTargetUpdated = multiBinding.NotifyOnTargetUpdated,
NotifyOnValidationError = multiBinding.NotifyOnValidationError,
StringFormat = multiBinding.StringFormat,
TargetNullValue = multiBinding.TargetNullValue,
UpdateSourceExceptionFilter = multiBinding.UpdateSourceExceptionFilter,
UpdateSourceTrigger = multiBinding.UpdateSourceTrigger,
ValidatesOnDataErrors = multiBinding.ValidatesOnDataErrors,
ValidatesOnExceptions = multiBinding.ValidatesOnDataErrors,
};
foreach (var validationRule in multiBinding.ValidationRules)
{
resultMultiBinding.ValidationRules.Add(validationRule);
}
foreach (var childBinding in multiBinding.Bindings)
{
resultMultiBinding.Bindings.Add(CloneBinding(childBinding, source));
}
return resultMultiBinding;
case PriorityBinding priorityBinding:
var resultPriorityBinding = new PriorityBinding
{
BindingGroupName = priorityBinding.BindingGroupName,
FallbackValue = priorityBinding.FallbackValue,
StringFormat = priorityBinding.StringFormat,
TargetNullValue = priorityBinding.TargetNullValue,
};
foreach (var childBinding in priorityBinding.Bindings)
{
resultPriorityBinding.Bindings.Add(CloneBinding(childBinding, source));
}
return resultPriorityBinding;
default:
throw new NotSupportedException("Failed to clone binding");
}
}
/// <summary>
/// A MultiValueConverter to merge all items bound bool value into one
/// </summary>
private class AllBoolStatusConverter : IMultiValueConverter
{
public static readonly AllBoolStatusConverter Default = new AllBoolStatusConverter();
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 0 || values.OfType<bool>().Count() != values.Length)
return false;
// detect all items are equals the first
var firstStatus = values.First();
foreach (var value in values)
{
//any one not equals to first then return null
if (!Equals(value, firstStatus))
return null;
}
return firstStatus;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
//if the check all CheckBox checked or unchecked then update all items bound value
var res = new object[targetTypes.Length];
for (int i = 0; i < res.Length; i++)
res[i] = value;
return res;
}
}
#endregion
}
}
Notice:
- The
CheckBox
in column header has created by column code. If you use the above code directly, please do not set theHeaders
of this column.- Its implementation is based on WPF "Binding Engine", and the code efficiency may be relatively low. If you have an extremely large amount of data, I recommended to use the internal logic implementation in your view model instead.
Upvotes: 1
Reputation: 632
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
Upvotes: 7
Reputation: 7325
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;
}
}
Upvotes: 3
Reputation: 13394
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>
Upvotes: 29