Friday, June 30, 2017

Published 11:04 PM by with 0 comment

How Do I Make A Datagrid With Checkboxes and a 'Select All' Header Using MVVM in C#?

I needed to do this recently and didn't find any tutorials when I googled, so I figured I'd make one for the next person who googles it...


Basic Requirements

  • Each row has a checkbox
  • Checkboxes are linked in some way
  • Ability to select and de-select all is available
  • No code-behind is used

Libraries Used

For MVVM in C#, I use Prism. I'm using 6.3 here but nothing I'm using is very new so older versions should work. You can get Prism through nuget...just get Prism.Core.


The main concepts I'm using are 'BindableBase' which allows you to bind properties very easily, and the Prism delegate commands which let you trigger commands really easily. Check the Prism examples for more.

Note that throughout I am not fully utilizing Prism or following the smoothest/purest MVVM setup as this is a simple demo focused on a very specific aspect. A normal Prism app will have a bootstrapper for example, I would have a custom View instead of just using the MainWindow, and there would be a purer Model or set of them with the ViewModel wrapping it more completely. I didn't want to overcomplicate here as it would distract from the meat of this which is showing how property changed events + commands let you accomplish the basic requirements...

Preview Of Result


ViewModel

The ViewModel does two main things here:
  • Provides a collection of the models to bind to populate the datagrid
  • Provides methods for handling datagrid interactions
The two ways that are provided for handling datagrid interactions correspond to the two categories of checkboxes. First, there's an event handler that watches for the checkbox in a row to be clicked:

third.PropertyChanged += Grid_PropertyChanged;

watched by

private void Grid_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    RecursiveSet(sender as TestModel);
}

This allows the ViewModel to edit the checkboxes in all rows according to their relationships when one of the rows is clicked. Second, there's a command that is called when the checkbox in the header is clicked:

CheckAllCommand = new DelegateCommand<object>((param) =>
{
    foreach (TestModel test in DisplayElements)
    {
        if ((bool)param)
        {
            test.Checked = true;
            test.Enabled = true;
        }
        else
        {
            test.Checked = false;
            RecursiveSet(test);
        }
    }
});



This allows the ViewModel to edit all checkboxes when the header one is clicked.

The basic link between checkboxes that I have for this demo is that rows can depend on other rows, and this is demonstrated through checkbox behavior. The rules I have are:
  • selecting a row enables all of its dependents if possible, but does not select them
  • deselecting a row unselects all of its dependents and disables their checkboxes
  • selecting the checkbox in the header makes all checkboxes match its state


Model

The model here consists of five fields:
  • Checked (is the checkbox checked?)
  • Enabled (is the checkbox enabled?)
  • DisplayText (something to put in another column)
  • Dependents (do any other models depend on me?)
  • DependsOn (do I depend on any other models?)

View

The view just consists of a datagrid. I added a little bit of styling (centered checkboxes and changed header colors), but the rest of this is basically binding logic. The first interesting one is the checkbox in the header that selects all:

<DataGridTemplateColumn.HeaderTemplate>
     <DataTemplate>
         <CheckBox IsChecked="True"
                       HorizontalAlignment="Center"
                       Command="{Binding Path=DataContext.CheckAllCommand,
                       RelativeSource={RelativeSource AncestorType=DataGrid}}"

                       CommandParameter="{Binding Path=IsChecked,RelativeSource={RelativeSource Self}}"/>
     </DataTemplate>
 </DataGridTemplateColumn.HeaderTemplate>

This does the following:
  • put a checkbox in the header
  • bind clicking it to a command called 'CheckAllCommand' in the ViewModel
  • pass the command the 'IsChecked' status of the checkbox
The other interesting binding is the checkboxes in each row:

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
  <CheckBox IsChecked="{Binding Checked, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
                  IsEnabled="{Binding Enabled}"
                  HorizontalAlignment="Center"/>
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>

The itemsource for my datagrid is set to the collection of my Models in the ViewModel, so I'm binding 'IsChecked' to the 'Checked' property in those models and I'm binding 'IsEnabled' to the 'Enabled' property. Since the 'Checked' property raises a property changed event, and the ViewModel is watching for those events, the ViewModel's event handler will be called every time one of those checkboxes is clicked.

Summary

Checkboxes on each row are handled through straight databinding to a property along with a property changed event and a handler for it in the ViewModel. Checkbox in the column header is handled with a command.

You can download the source here

      edit

0 comments:

Post a Comment