This post will show you how to write a very simple contentControl, that allows you to transition your old content with your new content. It is a little prototype I put together after wanting to ‘spice up’ another control I was working on!
Show me!
What?
A contentControl is a control that only has one reason in life: to show your content. You will use it in places where you have an arbitrary object that you wish to display. For instance, a ‘Person’ business object, or a ‘string’. Its two most useful properties are ‘Content’ and ‘ContentTemplate’. The latter will let you define how to show your person.
By bundling two contentControls, we can move the new content to the upper contentControl and show the old content in the lower contentControl. Then we can use blend to think up some great transition between those visuals.
Why?
The current contentControl does not allow you to do a transition effect because it has no concept of ‘old content’. This means the change can not be visualized in a rich manner. The only thing you could do is transition in the new content, but the old content will be gone instantly.
Why not?
So, the Silverlight team could have just build such an approach into the framework, why didn’t they? Well, I have not checked officially, but can come up with a bunch of reasons. Most of all, this will keep a reference to old content (for the time the storyboard runs) that you might not expect. Not a big deal on a string, but possibly a very big deal on a large business object. Also, using this approach will result in a much larger object graph inside of the visual tree than just a contentControl: we’re adding a second contentControl, a layout panel, two visualstates and a few storyboards. Finally, creation of a template is costly and now happens twice when the content gets changed.
So, it is not a good idea to use this control all over the place. Please use sparingly, with caution and very lightweight templates.
How?
It’s a pretty straightforward control. (I’ll not do the pictures thing this time.. let’s see how copying regular code feels!)
9 [TemplateVisualState(Name = "Transitioning", GroupName = "PresentationStates")]
10 [TemplateVisualState(Name = "Normal", GroupName = "PresentationStates")]
11 [TemplatePart(Name = "PreviousContentPresentationSite", Type = typeof(ContentControl))]
12 public class TransitioningControl : ContentControl
13 {
14 private ContentControl PreviousPresentationSite { get; set; }
15
16 public TransitioningControl()
17 {
18 DefaultStyleKey = typeof(TransitioningControl);
19 }
Like I said, we’ll need a second contentControl to host our old content. This is represented by line 11.
Pretty interesting is the onApplyTemplate:
21 public override void OnApplyTemplate()
22 {
23 base.OnApplyTemplate();
24
25 PreviousPresentationSite = GetTemplateChild("PreviousContentPresentationSite") as ContentControl;
26
27 Panel root = GetTemplateChild("LayoutRoot") as Panel;
28
29 Storyboard sb= null;
30
31 if (root != null)
32 {
33 sb =
34 (from stategroup in (VisualStateManager.GetVisualStateGroups(root) as Collection<VisualStateGroup>)
35 where stategroup.Name == "PresentationStates"
36 from state in (stategroup.States as Collection<VisualState>)
37 where state.Name == "Transitioning"
38 select state.Storyboard).FirstOrDefault();
39 }
40
41 if (sb != null)
42 {
43 sb.Completed += ((sender, e) =>
44 {
45 // go to normal state and release our hold on the old content.
46 VisualStateManager.GoToState(this, "Normal", false);
47 if (PreviousPresentationSite != null)
48 {
49 PreviousPresentationSite.Content = null;
50 }
51 });
52 }
53
54 VisualStateManager.GoToState(this, "Normal", false);
55 }
I’m looking for the Transitioning State and take its StoryBoard. Now I can register some code to execute when the storyboard finishes. This represents our ‘cleanup’.
Obviously, this is prototyping code, so do not just copy this and use in a production system! At the very least you would need to take care of unhooking the event, etc. etc.
57 protected override void OnContentChanged(object oldContent, object newContent)
58 {
59 if (PreviousPresentationSite != null)
60 {
61 PreviousPresentationSite.Content = oldContent;
62
63 base.OnContentChanged(oldContent, newContent);
64
65 // if busy with transitioning, let's skip to normal
66 VisualStateManager.GoToState(this, "Normal", false);
67 // and start our state
68 VisualStateManager.GoToState(this, "Transitioning", true);
69 }
70 }
So, when our content changes, we get a very convenient ‘old content’ object.
I just quickly change to Normal state, which probably already happened (line 46), but if the content changes again before the storyboard finishes, it looks better to start afresh.
This is my attempt at a transition:
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="PresentationStates">
<vsm:VisualState x:Name="Transitioning">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CurrentContentPresentationSite"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="-40"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="CurrentContentPresentationSite"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PreviousContentPresentationSite"
Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="PreviousContentPresentationSite"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.800" Value="40"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Normal">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000"
Storyboard.TargetName="PreviousContentPresentationSite" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
This is how the sample is setup:
<local:TransitioningControl x:Name="contentChanger"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{Binding Path=CurrentTime}">
<local:TransitioningControl.ContentTemplate>
<DataTemplate>
<Border BorderBrush="Blue" BorderThickness="2">
<ContentPresenter Content="{Binding}" />
</Border>
</DataTemplate>
</local:TransitioningControl.ContentTemplate>
</local:TransitioningControl>
CurrentTime is a string that changes each second. The datatemplate is not necessary at all, just showing that it works.
I’d appreciate some feedback on this. So let me know if you would find such a control useful.
Source