Welcome to MSDN Blogs Sign in | Join | Help

TreeMap in Silverlight Toolkit: How to write your own interpolator

One of the new concepts introduced in the TreeMap control are Interpolators. They are used for example to interpolate the colors of rectangles or the font size in the titles of the NHL teams in the sample TreeMap in the picture above. I would like to show how easy it is to create your new custom interpolator. Here is how we used interpolators in the sample above:

 

        <datavis:TreeMap x:Name="treeMapControl" Grid.Row="1">

            <datavis:TreeMap.Interpolators>

                <datavis:SolidColorBrushInterpolator TargetName="itemBorder" TargetProperty="Background"

                                           DataRangeBinding="{Binding Losses}" From="Green" To="Red" />

                <datavis:DoubleInterpolator TargetName="textBlk" TargetProperty="FontSize"

                                           DataRangeBinding="{Binding GoalsFor}" From="8" To="15" />

            </datavis:TreeMap.Interpolators>

 

            <datavis:TreeMap.ItemDefinition>

                <datavis:TreeMapItemDefinition ItemsSource="{Binding Children}" ValueBinding="{Binding Points}" ChildItemPadding="1">

                    <DataTemplate>

                        <Border x:Name="itemBorder" BorderBrush="Black" BorderThickness="1" ToolTipService.ToolTip="{Binding ToolTip}">

                            <TextBlock x:Name="textBlk"  Foreground="White" Text="{Binding Name}" VerticalAlignment="Center" Margin="2,2,0,0"

                                       TextWrapping="Wrap" TextAlignment="Center"/>

                        </Border>

                    </DataTemplate>

                </datavis:TreeMapItemDefinition>

            </datavis:TreeMap.ItemDefinition>

      </datavis:TreeMap> 

 

Now let’s say that we don’t like the color transitions and we would like to createour own interpolator to provide much more natural color transitions than going from green to red thru brown. No problem – let’s use HSL instead of the regular RGB color interpolation. All you need to do in order to introduce your new interpolator is to inherit from RangeInterpolator<Color> - abstract class delivered with the TreeMap control and implement object Interpolate(double value) method:

namespace System.Windows.Controls.Samples

{

    /// <summary>

    /// Interpolator which converts a numeric value from its [ActualDataMinimum, ActualDataMaximum]

    /// range to a color in the range [From, To].

    /// </summary>

    /// <QualityBand>Experimental</QualityBand>

    public class HSLSolidColorBrushInterpolator : RangeInterpolator<Color>

    {

        /// <summary>

        /// Interpolates the given value between its [ActualDataMinimum, ActualDataMaximum] range

        /// and returns a color in the range [From, To].

        /// </summary>

        /// <param name="value">Value to interpolate.</param>

        /// <returns>An interpolated color in the range [From, To].</returns>

        public override object Interpolate(double value)

        {

            Color color = From;

            if (DataMaximum - DataMinimum != 0)

            {

                double ratio = (value - ActualDataMinimum) / (ActualDataMaximum - ActualDataMinimum);

 

                color = color.FromAhsl(

                    (byte)((double)From.A + (ratio * (double)(To.A - From.A))),

                    From.GetHue() + (ratio * (To.GetHue() - From.GetHue())),

                    From.GetSaturation() + (ratio * (To.GetSaturation() - From.GetSaturation())),

                    From.GetLightness() + (ratio * (To.GetLightness() - From.GetLightness())));

            }

 

            return new SolidColorBrush(color);

        }

    }

}

 

Here is how it works: Interpolate method is called for each node with the actual value we interpolate on. Before it happens ActualDataMinimum and ActualDataMaximum are pre-calculated on the all leaf nodes (by default) or all nodes (if we choose InterpolationMode="AllNodes" as an attribute in the interpolator).

There is one more helper class with actual extension methods for RGB->HSL and HSL-> RGB conversions. It is included in the attachment to this post.

Now everything we have to do is to change the XAML:

                <Samples:HSLSolidColorBrushInterpolator TargetName="itemBorder" TargetProperty="Background"

                                           DataRangeBinding="{Binding Losses}" From="Green" To="Red" />

 

The result is like this:

 

You may ask why the HSL interpolator wasn’t chosen as the default one for Colors in the TreeMap. Two reasons:

·         Sometimes color transitions are counterintuitive with HSL (like with RGB presented here)

·         There is no native support in Blend for HSL interpolation

We may however consider adding this interpolator as optional in the next release to the default SolidColorBrushInterpolator.

Posted by marlat | 0 Comments
Attachment(s): HSL.ZIP

TreeMap control is comming to Silverlight Toolkit

Since this is my first post after over one month break I own an explanation. I have two excuses: one is of course vacations and the other one is really nice. Together with the colleagues of mine, I had the pleasure to work on a new control – TreeMap - that is included in the next release of Silverlight Toolkit.

 

 

I perceive the introduction of this control as especially important because it enables you to easily visualize more than one dimensional, correlated data in a way that is attractive and understandable to a user without any statistician or mathematical background.

New concepts in TreeMap control

Actually I’m going to concentrate on one particular feature that we hope to gather feedback for – Interpolators proposed by Cristian – a developer within our team. Their purpose is to visualize additional dimensions by mapping the particular range in your data to visual representation. Let’s analyze a sample that uses the NHL statistics:

 

        <datavis:TreeMap x:Name="treeMapControl" Grid.Row="1">

            <datavis:TreeMap.Interpolators>

                <datavis:SolidColorBrushInterpolator TargetName="itemBorder" TargetProperty="Background"

                                           DataRangeBinding="{Binding Losses}" From="Blue" To="Magenta" />

                <datavis:DoubleInterpolator TargetName="textBlk" TargetProperty="FontSize"

                                           DataRangeBinding="{Binding GoalsFor}" From="8" To="15" />

            </datavis:TreeMap.Interpolators>

 

            <datavis:TreeMap.ItemDefinition>

                <datavis:TreeMapItemDefinition ItemsSource="{Binding Children}" ValueBinding="{Binding Points}" ChildItemPadding="1">

                    <DataTemplate>

                        <Border x:Name="itemBorder" BorderBrush="Black" BorderThickness="1" ToolTipService.ToolTip="{Binding ToolTip}">

                            <TextBlock x:Name="textBlk"  Foreground="White" Text="{Binding Name}" VerticalAlignment="Center" Margin="2,2,0,0"

                                       TextWrapping="Wrap" TextAlignment="Center"/>

                        </Border>

                    </DataTemplate>

                </datavis:TreeMapItemDefinition>

            </datavis:TreeMap.ItemDefinition>

        </datavis:TreeMap>

 

We visualize three dimensions here:

·         The size of the rectangle represents the number of points for each team - ValueBinding="{Binding Points}"

·         The background color of each rectangle color reflects the number of losses - SolidColorBrushInterpolator

·         The font size used for team name represents the number of goals gained - DoubleInterpolator

They may look familiar to you. Yes – they’re mimicking to some degree KeyFrames used in storyboards. It makes this API both easy to use and familiar at the same time.

There are also extra flexibilities in Interpolators.

·         By default the ActualDataMaximum and ActualDataMinimum for your data are automatically calculated and you can be blissfully unaware of their existence, however you can fix their values using DataMaximum and DataMinimum properties. It may come useful if for example you visualize school grades let’s say in range 1 to 100 but two classes in your school got marks raging between 60-90 and 10-50 respectively. Probably you don’t want to use for ex. the same backgroung color for 90 in one class and 50 in the other, therefore you can fix color range to be interpolated bosing on 0 to 100 range.

·         Another feature you can be blissfully unaware but you can leverage is interpolation mode. The implicit behavior is that ActualDataMaximum and ActualDataMinimum are calculated and applied on leaf nodes only. No big deal in the example above. But you can introduce extra margin to non leaf nodes and get something like this:

 

 

To extend the interpolation to non leaf nodes you need to set InterpolationMode to AllNodes for a particular interpolator.

What’s coming next?

There are several points we considering to be included in the next release:

·         HSL and other modes for color interpolation

·         Design tools improvements – better Blend support

·         An encapsulating control adding support for zoom and panning

o   Rendering new nodes as you go (zoom and / or pan). On one hand that complicates the code for the control. On the other, this theoretically can enable you to create a TreeMaps with hundreds of thousands of nodes. Imagine for example the world population with levels such us continents, countries, regions, cities, districts.

·         Look into possible APIs to get the list of all selected nodes

·         New interpolators – for ex. border thickness, gradient brush…

·         Investigate ways to further improve rendering performance

·         One more killer feature but this I would like to remain a surprise J

While I have a particular inclination towards some of the point above, the list above is not by all means closed. If you have any particular requirements please give me a shout or start a discussion below. You can expect some more samples from David Anson soon.

Silverlight: How to use CompositionTarget.Rendering event to synchronize discrete and non-discrete animations

I’ve brought up this topic in the recent post, but I was pointed out it can be lost among other content and is not stressed enough.

Everything brings down to the fact that some actions, particularly animations cannot be easily enclosed in storyboards. For example let’s imagine that we animate an object X,Y positions. When object disappears out of the display area you would like to collapse the object visibility. This case is not that complicated.  You know its start X,Y position, end X,Y position and the animation duration so you can calculate when it disappears. Things are getting much more complicated if our animation is a little bit more complex – for example:

·         Together with X,Y position, we animate other properties that may affect visibility - for example - perspective (Z axis) at the same time and we wish to hide the object if it crosses -90, +90 degree boundary

·         There is an easing function applied to the X,Y animation like a bouncing effect (t axis is time, f(t) axis is the progress):

 

Imagine that your object disappears after 90% of its X/Y transition. In that case it appears and disappears couple of times. It is not easy to calculate and set such animations.

And now let's assume that both bullet points are valid in one storyboard. Code with such calculations would look like a nightmare. Even more than that - If you need to apply that style of animation to many objects at the same time, the task can become so formidably complicated, that your animation wouldn’t start instantly and that means a jumpy animation.

There is an easy way to handle that. Move all discrete animations to CompositionTarget.Rendering event handler. This event is fired each time before frame is rendered, giving you the last possibile moment to apply any changes.

 

        public SampleAnimPanel()

        {

            AnimationStoryboard = new Storyboard();

            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);

        }

 

        void CompositionTarget_Rendering(object sender, EventArgs e)

        {

            if (_animating && Children != null && Children.Count > 0)

            {

 

                foreach (var child in this.Children)

                {

                    if (GetElementVisibility(child) == Visibility.Collapsed &&

                        child.Visibility == Visibility.Visible)

                    {

                        child.Visibility = Visibility.Collapsed;

                    }

                    else if (GetElementVisibility(child) == Visibility.Visible &&

                              child.Visibility == Visibility.Collapsed)

                    {

                        child.Visibility = Visibility.Visible;

                    }                   

                }

            }

        } 

 

GetElementVisibility method just checks if X/Y of the object are in the view area

Two questions can arise:

Q: Why not to put all animations, non discrete too, in CompositionTarget.Rendering event handler?

A: Two reasons:

·         For non discrete animations it is just easier, more elegant and faster to count end position and start the animation. It is more efficient not only because usually calculating once and approximating progress with each frame is more efficient than calculate with each frame, but also because with the animation engine come several optimizations. For example animating X,Y dependency properties of an object does not means firing binding updates or callbacks attached to it with each generated frame. That is because all bindings are referring to lower priority, non-animated value. More about it here.

·         You would miss added value like for example easing effects.

Q: Why not to use for example a timer instead to apply all the logic above?

A: Because correlation with frame rate is really useful and your calculations won’t be fired more or less frequent than necessary.

 

GetBindingExpression – a good way to iterate over all dependency properties in visual tree in Silverlight 3?

Recently I’ve tried to iterate over all dependency properties in the subset of visual tree. Despite it can be fragile, it is useful in advanced scenarios when certain actions needs to be trigged basing on binding’s path, converter or converter parameters. As a lazy programmer, I tried to find something over the Internet. All I was able to find was specific to WPF due to Silverlight API limitations (more later in the post). It looked as an easy task due to a new method in Silverlight 3 in FrameworkElement – GetBindingExpression. Here is my first try:

 

        private void BuildBindingList(FrameworkElement element)

        {

            FieldInfo[] infos = element.GetType().GetFields(BindingFlags.Public | BindingFlags.FlattenHierarchy |

                BindingFlags.Instance | BindingFlags.Static);

            foreach (FieldInfo field in infos)

            {

                if (field.FieldType == typeof(DependencyProperty))

                {

                    DependencyProperty dp = (DependencyProperty)field.GetValue(null);

                    BindingExpression ex = element.GetBindingExpression(dp);

                    if (ex != null)

                    {

                        System.Diagnostics.Debug.WriteLine("Binding found with path: " + ex.ParentBinding.Path.Path);

                    }

                }

            }

 

            int children = VisualTreeHelper.GetChildrenCount(element);

            for (int i = 0; i < children; i++)

            {

                FrameworkElement child = VisualTreeHelper.GetChild(element, i) as FrameworkElement;

 

                if (child != null)

                {

                    BuildBindingList(child);

                }

            }

        }

 

The method consists of two for loops.

First one is responsible for iterating over all dependency properties. We need to use parameterized GetFields method to extract all fields, because if you check in reflector, the no-parameter version of this method is defined as

 

 public FieldInfo[] GetFields()

{
   return this.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
}

It lacks BindingFlags.FlattenHierarchy flag and as the result all inherited fields (majority of which are dependency properties) would be skipped.

The second “for” loop calls the method recursively in the visual tree using VisualTreeHelper.

Unfortunately we are not able to use the code above in Silverlight 2. The problem is not actually that it lacks GetBindingExpression, but that BindingExpression class is internal in the framework.

There is also one issue with the code above which. It does not iterate over attached properties. I found several ways to do it in WPF. Two of them are described in this thread, which involve:

·         MarkupObject / MarkupWriter

·         TypeDescriptor

No luck - they are not supported in Silverlight 3 and I didn’t find a graceful way to do it

I mentioned a gracefully way. There is one dirty way. It iterates over all types in assembly, lists their (also non-attached) dependency properties and tries to extract all possible dependency values from an object. For ex. For attached properties defined in your assembly it would be.

 

            Assembly a = Assembly.GetCallingAssembly();

 

            foreach (Type t in a.GetTypes())

            {

                foreach (FieldInfo fi in t.GetFields(BindingFlags.DeclaredOnly

                    | BindingFlags.Static | BindingFlags.Public))

                {

                    if (fi.FieldType == typeof(DependencyProperty))

                    {

                        DependencyProperty dp = (DependencyProperty)fi.GetValue(null);

                        BindingExpression ex = element.GetBindingExpression(dp);

                        if (ex != null)

                        {

                            System.Diagnostics.Debug.WriteLine("Binding found to element: " + ex.ParentBinding.Path.Path);

                        }

                    }

                }

            }

It is definitely not elegant or efficient way though. If I should miss something and it is possible to list all dependency properties in a more graceful ways, please let me know.

Animate anything anywhere with behaviors in Silverlight 3 (and 2!) (Animations part II)

Introduction

In the previous article on my Blog we have implemented a Panel with 3D (or rather projections) animations. It was cool but we had also two, innate shortcomings there. The first was that the architecture required to implement a new panel and the second was it didn’t provide a good separation of concerns between layout and animation algorithms. Therefore, we’ll look into alternative ways to provide animations.

You need Silverlight 3 Beta to consume samples attached. Blend 3 preview is recommended.

 

 

Animations via Behaviors per Element

Behaviors are really powerful weapon in the arsenal of a Silverlight 3 developer. I think that the most evident example of this is the physic engine showed during the MIX (starts around 60:00). For the readers who are not familiar with the topic: Behavior is basically an object that is attached to a visual element. It may change the properties of the element attached to or even properties of other elements in the tree. In our case we would like to achieve as follows:

            <StackPanel x:Name="PerElementAnimatedPanel">

...

                <Button Content="Button 1">

                    <i:Interaction.Behaviors>

                        <local:PerElementAnimationBehavior/>

                    </i:Interaction.Behaviors>

                </Button>

...

            </StackPanel>

The PerElementAnimationBehavior object will be responsible for defining all animations for a Button to which is attached. As we can see we would like to implement animations in an ordinary Stock Panel. How can we do it?

First we need to inherit from Behavior <T> class in which we shall override just one method - protected override void OnAttached(). The type T defines types to which the behavior can be attached. This method is called at the time we actually attach to the object in the visual tree and can serve us as a constructor:

        protected override void OnAttached()

        {

            _attachedElement = AssociatedObject;

 

...Initalize Animations and storyboard...

 

            _attachedElement.LayoutUpdated += OnLayoutUpdated;

        }

AssociatedObject is an object to which we attach our behavior (Button in the example above). Except for initializing the animations, we start listening to the LayoutUpdated event of a Button. Let’s see what we do when LayoutUpdated happens.

Despite the event handler is a bit long I have decided to list it fully. The code with comments will really help to understand how it works.  I would like to highlight one key aspect before looking into it: LayoutUpdated event is fired after ArrangeOverride and before the item is rendered. This means that the position of the object in the panel you get in this event handler is the target position after animation is completed. We fix it by applying a Render Transform to start animation in the position where the object was before ArrangeOverride. We also apply the render transform on the object directly as the animation won’t start before next frame is rendered.

 

        void OnLayoutUpdated(object sender, EventArgs e)

        {

            UIElement parent = _attachedElement.Parent as UIElement;

           

            if (parent == null)

            {

                return;

            }

 

            // Please notice that also in later calculations we must take into account the case

            // when the animation starts when a previous one is not completed (!)

            if (_animating)

            {

                StopAnimation();

            }

 

            // TransformToVisual returns a real position. By this I mean it takes into account

            // the actual value of the Render transform applied to the object (happens if previous

            // animation was in progress. We need to remove transform for calculations.

            // We do so by passing an actual transform value in the point structure

            Point targetPanelPostion = _attachedElement.TransformToVisual(parent).Transform(

                new Point(-_positionTransform.X, -_positionTransform.Y));

 

            // Calculates where we really are in the panel

            Point originPanelPostion = new Point(

                _lastTargetPanelPostion.X - targetPanelPostion.X + _positionTransform.X,

                _lastTargetPanelPostion.Y - targetPanelPostion.Y + _positionTransform.Y);

 

            // You can think as the transform is the element which applied on the target position

            // places the object into position in which we start the animation

            _positionAnimationX.From = originPanelPostion.X;

            _positionAnimationY.From = originPanelPostion.Y;

            _positionAnimationX.To = 0;

            _positionAnimationY.To = 0;

 

            // Doesn't make sense to start animation and consume CPU if nothing changed

            if (_positionAnimationX.To != _positionAnimationX.From ||

                _positionAnimationY.To != _positionAnimationY.From)

            {

                // Make sure that even before the animation starts the initial position is set

                // (means From position is rendered even before animation. It is possible to render a frame

                // before the animations starts)

                _positionTransform.X = originPanelPostion.X;

                _positionTransform.Y = originPanelPostion.Y;

                _attachedElement.RenderTransform = _positionTransform;

 

                double perUnitDuration = GetDurationFromParent(parent);

 

                // Sets duration

                TimeSpan duration = TimeSpan.FromSeconds(perUnitDuration * (Math.Sqrt(Math.Pow(originPanelPostion.X, 2) +

                                                                   Math.Pow(originPanelPostion.Y, 2)) / ANIM_UNITS));

 

                _containerStoryBoard.Duration = duration;

                _positionAnimationY.Duration = duration;

                _positionAnimationX.Duration = duration;

                _positionAnimationX.EasingFunction = EasingFunction;

                _positionAnimationY.EasingFunction = EasingFunction;

 

                StartAnimation();

            }

 

            // Remember last target position

            _lastTargetPanelPostion = targetPanelPostion;

}

Just one more comment. It is important to stop the animation before we start tinkering with animated parameters. We do it because today, the value of the parameter is practically not specified (in C++ standard meaning of this word) if modified in a running animation. More information can be found in David Anson post here.

Once we know the algorithm let’s see the extra plumbing.

First let’s see the behaviors support in blend. We expose EasingFunction as a dependency property in our behavior. To make it configurable from Blend we need to add just one Category attribute:

 

        /// <summary>

        /// Easing function used in animation

        /// </summary>

        [Category("VSM EasingFunction")]

        public EasingFunctionBase EasingFunction

        {

            get { return (EasingFunctionBase) GetValue(EasingFunctionProperty); }

            set { SetValue(EasingFunctionProperty, value); }

        }

 

        /// <summary>

        /// EasingFunction Dependency Property.

        /// </summary>

...

 

The effect in Blend when you check behavior properties is below:

 

Powerful and simple isn’t it?

A question that can arise: OK, what if I would like to have some object data shared by all elements in the panel? You can do it by defining an attached property like this:

        public static readonly DependencyProperty Per50PixelAnimationTimeProperty =

            DependencyProperty.RegisterAttached(

                "Per50PixelAnimationTime",

                typeof(double),

                typeof(Panel),

          new PropertyMetadata(0.5, new PropertyChangedCallback(Per50PixelAnimationTimeChanged)));

,then set it in a panel and consume via attached to object's parent reference in the behavior.

Animations via ContentControl

Now, there is a question if I can do it without behaviors in Silverlight 2?

Yes, You can just reverse the situation – in your panel each child would be a ContentControls in which we would define animations and the content of the ContentControl would be the element we actually animate. It will look like below:

         <StackPanel x:Name="PerElementAnimatedPanel">

...

             <local:PerElementAnimationContainer>

                    <local:PerElementAnimationContainer.EasingFunction>

                        <BounceEase Bounces="5" Bounciness="3"/>

                    </local:PerElementAnimationContainer.EasingFunction>

                    <Button Content="Button 3">

                    </Button>

             </local:PerElementAnimationContainer>

...

          </StackPanel>

You can ask how much code from the behavior defined in the paragraph earlier you can reuse. I guess you can bet 100$ that we have to change more than couple of lines? Well, then email me and I can give you my account number. The answer is: four lines. You don’t believe me? Let’s count:

One:

public class PerPanelAnimationBehavior : Behavior<Panel>

to:

public class PerElementAnimationContainer : ContentControl

Two:

protected override void OnAttached()

to:

public PerElementAnimationContainer()

Three:

_attachedElement = AssociatedObject;

to:

_attachedElement = this;

Four (in red):

        public static readonly DependencyProperty EasingFunctionProperty =

            DependencyProperty.Register(

                "EasingFunction",

                typeof(EasingFunctionBase),

                typeof(PerElementAnimationBehavior),

                new PropertyMetadata(new ExponentialEase { Exponent = 4.0 }, new PropertyChangedCallback(EasingFunctionChanged)));

to:

        public static readonly DependencyProperty EasingFunctionProperty =

            DependencyProperty.Register(

                "EasingFunction",

                typeof(EasingFunctionBase),

                typeof(PerElementAnimationContainer),

                new PropertyMetadata(new ExponentialEase { Exponent = 4.0 }, new PropertyChangedCallback(EasingFunctionChanged)));

 

Even three lines if we don’t change the class name. Cool isn’t it? The drawback of this solution is that we no longer get the Blend designers support that is specific to the Behaviors.

Animations via Behaviors per Panel

With regular panels and just one animation style per such panel, it can be tiresome to remember to attach the same behavior to each child in the container. In that case why not to attach just one behavior to the panel that would provide animations for all children in the panel.

 

            <StackPanel x:Name="PanelBehavior">

                <i:Interaction.Behaviors>

                    <local:PerPanelAnimationBehavior/>

                </i:Interaction.Behaviors>

                <Button ...

...

            </StackPanel>

 

To do it we exercise a design very similar to what we have implemented in the previous article. We shall need 2 elements:

·         Behavior attached to the panel

·         An attached dependency property object applied to each child and containing its animations and animation parameters.

 

Because the code is well documented and the design has been explained last time, I’ll limit code snippets just to the main animation loop:

 

            if (_animating)

            {

                StopAnimation();

            }

 

            // Remove all children - we'll add only object we need to animate.

            _panelStoryBoard.Children.Clear();

 

            foreach (UIElement child in _attachedElement.Children)

            {

                // TransformToVisual returns a real position. By this I mean it takes into account

                // the actual value of the Render transform applied to the object (happens if previous

                // animation was in progress. We need to remove transform for calculations.

                // We do so by passing an actual transform value in the point structure

                PerPanelAnimationBehaviorProperties animProperties = GetProperties(child);

 

                Point targetPanelPostion = child.TransformToVisual(_attachedElement).Transform(

                    new Point(-animProperties.PositionTransform.X, -animProperties.PositionTransform.Y));

 

                // Skip not animated objects

                if (animProperties.LastTargetPanelPostion == targetPanelPostion)

                {

                    continue;  

                }

 

... Animation calculations similar to ‘Animations via Behaviors per Element’

 

                _panelStoryBoard.Children.Add(animProperties.PositionAnimationX);

                _panelStoryBoard.Children.Add(animProperties.PositionAnimationY);

 

                // Remember last target position

                animProperties.LastTargetPanelPostion = targetPanelPostion;

            }

 

            if (maxDuration.Ticks > 0)

            {

                _panelStoryBoard.Duration = maxDuration;

                StartAnimation();

            }

 

If we wish to have specific animations per animated element we can use the same trick as before – an attached dependency property. I would think twice before doing it. If you need to do it, it is a good indicator you probably need to implement Animations via Behavior per element.

 

Can we implement it without behaviors? Yes, we can - All you need to do is basically attach to the panel LayoutUpdated event.

Summary

It’s a good moment to ask the question which architecture is superior. The answer is not simple:

·         Animations in Panel – when you need to implement a new panel and there is no other reasonable way to animate elements in the panel without changing the layout algorithm. A standard carousel could be an example here.

o   Easy to use - attaching anything to the panel is not required.

o   It is difficult to reuse algorithm between different type of panels

Yes, I know some purist would say that the only responsibility of a panel should be only the layout, and that animations should never be a part of it.

·         Animations via Behavior per Element or a Content Control – good if elements can be animated in different ways in your panel and/or you have already a panel to animate in

o   Provides a good separation of concerns between the layout and animation.

o   Requires attaching a behavior to each element added to the panel.

o   Can be reused between different Panels and specialized for particular ones and/or specialized for particular types of panels' children.

·         Animations via Behavior per Panel or an object attached to the LayoutUpdate event - good if elements in your panel can be reasonably animated only in one way at the same time and / or you have already a panel to animate in

o   Provides a good separation between the layout and animation concerns.

o   You need to attach behavior to the Panel only.

o   Can be reused between different Panels and specialized for particular ones.

If you implement for Silverlight 3 I would prefer Behaviors over non-behaviors alternatives as they provide an awesome designer support in Blend.

In next part in series we’ll look how the techniques we presented can be applied to ItemsControl and TreeView.

 

 

How to create generic animated panels in Silverlight 2 and 3 (Animations part I)

Introduction

I’ve always had mixed feelings toward animations in cases of generic controls, panels or item controls. From one side it is extremely easy to define standard animations associated with visual states. From the other implementing any more sophisticated animation means that a generic control becomes a user control. In this first article in series I shall present ways we know in our team to make Framework Elements with non-trivial animations without sacrificing theirs genericness. In short - it is easy to create a generic FrameworkElement, but it is hard to create such with advanced animations so it is still generic. In part number one we shall deal with animations in Panels.

Customizable animations

When we talk about animations in Silverlight the problem is that the nicer animation should look, the more difficult (or impossible) is to go with simple storyboards in XAML. That is especially painful for generic controls. Someone would say that something like this, at least for controls should be possible:

  <Storyboard>

    <ColorAnimationUsingKeyFrames Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)">

        <SplineColorKeyFrame KeyTime="0" Value="{Binding RelativeSource={RelativeSource TemplatedParent} Path=MyFancyColorViaDepProp}"/>

    </ColorAnimationUsingKeyFrames>

</Storyboard>


 

However that won’t work. The problem is that despite Silverlight, since version 3, supports relative binding, it does not support context inheritance. What is the context inheritance is well described in this Nick’s blog entry.   It is a pity as in this situation we won’t directly expose animation to user so it may be completely customized. We can mitigate it thought and for a UI Element containing animations we can expose two dependency properties:

·         Animation time

·         Easing Function (SL3 only)

If you think about it, despite it is far less elegant than customizable animations in XAML, a user is still able to do a lot with it. The problem is of course if we have many different animations in an UI element and number of such properties grows…

 

Animating panel with attached properties

I got a rather fancy, animated panel to refactor. Unfortunately not the one I’m presenting in the example… well NDA world… sigh... I hope one day you shall see that in a released product because it is very impressive. Anyway I’m going to present you all tools to do whatever kind of 3D panel you can imagine. The source code for sample can be downloaded from attachments to this post. It requires SL3 and contains a picture “roller” panel reacting to mouse move with pressed left button. I tried it to be as simple as possible and I didn’t include validation code in order to simplify the sample as much as possible.

The panel I got to refactor was implemented in a traditional way, displaying each frame in ArrangeOverride – one of many descriptions for this approach can be found here. I wanted something more because of following reasons:

·         This solution is not well scalable. Each frame means ArrangeOverride which causes the layout updates for all children, all callbacks with associated dependency property updates are fired etc.

·         We practically simulating animation mechanism that is already a part of Silverlight API. If we wish to have easing effects again we would need to implement them ourselves and calculations for each frame can be painful

·         It drains your UI thread to generate more frames than necessary (IMHO 30 per sec but 50 someone else would say), competing for CPU with other controls. Yes, you can probably workaround that but again you would mind reimplementing a part of animation engine on your own

In the solution I’m presenting ArrangeOverride is very light weighted and is fired only when visibility changes.

Dealing with animations

The approach is really straightforward. Each panel’s child needs to be animated in a different way – we can implement that using attached dependency property defined in the panel that will specify animation properties for each child. We need also an access function (GetProperties in our case) to handle the initialization of this property in children if necessary.

From the panel perspective we need to declare the attached property and handle its initialization for each child:

       

    /// <summary>

    /// An animated panel

    /// </summary>

    public class SampleAnimPanel : Panel

    {

        ...

        internal SampleAnimPanelProperties Properties

        {

            get { return (SampleAnimPanelProperties)GetValue(SampleAnimPanelPropertiesProperty); }

            set { SetValue(SampleAnimPanelPropertiesProperty, value); }

        }

 

        internal static readonly DependencyProperty SampleAnimPanelPropertiesProperty =

            DependencyProperty.RegisterAttached(

                "Properties",

                typeof(SampleAnimPanelProperties),

                typeof(SampleAnimPanel),

                null);

       

 

        private SampleAnimPanelProperties GetProperties(DependencyObject element)

        {

            object animProp = element.ReadLocalValue(SampleAnimPanelPropertiesProperty);

            if (animProp != DependencyProperty.UnsetValue)

            {

                return (SampleAnimPanelProperties)animProp;

            }

            else

            {

                SampleAnimPanelProperties properties = new SampleAnimPanelProperties((FrameworkElement)element, AnimationStoryboard);

                element.SetValue(SampleAnimPanelPropertiesProperty, properties);

 

                return properties;

            }

        }

    ...

     }

 

Then we need following animation properties in our SampleAnimPanelProperties:

    /// <summary>

    /// A class used to hold animation parameter in per panel element fashion

    /// </summary>

    public class SampleAnimPanelProperties

    {

        #region Animation Properties

       

        /// <summary>

        /// Gets or sets RotationAnimation used rotate the elements

        /// </summary>

        public DoubleAnimation RotationAnimation { get; set; }

 

        /// <summary>

        /// Gets or sets ElementVisibilityAnimation.

        /// </summary>

        public ObjectAnimationUsingKeyFrames ElementVisibilityAnimation { get; set; }

 

        /// <summary>

        /// Gets or sets when to show a control in animation

        /// </summary>

        public DiscreteObjectKeyFrame ShowKeyFrame { get; set; }

 

        /// <summary>

        /// Gets or sets when to hide a control in animation

        /// </summary>

        public DiscreteObjectKeyFrame HideKeyFrame { get; set; }

 

        /// <summary>

        /// Gets or sets Projection. Taken from UIElement projection to which

        /// properties are attached.

        /// </summary>

        public PlaneProjection Projection { get; set; }

 

        /// <summary>

        /// Reference to the UIElement we provide parameters to

        /// </summary>

        private UIElement _element;

 

        ...

    }

 

Now when we have all the ammo let’s see the heart of our animation:

        /// <summary>

        /// Method that starts the slide animation.

        /// </summary>

        private void Animate()

        {

            if (Children != null && Children.Count > 0)

            {

                _animating = true;

 

                ExponentialEase easingFunction = new ExponentialEase { Exponent = ExponentialEaseFactor};

 

                SampleAnimPanelProperties properties = GetProperties(Children[0]);

                double animationTime = FullRotatonAnimationTime *

                                       Math.Abs(properties.Projection.RotationY - _rotationOffset) / FULL_ROTATION;

 

                Duration animationDuration = new Duration(TimeSpan.FromSeconds(animationTime));

 

                StopAnimation();

                AnimationStoryboard.Duration = animationDuration;

 

                for (int i = 0; i < Children.Count; i++)

                {

                    properties = GetProperties(Children[i]);

 

                    properties.RotationAnimation.To = _rotationOffset + GetBaseRotation(i);

                    properties.RotationAnimation.EasingFunction = easingFunction;

                    SetVisibilityAnimation(properties, Children[i], i, animationTime);

 

                    properties.ElementVisibilityAnimation.Duration = animationDuration;

                    properties.RotationAnimation.Duration = animationDuration;

                }

 

                AnimationStoryboard.Begin();

            }

        }

 

We should comment couple of things here:

·         GetBaseRotation just gives back the initial rotation of the element

·         FullRotatonAnimationTime time is given as a time required for 180 degree animation of the item, therefore as couple of such rotations can happen we need to  calculate the total time basing on the actual rotation

·         We have to stop animation and persevere animated values in StopAnimation(). We need to do it because if it is not stopped we can’t add or remove KeyFrames to visibility animations.

·         SetVisibilityAnimation is a method dealing with visibility animation – we’ll look into it later.

 

Now let’s think about performance. We use the most straightforward approach – one storyboard and animation from all children attached to it. It can cause a problem if all calculations and the storyboard start takes too long (more than a visible frame - 1/30 sec at least). An alternative is to include the storyboard to children attached properties (one storyboard per each child), and do calculations in separate threads (leveraging multiple cores) then fire animations one by one, at cost of potentially (small) desynchronizations between children elements. Another benefit would be that starting one storyboard with numerous animations inside can also take a perceptible amount of time.

Another approach would be not to animate object that shall not be visible and in such case being able to tell their “current” position when this animation ends. It is doable in the example above, however it won’t fix anything in worst case scenario when every element needs to be visible at least for a brief moment.

Dealing with visibility

Now let’s look into visibility animation. We need to align KeyFrame(s) Visibility animation with Double animation. There is no rocket science here. We know the total distance in DoubleAnimation for every child object, we know total time so we can estimate when to hide or show particular children. Basically we have following cases:

        private void SetVisibilityAnimation(

            SampleAnimPanelProperties properties,

            UIElement element,

            double targetPosition,

            double animationTime)

        {

            // Remove key frames from previous runs

            properties.ElementVisibilityAnimation.KeyFrames.Clear();

 

            double currentPosition = properties.Projection.RotationY;

 

            // Can happen because timespan is rounded to milliseconds and

            // there can be discrepancy between visibility and the position

            if (GetElementVisibility(currentPosition) == Visibility.Collapsed &&

                element.Visibility == Visibility.Visible)

            {

                element.Visibility = Visibility.Collapsed;

            }

            else if ((GetElementVisibility(currentPosition) == Visibility.Visible &&

                      element.Visibility == Visibility.Collapsed))

            {

                element.Visibility = Visibility.Visible;

            }

 

            // Check if animation is necessary

            if (element.Visibility == Visibility.Collapsed)

            {

                if (GetElementVisibility(targetPosition) == Visibility.Visible)

                {

                    // Show the object

                    double invisDistance = GetInvisibleDistance(currentPosition);

                    double totDistance = Math.Abs(currentPosition - targetPosition);

                    properties.ShowKeyFrame.KeyTime = EaseVisibilityAnimation(

                                                         invisDistance,

                                                         totDistance,

                                                         animationTime);

                    properties.ElementVisibilityAnimation.KeyFrames.Add(properties.ShowKeyFrame);

                }

                else if (GetElementVisibility(targetPosition) == Visibility.Collapsed &&

                         currentPosition * targetPosition < 0)

                {

                    // Special case – show the object, then collapse

                    ...

                }

            }

            else

            {

                // Visible

                if (GetElementVisibility(targetPosition) == Visibility.Collapsed)

                {

                    // Hide the object

                    ...

                }

            }

        }

 

The special case mentioned in the code handles a perfectly legal case when in one animation we need to first show and later hide a child in the panel which is a special case in which we need to add two KyFrames – one for appearance, one for disappearance.

One optimization note from panels’ children point of view – despite there is no such event as VisibilityChanged you can bind to Visibility property and for ex. turn off unused gadgets in the panel which can have a tremendous effect on the scalability your application.

Dealing with easing

I wanted to have something cool and giving more natural, almost physical behavior. What’s the problem let’s apply easing to DoubleAnimation – no problem.

Once we applied the ease effect on DoubleAnimation used to animate RotiationY, we need to adjust show & hide keyframes to double animations. Seems like an easy task to do. Turns out, it is not:

·         First what you would try is to somehow apply easing effect on ObjectAnimationUsingKeyFrames.  Unfortunately the easing effect cannot be applied on it.

·         We can try to create a new dependency property in SampleAnimPanelProperties attach and bind it to animated dependency property (RotationY in our case) and on per frame fashion check if a panel object should be visible or not in a dependency change callback. That won’t work though. We would bind to a non-animated dependency property while the animated value is taking the precedence and our property wouldn’t get chance to be updated till we stop an animation.

·         Easing essentially is a function based on time and influencing target value so it would be great if at least for a given progress of DoubleAnimation, we can get the time when it is reached (progress to time). Unfortunately there is no such method – only an opposite transformation (time to progress) exists in the API – Ease(Double) in EasingFunctionBase. Moreover the reverse function wouldn’t exist for some easing effects like bouncing effect as it is not 1to1 function so the method would need to return a collection.

You can’t get it from the API, you need to do it yourself. First let’s visit the msdn page concerning exponential ease to check the ease function algorithm:

 

But here comes more. The exponential ease function works by default in EasingOut mode which influences the equation above. We can check how in reflector in Ease function in EasingFunctionBase:

        Public double Ease(double normalizedTime)

        {

            switch (this.EasingMode)

            {

                case EasingMode.EaseOut:

                    return (1.0 - this.EaseInCore(1.0 - normalizedTime));

 

                case EasingMode.EaseIn:

                    return this.EaseInCore(normalizedTime);

 

                case EasingMode.EaseInOut:

                    if (normalizedTime < 0.5)

                    {

                        return (this.EaseInCore(normalizedTime * 2.0) / 2.0);

                    }

                    return (((1.0 - this.EaseInCore(2.0 - (normalizedTime * 2.0))) / 2.0) + 0.5);

            }

            return normalizedTime;

        }

 

So in reality our equation is:

y = 1 – (e^(a(1-t))  -1)/(e^a-1)

And we’re looking for normalized time t, while y being the distance to go to reach hide/show point (numerator) divided by the total distance (denominator). By solving this equation we get:

 t = 1 -  (Math.Log(1 + (1 - numerator /denominator) *  (Math.Exp(ExponentialEaseFactor) - 1)) / ExponentialEaseFactor)));

And this is it. The only thing left is to multiply t by the total animation time to denormalize it. The function looks like this:

        /// <summary>

        /// This function simulates easing effect on visibility animation

        /// </summary>

        /// <param name="numerator">Uneased visibility change animation shift</param>

        /// <param name="denominator">Total animation shift</param>

        /// <param name="animTime">Total time in seconds for animation</param>

        /// <returns>TimeSpan with eased visibility change time point</returns>

        private TimeSpan EaseVisibilityAnimation(double numerator, double denominator, double animTime)

        {

            return TimeSpan.FromSeconds(animTime * (1 - (Math.Log(1 + (1 - (numerator / denominator)) *                                                                                                                       (Math.Exp(ExponentialEaseFactor) - 1)) / ExponentialEaseFactor)));

        }

 

Making animations configurable

We can make panel animations configurable by exposing two animation properties:

·         AnimationTime

·         Exponjential Ease Factor

Unfortunately we’re not able to expose Easing Function due to our Visibility animation.  We need to…

Get rid of the visibility animation

After some time I realized that there is a way around it by a different, much simpler approach to dealing with visibility. Here is the trick:

        public SampleAnimPanel()

        {

            AnimationStoryboard = new Storyboard();

            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);

        }

 

        void CompositionTarget_Rendering(object sender, EventArgs e)

        {

            if (_animating && Children != null && Children.Count > 0)

            {

 

                for (int i = 0; i < Children.Count; i++)

                {

                    double currentPosition = GetProperties(Children[i]).Projection.RotationY;

                    if (GetElementVisibility(currentPosition) == Visibility.Collapsed &&

                        Children[i].Visibility == Visibility.Visible)

                    {

                        Children[i].Visibility = Visibility.Collapsed;

                    }

                    else if (GetElementVisibility(currentPosition) == Visibility.Visible &&

                              Children[i].Visibility == Visibility.Collapsed)

                    {

                        Children[i].Visibility = Visibility.Visible;

                    }                    

                }

            }

        }  

 

Rendering event occurs when the core Silverlight rendering process renders a frame. For each frame to be displayed in animation we just check if an object should be visible or not. It should be faster than 150 lines of code we have just written for the visibility animation. I decided to leave the parts for Visibility Animation in article and samples, because they definitely have a high educational value. And now we can expose easing function as well J

What’s next?

In the next article I will be playing again with panels. I’ll show how to implement animations in panels without modifying them in both Silverlight 2 and 3.

 

Silverlight 3: Template Binding vs. Relative Binding

Playing with SL3, I did a small research to find differences between Relative Binding and TemplateBinding in SL3 when used in generic templates:

 

·         Automatic Conversion is not supported in template binding, so with priority being int type, that won’t work

 

<TextBlock Text="{TemplateBinding Priority}"/>

 

But this workaround will:

 

<TextBlock DataContext="{TemplateBinding Priority} Text="{Binding}"/>

 

·         Similar solution to the above must be used (still in SL3) to apply a converter:

 

<TextBlock DataText="{TemplateBinding Priority} Text="{Binding, Converter={StaticResource PriorityConverter}}"/>

 

, while it is more straight forward (and longer) with relative binding

 

Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Priority, Converter={StaticResource PriorityConverter}}"/>

 

·         While template binding is a rough equivalent to Binding with RelativeSource={RelativeSource TemplatedParent}, there are additional differences:

o   Template Binding is simpler and (usually unnoticeably) faster

o   Template Binding is always one-way - see also Bea Stollnitz blog

o   TemplateBinding does not support context inheritance (What is context inheritance is well described in Nick’s blog entry here), so something like this (Angle) won’t work:

 

                        <Viewbox Stretch="Uniform" RenderTransformOrigin="0.5,0.5">

                            <Viewbox.RenderTransform>

                                <TransformGroup>

                                    <RotateTransform Angle="{TemplateBinding FontSize}"/>

                                </TransformGroup>

                            </Viewbox.RenderTransform>

                               

Ok – the last point is only for WPF. There is no context inheritance in Silverlight 3. To be decided if that will be the case in SL 4 – keep you posted. It is a pity as it would bring an enormous value to generic control development allowing more things to be moved to XAML side.

Posted by marlat | 2 Comments

The blog has started!

Hi Everyone

In first post I would like to introduce myself. I’m Marek Latuskiewicz and I’m a development Lead in the team dealing (among other things) with Silverlight Visualizations for various types of data.

Why am I starting this blog?

The reason is I’ve notice that many programmers (especially without WPF background) when first writing Silverlight code are at lost and still many areas don't have well defined design patterns.

In my blog I’m going to post two types of entries

·         Ready to take architectural solutions for certain problems. These shall be as simple and as generic as possible. Always with working code

·         Short posts about certain aspects of Silverlight / C# programming

So what’s coming next?

In this week I post the part number one (of two) “How to create animated panels in Silverlight 2 and 3”.

I’m going to post once a week (unless on vacations ;)).

Posted by marlat | 1 Comments
 
Page view tracker