Resumé

Silverlight 2.0 Tweener(Of T) Ultimate Version

Cristian Merighi () 4.50

Latest - satisfying - version of my Tweener for Silverlight 2.0. Better design and support for actions like Pause, Resume, Stop and Rewind. Documentation, code and samples provided too!
This article is obsolete. Some functionalities might not work anymore. Comments are disabled.

Ok, as the latest of three steps which have seen the growth of this Tweening API for Silverlight 2.0, I can claim that the one described in this article is some sort of basic/ultimate version,
up to this point:

  • Tweener can target any DependencyProperty of any DependencyObject, with the limitation that the underlying property type must be a numeric type, a Color or a Point.
    As you'll see this "limitation" groups all the DependencyProperties that, in Silverlight, can sensibly pretend to be tweened.
  • Tweener has now a centralized handling of the Clock and its logic: that means there's just one DispatcherTimer for the entire application which takes care to trigger each changing step of each interpolated object; and it even stops by itself when there's ...nothing to do! (In my previous version one timer per type of DependencyProperty needed to be instantiated). To obtain this I think I've been coding the best polymorphic object of my developer's «career»; that was fun!
  • Tweener can handle Stop, Pause, Resume and Rewind actions. That gives perfect grip to the developer upon each tweened object.
    Specific handlers can be attached to listen to the key events that a tween broadcasts (MotionChanged is likely the most important and suggestive one).

New poster of the Tweening system is pictured below (please compare it to the one posted here to see how the project grew since its very beginning).

tweener class diagram

...and yes! There's also a demo available: I prepared a dummy sample showing how the Tweener works (sample that I will likely improve sooner or later); you've just to click on the following screenshot to start it, but let me introduce and describe it first:

  1. there's a rotating canvas with an embedded path geometry that has my baby face as its ImageBrush background. (!)
    Tween is applied, with bounce easing, to this canvas so that it rotates by 360 degrees back and forth alternatively (Yoyo FinishedBehavior is applied, it automatically rewinds the motion as it finishes). You can toggle Play and Pause statuses for this tween using the provided button on top of the UI.
  2. The Stroke of the path geometry is a SolidColorBrush whose ColorProperty can be tweened just by selecting the apposite listbox on the top left corner. You'll see the color of the Stroke blending linearly from the actual value to the selected one.
  3. One of the points that delimitate the path geometry can be tweened in its X an Y components by triggering the tiny sliders provided. With this third feature, the Tweener(Of Point) capabilities are also demonstrated.
tweener class sample (silverlight 2.0)

Let us take a sneak peek to the full XAML and C# code used in this demo App.

XAML:
(Please note that a MatrixTransform is the starting RenderTransform value for the Canvas cnv, just to demonstrate that no informations about the original status of an object get lost as a tween motion gets applied).

<UserControl x:Class="TestSilverlightApplication.Page"

   xmlns="http://schemas.microsoft.com/client/2007"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="600" Height="600" Loaded="UserControl_Loaded">

    <Grid x:Name="LayoutRoot" Background="#ffffffff" MouseLeftButtonUp="LayoutRoot_MouseLeftButtonUp">

 

        <Border x:Name="brd" CornerRadius="10,10,10,10" BorderThickness="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"

               >

            <Border.Background>

                <LinearGradientBrush EndPoint="0.5,0.5" StartPoint="1,0.5" SpreadMethod="Reflect">

                    <GradientStop Color="#FF3d3d3d" Offset="0"/>

                    <GradientStop Color="#ff8A9378" Offset="1"/>

                </LinearGradientBrush>

            </Border.Background>

            <Border.BorderBrush>

                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0" SpreadMethod="Reflect">

                    <GradientStop Color="#FFD8DFD7"/>

                    <GradientStop Color="#FF262B2E" Offset="1"/>

                </LinearGradientBrush>

            </Border.BorderBrush>

<Canvas x:Name="cnv" VerticalAlignment="Center" HorizontalAlignment="Center" Background="#ffc58263">

    <Canvas.RenderTransform>

    <MatrixTransform Matrix=".5,.5,-.866,.866,-5,-90" />

    </Canvas.RenderTransform><Path Stroke="#FFc0c0c0" StrokeThickness="4" x:Name="path">

            <Path.Fill>

                <ImageBrush ImageSource="images/CM.jpg" TileMode="None" Stretch="Fill" />

            </Path.Fill>

        <Path.Data>

        <PathGeometry>

            <PathFigure StartPoint="310,50">

                <LineSegment Point="0,50" />

                <LineSegment Point="0,180" />

                <LineSegment Point="150,180" x:Name="seg" />

                <LineSegment Point="310,50" />

            </PathFigure>

        </PathGeometry>

    </Path.Data>

</Path>

</Canvas>

 

 

        </Border>

<TextBlock x:Name="txt" TextWrapping="NoWrap" Visibility="Collapsed"

              Text="debug:" Width="Auto" Foreground="Red" FontSize="11" FontFamily="Courier New" />

 

        <StackPanel Orientation="Horizontal" Margin="10,10,0,0">

            <StackPanel Orientation="Vertical" Margin="0,0,10,0">

                <TextBlock Text="Border Color" Foreground="White" FontSize="11" />

        <ListBox x:Name="ddl" Width="100" SelectionChanged="ddl_SelectionChanged"

                HorizontalAlignment="Left" VerticalAlignment="Top" MinHeight="32">

            <ListBox.ItemTemplate>

                <DataTemplate>

                    <TextBlock Text="{Binding Key}" FontSize="11" />                   

                </DataTemplate>               

            </ListBox.ItemTemplate>

        </ListBox></StackPanel>

            <StackPanel Orientation="Vertical" Margin="0,0,10,0">

                <TextBlock Text="Corner Point (X, Y)" Foreground="White" FontSize="11" />

                    <Slider Minimum="1" Maximum="410" x:Name="sldX" Value="150" ValueChanged="sld_ValueChanged"></Slider>

                    <Slider Minimum="1" Maximum="200" x:Name="sldY" Value="180" ValueChanged="sld_ValueChanged"></Slider>

                </StackPanel>

            <StackPanel Orientation="Vertical" Margin="0,0,10,0">

                <TextBlock Text="Yoyo Rotation" Foreground="White" FontSize="11" />

                <Button Content="pause" Click="Button_Click" />

                </StackPanel>

        </StackPanel>

    </Grid>

</UserControl>

 

 

C#:

public partial class Page : UserControl

{

    public Page()

    {

        InitializeComponent();

        ddl.ItemsSource = this.ColorList;

    }

 

    Tween<double> tween = null;

    Tween<Color> tweenColor = null;

    Tween<Point> tweenPoint = null;

    bool resumed = false;

 

    private void UserControl_Loaded(object sender, RoutedEventArgs e)

    {

        tween = Tweener.CreateTween(cnv, TweenProperty.Angle, Bounce.EaseOut, 0, 360, TimeSpan.FromSeconds(3D));

        tween.FinishedBehavior = ClockFinishedBehavior.Yoyo;

    }

 

    void tween5_MotionFinished(object sender, EventArgs e)

    {

        resumed = true;

        Tweener<double>.Resume(tween);

    }

 

    void tween_MotionChanged(object o, TweenMotionChangedEventArgs<double> e)

    {

        txt.Text += string.Format("\n[{0:T}] value {1}", e.Elapsed, e.CurrentPosition);

        Tween<double> sender = (Tween<double>)o;

        if (!resumed && .5D * sender.Duration.TotalSeconds < e.Elapsed.TotalSeconds) Tweener<double>.Pause(sender);

    }

 

    void tween_MotionFinished(object sender, EventArgs e)

    {

        txt.Text = string.Format("\n[{0:T}] Finished!\ntrans children: {1}", DateTime.Now, ((TransformGroup)((UIElement)((ITween<double>)sender).Object).RenderTransform).Children.Count);

    }

 

    void tween_MotionStarted(object sender, EventArgs e)

    {

        txt.Text += string.Format("\n[{0:T}] Started!", DateTime.Now);

    }

 

    private void LayoutRoot_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)

    {

        List<Transform> coll = ((TransformGroup)cnv.RenderTransform).Children.ToList();

        txt.Text = string.Format("\ntrans children: {0}", coll.Count);

        coll.ForEach(delegate(Transform item)

        {

            txt.Text += string.Format("\n   type: {0}", item.GetType());

        });

    }

 

    private void ddl_SelectionChanged(object sender, SelectionChangedEventArgs e)

    {

        if (tweenColor != null) Tweener<Color>.Stop(tweenColor, true);

        tweenColor = Tweener<Color>.CreateTween((SolidColorBrush)path.Stroke, SolidColorBrush.ColorProperty, Linear.EaseNone,

            ((SolidColorBrush)path.Stroke).Color, GetSelectedColor(), TimeSpan.FromSeconds(3D));

    }

 

    private Color GetSelectedColor()

    {

        int j = 0;

        foreach (KeyValuePair<string, Color> kvp in ColorList)

        {

            if (j == ddl.SelectedIndex) return kvp.Value;

            j++;

        }

        return new Color();

    }

 

    private Dictionary<string, Color> _List = null;

    private Dictionary<string, Color> ColorList

    {

        get

        {

            if (_List == null){

                _List = new Dictionary<string,Color>(5);

                _List.Add("Gray", Colors.Gray);

                _List.Add("Red", Colors.Red);

                _List.Add("Blue", Colors.Blue);

                _List.Add("Green", Colors.Green);

                _List.Add("Yellow", Colors.Yellow);

            }

            return _List;

        }

    }

 

    private void sld_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)

    {

        Point tget = new Point(sldX.Value, sldY.Value);

        if (tweenPoint != null) Tweener<Point>.Stop(tweenPoint, true);

        tweenPoint = Tweener<Point>.CreateTween(seg, LineSegment.PointProperty, Expo.EaseOut, seg.Point, tget, TimeSpan.FromSeconds(2.8D), TimeSpan.FromSeconds(1D));

    }

 

    private void Button_Click(object o, RoutedEventArgs e)

    {

        Button sender = (Button)o;

        if (tween.Status == ClockStatus.Paused)

        {

            Tweener<double>.Resume(tween);

            sender.Content = "pause";

        }

        else

        {

            Tweener<double>.Pause(tween);

            sender.Content = "play";

        }

    }

}

 

 

Last, but obviously not least, keep in mind that these Tweening classes can be easily copied and pasted into a big-daddy WPF class library (already done). I'm now about to experience what can be done when you unleash'em onto, ...let's say a ModelVisual3D object!
...my mouth's foaming...

Stuff available for download:

  • Visual Studio 2008 Projects (Tweener code + sample application).
  • DLL library accompanied with Help documentation built using Sandcastle Help File Builder (great tool btw!).
zip file « download source code
zip file « download DLL + CHM

Take care. Bye.

Feedbacks

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    Shtong Tuesday, April 08, 2008 4.00

    This library is great, however there's something I don't understand in the implementation: with did you use an array as the parameter of the tween functions instead of simply using four double parameters ? The tween fonctions have to be as performant as possible, however, this technique makes them allocate four more double values on the stack, and you have to check if the parameter count is correct, which causes a lot of overhead. Moveover, I made some tests that showed me that using the classic parameters is slightly more performant that using the params array, even without size checking. I also noticed that the agTweener library uses the same signatures, so I wanted to know if I was missing anything ?

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    CMerighi Tuesday, April 08, 2008 0.00

    Hi Shtong, firstly thanks for the feedback! Then the answers... The reason why I took the array decision was to allow the user to plug his own easing func, with the flexibility of custom extra-arguments. As you correctly noticed, this intention isn't easy appliable at the moment (you should implement your own subclass of Tween&lt;T&gt; and override the HandleTick method to do that, so it makes no sense to plug a custom easing function). I think it is fairly right - at this point - to constrain the EasingFunc delegate to 4 sealed arguments of double (but I'll try also other ways). About the signatures of the easing functions, yes: I recycled 'em from those of Micheal Cameron. I contacted him and posted to him my library before to publish it onto my blog. He also asked me if he could put some of my Tweening logic in his next release, and I obviously agreed: it's all about open source stuff (and a bit of hedonism ;)). Thanks again.

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    Richard J Wednesday, June 18, 2008 5.00

    Excellent library, I animated a Point in my application with just a couple of lines! A couple of questions: 1. I can't see any information on licencing --- perhaps you can clarify the terms of use? 2. Is there a reason to limit the properties that can be animated to dependency properties? Could CLR properties be animated too?

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    CMerighi Wednesday, June 18, 2008 0.00

    Hi Richard, glad you appreciate it! I didn't provide licensing infos just because I didn't mind to... I'd certainly love some linking credits by the users of my tweener, but I know that fair coders will provide 'em whether selfish ones won't, no matter if I beg or roughly order them to do it... The tweener comprehends any dependency property whose type is numeric or color or point (I'm including also 3D properties from my personal 3D engine...) and also "TweenProperties" which wrap RenderTransformation parameters (Scale, Rotation...). This is because you essentially (I'd dare to say exclusively) need to tween UIElements in color, position or transformation params. These kinds of properties are all dependency properties in Silverlight and WPF and, more, you can lean on GetValue and SetValue methods to safely get and set relevant values. If you need to tween a generic property, you can always declare a dummy dependecy property for your usercontrol in Silverlight and handle its motionchanged event (in this manner you have a constantly updated tweened value that you can use for a whatever-you-want property)...

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    RichardJ Friday, June 20, 2008 0.00

    I did add a dependency property to my user control to animate that, and it works just fine. I now find I have a case where I really just need access to the eased progress value, to calculate some "result" values that need to be set at the same time. I would add a dependency property to the class for progress but the class is a simple class and Microsoft won't let me inherit directly from DependencyObject, as you can in WPF. I can work around this by attaching a property to a related class. However, I wonder whether it's a common enough use-case to consider extending the library? Just my thoughts...

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    CMerighi Sunday, June 22, 2008 0.00

    You could extend the FrameworkTemplate class (which is the lowest-level DependencyObject inheritable subclass available). I'll consider to extend the library anyway, maybe using by-reference variables aside of DependecyProperties... mmmhhh... we'll see... Cheers!...

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    ftoohhhhhhhhhhh Tuesday, December 09, 2008 0.00

    thank youuuuuuuuuuuuu

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    Josh Santangelo Wednesday, December 24, 2008 5.00

    In general, this looks pretty good, but I'm seeing a "jump" at the end of all of my tweens to their end destination. I am tweening custom DPs. Also, your example app should be updated to the release version of Silverlight 2.

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    Derek Wednesday, January 14, 2009 0.00

    Hey bud, your Silverlight version is out of date. Please upgrade to the latest so I can see your online demo.

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    Craig Muckleston Wednesday, February 25, 2009 5.00

    Excellent work! I have one question though. I am using the Bounce.EaseIn method o bring my object in from 0 to 1. I want to have my object start with a Scale of 0 and bounce in. If I set this in code first, then the tween doesn't bring it up to 1. If I leave the object as is, then the object is flashed up on screen before the tween runs (this is when the tween works perfectly). Any ideas how I can get around this?

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    CMerighi Friday, February 27, 2009 0.00

    I'm not sure if I got it, but if so... you could keep your object hidden (collapsed) since the tween has started (see Motionstarted event) then show it...

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    Frank Friday, March 06, 2009 0.00

    When I run the demo on your webpage by clicking the Play button, the new webpage thinks Silverlight needs to be installed. Can you tell me why that occurs when I already have the lastest version?

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    Frank Saturday, March 07, 2009 0.00

    On my previous comment, "lastest version" should be "latest version". Is it possible that the issue is on your server? I have developed websites using Silverlight, and I do not have the issue that I encounter at your site. Any help would be greatly appreciated.

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    Andrew Friday, March 13, 2009 4.00

    Great little class, any reason why every 3rd tween it jumps and staggers?

  • Re: Silverlight 2.0 Tweener(Of T) Ultimate Version

    empty Friday, March 13, 2009 4.00

    You mention you can use these classes with WPF, which I've tried and failed to accomplish. Is this something you'll be willing to share?

feedback
 

Syndicate

Author

Cristian Merighi facebook twitter google+ youtube

Latest articles

Top rated

Archive

Where am I?

Author

Cristian Merighi facebook twitter google+ youtube

I'm now reading

Feeds