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

WPF RadioButton Switched Content Control

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
2 Sep 2017CPOL4 min read 10.7K   111   8  
Presents a very simple control with an IsChecked property that allows definition of two different contents depending of if IsChecked property value is true or false

Introduction

I had a requirement for a functionality where there would display read-only information until it was selected, when it would then display controls that would allow editing, and this Control was in an ItemsControl. Only one of these would be open at a time for editing. There was originally a lot of code in the ViewModel to enable this functionality. I really did not like this and thought that this was the perfect application for a custom RadioButton.

The Design

There are two parts for this control: the C# class that is derived from RadioButton and the XAML that provides layout information. The C# class is as follows:

public class ContentChangeRadioButton : RadioButton
{
    public object ToggleOnContent
    {
        get => GetValue(ToggleOnContentProperty);
        set => SetValue(ToggleOnContentProperty, value);
    }

    public static readonly DependencyProperty ToggleOnContentProperty =
        DependencyProperty.Register("ToggleOnContent", typeof(object),
            typeof(ContentChangeRadioButton), new PropertyMetadata(null));
}

As can be seen all this class has to do is add the ToggleOnContent DependencyProperty to the RadioButton.

Then the XAML for the control is as follows:

<Style TargetType="{x:Type local:ContentChangeRadioButton}">
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:ContentChangeRadioButton}">
                <Grid>
                    <ContentPresenter x:Name="Part_ToggleOffContent"
                                      Content="{TemplateBinding Content}" />
                    <ContentPresenter x:Name="Part_ToggleOnContent"
                                      Content="{TemplateBinding ToggleOnContent}" />
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsChecked" Value="True">
                        <Setter TargetName="Part_ToggleOffContent"
                                Property="Visibility" Value="Collapsed" />
                        <Setter TargetName="Part_ToggleOnContent"
                                Property="Visibility" Value="Visible" />
                    </Trigger>
                    <Trigger Property="IsChecked" Value="False">
                        <Setter TargetName="Part_ToggleOnContent"
                                Property="Visibility" Value="Collapsed" />
                        <Setter TargetName="Part_ToggleOffContent"
                                Property="Visibility" Value="Visible" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

It just has two ContentPresenter controls, and the Triggers to control the Visibility of these two controls based on the IsChecked DependencyProperty of the RadioButton.

What I Learned

Originally I had much more complex C# code that would change the Visibility of the the toggle on and toggle off content, and had to also handle the initial Visibility of these Contents, but I did some thought and decided to try to use Triggers in the XAML instead, and this simplified the code extensively:

C#
[TemplatePart(Name = ToggleOnContentName, Type = typeof(ContentControl))]

[TemplatePart(Name = ToggleOffContentName, Type = typeof(ContentControl))]
public class ContentChangeRadioButton : RadioButton
{
    private const string ToggleOnContentName = "Part_ToggleOnContent";
    private ContentPresenter _ToggleOnContent;
    private const string ToggleOffContentName = "Part_ToggleOffContent";
    private ContentPresenter _ToggleOffContent;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _ToggleOnContent = (ContentPresenter)GetTemplateChild(ToggleOnContentName);
        _ToggleOffContent = (ContentPresenter)GetTemplateChild(ToggleOffContentName);
        _ToggleOnContent.Visibility = (IsChecked == true)
    ? Visibility.Visible : Visibility.Collapsed;
        _ToggleOffContent.Visibility = (IsChecked == true)
    ? Visibility.Collapsed : Visibility.Visible;
        Checked += ContentChangeRadioButton_Checked;
        Unchecked += ContentChangeRadioButton_Unchecked;
    }

    private void ContentChangeRadioButton_Unchecked(object sender, RoutedEventArgs e)
    {
        if (IsChecked == false) //Without this when opened combobox in content would uncheck button
        {
            _ToggleOnContent.Visibility = Visibility.Collapsed;
            _ToggleOffContent.Visibility = Visibility.Visible;
        }
    }

    private void ContentChangeRadioButton_Checked(object sender, RoutedEventArgs e)
    {
        _ToggleOnContent.Visibility = Visibility.Visible;
        _ToggleOffContent.Visibility = Visibility.Collapsed;
    }

    static ContentChangeRadioButton()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ContentChangeRadioButton),
            new FrameworkPropertyMetadata(typeof(ContentChangeRadioButton)));
    }

    public object ToggleOnContent
    {
        get { return (object)GetValue(ToggleOnContentProperty); }
        set { SetValue(ToggleOnContentProperty, value); }
    }

    public static readonly DependencyProperty ToggleOnContentProperty =
        DependencyProperty.Register("ToggleOnContent", typeof(object),
            typeof(ContentChangeRadioButton),
            new PropertyMetadata(null));

    public bool ToggleState
    {
        get { return (bool)GetValue(ToggleStateProperty); }
        set { SetValue(ToggleStateProperty, value); }
    }

    public static readonly DependencyProperty ToggleStateProperty =
        DependencyProperty.Register("ToggleState", typeof(bool),
            typeof(ContentChangeRadioButton),
            new PropertyMetadata(false));

    private static void ToggleStateChanged
    (DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = (ContentChangeRadioButton)d;
        if ((bool)e.NewValue)
        {
            control._ToggleOnContent.Visibility = Visibility.Visible;
            control._ToggleOffContent.Visibility = Visibility.Collapsed;
        }
        else
        {
            control._ToggleOnContent.Visibility = Visibility.Collapsed;
            control._ToggleOffContent.Visibility = Visibility.Visible;
        }
    }
}

There is one important DependencyProperty, the ToggleOnContent. The direct content of the RadioButton is used for when the IsChecked property is false, and then the content specified when the IsChecked property is true is specified in this DependencyProperty.

The OnApplyTemplate method uses the GetTemplateChild method to get references to the ContentPresenter for both the unchecked and checked states, and also hooks up to the Checked and Unchecked events, and ensures that the Visibility of the controls for the checked and unchecked states are correct for the current IsChecked value.

The Checked and Unchecked event handlers set the Visibility of the two ContentPresenter controls to the value appropriate for the IsChecked value. You will notice that there is an additional check when the Unchecked event is fired to ensure that the control is truely in the IsChecked == false. I had some issue with this when I had some content that included a ComboBox, and this event would be fired when the ComboBox dropdown closed.

Using the Control

To use this control, it simply has to be declared, and content specified for the ToggleOn state (which is declared with the ToggleOnContent attribute, and ToggleOff which is declared in the direct content:

XML
<local:ContentChangeRadioButton Margin="2"
                                HorizontalAlignment="Center"
                                VerticalAlignment="Center">
    <Border Width="75"
            Height="25"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            BorderBrush="Black"
            BorderThickness="1">
        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   Text="Toggle Off" />
    </Border>
    <local:ContentChangeRadioButton.ToggleOnContent>
        <Border Width="100"
                Height="30"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                BorderBrush="Blue"
                BorderThickness="1">
            <TextBlock HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       Text="Toggle On" />
        </Border>
    </local:ContentChangeRadioButton.ToggleOnContent>
</local:ContentChangeRadioButton>

The single instance with ToggleOn state is automatically accomplished because this control is derived from the RadioButton.

Moving onto the ToggleButton Version

I later had a need to do something like this but more of a CheckBox type requirement since wanted independence. The code is almost identical:

public class ContentChangeToggleButton : ToggleButton
 {
     static ContentChangeToggleButton()
     {
         DefaultStyleKeyProperty.OverrideMetadata(typeof(ContentChangeToggleButton),
             new FrameworkPropertyMetadata(typeof(ContentChangeToggleButton)));
     }

     public object ToggleOnContent
     {
         get => (object) GetValue(ToggleOnContentProperty);
         set => SetValue(ToggleOnContentProperty, value);
     }

     public static readonly DependencyProperty ToggleOnContentProperty =
         DependencyProperty.Register("ToggleOnContent", typeof(object),
             typeof(ContentChangeToggleButton),
             new PropertyMetadata(null));
 }

The code is identical except that it derives from ToggleButton instead of RadioButton. The XAML is basically also identical

The Sample

The sample has three of the RadioButton controls, each one having the same content, one for on and one for off. The Content is just different text inside a different Border. There is also a ToggleButton control with the same Content except that for the title "Toggle" instead of "Radio".

You will notice that when one of these RadioButton syles is clicked, it changes and if there is a control that had previously been selected, it will change to the Toggle Off state. Looking at the code, you will see that there is no code that is causing this change since the base Control is of Type RadioButton. Of course the ToggleButton style is independent of the RadioButton controls

Conclusion

This was a very simple control to create, and I think it is a nice little control that makes a lot of sense. It is basically a simplified Expander, but an Expander where the Header disappears. The simplicity is what is nice about it. To a certain extent, I am not happy with how complex some of the Microsoft controls are since you need to do so much work to change them, and simpler controls can be more easily modified to look exactly like you want them to, and creating more sophisticated designs could easily be either done directly or styles can be created. This is a very simple control.

History

  • 09/02/2017: Initial version
  • 09/26/2017: Complete rework of design and adding of the ToggleButton

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