WPF 选择 DataGrid 中的所有 CheckBox

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/48955781/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 14:09:48  来源:igfitidea点击:

WPF Select all CheckBox in a DataGrid

c#wpfxamldatagrid

提问by Marcos Brinner pikatoons

I'm trying to select all CheckBox in a DataGrid but I didn't get any result using this code bellow

我正在尝试选择 DataGrid 中的所有 CheckBox,但使用以下代码没有得到任何结果

This is the function that I'm calling when the main CheckBox is clicked

这是单击主 CheckBox 时调用的函数

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.

dgUsers 是 DataGrid,但我意识到找到了任何复选框。

This is the XAML that I'm using tho create the CheckBox in the datagrid

这是我正在使用的 XAML,用于在数据网格中创建复选框

<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

这是我的 DataGrid 图片

enter image description here

在此处输入图片说明

Is there some way to select all checkbox programatically ?

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

EditI already tried to follow this steps

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

that you can see that my code is the same there but didn't work to me

你可以看到我的代码在那里是一样的,但对我不起作用

回答by Manfred Radlwimmer

TLDR;This is what you want, code below:

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

Demo of what I'm about to explain

演示我将要解释的内容

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:

执行此操作的正确位置是在您的 ViewModel 中。您的 CheckBox 可以具有三种状态,您要使用所有这些状态:

  1. Checked - Every item is checked
  2. Unchecked - No item is checked
  3. Indeterminate - Some items are checked, some are not
  1. 已检查 - 每个项目都经过检查
  2. 未选中 - 未选中任何项目
  3. 不确定 - 有些项目被检查,有些没有

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.

您将希望在选中/取消选中某个项目时更新 CheckBox,并在更改 CheckBox 时更新所有项目 - 仅实施这种方法会使 CheckBox 处于无效状态,这可能会对用户体验产生负面影响。我的建议:一路走好并正确实施。为此,您需要知道是哪个导致了更改 - 条目的 CheckBox 或标题中的 CheckBox。

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 IsCheckedproperty.

首先,您的项目需要一个 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));
    }
}

Your main ViewModel will have a collection of all items. Whenever an item's IsCheckedproperty changes, you'll have to check if allitems are checked/unchecked and update the CheckBox in the header (or rather the value of its datasource).

您的主 ViewModel 将包含所有项目的集合。每当项目的IsChecked属性发生更改时,您都必须检查是否所有项目都被选中/取消选中并更新标题中的 CheckBox(或者更确切地说是其数据源的值)。

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:

演示 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>

回答by Rekshino

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 YourClassis the class from ViewModel, data object for grid's row.

您在示例中所做的是遍历数据项而不是控件(我想您没有控件作为 ItemsSource)。
在您发布的链接中,YourClass是 ViewModel 中的类,网格行的数据对象。

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 IsCheckedto the CommandParameter):

这个应该可以在您这边进行最少的代码更改(但我更喜欢在 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;
    }
}

回答by walterhuang

This is a modification based on @Manfred's solution. I use Commandinstead of event.

这是基于@Manfred 的解决方案的修改。我使用Command代替event.

XAML:

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

源代码可在此处获得