Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Style WPF radio button as toggle button with correct IsEnabled behaviour

Tags:

c#

wpf

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;
            }
        }
    }
}
like image 317
sclarke81 Avatar asked Feb 07 '17 11:02

sclarke81


2 Answers

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"/>
like image 149
John Fairbanks Avatar answered Nov 10 '22 23:11

John Fairbanks


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

like image 40
mm8 Avatar answered Nov 10 '22 23:11

mm8