4
votes

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?

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 on Microsoft Connect?Sheridan

1 Answers

2
votes

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.