Search code examples
wpfxamlstoryboard

Rotation Animation on Datatrigger only triggers once WPF


I have a path with an arrow displayed in my window, and he shall rotate based on the property "BarcodeScanne.MovementDirection" however the animation only plays once the value changes and then on the second value change it doesnt. Can anyone hint me to what i am missing:

<Path Data="M12,7L17,12H14V16H10V12H7L12,7M19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21M19,19V5H5V19H19Z">
<Path.Style>
    <Style TargetType="Path" BasedOn="{StaticResource {x:Type Path}}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding BarcodeScanner.MovementDirection}" Value="Stay">
                <Setter Property="Visibility" Value="Hidden"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding BarcodeScanner.MovementDirection}" Value="Up">
                <DataTrigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)" To="0" Duration="0:0:0.2" />
                        </Storyboard>
                    </BeginStoryboard>
                </DataTrigger.EnterActions>
            </DataTrigger>
            <DataTrigger Binding="{Binding BarcodeScanner.MovementDirection}" Value="Down">
                <DataTrigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)" To="180" Duration="0:0:0.2" />
                        </Storyboard>
                    </BeginStoryboard>
                </DataTrigger.EnterActions>
            </DataTrigger>
            <DataTrigger Binding="{Binding BarcodeScanner.MovementDirection}" Value="Left">
                <DataTrigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)" To="-90" Duration="0:0:0.2" />
                        </Storyboard>
                    </BeginStoryboard>
                </DataTrigger.EnterActions>
            </DataTrigger>
            <DataTrigger Binding="{Binding BarcodeScanner.MovementDirection}" Value="Right">
                <DataTrigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)" To="90" Duration="0:0:0.2" />
                        </Storyboard>
                    </BeginStoryboard>
                </DataTrigger.EnterActions>
            </DataTrigger>
        </Style.Triggers>
        <Setter Property="RenderTransformOrigin" Value="0.5, 0.5"/>
        <Setter Property="RenderTransform">
            <Setter.Value>
                <RotateTransform Angle="0"/>
            </Setter.Value>
        </Setter>
    </Style>
</Path.Style>

Solution

  • In order to get a smooth rotation from the current angle to the new target angle, I'd suggest to use an attached property like shown below, which runs an animation of the Angle property of the RotateTransform in a UIElement's RenderTransform.

    The rotation goes either clockwise or counterclockwise, depending on which is shorter. There is also a speed factor given in seconds for a full circle rotation.

    public class AnimatedRotation
    {
        public static double Speed { get; set; } = 0.5; // seconds for full circle
    
        public static readonly DependencyProperty TargetAngleProperty =
            DependencyProperty.RegisterAttached(
                "TargetAngle", typeof(double), typeof(AnimatedRotation),
                new PropertyMetadata(0d, TargetAnglePropertyChanged));
    
        public static double GetTargetAngle(UIElement element)
        {
            return (double)element.GetValue(TargetAngleProperty);
        }
    
        public static void SetTargetAngle(UIElement element, double value)
        {
            element.SetValue(TargetAngleProperty, value);
        }
    
        private static void TargetAnglePropertyChanged(
            DependencyObject o, DependencyPropertyChangedEventArgs args)
        {
            if (o is UIElement element)
            {
                var transform = element.RenderTransform as RotateTransform;
                var currentAngle = transform != null ? transform.Angle % 360 : 0;
                var targetAngle = (double)args.NewValue % 360;
                var relativeAngle = (targetAngle - currentAngle - 540) % 360 + 180;
    
                if (transform == null || transform.IsFrozen)
                {
                    transform = new RotateTransform(currentAngle);
                    element.RenderTransform = transform;
                }
    
                transform.BeginAnimation(
                    RotateTransform.AngleProperty,
                    new DoubleAnimation
                    {
                        By = relativeAngle,
                        Duration = TimeSpan.FromSeconds(Speed * Math.Abs(relativeAngle) / 360)
                    });
            }
        }
    }
    

    You would use it with DataTriggers like this:

    <Style TargetType="Path">
        <Style.Triggers>
            <DataTrigger Binding="{Binding BarcodeScanner.MovementDirection}" Value="Up">
                <Setter Property="local:AnimatedRotation.TargetAngle" Value="0"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding BarcodeScanner.MovementDirection}" Value="Down">
                <Setter Property="local:AnimatedRotation.TargetAngle" Value="180"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding BarcodeScanner.MovementDirection}" Value="Left">
                <Setter Property="local:AnimatedRotation.TargetAngle" Value="270"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding BarcodeScanner.MovementDirection}" Value="Right">
                <Setter Property="local:AnimatedRotation.TargetAngle" Value="90"/>
            </DataTrigger>
        </Style.Triggers>
        <Setter Property="RenderTransformOrigin" Value="0.5, 0.5"/>
    </Style>