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.
 
 
1 comments:
Thank you very much for this... very well written article -- clear and helpful. Now if there was a way to simply play a storyboard to animate from an old value to the new value (for instance, make an textblock and its ContentTemplate fade out uniformly when its Text field goes to null).
Post a Comment