using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace AvalonApplication1
{
public class PanelLayoutAnimator
{
public PanelLayoutAnimator(Panel panelToAnimate)
{
_panel = panelToAnimate;
_panel.LayoutUpdated += PanelLayoutUpdated;
}
public static readonly DependencyProperty IsAnimationEnabledProperty
= DependencyProperty.RegisterAttached("IsAnimationEnabled", typeof(bool), typeof(PanelLayoutAnimator),
new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsAnimationEnabledInvalidated)));
public static void SetIsAnimationEnabled(DependencyObject dependencyObject, bool enabled)
{
dependencyObject.SetValue(IsAnimationEnabledProperty, enabled);
}
///
/// Detaches this animator from the panel
///
public void Detach()
{
if (_panel != null)
{
_panel.LayoutUpdated -= PanelLayoutUpdated;
_panel = null;
}
}
///
/// Called when panel's layout is updated
///
///
/// Note: This is actually called when any layouts are updated
///
private void PanelLayoutUpdated(object sender, EventArgs e)
{
// At this point, the panel has moved the children to the new locations, but hasn't
// been rendered
foreach (UIElement child in _panel.Children)
{
// Figure out where child actually is right now. This is a combination of where the
// panel put it and any render transform currently applied
Point currentPosition = child.TransformToAncestor(_panel).Transform(new Point());
// See what transform is being applied
Transform currentTransform = child.RenderTransform;
// Compute where the panel actually arranged it to
Point arrangePosition = currentPosition;
if (currentTransform != null)
{
// Undo any transform we applied
arrangePosition = currentTransform.Inverse.Transform(arrangePosition);
}
// If we had previously stored an arrange position, see if it has moved
if (child.ReadLocalValue(SavedArrangePositionProperty) != DependencyProperty.UnsetValue)
{
Point savedArrangePosition = (Point)child.GetValue(SavedArrangePositionProperty);
// If the arrange position hasn't changed, then we've already set up animations, etc
// and don't need to do anything
if (!AreReallyClose(savedArrangePosition, arrangePosition))
{
// If we apply the current transform to the saved arrange position, we'll see where
// it was last rendered
Point lastRenderPosition = currentTransform.Transform(savedArrangePosition);
// Transform the child from the new location back to the old position
TranslateTransform newTransform = new TranslateTransform();
child.RenderTransform = newTransform;
// Decay the transformation with an animation
newTransform.BeginAnimation(TranslateTransform.XProperty, MakeAnimation(lastRenderPosition.X - arrangePosition.X));
newTransform.BeginAnimation(TranslateTransform.YProperty, MakeAnimation(lastRenderPosition.Y - arrangePosition.Y));
}
}
// Save off the previous arrange position
child.SetValue(SavedArrangePositionProperty, arrangePosition);
}
}
///
/// Called when IsAnimationEnabled is changed on an object.
///
private static void OnIsAnimationEnabledInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
Panel panel = dependencyObject as Panel;
if (panel != null)
{
if ((bool)e.NewValue)
{
// Turn animations on for the panel if there's not already a PanelAnimator attached
if (panel.ReadLocalValue(AttachedAnimatorProperty) == DependencyProperty.UnsetValue)
{
PanelLayoutAnimator animator = new PanelLayoutAnimator(panel);
panel.SetValue(AttachedAnimatorProperty, animator);
}
}
else
{
// clear animations
if (panel.ReadLocalValue(AttachedAnimatorProperty) != DependencyProperty.UnsetValue)
{
PanelLayoutAnimator animator = (PanelLayoutAnimator)panel.ReadLocalValue(AttachedAnimatorProperty);
animator.Detach();
panel.SetValue(AttachedAnimatorProperty, DependencyProperty.UnsetValue);
}
}
}
}
// Check if two points are really close. If you don't do epsilon comparisons, you can get lost in the
// noise of floating point operations
private bool AreReallyClose(Point p1, Point p2)
{
return (Math.Abs(p1.X - p2.X) < .001 && Math.Abs(p1.Y - p2.Y) < .001);
}
// Create an animation to decay from start to 0 over .5 seconds
private static DoubleAnimation MakeAnimation(double start)
{
DoubleAnimation animation = new DoubleAnimation(start, 0d, new Duration(TimeSpan.FromMilliseconds(500)));
animation.AccelerationRatio = 0.2;
return animation;
}
// dependency property we attach to children to save their last arrange position
private static readonly DependencyProperty SavedArrangePositionProperty
= DependencyProperty.RegisterAttached("SavedArrangePosition", typeof(Point), typeof(PanelLayoutAnimator));
// dependency property we attach to panels to save a reference to ourselves
private static readonly DependencyProperty AttachedAnimatorProperty
= DependencyProperty.RegisterAttached("AttachedAnimator", typeof(PanelLayoutAnimator), typeof(PanelLayoutAnimator));
private Panel _panel;
}
}