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

WPF Exclusive CheckBox Behavior with GroupName

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
17 Oct 2017CPOL3 min read 14K   181   9   5
A Behavior is presented that will ensure that only one CheckBox (ToggleButton) will be checked with the same GroupName within the visual tree of the control to which this behavior is attached.

Introuction

I had a situation where I had a requirement for two mutually exclusive CheckBox (ToggleButton) controls. 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. I have updated the project so that is also works with ToggleButton controls from which the CheckBox is derived.

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 the DependencyProperty IsEnabled that is set to 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 ToggleButtonChecked event:

C#
private List<togglebutton> _checkBoxes;
private UIElement _dependencyObject;

public ExclusiveCheckBoxBahavior(DependencyObject d, bool enabledWithVisibilityReset)
{
    ((FrameworkElement)d).Initialized += (sender, args) =>
    {
        _dependencyObject = (UIElement)d;
        _checkBoxes = FindVisualChildren<togglebutton>(d).ToList();
        _checkBoxes.ForEach(i => i.Checked += CheckBoxChecked);
        if (enabledWithVisibilityReset)
            _dependencyObject.IsVisibleChanged += OnIsVisibleChanged;
    };
}

private void OnIsVisibleChanged(object sender1,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
    if (!(bool)dependencyPropertyChangedEventArgs.NewValue)
        _checkBoxes.ForEach(i => i.IsChecked = false);
}

private void CheckBoxChecked(object sender, RoutedEventArgs e)
{
    var senderGroupName = GetGroupName((DependencyObject)sender);
    _checkBoxes.ForEach(i =>
    {
        if (!Equals(i, sender) && GetGroupName((DependencyObject)i) == senderGroupName)
    i.IsChecked = false;
    }
    );
}</togglebutton></togglebutton>

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 ToggleButton, save this collection for the Dispose method, and then associate the Checked event for each of these ToggleButton controls with the CheckBoxChecked method.

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

The last part of this instance code is the CheckBoxChecked method. This method will ensure all controls within the FrameworkElement that are not the ToggleButton that initiated this event and have the same attached GroupName DependencyProperty value as the initiating. If a that has a IsChecked property equal to true that is changed to false does not trigger this Checked event, and thus the ToggleButton IsChecked property can freely be changed from true to false. If the initiating ToggleButton does not have a GroupName DependencyProperty, then only those ToggleButton controls without a GroupName DependencyProperty will be affected.

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 ToggleButton controls:

XML
<Grid VerticalAlignment="Center" 
     exclusiveCheckBoxBahaviorSample:ExclusiveCheckBoxBahavior.State="Enabled">
    <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>

The Sample

The sample has three pairs of CheckBox controls. The first pair has a GroupName equal to "AA", the second "BB" and the third has no GroupName DependencyProperty. This shows how there can be multiple associated CheckBox controls within the same FrameworkElement that has the IsEnabled DependencyProperty set to true.

History

  • 2017-10-17: Initial version
  • 2018-03-07: Version that also supprts ToggleButton
  • 2018-03-07: Uploaded new code that changed IsEnabled DependencyProperty to State enumberaion with values Enabled, Disabled, and EnabledWithVisibilityReset. 

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

 
QuestionEvent are firing twice Pin
Member 123923297-Sep-18 4:05
Member 123923297-Sep-18 4:05 
GeneralMy vote of 5 Pin
cocis4819-Oct-17 5:37
cocis4819-Oct-17 5:37 
AnswerRe: My vote of 5 Pin
Clifford Nelson26-Oct-17 4:47
Clifford Nelson26-Oct-17 4:47 
QuestionAwesome Example Pin
Tee12318-Oct-17 14:33
Tee12318-Oct-17 14:33 
AnswerRe: Awesome Example Pin
Clifford Nelson26-Oct-17 4:47
Clifford Nelson26-Oct-17 4:47 

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.