Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF
Tip/Trick

WPF Exclusive CheckBox Behavior

Rate me:
Please Sign up or sign in to vote.
4.81/5 (9 votes)
12 Oct 2017CPOL2 min read 10.3K   103   9   3
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:

C#
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:

C#
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:

XML
<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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at qck1@hotmail.com.

Comments and Discussions

 
QuestionCannot Wait to Try This Out... How About Using it W/Multiple Instances of Dynamically Created UserControls? Pin
Hyland Computer Systems20-Feb-22 13:52
Hyland Computer Systems20-Feb-22 13:52 
PraiseSolves an important challenge! Pin
Pankaj Nikam12-Oct-17 23:02
professionalPankaj Nikam12-Oct-17 23:02 
AnswerRe: Solves an important challenge! Pin
Clifford Nelson16-Oct-17 2:50
Clifford Nelson16-Oct-17 2:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.