4

I have a WPF application where an image keeps rotating and, occasionally, has some kind of animation on it. The rotation keeps running smoothly when the application plays, except during a time when two keyframes hold the same value. For demonstration purposes, I've whittled it down to the bare essentials:

<Window x:Class="WpfApplication3.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525"
    x:Name="RootElement">
<Window.Resources>

    <Storyboard x:Key="Storyboard1">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="image">
            <SplineDoubleKeyFrame KeyTime="0" Value="0"/>
            <SplineDoubleKeyFrame KeyTime="0:0:1" Value="1"/>
            <SplineDoubleKeyFrame KeyTime="0:0:3" Value="1"/>
            <SplineDoubleKeyFrame KeyTime="0:0:4" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>

</Window.Resources>
<Window.Triggers>
    <EventTrigger RoutedEvent="FrameworkElement.Loaded">
        <BeginStoryboard Storyboard="{StaticResource Storyboard1}"/>
    </EventTrigger>
</Window.Triggers>
<Grid>
    <Image x:Name="image" Source="image.jpg" RenderTransformOrigin="0.5,0.5" >
        <Image.RenderTransform>
            <RotateTransform Angle="{Binding Angle, ElementName=RootElement}" />
        </Image.RenderTransform>
    </Image>
</Grid>

The window contains an image element that has a RotateTransform as its rendertransform, with the rotation's angle bound to a property in the window's codebehind.

There's also an animation that fades the image's opacity in from 0 to 1 in the first second, then holds it at 1 for the next two seconds, then fades it from 1 to 0 in the final second.

The codebehind looks like this:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();

        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromSeconds(1.0 / 30.0);
        timer.Tick += timer_Tick;
        timer.Start();
    }

    void timer_Tick(object sender, EventArgs e)
    {
        Angle += 360.0 * timer.Interval.TotalSeconds;
    }

    private DispatcherTimer timer;
    private double angle;

    public double Angle
    {
        get { return angle; }
        set 
        { 
            angle = value; 

            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Angle"));
            }
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;
}

There's a property Angle of type double that contains the angle value for the RotateTransform. It uses INotifyPropertyChanged's event handler to notify the bound item of changes. There's also a DispatcherTimer that fires 30 times a second, and on each tick it slightly increases the Angle property so that it keeps rotating.

If you run the application, you'll find that the image rotates at a rate of 1 rotation per second at 30fps. It rotates and fades in smoothly for the first second, but then during the two seconds during which opacity remains at 1, the rotation gets super choppy. Then, when the opacity starts fading back to 0, the rotation is smooth again.

Weirdly, this can be fixed if you just make sure opacity doesn't remain at 1: if you increase it ever so slightly during those two seconds:

<SplineDoubleKeyFrame KeyTime="0:0:3" Value="1.001"/>

...the rotation remains smooth.

What is going on here?

Bas
  • 1,946
  • 21
  • 38
  • 1
    +1 for finding an interesting `Animation` defect and for providing a good concise working example of the problem. While I can't tell you why that happens, I can also confirm that it can also be avoided by using ordinary `DoubleAnimation` elements instead, but of course that's no help if you actually need to use the `DoubleAnimationUsingKeyFrames` elements. If you don't get any answers here, perhaps you could submit a [bug report](https://connect.microsoft.com/VisualStudio/feedback/CreateFeedbackForm.aspx?FeedbackFormConfigurationID=5303&FeedbackType=1) on Microsoft Connect? – Sheridan Jul 28 '14 at 11:42

1 Answers1

2

The default priority of the DispatcherTimer is Background. This priority is described as the following: operations are processed after all other non-idle operations are completed.

The DoubleAnimationUsingKeyFrames likely optimizes away the redraw calls since the opacity isn't changing, and for whatever reason this seems to affect the dispatcher queue.

Either change your dispatcher timer priority using new DispatcherTimer(DispatcherPriority.Normal) or instantiate another DoubleAnimation to animate the angle rather than animating manually.

Julien Lebosquain
  • 40,639
  • 8
  • 105
  • 117
  • I figured that the choppiness would be due to the operation not being processed for some reason, but what I don't get is why the non-idle operations apparently become so time-consuming at the very point in the storyboard where nothing happens at all. – Bas Jul 28 '14 at 12:34
  • Yes, I don't get it either, I started diving into the Animation and Clock classes to understand, but there's quite a lot happening in here. – Julien Lebosquain Jul 28 '14 at 14:57