Introduction
Two issues covered in this sample, in response to a
forum question about grouping buttons and changing the background colour of the selected button.
These concepts will apply to WPF and Windows Phone development.
Building the Sample
Just download, unblock, unzip, load and run!
Description
This project contains two solutions. One uses ToggleButtons, the other is using RadioButtons to look like grouped Buttons. Both use a restyled Button ControlTemplate as their visual.
The problem with the ToggleButton is it seems you can't change the Background colour through a normal trigger. The ToggleButton uses Button Chrome in its ControlTemplate. ButtonChrome handles the colour changing itself, so we can't override it.
Default ControlTemplate for ToggleButton
XAML
Edit|Remove
xaml
<ControlTemplate x:Key="ButtonBaseControlTemplate1" TargetType="{x:Type ButtonBase}">
<Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" RenderDefaulted="{TemplateBinding Button.IsDefaulted}" SnapsToDevicePixels="True">
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Microsoft_Windows_Themes:ButtonChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="RenderDefaulted" TargetName="Chrome" Value="True"/>
</Trigger>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter Property="RenderPressed" TargetName="Chrome" Value="True"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="#FFADADAD"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ControlTemplate x:Key="ButtonBaseControlTemplate1" TargetType="{x:Type ButtonBase}">
<Microsoft_Windows_Themes:ButtonChrome x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" RenderMouseOver="{TemplateBinding IsMouseOver}" RenderPressed="{TemplateBinding IsPressed}" RenderDefaulted="{TemplateBinding Button.IsDefaulted}" SnapsToDevicePixels="True">
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Microsoft_Windows_Themes:ButtonChrome>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="RenderDefaulted" TargetName="Chrome" Value="True"/>
</Trigger>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter Property="RenderPressed" TargetName="Chrome" Value="True"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="#FFADADAD"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Instead, we can use our own Button ControlTemplate and handle all the visual states ourselves. Then we can trigger on IsChecked and change the Background colour.
For the base XAML, I copied the "SimpleButton" style out of Expression "Blend's Simple Styles.xaml" Dictionary.
SimpleStyles are a set of control templates without any Theme componants like ButtonChrome. If you haven't found this yet, simply click the Assets button, then type "simple" and you will get the list of styles.
If you drag one to a control, the whole SimpleStyles Dictionary is added to the project and merged in App.xaml resources. You can remove it later when you copy out what you want.
Here is the restyled button:
XAML
Edit|Remove
xaml
<Style x:Key="SimpleButton" TargetType="{x:Type ToggleButton}" BasedOn="{x:Null}">
<Setter Property="FocusVisualStyle" Value="{DynamicResource SimpleButtonFocusVisual}"/>
<Setter Property="Background" Value="{DynamicResource NormalBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource NormalBorderBrush}"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid x:Name="Grid">
<Border x:Name="Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"/>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" Value="{DynamicResource DefaultedBorderBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" Value="{DynamicResource DefaultedBorderBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="{DynamicResource MouseOverBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" Value="{DynamicResource PressedBrush}" TargetName="Border"/>
<Setter Property="BorderBrush" Value="{DynamicResource PressedBorderBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsEnabled" Value="true"/>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" Value="{DynamicResource DisabledBackgroundBrush}" TargetName="Border"/>
<Setter Property="BorderBrush" Value="{DynamicResource DisabledBorderBrush}" TargetName="Border"/>
<Setter Property="Foreground" Value="{DynamicResource DisabledForegroundBrush}"/>
</Trigger>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter Property="Background" Value="Red" TargetName="Border"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SimpleButton" TargetType="{x:Type ToggleButton}" BasedOn="{x:Null}">
<Setter Property="FocusVisualStyle" Value="{DynamicResource SimpleButtonFocusVisual}"/>
<Setter Property="Background" Value="{DynamicResource NormalBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource NormalBorderBrush}"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid x:Name="Grid">
<Border x:Name="Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}"/>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" Value="{DynamicResource DefaultedBorderBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" Value="{DynamicResource DefaultedBorderBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="{DynamicResource MouseOverBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" Value="{DynamicResource PressedBrush}" TargetName="Border"/>
<Setter Property="BorderBrush" Value="{DynamicResource PressedBorderBrush}" TargetName="Border"/>
</Trigger>
<Trigger Property="IsEnabled" Value="true"/>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" Value="{DynamicResource DisabledBackgroundBrush}" TargetName="Border"/>
<Setter Property="BorderBrush" Value="{DynamicResource DisabledBorderBrush}" TargetName="Border"/>
<Setter Property="Foreground" Value="{DynamicResource DisabledForegroundBrush}"/>
</Trigger>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter Property="Background" Value="Red" TargetName="Border"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In the Triggers above, I have added a trigger for the ToggleButton.IsChecked property.
Solution 1: grouping with a ValueConverter
This method uses
ToggleButtons and a
ValueConverter that is worth knowing, as it comes in handy for many similar grouping tasks, as shown in this other
Code Sample. I simply renamed it to
IsCheckedConverter and it works perfectly.
C#
Edit|Remove
csharp
public class IsCheckedConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((string)value == (string)parameter);
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return parameter;
}
}
public class IsCheckedConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((string)value == (string)parameter);
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return parameter;
}
}
This is used as follows:
XAML
Edit|Remove
xaml
<ToggleButton Content="Toggle Button 1" IsChecked="{Binding CurrentButton, ConverterParameter=1, Converter={StaticResource IsCheckedConverter}}" Focusable="False" Style="{StaticResource SimpleButton}"/>
<ToggleButton Content="Toggle Button 1" IsChecked="{Binding CurrentButton, ConverterParameter=1, Converter={StaticResource IsCheckedConverter}}" Focusable="False" Style="{StaticResource SimpleButton}"/>
Each group item must simply have it's own unique ConverterParameter.
All group items bind to the same back-end property which controls which one is checked.
C#
Edit|Remove
csharp
string _CurrentButton;
public string CurrentButton
{
get
{
return _CurrentButton;
}
set
{
if (_CurrentButton != value)
{
_CurrentButton = value;
RaisePropertyChanged("CurrentButton");
}
}
}
string _CurrentButton;
public string CurrentButton
{
get
{
return _CurrentButton;
}
set
{
if (_CurrentButton != value)
{
_CurrentButton = value;
RaisePropertyChanged("CurrentButton");
}
}
}
Solution 2: RadioButton Grouping
If you don't need the ToggleButtons' Click event, you can use RadioButtons to group what look like Buttons.
This method simply groups three RadioButtons as normal, with GroupName.
However in this case, the ControlTemplate is replaced with our SimpleButton style with the IsChecked Trigger.
XAML
Edit|Remove
xaml
<Style BasedOn="{StaticResource SimpleButton}" TargetType="RadioButton" />
<Style BasedOn="{StaticResource SimpleButton}" TargetType="RadioButton" />
Source Code Files
- MainWindow.xaml - The Startup window
- mainWindow.xaml.cs - Contains the ValueConverter and CurrentButton property
