Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF Exclusive CheckBox Behavior

0.00/5 (No votes)
12 Oct 2017 2  
A Behavior is presented that will ensure that only one Checkbox will be checked within the visual tree of the control to which this behavior is attached

Introduction

I had a situation where I had a requirement for two mutually exclusive checkboxes. I could have used the RadioButton but there was the case where the user could deselect both controls. I could have also done this in the ViewModel, but I really did not like this solution, although I consider it a reasonable approach. With some thought, I came up with the concept of creating this behavior that worked on CheckBox controls.

The Code

The following is static code for the behavior:

public static readonly DependencyProperty IsEnabledProperty =
    DependencyProperty.RegisterAttached("IsEnabled", typeof(bool),
    typeof(ExclusiveCheckBoxBahavior),
        new PropertyMetadata(false, OnIsEnabledChanged));

public static void SetIsEnabled(DependencyObject element, bool value)
{
    element.SetValue(IsEnabledProperty, value);
}

public static bool GetIsEnabled(DependencyObject element)
{
    return (bool)element.GetValue(IsEnabledProperty);
}

private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    GetInstance(d)?.Dispose();
    if ((bool)e.NewValue)
        SetInstance(d, new ExclusiveCheckBoxBahavior(d));
}

private static readonly DependencyProperty InstanceProperty
= DependencyProperty.RegisterAttached("Instance",
            typeof(ExclusiveCheckBoxBahavior), typeof(ExclusiveCheckBoxBahavior),
    new PropertyMetadata(null));

private static void SetInstance(DependencyObject element, ExclusiveCheckBoxBahavior value)
{
    element.SetValue(InstanceProperty, value);
}

private static ExclusiveCheckBoxBahavior GetInstance(DependencyObject element)
{
    return (ExclusiveCheckBoxBahavior)element.GetValue(InstanceProperty);
}

This is basically of the DependencyProperty IsEnabled that is set true to enable the bahavior, and a second private DependencyProperty Instance that is used to associate an instance of this behavior with a DependencyObject so that the Dispose method instance can be executed when the IsEnabled is changed to false. The IsEnabled DependencyProperty changed event is handled by the OnIsEnabledChanged method. This will call the Dispose of the instance of this class associated with the DependencyProperty argument, and then create an instance of this class if the value is true.

In the instance part, there is a constructor and Dispose method, and code to handle a CheckBox Checked event:

private List<CheckBox> checkBoxes;

public ExclusiveCheckBoxBahavior(DependencyObject d)
{
    ((FrameworkElement)d)<span style="display: none;"> </span>.Initialized += (sender, args) =>
    {
        checkBoxes = FindVisualChildren<CheckBox>(d).ToList();
        checkBoxes.ForEach(i => i.Checked += CheckBoxChecked);
    };
}

private void CheckBoxChecked(object sender, RoutedEventArgs e)
{
    checkBoxes.ForEach(i =>
        {
            if (!Equals(i, sender)) i.IsChecked = false;
        }
    );
}

public void Dispose()
{
    checkBoxes.ForEach(i => i.Checked -= CheckBoxChecked);
}

The constructor associates the FrameworkElement (which is passed as a type DependencyObject as an constructor argument) Initialized event which will find all children of the FrameworkElement of type CheckBox, save this collection for the Dispose method, and then associate the Checked event for each of these CheckBox controls with the CheckBoxChecked method.

The Dispose method unsubscribes all the CheckBox controls from the CheckBoxChecked method.

The last part of this instance code is the CheckBoxChecked method. This method will ensure all CheckBox controls within the FrameworkElement that are not the CheckBox that initiated this event has an IsChecked property is set to false. If a CheckBox that has a IsChecked property equal to true that is changed to false does not trigger this Checked event, and thus the CheckBox IsChecked property can freely be changed from true to false.

There is only other part of the code which is the code to find all children for a DependencyObject, FindVisualChildren<T>. It is quite likely a WPF project could already have such a method already, so this code could be removed.

Using this Property

All that is required is to attach this behavior to a FrameworkElement containing CheckBox controls:

<Grid VerticalAlignment="Center"

      exclusiveCheckBoxBahaviorSample:ExclusiveCheckBoxBahavior.IsEnabled="True">
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <CheckBox Margin="10,5"

              Content="Single-Channel"

              IsChecked="{Binding SelectedScriptViewModel.WrappedObject.IsSingleChannel}" />
    <CheckBox Grid.Column="1"

              Margin="10,5"

              Content="Multi-Channel"

              IsChecked="{Binding SelectedScriptViewModel.WrappedObject.IsMultiChannel}" />
</Grid>

Future

I would like to have a way of adding the equivalent of the RadioButton GroupName.

History

  • 2017-10-12: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here