Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPFSpark: 2 of n: ToggleSwitch

0.00/5 (No votes)
24 Dec 2011 1  
A toggle control in WPF

Introduction

This is the second article in the WPFSpark series. WPFSpark is an Open Source project in CodePlex containing a library of user controls which can be used by the developer community. I was planning to write this article a few weeks ago, but I was preoccupied with creating a few more exciting controls for the WPFSpark library. I will be describing these controls in my forthcoming articles.

In this article, I describe in detail about the second control in this library which I have developed. It is called the ToggleSwitch control and it derives from ToggleButton and provides a rich user experience. It supports only two states: True or False.

The previous article in the WPFSpark series can be accessed here:

  1. WPFSpark: 1 of n: SprocketControl

Background

ToggleButton

System.Windows.Controls.Primitives.ToggleButton is a base class for controls that can switch states, such as CheckBox. It supports three states: True, False, and Null.

FluidMoveBehavior

Before delving into the details of the ToggleSwitch, one more WPF feature (which plays an important role in the ToggleSwitch control) needs mention - FluidMoveBehavior. FluidMoveBehavior is an Expression Blend feature which allows you to animate a change in the position of an element. The animation looks very realistic as it allows the use of EasingFunctions. Here are a few links which describe the FluidMoveBehavior in much more detail:

ToggleSwitch Control Demystified

Simple ToggleSwitch

The first version of the ToggleSwitch control which I created was the Simple ToggleSwitch control. In order to create it, I defined a custom template for the ToggleButton, which had a Grid consisting of two columns and a single row.

The first column (Column 0) contained a TextBlock which would define the text for the Checked state. Similarly, the second column (Column 1) contained a TextBlock which would define the text for the Unchecked state.

Additionally, the template also contained a Border depicting the Thumb of the ToggleSwitch. The location of the Thumb, in the grid, would be either the first or the second column depending upon whether the ToggleSwitch is in the Checked or Unchecked state.

Adding the FluidMoveBehavior to the Grid will ensure that whenever the state of the ToggleSwitch changes, the movement of the Thumb from one column to the other will be animated and not instantaneous. (You should add references of Microsoft.Expression.Interactions.dll and System.Windows.Interactivity.dll to your project to get the FluidMoveBehaviour.)

Here is the control template for the Simple ToggleSwitch:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="clr-namespace:System.Windows.Interactivity;
                     assembly=System.Windows.Interactivity"
            xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">

    <Style x:Key="ToggleSwitchStyle"
           TargetType="{x:Type ToggleButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <ControlTemplate.Resources>
                        <Storyboard x:Key="Timeline1">
                            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
                                   Storyboard.TargetName="Glow"
                                   Storyboard.TargetProperty="(UIElement.Opacity)">
                                <SplineDoubleKeyFrame KeyTime="00:00:00.4000000"
                                                      Value="1" />
                            </DoubleAnimationUsingKeyFrames>
                        </Storyboard>
                        <Storyboard x:Key="Timeline2">
                            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
                                       Storyboard.TargetName="Glow"
                                       Storyboard.TargetProperty="(UIElement.Opacity)">
                                <SplineDoubleKeyFrame KeyTime="00:00:00.4000000"
                                        Value="0" />
                            </DoubleAnimationUsingKeyFrames>
                        </Storyboard>
                    </ControlTemplate.Resources>
                    <Grid MinWidth="120"
                          MinHeight="40"
                          Width="{TemplateBinding Width}"
                          Height="{TemplateBinding Height}"
                          Background="Black">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"></ColumnDefinition>
                            <ColumnDefinition Width="*"></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <i:Interaction.Behaviors>
                            <ei:FluidMoveBehavior AppliesTo="Children"
                                                  Duration="0:0:0.25">
                                <ei:FluidMoveBehavior.EaseX>
                                    <SineEase EasingMode="EaseIn" />
                                </ei:FluidMoveBehavior.EaseX>
                            </ei:FluidMoveBehavior>
                        </i:Interaction.Behaviors>
                        <Border Grid.ColumnSpan="2"
                                BorderBrush="LightGray"
                                Background="Transparent"
                                CornerRadius="6"
                                BorderThickness="1"></Border>
                        <TextBlock x:Name="OffText"
                               Grid.Column="0"
                               HorizontalAlignment="Center"
                               Text="Debug"
                               FontFamily="/CvisBuilder;Component/Resources/Font/#Segoe WP"
                               FontWeight="Light"
                               VerticalAlignment="Center"
                               Foreground="LawnGreen"
                               FontSize="{TemplateBinding FontSize}"></TextBlock>
                        <TextBlock x:Name="OnText"
                               Grid.Column="1"
                               HorizontalAlignment="Center"
                               FontFamily="/CvisBuilder;Component/Resources/Font/#Segoe WP"
                               FontWeight="Light"
                               Text="Release"
                               VerticalAlignment="Center"
                               Foreground="LawnGreen"
                               FontSize="{TemplateBinding FontSize}"></TextBlock>
                        <Border Name="Thumb"
                                Grid.Column="0"
                                BorderBrush="White"
                                BorderThickness="1"
                                Margin="3"
                                CornerRadius="4"
                                Background="#222222">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="0.507*" />
                                    <RowDefinition Height="0.493*" />
                                </Grid.RowDefinitions>
                                <Border Opacity="0"
                                        HorizontalAlignment="Stretch"
                                        x:Name="Glow"
                                        Width="Auto"
                                        Grid.RowSpan="2"
                                        CornerRadius="2">
                                    <Border.Background>
                                        <RadialGradientBrush>
                                            <RadialGradientBrush.RelativeTransform>
                                                <TransformGroup>
                                                    <ScaleTransform ScaleX="1.702"
                                                                    ScaleY="2.743" />
                                                    <SkewTransform AngleX="0"
                                                                   AngleY="0" />
                                                    <RotateTransform Angle="0" />
                                                    <TranslateTransform X="-0.368"
                                                                        Y="-0.152" />
                                                </TransformGroup>
                                            </RadialGradientBrush.RelativeTransform>
                                            <GradientStop Color="LawnGreen"
                                                          Offset="0" />
                                            <GradientStop Color="#44006400"
                                                          Offset="0.75" />
                                        </RadialGradientBrush>
                                    </Border.Background>
                                </Border>
                                <Border HorizontalAlignment="Stretch"
                                        Margin="0,0,0,0"
                                        x:Name="shine"
                                        Width="Auto"
                                        CornerRadius="2,2,0,0">
                                    <Border.Background>
                                        <LinearGradientBrush EndPoint="0.494,0.889"
                                                             StartPoint="0.494,0.028">
                                            <GradientStop Color="#99FFFFFF"
                                                          Offset="0" />
                                            <GradientStop Color="#33FFFFFF"
                                                          Offset="1" />
                                        </LinearGradientBrush>
                                    </Border.Background>
                                </Border>
                            </Grid>
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked"
                                 Value="true">
                            <Setter Property="Grid.Column"
                                    TargetName="Thumb"
                                    Value="1" />
                        </Trigger>
                        <Trigger Property="IsEnabled"
                                 Value="false">
                            <Setter Property="Opacity"
                                    Value="0.4" />
                            <Setter TargetName="OffText"
                                    Property="Foreground"
                                    Value="LightGray"></Setter>
                            <Setter TargetName="OnText"
                                    Property="Foreground"
                                    Value="LightGray"></Setter>
                        </Trigger>
                        <Trigger Property="IsPressed"
                                 Value="True">
                            <Setter Property="Opacity"
                                    TargetName="shine"
                                    Value="0.6" />
                        </Trigger>
                        <Trigger Property="IsMouseOver"
                                 Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard
                                    Storyboard="{StaticResource Timeline1}" />
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard x:Name="Timeline2_BeginStoryboard"
                                    Storyboard="{StaticResource Timeline2}" />
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Sophisticated ToggleSwitch

Though I was happy with the accomplishment of the Simple ToggleSwitch creation, I was far from being satisfied. I wanted to make the ToggleSwitch appear more realistic by making the TextBlocks (used for displaying the two states of the ToggleSwitch) appear along with the Thumb. I also wanted to make the ToggleSwitch control easily skinnable by modifying a few properties.

To accomplish this, the first requirement was a WPF Grid with rounded corners. Since the Grid does not have the CornerRadius property, the other option I had was to encapsulate the Grid within a Border and set the ClipToBounds property of the Border to true. Then I found out that Border (and all decorators) do not perform the clipping even though ClipToBounds is set to true.

For this purpose, I wrote the ClipBorder class. It also allows the user to specify different corner radius for each of the corners of the border. (For more details, check out my article in my blog - ClipBorder: A WPF Border that clips.)

Now the ClipBorder will encapsulate the Grid (named PART_RootGrid) that will be used to position the content of the ToggleSwitch based on its state. This Grid has three columns (Left, Center, and Right). The width of each column is dynamically calculated based on the width of the Thumb.

The Content of the ToggleSwitch is placed in another Grid (named PART_ContentGrid). This Grid contains five columns (Left, CenterLeft, Center, CenterRight, and Right).

The ContentGrid contains the following:

  • CheckedBorder - This defines the background that is displayed in the Checked state of the ToggleSwitch control. It occupies the Left and CentreLeft columns.
  • CheckedTextBlock - This contains the text that is displayed in the Checked state. It occupies the Left column.
  • Thumb - This contains the thumb of the ToggleSwitch control. It occupies the CenterLeft, Center, and CenterRight columns.
  • UncheckedBorder - This defines the background that is displayed in the Unchecked state of the ToggleSwitch control. It occupies the CentreRight and Right columns.
  • UncheckedTextBlock - This contains the text that is displayed in the Unchecked state. It occupies the Right column.

The Thumb contains another Grid which contains a Border which provides a shine to the Thumb.

The image below shows the outermost ClipBorder and the ContentGrid in details.

Here is the Exploded 3D view of a ToggleSwitch control obtained using a wonderful tool called Snoop.

The ContentGrid has a larger width than the RootGrid, but since both are encapsulated within a ClipBorder, the portion outside the ClipBorder is clipped. Based on the state of the ToggleSwitch (True or False) and the size of the thumb, the column within the RootGrid where the ContentGrid has to be placed is calculated.

The ThumbWidth dependency property of the ToggleSwitch control defines the width of the Thumb as a percentage of the Total Width of the ToggleSwitch control. It can have values ranging from 10 to 90 (inclusive). Based on the ThumbWidth, the width of each of the columns of the RootGrid and the ContentGrid and the margin of the ContentGrid are calculated. The code below shows the calculation:

/// <summary>
/// Calculates the width and margin of the contents of the ContentBorder
/// The following calculation is used: (Here p is the value of ThumbWidth)
/// p = [10, 90]
/// Margin = 1 - p
/// Left = (1 - p)/(2 - p)
/// Right = (1 - p)/(2 - p)
/// Center = 0.03
/// CenterLeft = 0.485 - Left
/// CenterRight = 0.485 - Left
/// </summary>
private void CalculateLayout()
{
  if ((rootGrid == null) || (contentGrid == null))
    return;

  // Convert the ThumbWidth value to a percentage
  double thumbPercentage = ThumbWidth / 100.0;
  // Calculate the percentage of Total width available for the content
  double contentPercentage = 1 - thumbPercentage;

  if (thumbPercentage <= 0.5)
  {
    // Calculate the width of the RootGrid Columns
    rootGrid.ColumnDefinitions[0].Width =
      new GridLength(thumbPercentage, GridUnitType.Star);
    rootGrid.ColumnDefinitions[1].Width =
      new GridLength(1.0 - (2 * thumbPercentage), GridUnitType.Star);
    rootGrid.ColumnDefinitions[2].Width =
      new GridLength(thumbPercentage, GridUnitType.Star);

    // Adjust the thumb
    TargetColumnInternal = 2;
    Grid.SetColumnSpan(contentBorder, 1);
  }
  else
  {
    // Calculate the width of the RootGrid Columns
    rootGrid.ColumnDefinitions[0].Width =
      new GridLength(contentPercentage, GridUnitType.Star);
    rootGrid.ColumnDefinitions[1].Width =
      new GridLength(1.0 - (2 * contentPercentage), GridUnitType.Star);
    rootGrid.ColumnDefinitions[2].Width =
      new GridLength(contentPercentage, GridUnitType.Star);

    // Adjust the thumb
    TargetColumnInternal = 1;
    Grid.SetColumnSpan(contentBorder, 2);
  }

  // Calculate the width of the ContentGrid Columns
  double leftRight = (1 - thumbPercentage) / (2 - thumbPercentage);
  double centerLeftRight = 0.485 - leftRight;
  double center = 0.03;
  contentGrid.ColumnDefinitions[0].Width =
      new GridLength(leftRight, GridUnitType.Star);
  contentGrid.ColumnDefinitions[1].Width =
      new GridLength(centerLeftRight, GridUnitType.Star);
  contentGrid.ColumnDefinitions[2].Width =
      new GridLength(center, GridUnitType.Star);
  contentGrid.ColumnDefinitions[3].Width =
      new GridLength(centerLeftRight, GridUnitType.Star);
  contentGrid.ColumnDefinitions[4].Width =
      new GridLength(leftRight, GridUnitType.Star);

  contentBorderMargin = contentPercentage;

  CalculateContentBorderMargin();

  InvalidateVisual();
}

/// <summary>
/// Calculates the margin of the contentBorder
/// </summary>
private void CalculateContentBorderMargin()
{
  if (contentBorder != null)
  {
    // Change the margin of the content border so that
    // its size is (1 + contentBorderMargin) times the width of
    // the Toggle switch
    contentBorder.Margin = new Thickness(-(this.Width * contentBorderMargin),
                           0, -(this.Width * contentBorderMargin), 0);
  }
}

Here is the Control Template for the Sophisticated ToggleSwitch control:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="clr-namespace:System.Windows.Interactivity;
                 assembly=System.Windows.Interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:wpfspark="clr-namespace:WPFSpark">

    <!-- ToggleSwitch Template -->
    <ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly=
                            wpfspark:ToggleSwitch, ResourceId=ToggleSwitchTemplate}"
                TargetType="{x:Type wpfspark:ToggleSwitch}">
        <ControlTemplate.Resources>
            <Storyboard x:Key="BeginGlow">
                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
                       Storyboard.TargetName="Glow"
                       Storyboard.TargetProperty="(UIElement.Opacity)">
                    <SplineDoubleKeyFrame KeyTime="00:00:00.4000000"
                                          Value="1" />
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
            <Storyboard x:Key="EndGlow">
                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
                               Storyboard.TargetName="Glow"
                               Storyboard.TargetProperty="(UIElement.Opacity)">
                    <SplineDoubleKeyFrame KeyTime="00:00:00.4000000"
                                          Value="0" />
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
        </ControlTemplate.Resources>

        <!-- Outermost border of the ToggleSwitch -->
        <wpfspark:ClipBorder Width="{TemplateBinding Width}"
                 Height="{TemplateBinding Height}"
                 BorderBrush="{TemplateBinding BorderBrush}"
                 Background="{TemplateBinding Background}"
                 CornerRadius="{Binding Path=CornerRadius,
                               RelativeSource={RelativeSource
                               AncestorType={x:Type wpfspark:ToggleSwitch}}}"
                 BorderThickness="{TemplateBinding BorderThickness}">
            <Grid Name="PART_RootGrid">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="0.4*"></ColumnDefinition>
                    <ColumnDefinition Width="0.2*"></ColumnDefinition>
                    <ColumnDefinition Width="0.4*"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <i:Interaction.Behaviors>
                    <ei:FluidMoveBehavior AppliesTo="Children"
                                          Duration="0:0:0.2">
                        <ei:FluidMoveBehavior.EaseX>
                            <QuarticEase  EasingMode="EaseIn" />
                        </ei:FluidMoveBehavior.EaseX>
                    </ei:FluidMoveBehavior>
                </i:Interaction.Behaviors>
                <!-- PART_Content -->
                <Border Name="PART_ContentBorder"
                        Grid.Column="0"
                        CornerRadius="0"
                        Background="Transparent"
                        BorderThickness="0">
                    <Grid Name="PART_ContentGrid">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="0.375*"></ColumnDefinition>
                            <ColumnDefinition Width="0.1*"></ColumnDefinition>
                            <ColumnDefinition Width="0.05*"></ColumnDefinition>
                            <ColumnDefinition Width="0.1*"></ColumnDefinition>
                            <ColumnDefinition Width="0.375*"></ColumnDefinition>
                        </Grid.ColumnDefinitions>
                        <!-- Checked State -->
                        <!-- Background displayed when
                                  the ToggleSwitch is in the Checked State -->
                        <Border x:Name="CheckedBorder"
                            Grid.Column="0"
                            Grid.ColumnSpan="2"
                            BorderThickness="0"
                            Margin="0"
                            CornerRadius="0"
                            Background="{Binding Path=CheckedBackground,
                                        RelativeSource={RelativeSource
                                        AncestorType={x:Type wpfspark:ToggleSwitch}}}">
                        </Border>
                        <!-- Text which is displayed when
                                  the ToggleSwitch is in the Checked State -->
                        <TextBlock x:Name="CheckedTextBlock"
                               Grid.Column="0"
                               Text="{Binding Path=CheckedText,
                                     RelativeSource={RelativeSource
                                     AncestorType={x:Type wpfspark:ToggleSwitch}}}"
                               HorizontalAlignment="Center"
                               VerticalAlignment="Center"
                               FontFamily="{TemplateBinding FontFamily}"
                               FontWeight="{TemplateBinding FontWeight}"
                               FontSize="{TemplateBinding FontSize}"
                               Foreground="{Binding Path=CheckedForeground,
                                           RelativeSource={RelativeSource
                                           AncestorType={x:Type wpfspark:ToggleSwitch}}}"
                        ></TextBlock>

                        <!-- Unchecked State -->
                        <!-- Background displayed when the ToggleSwitch
                                      is in the Unchecked State -->
                        <Border x:Name="UncheckedBorder"
                                Grid.Column="3"
                                Grid.ColumnSpan="2"
                                BorderThickness="0"
                                Margin="0"
                                CornerRadius="0"
                                Background="{Binding Path=UncheckedBackground,
                                            RelativeSource={RelativeSource
                                            AncestorType={x:Type wpfspark:ToggleSwitch}}}">
                        </Border>
                        <!-- Text which is displayed when the ToggleSwitch
                                     is in the Unchecked State -->
                        <TextBlock x:Name="UncheckedTextBlock"
                                   Grid.Column="4"
                                   Text="{Binding Path=UncheckedText,
                                         RelativeSource={RelativeSource
                                         AncestorType={x:Type wpfspark:ToggleSwitch}}}"
                                   HorizontalAlignment="Center"
                                   VerticalAlignment="Center"
                                   FontFamily="{TemplateBinding FontFamily}"
                                   FontWeight="{TemplateBinding FontWeight}"
                                   FontSize="{TemplateBinding FontSize}"
                                   Foreground="{Binding Path=UncheckedForeground,
                                               RelativeSource={RelativeSource
                                               AncestorType={x:Type wpfspark:ToggleSwitch}}}">
                        </TextBlock>

                        <!-- ToggleSwitch Thumb -->
                        <wpfspark:ClipBorder x:Name="PART_Thumb"
                             Grid.Column="1"
                             Grid.ColumnSpan="3"
                             Margin="0"
                             BorderBrush="{Binding Path=ThumbBorderBrush,
                                          RelativeSource={RelativeSource
                                          AncestorType={x:Type wpfspark:ToggleSwitch}}}"
                             BorderThickness="{Binding Path=ThumbBorderThickness,
                                              RelativeSource={RelativeSource
                                              AncestorType={x:Type wpfspark:ToggleSwitch}}}"
                             CornerRadius="{Binding Path=ThumbCornerRadius,
                                           RelativeSource={RelativeSource
                                           AncestorType={x:Type wpfspark:ToggleSwitch}}}"
                             Background="{Binding Path=ThumbBackground,
                                         RelativeSource={RelativeSource
                                         AncestorType={x:Type wpfspark:ToggleSwitch}}}">
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="0.507*" />
                                    <RowDefinition Height="0.493*" />
                                </Grid.RowDefinitions>
                                <Border x:Name="Glow"
                                    Opacity="0"
                                    HorizontalAlignment="Stretch"
                                    Width="Auto"
                                    Grid.RowSpan="2"
                                    CornerRadius="{Binding Path=ThumbCornerRadius,
                                                  RelativeSource={RelativeSource
                                                  AncestorType=
						{x:Type wpfspark:ToggleSwitch}}}">
                                    <Border.Background>
                                        <RadialGradientBrush>
                                            <RadialGradientBrush.RelativeTransform>
                                                <TransformGroup>
                                                    <ScaleTransform ScaleX="1.702"
                                                                    ScaleY="2.743" />
                                                    <SkewTransform AngleX="0"
                                                                   AngleY="0" />
                                                    <RotateTransform Angle="0" />
                                                    <TranslateTransform X="-0.368"
                                                                        Y="-0.152" />
                                                </TransformGroup>
                                            </RadialGradientBrush.RelativeTransform>
                                            <GradientStop
                                              Color="{Binding Path=ThumbGlowColor,
                                                     RelativeSource={RelativeSource
                                                     AncestorType=
						{x:Type wpfspark:ToggleSwitch}}}"
                                              Offset="0.1" />
                                            <GradientStop Color="#44222222"
                                                          Offset="0.8" />
                                        </RadialGradientBrush>
                                    </Border.Background>
                                </Border>
                                <Border x:Name="Shine"
                                        HorizontalAlignment="Stretch"
                                        Margin="0,0,0,0"
                                        Width="Auto"
                                        CornerRadius="{Binding Path=ThumbShineCornerRadius,
                                                      RelativeSource={RelativeSource
                                                      AncestorType=
						{x:Type wpfspark:ToggleSwitch}}}">
                                    <Border.Background>
                                        <LinearGradientBrush EndPoint="0.494,0.889"
                                                             StartPoint="0.494,0.028">
                                            <GradientStop Color="#99FFFFFF"
                                                          Offset="0" />
                                            <GradientStop Color="#33FFFFFF"
                                                          Offset="1" />
                                        </LinearGradientBrush>
                                    </Border.Background>
                                </Border>
                            </Grid>
                        </wpfspark:ClipBorder>

                    </Grid>
                </Border>
            </Grid>
        </wpfspark:ClipBorder>

        <ControlTemplate.Triggers>
            <Trigger Property="ToggleButton.IsChecked"
                     Value="false">
                <Setter Property="Grid.Column"
                        TargetName="PART_ContentBorder"
                        Value="{Binding Path=TargetColumnInternal,
                               RelativeSource={RelativeSource
                               AncestorType={x:Type wpfspark:ToggleSwitch}}}" />
            </Trigger>
            <Trigger Property="IsEnabled"
                     Value="false">
                <Setter Property="Opacity"
                        Value="0.4" />
                <Setter TargetName="UncheckedTextBlock"
                        Property="Foreground"
                        Value="LightGray"></Setter>
                <Setter TargetName="CheckedTextBlock"
                        Property="Foreground"
                        Value="LightGray"></Setter>
            </Trigger>
            <Trigger Property="ToggleButton.IsPressed"
                     Value="True">
                <Setter Property="Opacity"
                        TargetName="Shine"
                        Value="0.6" />
            </Trigger>
            <Trigger Property="ToggleButton.IsMouseOver"
                     Value="True">
                <Trigger.EnterActions>
                    <BeginStoryboard Storyboard="{StaticResource BeginGlow}" />
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard Storyboard="{StaticResource EndGlow}" />
                </Trigger.ExitActions>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>

    <Style TargetType="{x:Type wpfspark:ToggleSwitch}">
        <Setter Property="Template"
           Value="{StaticResource {ComponentResourceKey
                  TypeInTargetAssembly=wpfspark:ToggleSwitch,
                  ResourceId=ToggleSwitchTemplate}}" />
    </Style>
</ResourceDictionary>

Update: WPFSpark v1.0

With the release of WPFSpark v1.0, the following changes have been incorporated into ToggleSwitch:

  • Added the IsCheckedLeft dependency property which indicates whether the checked content appears in the left or right side of the ToggleSwitch.
  • Added the CheckedToolTip property which is displayed when the ToggleSwitch is in the Checked state. Set this property to String.Empty( "" ) to prevent this tooltip from displaying.
  • Added the UncheckedToolTip property which is displayed when the ToggleSwitch is in the Unchecked state. Set this property to String.Empty( "" ) to prevent this tooltip from displaying.

Check out the latest source code available at CodePlex to get the latest ToggleSwitch.

ToggleSwitch Properties

Dependency Property Type Description Default Value
CheckedBackground Brush Gets or sets the Background of the CheckedBorder in the ToggleSwitch control. White
CheckedForeground Brush Gets or sets the Foreground color of the CheckedText. Black
CheckedText String Gets or sets the text that is displayed when the ToggleSwitch control is in the Checked state. String.Empty
CheckedToolTip String Gets or sets the ToolTip that is displayed when the ToggleSwitch is in the Checked state. Set this property to String.Empty( "" ) to prevent this tooltip from displaying. String.Empty
CornerRadius CornerRadius Gets or sets the CornerRadius of the outermost ClipBorder. 0
IsCheckedLeft Boolean Gets or sets whether the checked content appears in the left or right side of the ToggleSwitch. True
TargetColumnInternal Int32 Gets or sets the column in the RootGrid where the ContentGrid must be placed when the ToggleSwitch is in the Unchecked state. This property is used internally and should not be set by the user. 0
ThumbBackground Brush Gets or sets the Background brush of the Thumb. Black
ThumbBorderBrush Brush Gets or sets the color of the Thumb Border. Gray
ThumbBorderThickness Thickness Gets or sets the thickness of the Thumb Border. 0
ThumbCornerRadius CornerRadius Gets or sets the CornerRadius of the Thumb Border. 0
ThumbGlowColor Color Gets or sets the Color of the glow over the Thumb when the mouse hovers over it. LawnGreen
ThumbShineCornerRadius CornerRadius Gets or sets the CornerRadius of the Thumb Border's shine. 0
ThumbWidth Double Gets or sets the width of the Thumb as a percentage of the Total width of the ToggleSwitch control. Value Range: 10 - 90 (inclusive). 40
UncheckedBackground Brush Gets or sets the Background of the UncheckedBorder in the ToggleSwitch control. White
UncheckedForeground Brush Gets or sets the Foreground color of the CheckedText. Black
UncheckedText String Gets or sets the text that is displayed when the ToggleSwitch control is in the Unchecked state. String.Empty
UncheckedToolTip String Gets or sets the ToolTip that is displayed when the ToggleSwitch is in the Unchecked state. Set this property to String.Empty( "" ) to prevent this tooltip from displaying. String.Empty

Skinning the ToggleSwitch

You can skin the ToggleSwitch by modifying the above dependency properties. You can modify the background of the Checked and Unchecked states of the control. You can even modify the CornerRadius of the Thumb to get a variety of Thumb shapes ranging from rectangle to rounded rectangles and even to circles too!

Here are a few examples of the various skins of the ToggleSwitch control.

EndPoint

If you want to learn more about custom control development in WPF, I would suggest you read the book WPF Control Development Unleashed by Pavan Podila. It is deeply informative and contains lots of tips and tricks and other interesting facts regarding custom controls.

History

  • December 21, 2011: WPFSpark v1.0 released
  • July 31, 2011: WPFSpark v0.6 released

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here