Introduction
This article describes a simple WPF two state slider button. The text displayed for each state, and the size of the slider button are configurable from XAML.
The slider button is configured using a combination of dependency properties and template resources. The sample code includes three template resources which allow you to create the following:
- A slider control with round ends, a round button, and a label on the left
- A slider control with round ends, a round button, and a label on the slider track
- A slider control with square ends, a square button, and a label on the slider track
Background
We needed a simple slider button for our product, and since Microsoft does not provide one as standard, I was forced to write a custom control. There are many examples online but most if not all tend to hard code the appearance. Thus the colours are fixed, or the text is fixed, or the size is fixed. The more flexible ones tend to be rather complex. We wanted something that was more flexible, with the appearance including the size (width and height) controlled by attributes in the XAML code. A key requirement was for the code to be as simple as possible, which ensures that it is easy to understand, and easy to extend to suit your own needs if required. I could have made it even more flexible, but the current version does everything I require.
Ideally, you should be familiar with the basics of dependency properties, resource dictionaries, and control templates. However, even if your knowledge is limited, you should be able to figure out how to use the code, and be able to make simple changes to suit your own requirements.
Screenshots
The demonstration application includes examples of all three styles:
Using the Code
The SliderControl
is derived from the standard WPF ToggleButton
class. It adds three dependency properties:
ButtonWidth | The width of the slider button |
OnLabel | The text to display when the button is set to the on position |
OffLabel | The text to display when the button is set to the off position |
The overall width of the button is controlled by the Width
property of the base control. The height is controlled by the ButtonWidth
custom property.
There are three styles which define three forms of slider button:
styleSliderButtonSideLabel | A slider button with a label on the left hand side |
styleSliderButton | A slider button with a circular button |
styleSliderButtonRectangular | A slider button with a rectangular button |
Each slider button style defines a template that overrides the appearance of the control. Each slider button is constructed from a combination of ellipse, border and label controls aligned using grid controls. Triggers are used to show and hide component controls as and when the button is checked and unchecked.
The SliderButton Class
The SliderButton
class is derived from the standard ToggleButton
class and adds three dependency properties:
namespace WpfSliderButtonDemo.View
{
public class SliderButton : System.Windows.Controls.Primitives.ToggleButton
{
public double ButtonWidth
{
get
{
return (double)GetValue(ButtonWidthProperty);
}
set
{
SetValue(ButtonWidthProperty, value);
}
}
public static readonly System.Windows.DependencyProperty ButtonWidthProperty =
System.Windows.DependencyProperty.Register("ButtonWidth", typeof(double),
typeof(SliderButton), new System.Windows.PropertyMetadata(0.0));
public string OnLabel
{
get
{
return (string)GetValue(OnLabelProperty);
}
set
{
SetValue(OnLabelProperty, value);
}
}
public static readonly System.Windows.DependencyProperty
OnLabelProperty = System.Windows.DependencyProperty.Register
("OnLabel", typeof(string), typeof(SliderButton),
new System.Windows.PropertyMetadata(""));
public string OffLabel
{
get
{
return (string)GetValue(OffLabelProperty);
}
set
{
SetValue(OffLabelProperty, value);
}
}
public static readonly System.Windows.DependencyProperty OffLabelProperty =
System.Windows.DependencyProperty.Register("OffLabel", typeof(string),
typeof(SliderButton), new System.Windows.PropertyMetadata(""));
}
}
The styleSliderButton Style
The styleSliderButton
style defines a slider button with a circular button and a label on the slider track:
<Style x:Key="styleSliderButton" TargetType="{x:Type local:SliderButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SliderButton}">
<Grid x:Name="mainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Name="_borderOn"
Background="Transparent" Width="{TemplateBinding Width}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Row="0" Grid.RowSpan="1"
Grid.Column="0" Grid.ColumnSpan="2"
Width="{TemplateBinding ButtonWidth}"
Height="{TemplateBinding ButtonWidth}"
Style="{StaticResource styleEllipseGreyButton}"
Panel.ZIndex="3" />
<Border Grid.Row="0" Grid.RowSpan="1" Grid.Column="1"
Grid.ColumnSpan="3" Background="ForestGreen"
BorderBrush="Gray" BorderThickness="0,1,0,1" Panel.ZIndex="1"/>
<Label Grid.Row="0" Grid.RowSpan="1" Grid.Column="2"
Grid.ColumnSpan="3" Name="_labelOn"
Content="{TemplateBinding OnLabel}"
SPanel.ZIndextyle=
"{StaticResource styleSliderButtonLabel}" ="2"/>
<Ellipse Grid.Row="0" Grid.RowSpan="1" Grid.Column="3"
Grid.ColumnSpan="2" Width="{TemplateBinding ButtonWidth}"
Height="{TemplateBinding ButtonWidth}"
Style="{StaticResource styleEllipseButton}"
Fill="ForestGreen" Panel.ZIndex="0"/>
</Grid>
</Border>
<Border Grid.Column="0" Name="_borderOff"
Background="Transparent" Width="{TemplateBinding Width}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Row="0" Grid.RowSpan="1" Grid.Column="0"
Grid.ColumnSpan="2" Width="{TemplateBinding ButtonWidth}"
Height="{TemplateBinding ButtonWidth}"
VerticalAlignment="Stretch" Fill="Crimson"
Stroke="Gray" Panel.ZIndex="0"/>
<Label Grid.Row="0" Grid.RowSpan="1" Grid.Column="0"
Grid.ColumnSpan="3" Name="_labelOff"
Content="{TemplateBinding OffLabel}"
Style="{StaticResource styleSliderButtonLabel}"
Panel.ZIndex="2"/>
<Border Grid.Row="0" Grid.RowSpan="1" Grid.Column="1"
Grid.ColumnSpan="3" Background="Crimson" BorderBrush="Gray"
BorderThickness="0,1,0,1" Panel.ZIndex="1"/>
<Ellipse Grid.Row="0" Grid.RowSpan="1"
Grid.Column="3" Grid.ColumnSpan="2"
Width="{TemplateBinding ButtonWidth}"
Height="{TemplateBinding ButtonWidth}"
Style="{StaticResource styleEllipseGreyButton}"
Panel.ZIndex="3"/>
</Grid>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="_borderOff" Property="Visibility"
Value="Collapsed" />
<Setter TargetName="_borderOn" Property="Visibility"
Value="Visible" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="_borderOff" Property="Visibility"
Value="Visible" />
<Setter TargetName="_borderOn" Property="Visibility"
Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The style overrides the Template
property which defines the appearance and behaviour of the control. The control is constructed from a grid with two compound elements, one which is shown when the control is checked, and the other when it is unchecked. Each compound element consists of a grid with ellipse, border and label child elements which together create the appearance of a slider control.
Note the use of the Panel.ZIndex
attached property to allow the slider button ellipse to appear in front of the track, and the track to appear in front of the ellipse that represents the track end.
Note also the use of the TemplateBinding
markup extension, which allows an element in the template to use a property defined on the templated control, i.e., the SliderButton
, including properties defined on the ToggleButton
control class.
The slider button is created using an ellipse element with the styleEllipseGreyButton
style:
<Style x:Key="styleEllipseGreyButton" TargetType="{x:Type Ellipse}"
BasedOn="{StaticResource styleEllipseButton}">
<Setter Property="Fill">
<Setter.Value>
<LinearGradientBrush EndPoint="0.504,1.5" StartPoint="0.504,0.03">
<GradientStop Color="#FFFFFF" Offset="0"/>
<GradientStop Color="#BBBBBB" Offset="0.567"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
The appearance of the label text is defined using the styleSliderButtonLabel
style:
<Style x:Key="styleSliderButtonLabel" TargetType="{x:Type Label}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="0"/>
</Style>
Using the Control
Adding the custom slider control to a view is extremely easy:
<view:SliderButton Grid.Row="7" Grid.Column="1" OnLabel="On" OffLabel="Off"
Width="110" ButtonWidth="50" Style="{StaticResource styleSliderButton}"
HorizontalAlignment="Center" IsChecked="{Binding IsEnabled}" FontSize="30"/>
Sample Application
I have provided a simple demonstration application that you can download. It includes the code for the slider button, and a main window containing numerous examples of the slider button using the provided styles.
Further Enhancements
Future enhancements include the creation of a vertical slider button, and the addition of dependency properties to control the background colours for the two states. These tasks are left as an exercise for the reader.
Bugs
I was unable to get the label text to be vertically centred. This is not obvious, but ideally it should be fixed.
Points of Interest
WPF is extremely flexible, and there are many ways to create the illusion of a toggle button using standard controls.
History
- 24th April, 2019: First version