During our time developing for Silverlight 3 and Windows Phone 7, we found a few holes in functionality and structure that we wanted to fill. Being WPF developers by day, the gulf in functionality between the runtimes provided some challenges for us to overcome; Concepts like basic commanding and forms of UI virtualisation were things that we felt were sorely needed, especially in a limited environment like WP7, so we’ve been going about implementing stuff that we think the environment needs and we wanted to pull back the curtain and share some of what we’ve done in an effort to help the platform mature a bit. We absolutely love and want to acknowledge the importance open source communities like Codeplex, so we’d like to start giving back too where we can.
With that said, this is the first in a series of posts about things we’ve developed as a team that we think other developers would find useful. The bigger, more important things that have been implemented will come down the track as we thought we’d start out small, with a simple control that we’ve developed that we think would be useful to those that would like some finer control over a TransitioningContentControl.
Before continuing on with what this control is, how it can be used and how it can be extended, I’d like to acknowledge that there is a TransitioningContentControl as part of the Silverlight Control Toolkit, but we have come across situations where there’s a need to be able to control which part of a transition we’d like to use and be able to change that as we see fit and we also wanted to extend the number of transitions that were available and be able to easily add and remove new ones as we saw fit. This control isn’t radically different to the one available on Codeplex, but it does offer some things that the other one doesn’t, and we find those things pretty useful.
How this control is different
At a fundamental level, this control is different in the way that it can be controlled. The control itself allows the developer to define transitions easily in XAML, then with some minimal, but appropriate plumbing in code, be able to define which parts of the transition are able to be manipulated.
What we’ve found is that there are 2 parts to a TransitioningContentControl, the Out transition (where old content goes away) and the In transition (where new content comes in), so we’ve created a control that allows the developer to define an Out transition, an In transition or, if these other two transitions are not appropriate to be separated, a combined OutIn transition and then control which part is used. Transitions with both an Out and an In state can be linked together to form an OutIn transition.
How to use the control
This is the easy part! Simply use the following markup in your project:
<Controls:TransitioningContentControl
Content="{Binding}"
Transition="Fade"/>
<!-- Obviously you can bind to whatever content you like, this is just an example. -->
To control which part of the transition to use, simply use the TransitionPart property with either Out, In, OutIn along with a particular transition. If a transition cannot be split into separate parts, the designer will let you know.
How to create your own transitions
There are a couple of steps to this, the first is defining the transition in XAML. Note that the naming convention is important here.
<!-- The following transition will make the new content fade in and slide down from above while the old content will slide down and fade out -->
<VisualState x:Name="FadeDownTransition_OutIn">
<Storyboard>
<DoubleAnimation
BeginTime="00:00:00" Duration="00:00:00.3"
Storyboard.TargetName="PART_CurrentContentPresentationSite"
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)"
From="-30" To="0"/>
<DoubleAnimation
BeginTime="00:00:00" Duration="00:00:00.3"
Storyboard.TargetName="PART_PreviousContentPresentationSite"
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)"
From="0" To="30"/>
<DoubleAnimation
BeginTime="00:00:00" Duration="00:00:00.3"
Storyboard.TargetName="PART_CurrentContentPresentationSite"
Storyboard.TargetProperty="(UIElement.Opacity)"
From="0" To="1"/>
<DoubleAnimation
BeginTime="00:00:00" Duration="00:00:00.3"
Storyboard.TargetName="PART_PreviousContentPresentationSite"
Storyboard.TargetProperty="(UIElement.Opacity)"
From="1" To="0"/>
</Storyboard>
</VisualState>
This is all pretty simple really, just go into the Themes/Generic.xaml file and define your transition as you’d like to see it. In this case, this is an OutIn transition as it didn’t make sense to not have these two parts linked together.
<!-- SlideLeftTransition -->
<VisualState x:Name="SlideLeftTransition_In">
<Storyboard>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="PART_CurrentContentPresentationSite"
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="-90"/>
<EasingDoubleKeyFrame KeyTime="00:00:00.4" Value="-90"/>
<EasingDoubleKeyFrame KeyTime="00:00:00.7" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<CircleEase EasingMode="EaseOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="PART_CurrentContentPresentationSite"
Storyboard.TargetProperty="(UIElement.Opacity)">
<DiscreteDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<DiscreteDoubleKeyFrame KeyTime="00:00:00.4" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="PART_PreviousContentPresentationSite"
Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="SlideLeftTransition_Out">
<Storyboard>
<DoubleAnimation
BeginTime="00:00:00" Duration="00:00:00.2"
Storyboard.TargetName="PART_PreviousContentPresentationSite"
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)"
From="0" To="-90"/>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="PART_CurrentContentPresentationSite"
Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
The above is an example of a transition type that can be split into an Out and an In state and be used independently. We’ve found that it’s useful to be able to just have one element slide out and hold, or just have one element slide in for example. Or in the case where there’s a blank canvas placeholder as an old content, we’d like to be able to just transition in instead of play the entire outin transition.
OK, once that’s done, it’s time to add the transition to the TransitionType enum in the control’s code, so to add the above transitions, do the following:
/// <summary>
/// Represents the type of transition that a TransitioningContentControl will perform.
/// </summary>
public enum TransitionType
{
...
/// <summary>
/// A transition that fades the new element in from the top.
/// </summary>
FadeDown,
...
/// <summary>
/// A transition that slides old content left and out of view, then slides new content back in from the same direction.
/// </summary>
SlideLeft
}
We’re almost done, there just needs to be a couple of finer details added to make sure the Control doesn’t decide to throw a fit. The developer needs to perform a check to ensure that a particular transition can be split or not. If the transition cannot be split (in the case of an OutIn transition), then it’s good practice to inform users that it cannot be split, this is done in the VerifyCanSplitTransition method. It’s a bit icky, but it provides a nice designtime experience.
private static bool VerifyCanSplitTransition(TransitionType transition, TransitionPartType transitionPart)
{
// Check whether the TransitionPart is compatible with the current transition.
var canSplitTransition = true;
if (transition == TransitionType.Fade || transition == TransitionType.FadeDown)
{
if (transitionPart != TransitionPartType.OutIn)
{
throw new InvalidOperationException("Cannot split this transition.");
}
canSplitTransition = false;
}
return canSplitTransition;
}
That’s pretty much it! If there are any finer changes that you’d like to make to particular transitions (such as scaling the magnitude of a transition depending on the size of a transition, it can be done in the SetTransitionDefaultValues method:
/// <summary>
/// Sets default values for certain transition types.
/// </summary>
private void SetTransitionDefaultValues()
{
...
if (this.Transition == TransitionType.SlideLeft)
{
if (this.CompletingTransition != null)
{
var completingDoubleAnimation = (DoubleAnimationUsingKeyFrames)this.CompletingTransition.Children[0];
completingDoubleAnimation.KeyFrames[1].Value = -this.ActualWidth;
}
if (this.StartingTransition != null)
{
var startingDoubleAnimation = (DoubleAnimation)this.StartingTransition.Children[0];
startingDoubleAnimation.To = -this.ActualWidth;
}
return;
}
}
Often times, the transitions will be fine with the default values set in XAML, but it might be nice for certain transitions to change them as the developer sees fit.
So that’s it for this post; it was a long one, but it’s nice to have all of the information presented right in front of you as a developer, so you don’t have to trawl through obfuscated code and poor commenting to be able to understand how to use certain things. There’s a code project associated with this so you can build it and plug it straight into your own codebase (with some default transitions that you can add to if you’d like). It’s provided “as is” so you use this at your own risk. Please enjoy!
LandDolphin.Samples