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

WPF Exclusive CheckBox Behavior with GroupName

0.00/5 (No votes)
17 Oct 2017 1  
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:

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:

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:

<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 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