Tuesday, August 24, 2010

Animation in WPF

One of the coolest things about WPF is the animation power it has. WPF has moved past the old Win32 and GDI/GDI+ libraries and instead uses DirectX. This allows for more powerful animation and graphics.

The thing to remember though is in order to have a nice looking animation, it should not lock the UI while running. If it does, you probably won't have a chance to see the animation happen!

So, here is a small sample of how to do this:


We start with our WPF window:

<Grid>
    <StackPanel Orientation="Vertical" HorizontalAlignment="Center">
        <Label Margin="5">Press button to start work:</Label >
        <Button Name="btnStart" Width="100" Height="25" Content="Start!"
                Margin="5" Click="btnStart_Click" />
        <Image Name="imgWork" Source="/WPFTestApp;component/Image/working.png"
                Width="40" Margin="5"></Image>
        <Label Name="lblDone" Visibility="Hidden" HorizontalAlignment="Center">Done!</Label>
    </StackPanel >
</Grid>

I simply added a button that starts the animation and a label that will appear once done. An image that spins will be my animating object.

Now on to the code:

We need to reference the following:
System.Windows.Media.Animation;               // For the animation
System.Windows.Threading;              // For the threading
System.ComponentModel;                 // For the background worker processes
There are a few ways to go around this problem, but I find that using background worker processes is the most easy and clean way to code the solution.

So, we need to initialize a background worker and instruct it what to do:

    _bg = new BackgroundWorker();

    _bg.DoWork += delegate(object s, DoWorkEventArgs e)
    {
        lblDone.Visibility = System.Windows.Visibility.Hidden;

        animateButton(imgWork);

        System.Threading.Thread.Sleep(5000);
    };

    _bg.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs e)
    {
        disableAnimation(imgWork);
        lblDone.Visibility = System.Windows.Visibility.Visible;
    };
Let's declare the animateButton and disableAnimation methods:

private void animateButton(Image target)
    {

        if (target.Dispatcher.CheckAccess())
        {
            DoubleAnimation da = new DoubleAnimation
                        (360, 0, new Duration(TimeSpan.FromSeconds(3)));
            RotateTransform rt = new RotateTransform();
            target.RenderTransform = rt;
            target.RenderTransformOrigin = new Point(0.5, 0.5);
            da.RepeatBehavior = RepeatBehavior.Forever;
            rt.BeginAnimation(RotateTransform.AngleProperty, da);
        }
        else
        {
            target.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                (Action)(() =>
                {
                    DoubleAnimation da = new DoubleAnimation
                        (360, 0, new Duration(TimeSpan.FromSeconds(3)));
                    RotateTransform rt = new RotateTransform();
                    target.RenderTransform = rt;
                    target.RenderTransformOrigin = new Point(0.5, 0.5);
                    da.RepeatBehavior = RepeatBehavior.Forever;
                    rt.BeginAnimation(RotateTransform.AngleProperty, da);
                }));
        }
    }

    private void disableAnimation(Image target)
    {
        if (target.Dispatcher.CheckAccess())
        {
            target.RenderTransform = null;
        }
        else
        {
            target.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                    (Action)(() =>
                    {
                        target.RenderTransform = null;
                    }));
        }
    }
I use Dispatcher.CheckAccess to ensure that the calling thread has access to the object. If not, I invoke what I need to do in a separate thread.

So, all we need now is to add the code for the button:
    _bg.RunWorkerAsync();
to run the background worker asynchronously.

That's it!

No comments:

Post a Comment