5

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>
Community
  • 1
  • 1
JCH2k
  • 3,361
  • 32
  • 25

1 Answers1

6

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>
bars222
  • 1,652
  • 3
  • 13
  • 14
  • Works like a charm - and so easy! I wonder why this is not part of ToggleButton style examples from MS, because all styles I have seen using animations had this problem. – JCH2k Dec 16 '15 at 13:28
  • Actually I've found it in the Visual Studio 2010 help at the page 'Storyboards Overview' =) But I've spent more time, than expected to found it. – bars222 Dec 16 '15 at 13:32
  • In my real App I had to make the EventTrigger bind to something else, e.g. `` and name one of the borders 'Border' to make it work right with lazy loading. Otherwise I had the initial effect again... I'll post my working code above... – JCH2k Dec 16 '15 at 17:13
  • Thanks, it's interesting. – bars222 Dec 17 '15 at 06:22