我试图选择 DataGrid 中的所有复选框,但使用下面的代码没有得到任何结果
这是我在单击主复选框时调用的函数
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 是 DataGrid,但我意识到找到了任何复选框。
这是我在数据网格中创建复选框时使用的 XAML
<DataGrid.Columns>
<DataGridCheckBoxColumn x:Name="col0" HeaderStyle="{StaticResource ColumnHeaderGripperStyle}">
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Click="CheckUnCheckAll" >
</CheckBox>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
<DataGrid.Columns>
这是我的 DataGrid 的图片
有没有办法以编程方式选择所有复选框?
编辑 我已经尝试按照此步骤
你可以看到我的代码是相同的,但对我不起作用
TLDR; 这就是你想要的,代码如下:
执行此操作的正确位置是在您的 ViewModel 中。您的复选框可以具有三种状态,您想利用所有这些状态:
您将希望在选中/取消选中某个项目时更新 CheckBox,并在更改 CheckBox 时更新所有项目 - 仅实现此一种方法将使 CheckBox 处于无效状态,这可能会对用户体验产生负面影响。我的建议是:全力以赴,抓好落实。为此,您需要了解导致更改的原因 - 条目的复选框或标题中的复选框。
我会这样做:
首先,您的项目需要一个 ViewModel,我在这里使用了一个非常简化的视图模型,仅包含
IsChecked
属性。
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));
}
}
您的主 ViewModel 将包含所有项目的集合。每当项目的
IsChecked
属性发生更改时,您都必须检查 all 项目是否已选中/取消选中,并更新标题中的复选框(或者更确切地说是其数据源的值)。
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));
}
}
演示 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>
这是基于@Manfred 的解决方案的修改。我用
Command
而不是 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>
视图模型:
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));
}
}
源代码可在这里
您在示例中所做的是迭代数据项而不是通过控件(我想您没有作为 ItemsSource 的控件)。
在您发布的链接中,
YourClass
是ViewModel中的类,是网格行的数据对象。
这个应该可以在您这边进行最少的代码更改(但我更喜欢在 ViewModel 中使用 CheckUncheckCommand + 将 IsChecked
绑定到
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;
}
}
DataGridColumn
,无需任何其他代码。XAML 代码:
<DataGrid IsReadonly="true">
<DataGrid.Columns>
<fc:DataGridCheckAllColumn Binding="{Binding IsChecked}" />
<!-- else columns -->
<DataGridTextColumn Binding="{Binding EntityName}" />
</DataGrid.Columns>
</DataGrid>
C#代码:
// Code Auther: Flithor (Mr. Squirrel.Downy)
// License: MIT
// =======WARNING=======
// Use this 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 display items
private IList<object>? currentItems;
//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(DataGridCheckBoxColumn), 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) return;
ownerDatagrid = GetParentDataGrid();
if (ownerDatagrid == null) return;
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();
}
//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
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];
Array.Fill(res, Equals(value, true));
return res;
}
}
#endregion
}
}