Hierarchical check boxes

A friend asked me to help him solve a problem with check boxes in a tree control. He had a check box on the parent items that indicated whether any of the children were checked. When you check the parent, it checks all of the children. And when you check a child, it checks the parent. His problem was that after the user checks a child, the side-effect of checking the parent caused all children to be checked.

The solution: eliminate side-effects.

Parent checked is dependent

The core of the problem was that he was treating the parent checked state as independent. In short, he was storing it in a field. In storing it, he took on the responsibility of updating it in response to events. His events, therefore, had to have side-effects.

Only store the values that the user can directly change. Calculate the rest.

Get Microsoft Silverlight

If you have Silverlight installed, you should be seeing a tree of options. These options are grouped together to form a tree. If you check or uncheck a group, then all child options are checked or unchecked. We have two types of options in our data model: groups and leaves.

image

Only a leaf has a _selected field. This is the only thing that the user can actually change. When the user checks a group, they are actually performing a macro operation: select all leaves. And when the user observes a group node, they are actually observing the aggregate state of all leaves. The group check box is dependent.

The view model

Every option in the model is associated with an OptionViewModel. This view model has a nullable boolean property that is bound to the check box. To calculate the checked state of an option, it examines all of the leaves. And when the user checks or unchecks the box, it sets all of the leaves.

public bool? IsChecked
{
    get
    {
        bool anySelected = _option.Leaves.Any(leaf => leaf.Selected);
        bool anyNotSelected = _option.Leaves.Any(leaf => !leaf.Selected);
        return
            (anySelected && !anyNotSelected) ? true :
            (!anySelected && anyNotSelected) ? false :
            (bool?)null;
    }
    set
    {
        foreach (OptionLeaf leaf in _option.Leaves)
            leaf.Selected = value ?? false;
    }
}

By storing only what the user can directly change, we have eliminated the need for side-effects. We no longer need to handle the event of checking a child in order to impose the side-effect of checking the parent. The parent’s checked state is simply dependent upon the child.

INotifyPropertyChanged is a side-effect

This example uses Update Controls to manage dependencies. An implementation that does not employ a dependency management library would have to fire PropertyChanged events up the tree. It would be cumbersome to manage these events. They have to be fired on just the right view models, and at just the right time. Firing these events would be a side-effect of storing the user’s selection.

When performing the macro operation of selecting a group, a naive implementation would fire PropertyChanged as each leaf is modified. This would cause the side-effect of firing PropertyChanged on the parent node multiple times. Update Controls collects these changes and ensures that the parent is updated only once.

Download the source code and try it for yourself.