public enum AnimateType
{
Right,
Top,
Down,
Left,
Opacity,
}
/// <summary>
/// A ContentControl that animates the transition between content
/// </summary>
[TemplatePart(Name = "PART_PaintArea", Type = typeof(Shape)),
TemplatePart(Name = "PART_MainContent", Type = typeof(ContentPresenter))]
public class AnimatedContentControl : ContentControl
{
public AnimateType AnimateType
{
get { return (AnimateType)GetValue(AnimateTypeProperty); }
set { SetValue(AnimateTypeProperty, value); }
}
// Using a DependencyProperty as the backing store for AnimateType. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AnimateTypeProperty =
DependencyProperty.Register("AnimateType", typeof(AnimateType), typeof(AnimatedContentControl), new PropertyMetadata(AnimateType.Left));
#region Generated static constructor
static AnimatedContentControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AnimatedContentControl), new FrameworkPropertyMetadata(typeof(AnimatedContentControl)));
}
#endregion Generated static constructor
public TimeSpan AnimationDuration
{
get { return (TimeSpan)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
// Using a DependencyProperty as the backing store for AnimationDuration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AnimationDurationProperty =
DependencyProperty.Register("AnimationDuration", typeof(TimeSpan), typeof(AnimatedContentControl), new PropertyMetadata(TimeSpan.FromSeconds(0.5)));
public IEasingFunction EasingFunction
{
get { return (IEasingFunction)GetValue(EasingFunctionProperty); }
set { SetValue(EasingFunctionProperty, value); }
}
// Using a DependencyProperty as the backing store for EasingFunction. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EasingFunctionProperty =
DependencyProperty.Register("EasingFunction", typeof(IEasingFunction), typeof(AnimatedContentControl), new PropertyMetadata(new PowerEase() { EasingMode = EasingMode.EaseInOut }));
private Shape m_paintArea;
private ContentPresenter m_mainContent;
/// <summary>
/// This gets called when the template has been applied and we have our visual tree
/// </summary>
public override void OnApplyTemplate()
{
m_paintArea = Template.FindName("PART_PaintArea", this) as Shape;
m_mainContent = Template.FindName("PART_MainContent", this) as ContentPresenter;
base.OnApplyTemplate();
}
protected override void OnContentTemplateSelectorChanged(DataTemplateSelector oldContentTemplateSelector, DataTemplateSelector newContentTemplateSelector)
{
base.OnContentTemplateSelectorChanged(oldContentTemplateSelector, newContentTemplateSelector);
if (ContentTemplateSelector != null)
{
ContentTemplate = ContentTemplateSelector.SelectTemplate(Content, null);
}
}
protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate)
{
base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);
}
/// <summary>
/// This gets called when the content we're displaying has changed
/// </summary>
/// <param name="oldContent">The content that was previously displayed</param>
/// <param name="newContent">The new content that is displayed</param>
protected override void OnContentChanged(object oldContent, object newContent)
{
if (m_paintArea != null && m_mainContent != null)
{
m_paintArea.Fill = CreateBrushFromVisual(m_mainContent);
BeginAnimateContentReplacement();
}
if (ContentTemplateSelector != null)
{
ContentTemplate = ContentTemplateSelector.SelectTemplate(newContent, null);
}
base.OnContentChanged(oldContent, newContent);
}
/// <summary>
/// Starts the animation for the new content
/// </summary>
private void BeginAnimateContentReplacement()
{
var newContentTransform = new TranslateTransform();
var oldContentTransform = new TranslateTransform();
m_paintArea.RenderTransform = oldContentTransform;
m_mainContent.RenderTransform = newContentTransform;
m_paintArea.Visibility = Visibility.Visible;
switch (AnimateType)
{
case AnimateType.Top:
newContentTransform.BeginAnimation(TranslateTransform.YProperty, CreateAnimation(-this.ActualHeight, 0));
oldContentTransform.BeginAnimation(TranslateTransform.YProperty, CreateAnimation(0, this.ActualHeight, (s, e) => m_paintArea.Visibility = Visibility.Hidden));
break;
case AnimateType.Down:
newContentTransform.BeginAnimation(TranslateTransform.YProperty, CreateAnimation(this.ActualHeight, 0));
oldContentTransform.BeginAnimation(TranslateTransform.YProperty, CreateAnimation(0, -this.ActualHeight, (s, e) => m_paintArea.Visibility = Visibility.Hidden));
break;
case AnimateType.Left:
newContentTransform.BeginAnimation(TranslateTransform.XProperty, CreateAnimation(-this.ActualWidth, 0));
oldContentTransform.BeginAnimation(TranslateTransform.XProperty, CreateAnimation(0, this.ActualWidth, (s, e) => m_paintArea.Visibility = Visibility.Hidden));
break;
case AnimateType.Right:
newContentTransform.BeginAnimation(TranslateTransform.XProperty, CreateAnimation(this.ActualWidth, 0));
oldContentTransform.BeginAnimation(TranslateTransform.XProperty, CreateAnimation(0, -this.ActualWidth, (s, e) => m_paintArea.Visibility = Visibility.Hidden));
break;
case AnimateType.Opacity:
m_mainContent.BeginAnimation(UIElement.OpacityProperty, CreateAnimation(0, 1));
m_paintArea.BeginAnimation(UIElement.OpacityProperty, CreateAnimation(1, 0, (s, e) => m_paintArea.Visibility = Visibility.Hidden));
break;
default:
break;
}
}
/// <summary>
/// Creates the animation that moves content in or out of view.
/// </summary>
/// <param name="from">The starting value of the animation.</param>
/// <param name="to">The end value of the animation.</param>
/// <param name="whenDone">(optional) A callback that will be called when the animation has completed.</param>
private AnimationTimeline CreateAnimation(double from, double to, EventHandler whenDone = null)
{
var anim = new DoubleAnimation(from, to, AnimationDuration) { EasingFunction = EasingFunction };
if (whenDone != null)
anim.Completed += whenDone;
anim.Freeze();
return anim;
}
/// <summary>
/// Creates a brush based on the current appearnace of a visual element. The brush is an ImageBrush and once created, won't update its look
/// </summary>
/// <param name="v">The visual element to take a snapshot of</param>
private Brush CreateBrushFromVisual(Visual v)
{
if (v == null)
throw new ArgumentNullException("v");
var target = new RenderTargetBitmap((int)this.ActualWidth, (int)this.ActualHeight, 96, 96, PixelFormats.Pbgra32);
target.Render(v);
var brush = new ImageBrush(target) { AlignmentY = AlignmentY.Top, Stretch = Stretch.None };
brush.Freeze();
return brush;
}
}