WPF 选择 DataGrid 中的所有复选框

问题描述 投票:0回答:4

我试图选择 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 的图片

有没有办法以编程方式选择所有复选框?

编辑 我已经尝试按照此步骤

你可以看到我的代码是相同的,但对我不起作用

c# wpf xaml datagrid
4个回答
29
投票

TLDR; 这就是你想要的,代码如下:

执行此操作的正确位置是在您的 ViewModel 中。您的复选框可以具有三种状态,您想利用所有这些状态:

  1. 已检查 - 每一项都已检查
  2. 未选中 - 未选中任何项目
  3. 不确定 - 有些项目已检查,有些则未检查

您将希望在选中/取消选中某个项目时更新 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>

7
投票

这是基于@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));
    }
}

源代码可在这里


3
投票

您在示例中所做的是迭代数据项而不是通过控件(我想您没有作为 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; } }
    

0
投票
这是 DataGrid 列定义用于检查所有内容的工作,只需将其用作普通

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 } }
    
© www.soinside.com 2019 - 2024. All rights reserved.