Style WPF radio button as toggle button with correct IsEnabled behaviour

10,272

Solution 1

This is how a disabled ToggleButton looks like. If you want to change its appearance you should define custom ControlTemplate. Please refer to the following example:

<Window x:Class="ToggleButtonDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:ToggleButtonDemo"
    mc:Ignorable="d"
    Title="MainWindow" Height="200" Width="200"
    Name="demoWindow"
    DataContext="{Binding ElementName=demoWindow}">
<Window.Resources>
    <Style x:Key="FocusVisual">
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate>
                    <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/>
    <SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/>
    <SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/>
    <SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/>
    <SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/>
    <SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
    <SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>
    <SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/>
    <SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/>
    <Style TargetType="{x:Type ToggleButton}">
        <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
        <Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
        <Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/>
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Padding" Value="1"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
                        <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="Button.IsDefaulted" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
                            <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="true">
                            <Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
                            <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
                        </Trigger>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter Property="Background" TargetName="border" Value="#FFBCDDEE"/>
                            <Setter Property="BorderBrush" TargetName="border" Value="#FF245A83"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Opacity" TargetName="border" Value="0.7"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
<StackPanel>
    <GroupBox Header="Radio" IsEnabled="{Binding Enable}">
        <StackPanel Orientation="Horizontal">
            <RadioButton Name="radio1" Content="One" GroupName="RadioGroup" IsChecked="True"/>
            <RadioButton Name="radio2" Content="Two" GroupName="RadioGroup"/>
        </StackPanel>
    </GroupBox>
    <GroupBox Header="Toggle" IsEnabled="{Binding Enable}">
        <StackPanel Orientation="Horizontal">
            <RadioButton Name="toggle1" Content="One" GroupName="ToggleGroup" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="True"/>
            <RadioButton Name="toggle2" Content="Two" GroupName="ToggleGroup" Style="{StaticResource {x:Type ToggleButton}}"/>
        </StackPanel>
    </GroupBox>
    <Button Name="toggle" Content="Toggle enabled" Click="toggle_Click"/>
    <Button Name="changeValue" Content="Change value" Click="changeValue_Click"/>
</StackPanel>
</Window>

enter image description here

Solution 2

I got this answer from the link I reference below... I've never seen this done before but it works great with a lot less XAML than grabbing the full style definition out of Blend (unless you need to customize the style):

https://social.msdn.microsoft.com/Forums/vstudio/en-US/e0ce86f0-8fa3-4aa9-9617-4157326ee077/make-radiobutton-appear-as-togglebutton?forum=wpf

<RadioButton Style="{StaticResource {x:Type ToggleButton}}">Yes</RadioButton>

<RadioButton Style="{StaticResource {x:Type ToggleButton}}">no</RadioButton>

You can also define a Style for a RadioButton and base it on the ToggleButton so you don't have to specify the Style for each RadioButton.

<Style BasedOn="{StaticResource {x:Type ToggleButton}}" TargetType="RadioButton"/>
Share:
10,272

Related videos on Youtube

sclarke81
Author by

sclarke81

Updated on July 05, 2022

Comments

  • sclarke81
    sclarke81 almost 2 years

    I need to style some grouped radio buttons as toggle buttons. To do this I've applied the following style to the radio buttons:

    Style="{StaticResource {x:Type ToggleButton}}"
    

    This gives me the style I'd like, but I noticed an annoying side effect. I need to be able to change the selected button whilst the controls are disabled. This works as expected with normal radio buttons. However, with the toggle button styled buttons it no longer shows one of the buttons as being selected.

    In the following demo if you repeatedly click the 'Toggle enabled' button you can see that the selected button is still highlighted when it is re-enabled. However, if you change the selected button while they're disabled and then re-enable (click 'Toggle enabled', 'Change value', 'Toggle enabled'), neither of the buttons are highlighted.

    What I'm trying to achieve:

    1. Keep as close as possible to the style of the ToggleButton.
    2. When a checked button is disabled keep the blue background but with opacity.
    3. Have the standard ToggleButton style when the button is re-enabled regardless of whether the value has changed.

    XAML:

    <Window x:Class="ToggleButtonDemo.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:ToggleButtonDemo"
            mc:Ignorable="d"
            Title="MainWindow" Height="200" Width="200"
            Name="demoWindow"
            DataContext="{Binding ElementName=demoWindow}">
        <StackPanel>
            <GroupBox Header="Radio" IsEnabled="{Binding Enable}">
                <StackPanel Orientation="Horizontal">
                    <RadioButton Name="radio1" Content="One" GroupName="RadioGroup" IsChecked="True"/>
                    <RadioButton Name="radio2" Content="Two" GroupName="RadioGroup"/>
                </StackPanel>
            </GroupBox>
            <GroupBox Header="Toggle" IsEnabled="{Binding Enable}">
                <StackPanel Orientation="Horizontal">
                    <RadioButton Name="toggle1" Content="One" GroupName="ToggleGroup" Style="{StaticResource {x:Type ToggleButton}}" IsChecked="True"/>
                    <RadioButton Name="toggle2" Content="Two" GroupName="ToggleGroup" Style="{StaticResource {x:Type ToggleButton}}"/>
                </StackPanel>
            </GroupBox>
            <Button Name="toggle" Content="Toggle enabled" Click="toggle_Click"/>
            <Button Name="changeValue" Content="Change value" Click="changeValue_Click"/>
        </StackPanel>
    </Window>
    

    Code behind:

    using System.ComponentModel;
    using System.Windows;
    
    namespace ToggleButtonDemo
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window, INotifyPropertyChanged
        {
    
            private bool mEnable = true;
    
            public bool Enable
            {
                get
                {
                    return mEnable;
                }
                set
                {
                    mEnable = value;
                    OnPropertyChanged(nameof(Enable));
                }
            }
    
            public MainWindow()
            {
                InitializeComponent();
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            public virtual void OnPropertyChanged(string propertyName)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
    
            private void toggle_Click(object sender, RoutedEventArgs e)
            {
                Enable = !Enable;
            }
    
            private void changeValue_Click(object sender, RoutedEventArgs e)
            {
                if (radio1.IsChecked == true)
                {
                    radio2.IsChecked = true;
                }
                else if (radio2.IsChecked == true)
                {
                    radio1.IsChecked = true;
                }
    
                if (toggle1.IsChecked == true)
                {
                    toggle2.IsChecked = true;
                }
                else if (toggle2.IsChecked == true)
                {
                    toggle1.IsChecked = true;
                }
            }
        }
    }
    
  • sclarke81
    sclarke81 over 7 years
    This is great. It gives me the behaviour I'm looking for. Is it possible to achieve this without having to redefine so much of the style? I'd really like to retain more of the standard ToggleButton style.
  • mm8
    mm8 over 7 years
    No, you have to re-define the entire template. You cannot override only part of a template. But this is how the default template looks like on Windows 10 with some slight modifications. You can copy the default into your XAML markup by right-clicking on a ToggleButton in design mode in VS or in Blend and choose Edit Template->Edit a Copy and then modify it as per your requirements.
  • sclarke81
    sclarke81 over 7 years
    Thanks, this got me what I needed.

Related