views:

396

answers:

1

I'm pretty new to WPF (and completely new to animations), so I would imagine there's just something that I'm missing here. I'm in the process of creating a new layout Panel that lays the controls out in a particular way; the details aren't (or shouldn't be) especially important here. What I'd like to do is animate the movement of the elements that I'm managing when I call Arrange on them.

In general, what I do is keep track of the last Rect that I used to Arrange each child element. When ArrangeOverride is called...

  • If animation is enabled and there is a previous bounding Rect for the element, I Arrange it to the X and Y of that bounding rect with the new Width and Height, then use two DoubleAnimations (one for X and one for Y) to animate it to the X and Y of the new bounds
  • If animation is disabled or there is no existing bounding Rect for the element, I just Arrange it to the new bounds

When I disable animation, everything renders correctly (which is a good thing). When I enable animation, the elements render in the incorrect locations. If I resize the container, they generally animate themselves back into the correct spot.

Edit for clarification

In general, the error appears to be directly related to the coordinates of the bounds of the control. In other words, if the control is farther to the right to begin with, then the TranslateTransform appears to offset it more than a control that is not as far to the right. The same goes for up/down. Again, resizing seems to correct the problem (in fact, the controls animate themselves into the correct spot, which is frustratingly neat), but only if the entire set of controls is visible in the new size.

This is an abbreviated version of the class:

public class MyPanel : Panel
{
    private Dictionary<UIElement, Rect> lastBounds = new Dictionary<UIElement, Rect>();

    protected override Size MeasureOverride(Size availableSize)
    {
        // do all of my measurements here and delete anything from lastBounds that
        // isn't on the panel any more. This works fine
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        foreach(UIElement child in Children)
        {
            Rect newBounds = // get the previously calculated new bounds;

            TransformGroup group= child.RenderTransform as TransformGroup;

            if (group == null)
            {
                group = new TransformGroup();

                group.Children.Add(new TranslateTransform());

                child.RenderTransform = group;
            }

            Rect lastBounds;

            if (!this.lastBounds.TryGetValue(child, out lastBounds)) 
                lastBounds = Rect.Empty;

            if (!lastBounds.IsEmpty && EnableAnimation)
            {
                Rect tempBounds = new Rect(lastBounds.X, lastBounds.Y, newBounds.Width, newBounds.Height);

                child.Arrange(tempBounds);

                int animationDuration = 300;

                DoubleAnimation xAnim = new DoubleAnimation(newBounds.X - lastBounds.X, TimeSpan.FromMilliseconds(animationDuration));
                DoubleAnimation yAnim = new DoubleAnimation(newBounds.Y - lastBounds.Y, TimeSpan.FromMilliseconds(animationDuration));

                xAnim.AccelerationRatio = yAnim.AccelerationRatio = 0.2;
                xAnim.DecelerationRatio = yAnim.DecelerationRatio = 0.7;

                TranslateTransform translate = group.Children[0] as TranslateTransform;

                translate.BeginAnimation(TranslateTransform.XProperty, xAnim);
                translate.BeginAnimation(TranslateTransform.YProperty, yAnim);
            }
            else
            {
                child.Arrange(newBounds);
            }
        }
    }
}
+1  A: 

You appear to be arranging the children to their old location, then applying a render transform to get them to appear in their new location. This may be an issue because the layout engine might have a problem with you giving it a location that is now outside the parent layout container. You may have better luck always arranging to the new bounds (within the parent element), then applying the render translate animation to move it back to where it really is:

child.Arrange(newBounds);

int animationDuration = 300;

DoubleAnimation xAnim = new DoubleAnimation(lastBounds.X - newBounds.X, 0, TimeSpan.FromMilliseconds(animationDuration));
DoubleAnimation yAnim = new DoubleAnimation(lastBounds.Y - newBounds.Y, 0, TimeSpan.FromMilliseconds(animationDuration));
RandomEngy
I'm not sure how that could work...wouldn't that just move it back where it *was*?
Adam Robinson
No, it's putting the actual location of the child to its new, real location (in layout terms). The animation applied is a render transform to show it sliding there. If it was moving from 0,0 to 10,10, it would be arranged to 10,10, then start a render transform going from -10,-10 back down to 0,0. Which, when applied to its new location, means it appears to be moving from 0,0 to 10,10 as it should.
RandomEngy
Ah, that makes sense. This took care of the problem, thanks!
Adam Robinson