I want to create an animated "Switch" style for a ToggleButton (like on Smartphones).
The animation itself is not the problem, the switch looks nice and animates when I click on it. But it also animates when it's loaded and is switched on.
When I load my view, it should show some switches switched on and some switched off. But it shows only "off" and automatically starts the "switch on" animation.
The following simple code shows my problem: Instead of switches I make the Button 150px wide when it is checked.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AnimationTest2.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource {x:Type ToggleButton}}" x:Key="SwitchStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard Storyboard.TargetProperty="Width">
<DoubleAnimation To="150"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard Storyboard.TargetProperty="Width">
<DoubleAnimation To="50"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<ToggleButton Width="50" Content="Button" Style="{StaticResource ResourceKey=SwitchStyle}"/>
<ToggleButton Width="50" Content="Button" Style="{StaticResource ResourceKey=SwitchStyle}" IsChecked="True"/>
</StackPanel>
</Window>
The first ToggleButton works well. It's not checked and animates to 150px when clicked. The second ToggleButton is checked by default and should be 150px right away, but instead it starts growing when I start my application!
Somehow I need to define an initial state for my ToggleButtons depending on the IsChecked state and start the animation only on click. How can I achieve this?
I've also tried with the VisualStateManager, much like in this answer, but then I have the same problem...
UPDATE
This is the actual working switch, if someone's not only interested in the proof-of-concept:
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource {x:Type ToggleButton}}" x:Key="SwitchToggleButton">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="Background" Value="Gray"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Width" Value="70"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Border x:Name="BackBorder" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"/>
<TextBlock x:Name="Off" Margin="30,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Center"><Run Text="OFF"/></TextBlock>
<TextBlock x:Name="On" Margin="0,0,30,0" VerticalAlignment="Center" HorizontalAlignment="Center"><Run Text="ON"/></TextBlock>
<Border x:Name="Slider" Background="LightGray" Width="30" Height="30" HorizontalAlignment="Left" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"/>
</Grid>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Border.Loaded" SourceName="Slider">
<SkipStoryboardToFill BeginStoryboardName="checkedSB" />
<SkipStoryboardToFill BeginStoryboardName="checkedSB2" />
<SkipStoryboardToFill BeginStoryboardName="uncheckedSB" />
<SkipStoryboardToFill BeginStoryboardName="uncheckedSB2" />
</EventTrigger>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="checkedSB">
<Storyboard Storyboard.TargetProperty="Margin" Storyboard.TargetName="Slider">
<ThicknessAnimation To="40,0,0,0" Duration="00:00:00.2"/>
</Storyboard>
</BeginStoryboard>
<BeginStoryboard x:Name="checkedSB2">
<Storyboard Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Color)">
<ColorAnimation To="LightSeaGreen" Duration="00:00:00.3"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard x:Name="uncheckedSB">
<Storyboard Storyboard.TargetProperty="Margin" Storyboard.TargetName="Slider">
<ThicknessAnimation To="0" Duration="00:00:00.2"/>
</Storyboard>
</BeginStoryboard>
<BeginStoryboard x:Name="uncheckedSB2">
<Storyboard Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Color)">
<ColorAnimation To="{x:Null}" Duration="00:00:00.3"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can add SkipStoryboardToFill
objects into Loaded
EventTrigger
. So the code would look like this.
<Style.Triggers>
<EventTrigger RoutedEvent="ToggleButton.Loaded">
<SkipStoryboardToFill BeginStoryboardName="checkedSB" />
<SkipStoryboardToFill BeginStoryboardName="uncheckedSB" />
</EventTrigger>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource Self}}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Name="checkedSB">
<Storyboard Storyboard.TargetProperty="Width">
<DoubleAnimation To="150"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Name="uncheckedSB">
<Storyboard Storyboard.TargetProperty="Width">
<DoubleAnimation To="50"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With