Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / WPF

WPFSpark: 2 of n: ToggleSwitch

Rate me:
Please Sign up or sign in to vote.
4.88/5 (34 votes)
24 Dec 2011Ms-PL8 min read 130.5K   4.8K   72   27
A toggle control in WPF

Image 1

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.

Image 2

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.

Image 3

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.

Image 4

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.

Image 5

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.

Image 6

Image 7

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:

XML
<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.

Image 8

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

Image 9

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.

Image 10

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

Image 11

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:

C#
/// <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:

XML
<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 PropertyTypeDescriptionDefault Value
CheckedBackgroundBrushGets or sets the Background of the CheckedBorder in the ToggleSwitch control.White
CheckedForegroundBrushGets or sets the Foreground color of the CheckedText.Black
CheckedTextStringGets or sets the text that is displayed when the ToggleSwitch control is in the Checked state.String.Empty
CheckedToolTipStringGets 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
CornerRadiusCornerRadiusGets or sets the CornerRadius of the outermost ClipBorder.0
IsCheckedLeftBooleanGets or sets whether the checked content appears in the left or right side of the ToggleSwitch.True
TargetColumnInternalInt32Gets 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
ThumbBackgroundBrushGets or sets the Background brush of the Thumb.Black
ThumbBorderBrushBrushGets or sets the color of the Thumb Border.Gray
ThumbBorderThicknessThicknessGets or sets the thickness of the Thumb Border.0
ThumbCornerRadiusCornerRadiusGets or sets the CornerRadius of the Thumb Border.0
ThumbGlowColorColorGets or sets the Color of the glow over the Thumb when the mouse hovers over it.LawnGreen
ThumbShineCornerRadiusCornerRadiusGets or sets the CornerRadius of the Thumb Border's shine.0
ThumbWidthDoubleGets or sets the width of the Thumb as a percentage of the Total width of the ToggleSwitch control. Value Range: 10 - 90 (inclusive).40
UncheckedBackgroundBrushGets or sets the Background of the UncheckedBorder in the ToggleSwitch control.White
UncheckedForegroundBrushGets or sets the Foreground color of the CheckedText.Black
UncheckedTextStringGets or sets the text that is displayed when the ToggleSwitch control is in the Unchecked state.String.Empty
UncheckedToolTipStringGets 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.

Image 12

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, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Software Developer
United States United States
An individual with more than a decade of experience in desktop computing and mobile app development primarily on the Microsoft platform. He loves programming in C#, WPF & XAML related technologies.
Current interests include web application development, developing rich user experiences across various platforms and exploring his creative side.

Ratish's personal blog: wpfspark.wordpress.com

Comments and Discussions

 
BugError Pin
cassini011-Oct-15 5:18
cassini011-Oct-15 5:18 
SuggestionExcellent, One Suggestion regarding threeState Pin
AditSheth1-Nov-13 1:14
AditSheth1-Nov-13 1:14 
Questionusing WPFSpark with Visual Studio Pin
Member 103258859-Oct-13 5:29
Member 103258859-Oct-13 5:29 
GeneralMy vote of 5 Pin
Anindya00211-Jul-13 23:27
Anindya00211-Jul-13 23:27 
Questionexcellent article Pin
Anindya00211-Jul-13 23:25
Anindya00211-Jul-13 23:25 
AnswerRe: excellent article Pin
Ratish Philip16-Jul-13 1:43
Ratish Philip16-Jul-13 1:43 
GeneralMy vote of 5 Pin
Bullimann13-Dec-12 19:45
Bullimann13-Dec-12 19:45 
GeneralRe: My vote of 5 Pin
Ratish Philip13-Dec-12 19:57
Ratish Philip13-Dec-12 19:57 
QuestionExcellent, thanks Pin
IssaharNoam2-Dec-12 23:37
IssaharNoam2-Dec-12 23:37 
AnswerRe: Excellent, thanks Pin
Ratish Philip3-Dec-12 0:23
Ratish Philip3-Dec-12 0:23 
GeneralMy vote of 5 Pin
Petr Kohout27-Aug-12 17:20
Petr Kohout27-Aug-12 17:20 
GeneralRe: My vote of 5 Pin
Ratish Philip3-Dec-12 0:26
Ratish Philip3-Dec-12 0:26 
QuestionFluidMoveBehavior Bug Pin
Bullimann13-Aug-12 3:06
Bullimann13-Aug-12 3:06 
AnswerRe: FluidMoveBehavior Bug Pin
Ratish Philip13-Dec-12 18:40
Ratish Philip13-Dec-12 18:40 
QuestionFixed Thumb size Pin
thesleeper22-Feb-12 6:52
thesleeper22-Feb-12 6:52 
AnswerRe: Fixed Thumb size Pin
Ratish Philip26-Feb-12 23:23
Ratish Philip26-Feb-12 23:23 
GeneralMy vote of 5 Pin
hoernchenmeister25-Aug-11 1:09
hoernchenmeister25-Aug-11 1:09 
GeneralRe: My vote of 5 Pin
Ratish Philip13-Jan-12 4:53
Ratish Philip13-Jan-12 4:53 
QuestionMy vote of 5 Pin
Filip D'haene6-Aug-11 1:19
Filip D'haene6-Aug-11 1:19 
AnswerRe: My vote of 5 Pin
Ratish Philip6-Aug-11 22:16
Ratish Philip6-Aug-11 22:16 
Welcome Smile | :)
cheers,
Ratish Philip
Blog: http://wpfspark.wordpress.com/

QuestionVery nice Pin
BillW331-Aug-11 7:35
professionalBillW331-Aug-11 7:35 
AnswerRe: Very nice Pin
Ratish Philip1-Aug-11 15:51
Ratish Philip1-Aug-11 15:51 
GeneralRe: Very nice Pin
BillW333-Aug-11 1:26
professionalBillW333-Aug-11 1:26 
GeneralMy vote of 4 Pin
Slacker00731-Jul-11 1:17
professionalSlacker00731-Jul-11 1:17 
GeneralRe: My vote of 4 Pin
Ratish Philip31-Jul-11 2:24
Ratish Philip31-Jul-11 2:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.