1
votes

I am working on wpf (mvvm architecture). A am using datagrid to list the checkboxes and a SELECT ALL checkbox in the header on click of which I want all the checkboxes to be checked and vice-versa.Please help.

I am giving my code description here... This is the View.xaml

<DataGridCheckBoxColumn Binding="{Binding IsSelected}" Width="50" >
    <DataGridCheckBoxColumn.HeaderTemplate>
        <DataTemplate x:Name="dtAllChkBx">
            <CheckBox Name="cbxAll" Content="All" IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
        </DataTemplate>
    </DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
<DataGridTemplateColumn Header="Name" Width="SizeToCells" IsReadOnly="True">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding UsecaseName}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>             

The below is the viewmodel class

private bool _IsSelected;
public bool IsSelected
{
    get { return _IsSelected; }
    set
    {
        _IsSelected = value;
        OnPropertyChanged("IsSelected");
    }
}

private bool _AllSelected;
public bool AllSelected
{
    get { return _AllSelected; }
    set
    {
        _AllSelected = value;
        foreach (var reportListItemModel in UsecaseListItems)
        {
            reportListItemModel.IsSelected = this._AllSelected;
        }
        OnPropertyChanged("IsSelected");

    }
}   

private ObservableCollection<UseCase> _usecaseListItems = new ObservableCollection<UseCase>();
public ObservableCollection<UseCase> UsecaseListItems
{
    get { return _usecaseListItems; }
    set {
        _usecaseListItems = value;
        OnPropertyChanged("UsecaseListItems");
    }
}

public class UseCase: BaseNotifyPropertyChanged { public string UsecaseName { get; set; }

    public bool IsSelected { get; set; }

}

What else should be done so that the select all check box works properly??

3
Yes .. It is working now.. :)ankita kumari

3 Answers

3
votes

you UseCase Class must implement INotifyPropertyChanged Interface

public class UseCase : INotifyPropertyChanged
{
    //...
    private bool _isSelected;
    public bool IsSelected
    {
        get
        {
            return _isSelected;
        }
        set
        {
            _isSelected = value;
            NotifyPropertyChanged("IsSelected");
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

Edit: Here I get a full example that works well

*.xaml (View)

<DataGrid x:Name="dataGrid" ItemsSource="{Binding MyCollection}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridCheckBoxColumn Binding="{Binding IsSelected}" Width="50" >
            <DataGridCheckBoxColumn.HeaderTemplate>
                <DataTemplate x:Name="dtAllChkBx">
                    <CheckBox Name="cbxAll" DataContext="{Binding ElementName=dataGrid, Path=DataContext}" Command="{Binding MyCommand}" CommandParameter="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}"/>
                </DataTemplate>
            </DataGridCheckBoxColumn.HeaderTemplate>
        </DataGridCheckBoxColumn>
        <DataGridTemplateColumn Header="Name" Width="SizeToCells" IsReadOnly="True">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding UsecaseName}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

*.cs (ViewModel)

public class MainViewModel : INotifyPropertyChanged
{
    public MainViewModel()
    {
        for (int i = 0; i < 100; i++)
        {
            MyCollection.Add(new UseCase { UsecaseName = "name " + i, IsSelected = false });
        }
        MyCommand = new RelayCommand(MyCommandAction);
    }


    private ObservableCollection<UseCase> myCollection = new ObservableCollection<UseCase>();

    public ObservableCollection<UseCase> MyCollection
    {
        get
        {
            return myCollection;
        }
        set
        {
            myCollection = value;
            NotifyPropertyChanged("MyCollection");
        }
    }

    public RelayCommand MyCommand { get; set; }
    private  void MyCommandAction(object obj)
    {
        foreach (var item in MyCollection)
        {
            item.IsSelected = (bool)obj;
        }
    }



    //NotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

*.cs (Model)

public class UseCase : INotifyPropertyChanged
{
    public string UsecaseName { get; set; }
    private bool _isSelected;
    public bool IsSelected
    {
        get
        {
            return _isSelected;
        }
        set
        {
            _isSelected = value;
            NotifyPropertyChanged("IsSelected");
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}
2
votes

Though many solutions exist, but I want to hit a particular problem statement that I come across frequently, that is similar to: In a WPF DataGrid, bind through MVVM approach,

  1. If I check "Select All" checkbox in the header, all the checkboxes in that column should be checked.
  2. If I uncheck "Select All" checkbox in the header, all the checkboxes in that column should be unchecked
  3. If I uncheck any of the checkbox in that column of any row, then the "Select All" checkbox in the header should be unchecked.
  4. If all checkboxes in that column are checked(one each row), then the "Select All" checkbox in the header should be checked (left to your exploration ;))

Model:

public class Employee : INotifyPropertyChanged
{
        private int _id;
        private string _name;
        private double _salary;
        private bool _isSelected;

        public int Id { get => _id; set => _id = value; }
        public string Name { get => _name; set => _name = value; }
        public double Salary { get => _salary; set => _salary = value; }
        public bool IsSelected
        {
            get
            {
                return _isSelected;
            }
            set
            {
                _isSelected = value;
                OnPropertyChanged(nameof(IsSelected));
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyname)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
            }
        }
}

ViewModel:

public class EmployeeDetailsViewModel
{
        public EmployeeDetailsViewModel()
        {
            SelectAllCommand = new DelegateCommand<bool?>(HandleSelectAllCommand);
        }

        ObservableCollection<Employee> _employees = null;
        /// <summary>
        /// Collection bound to DataGrid
        /// </summary>
        public ObservableCollection<Employee> Employees { get => _employees; set { _employees = value; } }

        /// <summary>
        /// Command bound to SelectAll checkbox in XAML
        /// </summary>
        public ICommand SelectAllCommand { get; set; }

        private void HandleSelectAllCommand(bool? isChecked)
        {
            if (isChecked.HasValue)
            {
                foreach (var employee in Employees)
                {
                    employee.IsSelected = isChecked.Value;
                }
            }
        }

        void PrepareData()
        {
            this.Employees = new ObservableCollection<Employee>()
            {
                new Employee{Id=1, Name="abc", Salary=100000.00d},
                new Employee{Id=2, Name="def", Salary=200000.00d},
                new Employee{Id=3, Name="ghi", Salary=300000.00d},
                new Employee{Id=4, Name="jkl", Salary=400000.00d}
            };
        }
}

View(XAML):

<DataGrid ItemsSource="{Binding Employees}" AutoGenerateColumns="False" CanUserAddRows="False" >
        <DataGrid.Columns>
            <DataGridTemplateColumn >
                <DataGridTemplateColumn.Header>
                    <CheckBox x:Name="chkSelectAll">
                        <i:Interaction.Triggers>
                            <i:EventTrigger  EventName="Click">
                                <i:InvokeCommandAction Command="{Binding Path = DataContext.SelectAllCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid,AncestorLevel=1}}" 
                                                       CommandParameter="{Binding ElementName=chkSelectAll, Path=IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </CheckBox>
                </DataGridTemplateColumn.Header>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                            <i:Interaction.Triggers>
                                <i:EventTrigger  EventName="Unchecked">
                                    <local:EventTriggerPropertySetAction TargetObject="{Binding ElementName=chkSelectAll}" PropertyName="IsChecked" PropertyValue="False"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </CheckBox>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>

            </DataGridTemplateColumn>
            <DataGridTextColumn Binding="{Binding Id}" Header="Id"/>
            <DataGridTextColumn Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Header="Name"/>
            <DataGridTextColumn Binding="{Binding Salary, UpdateSourceTrigger=PropertyChanged}" Header="Salary"/>
        </DataGrid.Columns>
</DataGrid>

To access the interaction triggers, you need to add following name space to Window/UserControl markup tags:

<Window x:Class="TestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
....
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">

The important part of simplification is due to <local:SetPropertyAction ... based on this SO answer or this SO answer.

Don't forget to up-vote and encourage, when you visit those link ;)

Hope this help some one!

0
votes

You can use RegisteredAttachedProperty to make it possible. Here, you can find a sample. you can be inspired from there.