WPF: How to smoothly stop a rotation animation with the DataTrigger

2.4k views Asked by At

The animation hardly stops and the image resets its position but I want the animation to finish to the next full 360°.

Any ideas on that?

The XAML code:

<Button
    BorderThickness="0"
    Cursor="Hand"
    Command="{Binding RefreshCommand}">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border>
                <ContentPresenter />
            </Border>
        </ControlTemplate>
    </Button.Template>
    <Image Source="../Resources/RefreshIcon.png">
        <Image.Style>
            <Style>
                <Setter Property="Image.RenderTransform">
                    <Setter.Value>
                        <RotateTransform />
                    </Setter.Value>
                </Setter>
                <Setter Property="Image.RenderTransformOrigin" Value=".5,.5">
                </Setter>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsRefreshing}" Value="True">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard Name="RotationStoryboard">
                                <Storyboard>
                                    <DoubleAnimation
                                        Storyboard.TargetProperty="RenderTransform.Angle"
                                        From="0"
                                        To="360"
                                        Duration="0:0:0.8"
                                        RepeatBehavior="Forever" />
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding IsRefreshing}" Value="False">
                        <DataTrigger.EnterActions>
                            <RemoveStoryboard BeginStoryboardName="RotationStoryboard"/>
                        </DataTrigger.EnterActions>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Image.Style>
    </Image>
</Button>

The final solution except the fact that it has to be put into a UserControl to make it more generic (also see @Sheridans post and my comment below):

<Button
    BorderThickness="0"
    Cursor="Hand"
    Command="{Binding RefreshCommand}">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border>
                <ContentPresenter />
            </Border>
        </ControlTemplate>
    </Button.Template>
    <Image
        Name="RefreshImage"
        Source="../Resources/RefreshIcon.png"
        RenderTransformOrigin=".5,.5">
        <Image.Resources>
            <Storyboard
                x:Key="RotationStoryboard"
                Completed="RotationStoryboardCompleted">
                <DoubleAnimation
                    Storyboard.Target="{Binding ElementName=RefreshImage}"
                    Storyboard.TargetProperty="RenderTransform.Angle"
                    From="0"
                    To="360"
                    Duration="0:0:1.5">
                    <DoubleAnimation.EasingFunction>
                        <CircleEase EasingMode="EaseInOut"></CircleEase>
                    </DoubleAnimation.EasingFunction>
                </DoubleAnimation>
            </Storyboard>
        </Image.Resources>
        <Image.Style>
            <Style>
                <Setter Property="Image.RenderTransform">
                    <Setter.Value>
                        <RotateTransform />
                    </Setter.Value>
                </Setter>
                <Setter Property="Image.RenderTransformOrigin" Value=".5,.5">
                </Setter>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsRefreshing}" Value="True">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard>
                                <StaticResource ResourceKey="RotationStoryboard"/>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Image.Style>
    </Image>
</Button>

Code behind:

public partial class SomeView
{
    public SomeView()
    {
        InitializeComponent();
    }

    private void RotationStoryboardCompleted(object sender, EventArgs e)
    {
        var viewModel = (ISomeViewModel)DataContext;
        var storyboard = (Storyboard)((ClockGroup)sender).Timeline;

        if (viewModel.IsRefreshing)
        {
            storyboard.Begin();
        }
    }
}
1

There are 1 answers

2
Sheridan On BEST ANSWER

I don't know of any way to do this in XAML, but with code, there is a Timeline.Completed Event that you can use. If you attach a handler to your Storyboard, then it will get called when it is finished and you can do whatever you want:

<BeginStoryboard>
    <Storyboard Name="RotationStoryboard" Completed="StoryboardCompleted">
        <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Angle" From="0" 
To="360" Duration="0:0:0.8" />
    </Storyboard>
</BeginStoryboard>

In code:

private void StoryboardCompleted(object sender, EventArgs e)
{
    // Restart your Storyboard here each time until you want it to stop.
}

For further help, please see the example in the linked page on MSDN.