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

Flexible WPF XAML ToggleButton/RadioButton/CheckBox reset behavior

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
27 Apr 2018CPOL3 min read 9.1K   83   7  
This is a behavior that that will reset ToggleButton derived controls to false (or a specified default value) when the container's visibility becomes false.

Introduction

There are times when there could be a number of RadioButton/ToggleButton/CheckBox controls, and after the containing Control loses Visibility, it is desirable that they return to a default state. This can be done usually in the associated ViewModel, but it could be easier just to have the controls reset automatically, sort of like when you initially create a View, then any contained control derived from ToggleButton is false unless the ViewModel has set it to true, and the ViewModel will also have the default of false for any property, including those associated with a IsChecked property.

Note:

This behavior is really intended for the case where all contained ToggleButton derived controls have thier IsChecked set to false when the parent container has a Visibility changed to false. It has the ability to change this behavior for some ToggleButton controls to be set to to any of its IsChecked values. If most the ToggleButton derived controls will not be set to false, then a different behavior should be used.

The Behavior

The following is the C# code for the behavior:

C#
public class ResetToggleButtonContentOnVisibilityChangedBehavior
{//
    public enum State { Disabled, Enabled, EnabledDeep }
    public enum DefaultValues { Unset, True, False, Indeterminate}
    public static State GetState(DependencyObject obj)
    {
        return (State)obj.GetValue(StateProperty);
    }

    public static void SetState(DependencyObject obj, State value)
    {
        obj.SetValue(StateProperty, value);
    }

    public static readonly DependencyProperty StateProperty =
        DependencyProperty.RegisterAttached("State",
            typeof(State), typeof(ResetOnVisibilityChangedBehavior),
            new UIPropertyMetadata(OnStatePropertyChanged));

    private static void OnStatePropertyChanged(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var element = (FrameworkElement)d;
        if ((State)e.NewValue != State.Disabled)
            element.IsVisibleChanged += ElementOnIsVisibleChanged;
        else
            element.IsVisibleChanged -= ElementOnIsVisibleChanged;
    }

    public static DefaultValues GetDefaultValue(DependencyObject obj)
    {
        return (DefaultValues)obj.GetValue(DefaultValueProperty);
    }

    public static void SetDefaultValue(DependencyObject obj, DefaultValues value)
    {
        obj.SetValue(DefaultValueProperty, value);
    }

    public static readonly DependencyProperty DefaultValueProperty =
        DependencyProperty.RegisterAttached("DefaultValue",
            typeof(DefaultValues), typeof(ResetOnVisibilityChangedBehavior),
            new PropertyMetadata(DefaultValues.Unset));

    private static void ElementOnIsVisibleChanged(object sender,
        DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        var dependencyObject = (UIElement)sender;

        if (!(bool)dependencyPropertyChangedEventArgs.NewValue)
        {
            var toggleButtons = GetState(dependencyObject) == State.Enabled
                ? FindImmediateVisualChildren<ToggleButton>(dependencyObject).ToList()
                : FindVisualChildren<ToggleButton>(dependencyObject).ToList();
            toggleButtons.ForEach(i => i.IsChecked = SetToggleButtonDefault(i));
        }
    }

    private static bool? SetToggleButtonDefault(ToggleButton toggleButton)
    {
        switch (GetDefaultValue(toggleButton))
        {
            case DefaultValues.Unset:
            case DefaultValues.False:
                return false;
            case DefaultValues.True:
                return true;
            default:
                return null;
        }
    }

    #region private static
    private static IEnumerable<T> FindVisualChildren<T>(DependencyObject root)
        where T : DependencyObject
    {
        if (root != null)
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(root, i);
                if (child is T variable) yield return variable;
                foreach (T childOfChild in FindVisualChildren<T>(child))
                    yield return childOfChild;
            }
    }
    private static IEnumerable<T> FindImmediateVisualChildren<T>(DependencyObject root)
        where T : DependencyObject
    {
        if (root != null)
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(root, i);
                if (child is T variable) yield return variable;
            }
    }
    #endregion
}

There are two DependencyProperty definitions. The State DependencyProperty sets how this behavior functions:

Disabled: does nothing

Enabled: Will work against any Control derived from ToggleButton that is a direct Child of the Control this behavior is attached to.

EnabledDeep: Will work against any Control derived from ToggleButton that is any level of Child under the Control this behavior is attached to.

The DefaultValue DependencyProperty is used to change the IsChecked value of each specific Control derived from ToggleButton is set to when the Visibility changes. This can be True, False, or Indeterminate (used to set IsChecked property to true, false, and null respectively). The default value if this property is not set is false.

The event handler for its DependencyProperty changed event, OnStatePropertyChanged, adds the method ElementOnIsVisibleChanged to the IsVisibleChanged event (or removes it if set to State is set to Disabled). When the e.NewValue part of the ElementOnIsVisibleChanged DependencyPropertyChangedEventArgs argument is not set to true, then the containing Control's VisualChildren are searched for any instances derived from ToggleButton, For each Togglebutton, if the ToggleButton does not have an attached DefaultValue DependencyProperty, then its IsChecked property set to false, otherwise the IsChecked is set to the value according to the DefaultValue DependencyProperty.

I have included the private method FindVisualChildren nad FindImmediateVisualChildren in the behavior for portability, but more than likely would want to put this code in a separate class within the solution for utility code.

The Sample

To use the sample, you just have to click the ToggleButton on the Window so that RadioButton and ToggleButton controls within a Grid to which this behavior is attached are visible, then can select any of these controls to any value, and when Visibility is again Visible, then the controls will be at their default (or false) values. Hide and show the RadioButton controls using the ToggleButton.

Image 1

Image 2

Using the Code

Using this behavior is like any other behavior, the behavior is included in the XAML for the parent or grandparent, or any other ancestor Control containing RadioButton and Togglebutton controls. Its Enable property just has to be set to Enabled:

XML
        <Grid HorizontalAlignment="Center"
              VerticalAlignment="Center"
              local:ResetToggleButtonContentOnVisibilityChangedBehavior.State="Enabled"
              Visibility="{Binding ElementName=ToggleButton,
                                   Path=IsChecked,
                                   Converter={local:IsTrueConverter},
                                   ConverterParameter=Visible:Hidden}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <RadioButton Content="RadioButton One" />
            <ToggleButton Grid.Row="0"
                          Grid.Column="1"
                          Content="ToggleButton One" />
            <RadioButton Grid.Row="1"
                         Grid.Column="0"
                         local:ResetToggleButtonContentOnVisibilityChangedBehavior.DefaultValue="True"
                         Content="RadioButton Two (Default true)" />
            <ToggleButton Grid.Row="1"
                          Grid.Column="1"
                          local:ResetToggleButtonContentOnVisibilityChangedBehavior.DefaultValue="True"
                          Content="ToggleButton Two (Default true)"
                          IsThreeState="True" />
            <RadioButton Grid.Row="2"
                         Grid.Column="0"
                         Content="RadioButton Three" />
            <ToggleButton Grid.Row="2"
                          Grid.Column="1"
                          local:ResetToggleButtonContentOnVisibilityChangedBehavior
				.DefaultValue="Indeterminate"
                          Content="ToggleButton Three (Default Indeterminate)"
                          IsThreeState="True" />
            <RadioButton Grid.Row="3"
                         Grid.Column="0"
                         Content="RadioButton Four" />
            <ToggleButton Grid.Row="3"
                          Grid.Column="1"
                          Content="ToggleButton Four"
                          IsThreeState="True" />
        </Grid>

History

This is a major update to a previous tip: WPF Behavior to Reset RadioButtons on VisibilityChanged. It also recognizes that the behavior actually works with more that RadioButtons since it works with anything that is derived from ToggleButton.

It adds the following functionality to the previous version

  • It can be configured to only search for direct Children, of all Children.
  • A different default value can be specified for each ToggleButton derived Control.

Versions:

  • 04/27/2018: 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

 
-- There are no messages in this forum --