WPF/MVVM: Binding the IsChecked Property of a CheckBox to Several Other CheckBoxes

Table of Contents

Introduction

This article provides an example on how you can use a three-state Checkbox control to set the IsChecked property of several other related CheckBoxes in a data-bound ItemsControl, or any other control that derives from the ItemsControl such as the DataGrid,
ListView or TreeView controls, in WPF using the MVVM pattern.

By setting the IsThreeState property of a CheckBox control to true, the IsChecked property can also be set to NULL as a third state in addition to the two default ones; true and false.

A three-state CheckBox is normally used to reflect an overall state of some other related checkboxes. It should appear checked if all related checkboxes are checked, unchecked if none of the related checkboxes are checked, or in an indeterminate state if
only some of the related CheckBox controls are checked:

The sample code in this post uses a view model that exposes a single collection of
Country objects with each country having a property named IsSelected of type
System.Boolean (bool) that will be bound to a CheckBox control in the view, and string properties for the name of the country and the continent to which it belongs to respectively:

Grouping in XAML

The countries will be grouped and listed by continent in the view with each continent being represented by a three-state CheckBox whose IsChecked property value will be dependent of the value of the IsSelected property of each of the related
Country objects.

To be able to group a collection of data in the view you can use the CollectionViewSource class. It allows you to specify sorting and grouping conditions directly in XAML without having to do anything in code. In this particular example you simply
bind its Source property to the collection of Country objects in the view model and then group the countries by the ContinentName property by adding a
PropertyGroupDescription as follows:

<Window.Resources>

<CollectionViewSourcex:Key="countries"Source="{Binding Countries}">

<CollectionViewSource.GroupDescriptions>

<PropertyGroupDescriptionPropertyName="ContinentName"/>

</CollectionViewSource.GroupDescriptions>

</CollectionViewSource>

</Window.Resources>

Data Templating

The ItemssSource property of the ItemsControl is then bound to the CollectionViewSource instead of the collection property of the view model. You can then use a DataTemplate to define the appearance of a
Country object. The sample markup below has a CheckBox bound to the IsChecked property of the
Country class and a TextBlock to display the name of the country.

Using a DataTemplate with the DataType property set to the appropriate data type and without specifying an x:Key will automatically apply the template to all objects of the given type. This is arguably one of the most powerful and useful features of WPF.

Defining a GroupStyle

To define the appearance of each group of countries, i.e. a continent, you specify a
GroupStyle and add it to the GroupStyle property of the ItemsControl. This property contains a collection of
GroupStyle objects that determine the group style for each level of groups (to support cases where you may have multiple levels of grouping) with the entry at index 0 describing the top-level group, the entry at index 1 describing the next level and
so on.

The GroupStyle class has a ContainerStyle property of type System.Windows.Style that is used to determine the style for each group item in the same level. You can completely redefine the look and feel of a group item by specifying a style
that sets the Template property to a custom ControlTemplate. In the sample markup below, an Expander control with a three-state CheckBox control and a TextBlock for displaying the name of the group in its header are used. The ItemsPresenter element is used
within a template to specify where the ItemsPanel with the bound objects, i.e. the
Country objects in this case, defined by the ItemsControl is to be added in the control’s visual tree.

Note that the style will be applied to elements of type System.Windows.Controls.GroupItem and the DataContext of each of these group elements is a
MS.Internal.Data.CollectionViewGroupInternal object which you can see if you use the WPF Visualizer – a tool that lets you search and drill down the visual tree produced by WPF – when debugging the application in Visual Studio:

The CollectionViewGroupInternal class is derived from the public System.Windows.Data.CollectionViewGroup class and this one has a Name property that returns the value of the property that is used to divide items into groups, i.e. the value
of the ContinentName property in this example. The TextBlock in the above markup binds to this one. There is also an Items property that returns a collection of the items, i.e. the
Country objects, or subgroups in the group.

The binding

You might be tempted to bind the IsChecked property of the three-state CheckBox control in the Expander control to the CollectionViewGroup.Items property and perhaps use a converter to iterate through all
Country objects in the collection and return true, false or NULL depending on the number of objects that have the IsSelected property set to true. However, if you want the IsChecked property of the three-state CheckBox control to get updated whenever
the user checks or unchecks a country you somehow need to refresh the binding associated with the IsChecked property when this happens. In other words, you want the binding to the collection of countries to be refreshed as soon as the value of the IsSelected
property for any Country objects changes in order for the code in the converter class to be executed again.

This can be accomplished by adding some code to the view model. The ObservableCollection<T> class has a CollectionChanged event that occurs whenever an item gets added, removed, replaced, moved or when the entire collection is refreshed. Note that
it doesn’t occur when a property value of an item inside the collection changes which is exactly what you want in this particular case. The trick to make this happen is to add a PropertyChangedEventHandler to all individual items that simply tells WPF to query
the get accessor of the Countries property of the view model again as soon as any property of any
Country object in the collection changes. For this to work, both the
Country class and the view model must implement the INotifyPropertyChanged interface. Also note that you must hook up the ObservableCollection<T>.CollectionEvent before adding any items to the collection:

Now you can bind the three-state CheckBox control’s IsChecked property to the Countries property of the view model using a converter. Converters change data from one type to another and provide a way to apply custom logic to a data binding. In the below
code, a MultiBinding is used to pass the collection of countries and the name of the continent to a converter class that returns an appropriate value of type
System.Nullable<System.Boolean> (bool?) that can be set on the three-state CheckBox control’s IsChecked property. A
MultiBinding describes a collection of Binding objects and allows you to bind a dependency property to a list of source properties. You create your own converter by creating a class that implements the
System.Windows.Data.IMultiValueConverter interface and its Convert and ConvertBack methods:

Selecting all CheckBoxes

There is one last thing you need to do to if you want all countries of a continent to be automatically selected when you check their “parent” three-state CheckBox and deselected when you uncheck it. Add two DelegateCommands – for more information about commands
and how to handle events in MVVM, see my last article
here – to the view model that each takes the name of the continent as a parameter and sets the IsSelected property of each
Country object that belongs to this continent to true or false:

You then simply attach these commands to the Checked and Unchecked events of the three-state CheckBox respectively in the view using Expression Blend interaction triggers, as also described in
my article from last month:

Remember to add a reference to the System.Windows.Interactivity.dll for this to compile. With this in place, the CheckBox controls for all countries of a continent should be checked when you check the three-state CheckBox control and unchecked when
you uncheck it. The state of the three-state CheckBox should also be updated whenever you check or uncheck a country.