11

In my WPF application, the user presses a button to start a 3D model rotating smoothly, and lets up on the button to stop the rotation.

To do this, I create a DispatcherTimer:

DispatcherTimer timer = new DispatcherTimer();
timer.Tick += new EventHandler( timer_Tick );
timer.Interval = new TimeSpan( 0, 0, 0, 0, 30 );

And when the button is pressed I call timer.Start() and when the button is let up I call timer.Stop().

The timer_Tick function changes the rotation of the model:

    void timer_Tick( object sender, EventArgs e )
    {
        spin = ( spin + 2 ) % 360;
        AxisAngleRotation3D rotation = new AxisAngleRotation3D( new Vector3D( 0, 1, 0 ), spin );
        Transform3D rotate = new RotateTransform3D( rotation );
        model2.Transform = rotate;
    }

What I notice is that the model spins smoothly for the most part, but often freezes and stutters, pausing for various durations, sometimes up to like 1/4 second.

Is there a way to make this smoother? I understand that by using DispatcherTimer (as opposed to, say, System.Timers.Timer) the callbacks happen on the UI thread. But it's necessary for me to be on the UI threat in order to run the line

        model2.Transform = rotate;

I have read about various ways to get a timer callback on some other thread. But it seems like in the end I have to synchronize with the UI thread to call that line. If I use Invoke() to marshal from, say, the System.Timers.Timer callback thread to the UI thread, will that give an overall smoother animation? It seems like it shouldn't, since it's having to synchronize with the UI thread just like DispatcherTimer presumably does. And for that matter it seems like any scheme for setting model2.Transform on a regular interval would be in the same boat with respect to the UI thread, no?

(As a perhaps secondary question, I'm trying to understand what's causing the pauses in the first place. As far as I can know, there's nothing else significant that the UI thread is doing. So I don't understand what's happening during those pauses. Garbage collection? It doesn't seem like there should be much garbage to collect, and it doesn't seem like the pause would be so extreme.)

acrilige
  • 2,436
  • 20
  • 30
M Katz
  • 5,098
  • 3
  • 44
  • 66
  • I think you can use _model2.Dispatcher.BeginInvoke(new Action(() => { model2.Transform = rotate; }))_ to invoke it in model2 parent thread – acrilige Jan 16 '13 at 09:05
  • You seem to know that by using an alternative timer you need to dispatch back onto the UI thread, so why ask if doing that would produce better results? Have you tried it? – Daniel Kelley Jan 16 '13 at 09:17
  • What kind of object do you want to rotate? It's possible, that some visual elements, e.g. drop shadows or opacity masks, cause a poor render performance... – DHN Jan 16 '13 at 09:20
  • Can you precompute the rotation transforms and store them? Or another strategy would be to measure the elapsed time between timer ticks and compute a rotation based on your desired degrees per second. Timers don't typically guarantee any more accuracy than the system clock, about 15 ms. – Mike Zboray Jan 16 '13 at 09:36
  • @mike z: Precomputing would make sense if I believed those rotate computations were the bottleneck. But that seems really unlikely to me given how simple they are. – M Katz Jan 16 '13 at 18:13
  • @DHN: My model contains a lot of triangles, but is otherwise pretty simple. My understanding is that once the models are created in WPF the graphics hardware mostly takes over and keeps things fast. But you're right that I could try a baby model to see if that makes a difference. – M Katz Jan 16 '13 at 18:15
  • @Daniel Kelly: Yes, you are right, I could have just tried it. But I was reading about a number of possible routes (regular timer with Invoke, using an animation, and some others), and it all just felt like shots in the dark, and more complicated/hackish than should be necessary. – M Katz Jan 16 '13 at 18:18
  • 1
    @acrilige and Daniel Kelly: I used the BeginInvoke() line acrilige shows above with the regular timer, and it does in fact give a smooth rotation. Henk's answer below is still useful to me because it helps show that it's the priority of the DispatchTimer itself, and not synchronizing with the UI thread per se, that causes the stutter. – M Katz Jan 16 '13 at 18:25
  • 1
    I know it's very old, but if someone stumbles upon this question - I'd recommend using Storyboard instead. What we see here is not a WPF way of doing render transformations. I wonder how it works at all. – Endrju Dec 20 '17 at 22:11

1 Answers1

17

Set a priority. The default is Background, that might explain the stuttering you see. I think Render is the level you want but do experiment.

DispatcherTimer timer = new DispatcherTimer(DispatcherPriority.Render);

If that isn't smooth enough you could try setting it up as an Animation.

H H
  • 263,252
  • 30
  • 330
  • 514
  • 2
    It works. Thanks for the simple solution. I'd still like to understand what was happening during those pauses when the DispatcherTimer had a lower priority. The framework is just deciding to starve the DispatcherTimer for a while? Why? What's it busy doing? – M Katz Jan 16 '13 at 18:11
  • 1
    @MKatz I suspect that Windows will process any messages in the applications message queue first before calling your timer –  Aug 10 '16 at 04:32