Infosys Microsoft Alliance and Solutions blog

« Silverlight - Are you missing WatermarkedTextBox? | Main | ASP.NET, ASP.NET AJAX and Silverlight - Which way would you go? »

Silverlight - Animating Wrap Panel

Panels, as we know, are pretty much the basis of building any Silverlight application. They help arrange the other Silverlight controls in specific manner like Stack allowing you to stack controls either horizontally or vertically, Grid allowing you to position controls in rows an columns and Canvas giving you a free flow behavior. This blog however isn't however a primer on panels and you can easily find information about them on the net. Check this for information on how to layout controls using these various panels.

At Mix 2008, in one of the sessions the AnimatingStackPanel was demonstrated. I found the code for the same here. I decided to go ahead and try this. However my requirement was for a wrap panel and not a stack panel. I found one here. This would serve my purpose but there wasn't any animation in it. In my earlier work with WPF, I had looked at Kevin's bag of tricks and liked the AnimatingTilePanel.

I hence wanted to get something like that for my Silverlight application. I did find one such animating wrap panel as part of HackingSilverlight Library. But this wasn't completely what I was looking for, for the reasons below

  1. The animation in this panel always happens from bottom of the control
  2. It happens every time i add a new control to the panel. So instead of just animating the new control (a feature that is present in Kevin's AnimatingTilePanel), it animates all the controls again.

For these two reasons, I decided to try and merge the wrap panel and animating panel code that i had found (see the links above). If you want to write you own custom panel, you can check this for a good introduction. I realized that the animating stack panel code was in VB but I always work with C#  (I don't even have VB.NET installed on my machine). I decided to convert the code to C# and used this free VB.NET to C# converter. There were a few additional issues that i had to fix in the AnimatingPanelBase c# code, like the usage of states Dictionary object, adding System.Collection.Generic and System.Windows.Threading namespaces. Apart from this class I also converted and included the Vector structure.

With these, I then changed the WrapPanel class as per my needs. Since I wanted to work with horizontally oriented panel, I did changes only to that part of the code and didnt' touch the one that handles the vertical orientation. The key changes were to change the base class to AnimatingPanelBase, add the AnimationCompleted event handler, and finally in ArrangeOverride function. I used the AnimatingStackPanel class from the original VB project as a reference to make the code changes here.

First I modified the ArrangeOverride to include the positioning of elements twice. However unlike the sample from Mix where the animation happened from bottom, I wanted it to work more like the way it happens in Kevin's AnimationTilePanel. So I modified the initial position for the child element accordingly to start from a location such that the X coordinate is minus of control's width. In the second pass, this would be set to the actual initial point.

I then modified the test program a bit so that i could dynamically add new controls and see how they would animate to their right position. With this basic change when I ran the code, the controls did animate from the top now, but every time I would add a new control, It would still cause all existing controls to reanimate. I looked at the code a bit but could not pin-point where to make changes. Running the application a few times, proposed the answer. I observed that if I would click the button quickly without letting the first animation to finish, the new control would add in the way I wanted and existing controls won't animate again. So I removed the animation completion handler and things fell in place. The modified WrapPanel class is as below

    public class WrapPanel : AnimatingPanelBase

    {

 

        public Orientation Orientation

        {

            get { return (Orientation)GetValue(OrientationProperty); }

            set { SetValue(OrientationProperty, value); }

        }

 

        public static readonly DependencyProperty OrientationProperty =

            DependencyProperty.Register("Orientation", typeof(Orientation), typeof(WrapPanel), null);

 

        public WrapPanel()

        {

            // default orientation

            Orientation = Orientation.Horizontal;

        }

 

        protected override Size MeasureOverride(Size availableSize)

        {

            foreach (UIElement child in Children)

            {

                child.Measure(new Size(availableSize.Width, availableSize.Height));  

            }

            return availableSize;

        }

 

        protected override Size ArrangeOverride(Size finalSize)

        {

            Point point = new Point(0,0);

            int i = 0;

 

            if (Orientation == Orientation.Horizontal)

            {

                double largestHeight = 0.0;

 

                foreach (UIElement child in Children)

                {

                        SetElementLocation(child, new Rect(point, new Point(point.X + child.DesiredSize.Width, point.Y + child.DesiredSize.Height)));

 

                    if (child.DesiredSize.Height > largestHeight)

                        largestHeight = child.DesiredSize.Height;

 

                    point.X = point.X + child.DesiredSize.Width;

 

                    if ((i + 1) < Children.Count)

                    {

                        if ((point.X + Children[i + 1].DesiredSize.Width) > finalSize.Width)

                        {

                            point.X = 0;

                            point.Y = point.Y + largestHeight;

                            largestHeight = 0.0;

                        }

                    }

                    i++;

                }

            }

            else

            {

                double largestWidth = 0.0;

 

                foreach (UIElement child in Children)

                {

                    SetElementLocation(child, new Rect(point, new Point(point.X + child.DesiredSize.Width, point.Y + child.DesiredSize.Height)));

 

                    if (child.DesiredSize.Width > largestWidth)

                        largestWidth = child.DesiredSize.Width;

 

                    point.Y = point.Y + child.DesiredSize.Height;

 

                    if ((i + 1) < Children.Count)

                    {

                        if ((point.Y + Children[i + 1].DesiredSize.Height) > finalSize.Height)

                        {

                            point.Y = 0;

                            point.X = point.X + largestWidth;

                            largestWidth = 0.0;

                        }

                    }

                    i++;

                }

            }

            return finalSize;

        }

    }

However new controls were now appearing from top left corner and not from a position that is minus their width. I tried to make changes to the ArrangeOverride and realized that I can actually use a much simpler version. The animation still worked, but it was still happening from (0,0). I then started to dig into the AnimatingPanelBase class and finally hit upon the source of this issue. In the constructor of ElementState, the CurrentRect was being initialized to (0, 0). I modified this as below and with that finally got the wrap panel behaving in much similar manner as does Kevin's AnimatingTilePanel. Along wit this, another minor change to the constructor to handle if the animation is enabled for wrap panel or not as below and I was all set.

            public ElementState(Rect targetRect, double randomness, bool animationEnabled, )

            {

                if (animationEnabled)

                    CurrentRect = new Rect((targetRect.X - targetRect.Width), targetRect.Y, targetRect.Width, targetRect.Height);

                else

                    CurrentRect = targetRect;

 

                TargetRect = targetRect;

                LocationVelocity = new Vector(0, 0);

 

                SizeVelocity = new Vector(0, 0);

 

                AttractionMultiplier = (_Random.NextDouble() * randomness) + 0.4;

                NeedsArrange = false;

            }

The code is by no means handling all scenarios. Like I said, I haven't touched the vertical orientation part and also the code right now doesn't handle margins, padding, alignment and visibility etc. Some of that is handled in the code here.

Comments, welcome !

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Please key in the two words you see in the box to validate your identity as an authentic user and reduce spam.

Subscribe to this blog's feed

Follow us on

Blogger Profiles

Infosys on Twitter