Douglas Stockwell has blogged about how he implements transitions in WPF, actually, I really like the animation effects he creates, but I think his implementation is bit cumbersome, so I finally wind up creating my own transition by defining a custom control aka TransitionControl, here comes the code for this control:
[TemplatePartAttribute(Name = "PART_ContentHost", Type = typeof(ContentPresenter))]
[TemplatePartAttribute(Name = "PART_StaleContentHost", Type = typeof(ContentPresenter))]
public class TransitionControl : ContentControl
{
private static readonly DependencyPropertyKey IsContentChangedPropertyKey;
public static readonly DependencyProperty StaleContentProperty;
public static readonly DependencyProperty IsContentChangedProperty;
public static readonly DependencyProperty TransitionEffectProperty;
static TransitionControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(TransitionControl),
new FrameworkPropertyMetadata(typeof(TransitionControl)));
TransitionEffectProperty = DependencyProperty.Register(
"TransitionEffect",
typeof(TransitionEffects),
typeof(TransitionControl),
new FrameworkPropertyMetadata(TransitionEffects.None));
StaleContentProperty = DependencyProperty.Register(
"StaleContent",
typeof(Object),
typeof(TransitionControl),
new FrameworkPropertyMetadata(null));
ContentControl.ContentProperty.OverrideMetadata(
typeof(TransitionControl),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnContentChanged)));
IsContentChangedPropertyKey = DependencyProperty.RegisterReadOnly(
"IsContentChanged",
typeof(Boolean),
typeof(TransitionControl),
new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
IsContentChangedProperty = IsContentChangedPropertyKey.DependencyProperty;
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Boolean IsContentChanged
{
get { return (Boolean) GetValue(IsContentChangedProperty); }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Object StaleContent
{
get { return base.GetValue(StaleContentProperty); }
set { base.SetValue(StaleContentProperty, value); }
}
public TransitionEffects TransitionEffect
{
get { return (TransitionEffects) base.GetValue(TransitionEffectProperty); }
set { base.SetValue(TransitionEffectProperty, value); }
}
private static void OnContentChanged(DependencyObject element, DependencyPropertyChangedEventArgs e)
{
TransitionControl tc = element as TransitionControl;
tc.SetValue(IsContentChangedPropertyKey, BooleanBoxes.FalseBox);
tc.StaleContent = e.OldValue;
if (e.OldValue != null && e.NewValue != null)
{
if (!tc.IsLoaded)
{
tc.Loaded += delegate(Object sender, RoutedEventArgs args)
{
tc.SetValue(IsContentChangedPropertyKey, BooleanBoxes.TrueBox);
};
}
else
{
tc.SetValue(IsContentChangedPropertyKey, BooleanBoxes.TrueBox);
}
}
}
}
The basic idea is to override the ContentProperty's default metadata by adding a PropertyChangedCallback to track the change of ContentProperty, inside the callback, I assign the old value of the ContentProperty to a custom DP I create (I've no idea of how to come up with a sexy name for this DP, so I just call it StaleContent), and I also toggle the value of IsContentChanged read-only DP to notify that the content of this control is changed, so this is the plumbing I create for my TransitionControl, and the tedious part is over, next question is how to apply transition effects, that's the job of control template, I define different control templates for different transition effects, take waving transition for example, the definition of the control template is as follows:
<!--Waving Transition Template-->
<ControlTemplate TargetType="{x:Type cc:TransitionControl}" x:Key="WavingTemplate">
<Grid>
<ContentPresenter
Name="PART_ContentHost"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" />
<ContentPresenter
Name="PART_StaleContentHost"
IsHitTestVisible="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding StaleContent}">
<ContentPresenter.OpacityMask>
<RadialGradientBrush GradientOrigin="0.5,0.5" Center="0.5,0.5" RadiusX="1" RadiusY="1">
<RadialGradientBrush.GradientStops>
<GradientStop Offset="0" Color="#00000000"/>
<GradientStop Offset="0" Color="#FF000000"/>
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</ContentPresenter.OpacityMask>
</ContentPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsContentChanged" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
From="0"
To="1"
Duration="00:00:0.7"
Storyboard.TargetProperty="OpacityMask.(GradientBrush.GradientStops)[0].Offset"
Storyboard.TargetName="PART_StaleContentHost"/>
<DoubleAnimation
From="0"
To="1"
Duration="00:00:0.7"
Storyboard.TargetProperty="OpacityMask.(GradientBrush.GradientStops)[1].Offset"
Storyboard.TargetName="PART_StaleContentHost"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
You can see that I create two ContentPresenters, one for the old content(StaleContentProperty), and one for the new content(ContentProperty), the PART_StaleContentHost ContentPresenter overlapps the PART_ContentHost ContentPresenter, so to achieve transition effects here, I simply "erase" the PART_StaleContentHost to let the PART_ContentHost show through, so here I use Trigger the track the IsContentChanged property, and apply appropriate animation to the PART_StaleContentHost accordingly.
You can see my implementation of transitions is pretty streightforward, and much more clear, and a bit more performant than Douglas Stockwell's approach.
I've upload the control to Channel9's Sandbox, so you can download the source from here.