The decidedly badly named LoadingCircle WPF Control Pt 2

Badly named Loading Circle PT2
--------------------------------------------

So, in my last post (http://geekswithblogs.net/cskardon/archive/2008/07/04/the-decidedly-badly-named-loadingcircle-wpf-control-pt-1.aspx), I created a square LoadingCircle UserControl, using old skool WinForms multi-threading in the code behind file, and Xamly fun on the front end..
But there were problems (of course there were), firstly, the animation, whilst it *did* move, was a very 'on/off' affair, i.e. the Ellipse was either filled with White, or Black - there was no middle ground, no nice fading etc. Secondly, disposing of the UserControl involved using a work-around that took quite a while to hunt down. Thirdly (of the major ones) I *had* to dispose - due to firing off a new thread, disposing is clearly *not* what a WPF UserControl should really be doing.

We're using nifty Xaml, and with nifty Xaml comes Animations, Storyboards etc, which means that we can do *all* of the animating purely in the Xaml! This has several benefits - which solve all three major problems:
  1. The animation can be more - animated, i.e. it can tween between colour 1 and 2, making the transition gradual - which is nice!
  2. There is no need to dispose of the control, as there are no threads, and no extra collections etc that can clog up the memory / cpu.

Let's get to it!

We're keeping the same base <Grid>...</Grid> code as before, but this time we're going to add a StoryBoard to the top of the file...
        <Storyboard x:Key="Animation" RepeatBehavior="Forever">
            <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="_11" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                <SplineColorKeyFrame KeyTime="00:00:00.400000" Value="{StaticResource UnfilledColor}"/>
            </ColorAnimationUsingKeyFrames>
            <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="_00" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                <SplineColorKeyFrame KeyTime="00:00:00.500000" Value="{StaticResource FilledColor}"/>
                <SplineColorKeyFrame KeyTime="00:00:01.000000" Value="{StaticResource UnfilledColor}"/>
            </ColorAnimationUsingKeyFrames>
            <ColorAnimationUsingKeyFrames BeginTime="00:00:00.5" Storyboard.TargetName="_01" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                <SplineColorKeyFrame KeyTime="00:00:00.500000" Value="{StaticResource FilledColor}"/>
                <SplineColorKeyFrame KeyTime="00:00:01.000000" Value="{StaticResource UnfilledColor}"/>
            </ColorAnimationUsingKeyFrames>
            <ColorAnimationUsingKeyFrames BeginTime="00:00:01" Storyboard.TargetName="_02" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                <SplineColorKeyFrame KeyTime="00:00:00.500000" Value="{StaticResource FilledColor}"/>
                <SplineColorKeyFrame KeyTime="00:00:01.000000" Value="{StaticResource UnfilledColor}"/>
            </ColorAnimationUsingKeyFrames>
 
      <!-- MORE CODE HERE -->
 
            <ColorAnimationUsingKeyFrames BeginTime="00:00:5" Storyboard.TargetName="_10" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                <SplineColorKeyFrame KeyTime="00:00:00.500000" Value="{StaticResource FilledColor}"/>
                <SplineColorKeyFrame KeyTime="00:00:01.000000" Value="{StaticResource UnfilledColor}"/>
            </ColorAnimationUsingKeyFrames>
            <ColorAnimationUsingKeyFrames BeginTime="00:00:05.5" Storyboard.TargetName="_11" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
                <SplineColorKeyFrame KeyTime="00:00:00.500000" Value="{StaticResource FilledColor}"/>
            </ColorAnimationUsingKeyFrames>
        </Storyboard>
There are a few things with this Storyboard to look at, first we'll start with the attributes to the Storyboard itself:
    <Storyboard x:Key="Animation" RepeatBehavior="Forever">
We need to give the Storyboard a key as we're actually storing the Storyboard in the local ResourceDictionary of the control, and all items in a ResourceDictionary need a key, in this case we're just calling it 'Animation' as there aren't others there, normally I guess you'd go with a more descriptive name! The other attribute is 'RepeatBehavior' which does exactly as it says on the tin, 'Forever' as the value just means that the Storyboard will loop forever.

Inside the Storyboard is a collection of Animations, (oh yes, not just one biggun!), all of them are 'ColorAnimationUsingKeyFrames' animations, and (once again), the name is pretty self explanitory, but lets take one and disect it....
        <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="_00" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
            <SplineColorKeyFrame KeyTime="00:00:00.500000" Value="{StaticResource FilledColor}"/>
            <SplineColorKeyFrame KeyTime="00:00:01.000000" Value="{StaticResource UnfilledColor}"/>
        </ColorAnimationUsingKeyFrames>

  * BeginTime: which in this case is 00, so as soon as the Storyboard begins: start this Animation,
  * Storyboard.TargetName: which item we're going to be animating (in this case the _00 ellipse),
  * Storyboard.TargetProperty: which property on the item we'll be changing, in this case, we know _00 is a shape, so we'll change 'Fill' property using a SolidColorBrush.Color,

The animation itself will do nothing without those all important KeyFrames, a KeyFrame is a point in time when something will happen, in this case, a change of colour. We have two KeyFrames in this Animation, both 'SplineColorKeyFrame's the first changes the colour of the Ellipse to the 'FilledColor' resource, the second, back to the 'UnfilledColor'. The 'KeyTime' attribute means that at 0.5 seconds, the Fill colour *will* be the 'FilledColor', but - as we're using a SplineColorKeyFrame, it means that in the period from 00.00 to 00.50 the Fill colour will be gradually filling - which is what we want, (well, what *I* want)!

to etc

'FilledColor' and 'UnfilledColor' are two other resource elements:
     <Color x:Key="FilledColor" A="255" B="255" R="0" G="0"/>
     <Color x:Key="UnfilledColor" A="0" B="255" R="0" G="0"/>
The 'A' (Alpha) sets the opacity of the color, i.e 0 means the color is transparent, 255 means fully opaque, the other attributes (RGB) are pretty obvious (Red, Green, Blue), in this case FilledColor is an opaque blue, Unfilled is a transparent blue. We could change the colors (for example) and the fade / in would look different - and who knows - snazzier!

So at the moment with have resources in the LoadingCircle looking like this:
     <Color x:Key="FilledColor" A="255" B="255" R="0" G="0"/>
     <Color x:Key="UnfilledColor" A="0" B="255" R="0" G="0"/>
OK, so if we fire up the application, well, nothing happens. Hmmm, why? Well, we need to link the Storyboard to a Trigger...
    <UserControl.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard Storyboard="{StaticResource Animation}"/>
        </EventTrigger>
    </UserControl.Triggers>
That's about it really, in this phase of development, all the CodeBehind code has been deleted (except the constructor and it's InitializeComponent() call) and the animation is entirely done in the Xaml - which is great news. There is no need to dispose of anything as WPF keeps that all under control for us, and quite frankly it looks better. One of the good things is that the Filled / Unfilled colors can be modified easily and quickly change the look of the control.

So, we're done right? Wrong!
Ha HA!

There are more things to do, firstly, make it a circle - I don't really anticipate this being an issue, so I'll get that done quickly, but secondly, make it a better custom control, add some DependencyProperties to make the shape configurable, perhaps make the control all in C# (i.e. no xaml) so it can be skinned, which is perhaps the ultimate end goal!

Print | posted @ Wednesday, July 9, 2008 10:57 AM

Comments on this entry:

No comments posted yet.

Post A Comment
Title:
Name:
Email:
Comment:
Verification: