Animation Lab (Custom Controls & VSM)

0
450

Animation Lab enables you to experiment with all sorts of transforms and animations. There’s nothing like seeing the various easing functions and other settings in action to understand how the topics in this part of the book really work. So Animation Lab is a useful developer education tool.

Instead, this chapter takes a look at a custom control used by this app and almost every other app in this book: AnimatedFrame. This subclass of PhoneApplicationFrame is responsible for producing the page-flip animations when navigating between pages. The apps in this book include it by referencing its assembly (AdamNathanAppsLibrary.dll) and then changing one line of code in App.xaml.cs from

[code]

RootFrame = new PhoneApplicationFrame();

[/code]

to

[code]

RootFrame = new AdamNathanAppsLibrary.AnimatedFrame();

[/code]

The Silverlight forWindows Phone Toolkit contains slick support for performing a variety of animated page transitions. It defines a new notion of a transition, which is like a storyboard but with automatic enabling of bitmap caching and automatic disabling of hit testing while the animation is underway. It includes five built-in transitions: TurnstileTransition (the typical page-flip that is expected for page transitions), RollTransition, SlideTransition, SwivelTransition, and RotateTransition.The last one is intended for animating a change in page orientation.

A transition can be applied to any UI element and can be triggered at any time with a call to its Begin method, so they are quite powerful. However, there are two reasons that the apps in this book do not use transitions for animating page navigation:

  1. Proper page-navigation transitions are astoundingly verbose. It involves four distinct transitions (animating in when navigating forward, animating in when navigating backward, animating out when navigating forward, and animating out when navigating backward), and they must be assigned individually.This looks like the following:

    [code]
    <phone:PhoneApplicationPage …>
    <toolkit:TransitionService.NavigationInTransition>
    <toolkit:NavigationInTransition>
    <toolkit:NavigationInTransition.Backward>
    <toolkit:TurnstileTransition Mode=”BackwardIn”/>
    </toolkit:NavigationInTransition.Backward>
    <toolkit:NavigationInTransition.Forward>
    <toolkit:TurnstileTransition Mode=”ForwardIn”/>
    </toolkit:NavigationInTransition.Forward>
    </toolkit:NavigationInTransition>
    </toolkit:TransitionService.NavigationInTransition>
    <toolkit:TransitionService.NavigationOutTransition>
    <toolkit:NavigationOutTransition>
    <toolkit:NavigationOutTransition.Backward>
    <toolkit:TurnstileTransition Mode=”BackwardOut”/>
    </toolkit:NavigationOutTransition.Backward>
    <toolkit:NavigationOutTransition.Forward>
    <toolkit:TurnstileTransition Mode=”ForwardOut”/>
    </toolkit:NavigationOutTransition.Forward>
    </toolkit:NavigationOutTransition>
    </toolkit:TransitionService.NavigationOutTransition>

    </phone:PhoneApplicationPage>
    [/code]
    Imagine how long this book would be if each page’s XAML file contained that!

  2. Using transitions for page navigation has the same requirement of replacing one line of code in App.xaml.cs, so using them doesn’t save you from that awkwardness. In this case, the new line of code is:

    [code]
    RootFrame = new TransitionFrame();
    [/code]

Animation Lab also uses a custom control called PickerBox, which acts like a full-mode list picker. Although it is more limited than the list picker control in the Silverlight for Windows Phone Toolkit it has one advantage leveraged by this app: It supports a multiselect mode that follows the Windows Phone design guidelines.

The Silverlight for Windows Phone Toolkit also contains a TiltEffect class that is similar to the custom Tilt class used by just about every app in this book. Instead of setting Tilt.IsEnabled to true, you can set TiltEffect.IsTiltEnabled to true.Whereas Tilt.IsEnabled must be applied directly to the elements you wish to tilt, TiltEffect.IsTiltEnabled searches for any “tiltable” child elements and automatically applies the effect (unless such an element has TiltEffect.SuppressTilt set to true). By default, this includes all buttons and list box items, but you can change this behavior by modifying TiltEffect’s TiltableItems list.

Creating a Custom Control

Silverlight has two schemes for writing your own controls: writing a user control (the easier of the two) and writing a custom control (the more complicated but also more flexible variety). Although user controls have a distinct UserControl base class, what really distinguishes them is the process by which they are created. When you create a new one in Visual Studio, you are given a XAML file and a code-behind file, just like with a page.

Custom controls act more like the built-in Silverlight controls. They can derive from any Control subclass (or Control itself), but their important distinction is also how they are built. They are implemented with as little dependence on their specific visual representation as possible. Although they provide a default style in a special XAML file, they allow consumers to swap it out with a new style that potentially overhauls the visuals with a new control template.

Writing a custom control is a two-step process. Step 1 is implementing the class. The following class is just about the simplest possible custom control with no behavior above and beyond what it inherits from its base class:

[code]

using System.Windows.Controls;
namespace WindowsPhoneApp
{
public class MyCustomControl : Control
{
public MyCustomControl()
{
// Apply a style with the target type of MyCustomControl instead of Control
this.DefaultStyleKey = typeof(MyCustomControl);
}
}
}

[/code]

The assignment of DefaultStyleKey is important. Setting it to the type of this control enables a default style to be provided specifically for it. Without setting this, it would pick up the default style for its base class (the most-derived one that assigns DefaultStyleKey accordingly).

Step 2 is to place a default style for your control in your project’s generic theme dictionary. This is a XAML file containing a resource dictionary as its root element. This file must be named generic.xaml, it must be in a folder called Themes in the root of your project, and it must have a Build Action of Resource. If any of these conditions are not met, your default style will not be found.

The following is an example generic.xaml file containing a default style for MyCustomControl:

[code]

<ResourceDictionary
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:local=”clr-namespace:WindowsPhoneApp”>
<!– MyCustomControl default style –>
<Style TargetType=”local:MyCustomControl”>
<Setter Property=”Foreground” Value=”Blue”/>
<Setter Property=”Width” Value=”75”/>
<Setter Property=”Height” Value=”75”/>
<Setter Property=”Template”>
<Setter.Value>
<ControlTemplate TargetType=”local:MyCustomControl”>
<Ellipse Fill=”{TemplateBinding Foreground}”
Width=”{TemplateBinding Width}”
Height=”{TemplateBinding Height}”/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

[/code]

With that, the control can now be consumed. The following stack panel contains three instances. The first instance uses its default settings, the second instance customizes its appearance with a few properties (that are fortunately respected in the default control template, otherwise setting them as done below would have no effect), and the third instance has a custom style with a custom control template applied. The result of this is rendered in Figure 19.1.

FIGURE 19.1 A consumer of MyCustomControl can completely change its visual appearance, such as turning the ellipse into a rectangle.
FIGURE 19.1 A consumer of MyCustomControl can completely change its visual appearance, such as turning the ellipse into a rectangle.

[code]

<StackPanel>
<StackPanel.Resources>
<Style x:Key=”CustomStyle” TargetType=”local:MyCustomControl”>
<Setter Property=”Template”>
<Setter.Value>
<ControlTemplate TargetType=”local:MyCustomControl”>
<Rectangle Fill=”{TemplateBinding Foreground}”
Width=”{TemplateBinding Width}”
Height=”{TemplateBinding Height}”/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<local:MyCustomControl/>
<local:MyCustomControl Foreground=”Red” Width=”200” Height=”100”/>
<local:MyCustomControl Style=”{StaticResource CustomStyle}”/>
</StackPanel>

[/code]

Defining States

A good control has logic that is independent from whatever specific visuals are currently applied (such as the rectangle instead of the ellipse). To help enable controls to keep their logic separated from their appearance, the Visual State Manager (VSM) can give control templates more power and expressiveness.

The Visual State Manager enables a control author to formally specify different states for their controls, so a consumer creating a custom control template can specify different visuals for each one. Expression Blend takes advantage of this information to provide a nice experience for control template authors.

On the control side, the author can define several named states and then add internal logic to transition to these states at the right times (by calling a static VisualStateManager.GoToState method). The states must be grouped into one or more mutually exclusive state groups. Table 19.1 lists the states (and their state groups) exposed by five built-in controls.

TABLE 19.1 State Groups and States Used by Five of Silverlight’s Built-In Controls
TABLE 19.1 State Groups and States Used by Five of Silverlight’s Built-In Controls

At any time, a control is in one state from every group. For example, a button is Normal and Unfocused by default. This grouping mechanism exists to help prevent a long list of states meant to cover every combination of independent properties (such as NormalUnfocused, NormalFocused, DisabledUnfocused, DisabledFocused, and so on). Sometimes a control’s states aren’t as neatly organized as they ideally would be, such as the ones in text box’s ValidationStates group that overlap with FocusStates when it comes to the notion of having focus.

After the next section, we’ll look at code that defines and uses states, as well as a control template that acts upon state changes.

Defining Parts

Sometimes it’s hard to avoid writing a control that makes some assumptions about its visual appearance. And that’s okay, as long as the control doesn’t cause unhandled exceptions when these assumptions aren’t valid. When a control wants certain elements to be in its control template, these elements are called parts.

Parts are just named elements that the control can retrieve, similar to a page using an element’s FindName method to retrieve an element by name. In this case, a control can call its inherited (protected) GetTemplateChild method, which is able to retrieve an element defined the current control template.

Table 19.2 shows all the parts expected by the same five controls from Table 19.1. These controls apply some logic to these named elements when present in the control template. For example, when a progress bar control template has elements named ProgressBarIndicator and ProgressBarTrack, the control ensures that the width of ProgressBarIndicator remains the correct percentage of the width of ProgressBarTrack, based on the progress bar’s Value, Minimum, and Maximum properties. For a control template that wishes to look somewhat like a normal progress bar, taking advantage of this support greatly simplifies it (and removes the need for custom code for doing the math).

TABLE 19.2 Named Parts Used by Five of Silverlight’s Built-In Controls
TABLE 19.2 Named Parts Used by Five of Silverlight’s Built-In Controls

The AnimatedFrame Custom Control Code

With an understanding of how custom controls are defined and the ideas behind Visual State Manager’s parts and states model, let’s examine how AnimatedFrame is implemented. Listing 19.1 contains the code (not code-behind!) for AnimatedFrame. This code resides in the AdamNathanAppsLibrary DLL project that builds the DLL shared by almost every app

LISTING 19.1 AnimatedFrame.cs—The Implementation of the AnimatedFrame Custom Control

[code]

using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
namespace AdamNathanAppsLibrary
{
// Documentation of parts for tools like Expression Blend
[TemplatePart(Name=”OldContent”, Type=typeof(ContentPresenter))]
[TemplatePart(Name=”NewContent”, Type=typeof(ContentPresenter))]
[TemplatePart(Name=”ClientArea”, Type=typeof(FrameworkElement))]
// Documentation of states for tools like Expression Blend
[TemplateVisualState(Name=”GoingBack”, GroupName=”NavigationStates”)]
[TemplateVisualState(Name=”GoingForward”, GroupName=”NavigationStates”)]
[TemplateVisualState(Name=”NavigationDone”, GroupName=”NavigationStates”)]
public class AnimatedFrame : PhoneApplicationFrame
{
bool goingForward = true;
ContentPresenter oldContentPresenter;
ContentPresenter newContentPresenter;
VisualStateGroup stateGroup;
public AnimatedFrame()
{
// Apply a style with the target type of AnimatedFrame
// instead of PhoneApplicationFrame
this.DefaultStyleKey = typeof(AnimatedFrame);
this.Navigating += AnimatedFrame_Navigating;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Get the two named parts expected to be in the current control template
this.oldContentPresenter =
GetTemplateChild(“OldContent”) as ContentPresenter;
this.newContentPresenter =
GetTemplateChild(“NewContent”) as ContentPresenter;
// Remove the old handler if this isn’t the first template applied
if (this.stateGroup != null)
this.stateGroup.CurrentStateChanged -= VisualStateGroup_StateChanged;
// Retrieve the visual state group from the current control template,
// if it’s there
this.stateGroup = GetTemplateChild(“NavigationStates”) as VisualStateGroup;
// Add a handler for the CurrentStateChanged event
if (this.stateGroup != null)
this.stateGroup.CurrentStateChanged += VisualStateGroup_StateChanged;
// Force a call to OnContentChanged
if (this.Content != null)
OnContentChanged(null, this.Content);
}
// Changes the content and performs the state change
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
if (newContent == null || oldContent == null)
return;
// Assign the contents to the correct parts in the current visual tree
if (this.newContentPresenter != null)
this.newContentPresenter.Content = newContent;
if (this.oldContentPresenter != null)
this.oldContentPresenter.Content = oldContent;
// Do an instant switch to the GoingForward or GoingBack state
bool success = VisualStateManager.GoToState(this, /* control */
this.goingForward ? “GoingForward” : “GoingBack”, /* stateName */
false /* useTransitions */);
if (success)
{
// Now transition to the NavigationDone state
if (VisualStateManager.GoToState(this, /* control */
“NavigationDone”, /* stateName */
true /* useTransitions */))
return;
}
// One of the state changes was not successful
if (this.oldContentPresenter != null)
this.oldContentPresenter.Content = null;
}
void AnimatedFrame_Navigating(object sender, NavigatingCancelEventArgs e)
{
// Record whether we’re going forward or back
this.goingForward = (e.NavigationMode != NavigationMode.Back);
// Detach the old content
if (this.oldContentPresenter != null)
this.oldContentPresenter.Content = null;
}
// Removes the old content when the animation is done
void VisualStateGroup_StateChanged(object sender,
VisualStateChangedEventArgs e)
{
// Detach the old content when the transition to NavigationDone is done
if (e.NewState.Name == “NavigationDone” && this.oldContentPresenter != null)
this.oldContentPresenter.Content = null;
}
}
}

[/code]

Notes:

  • Although it has no impact on runtime behavior, a control should document all of its parts with TemplatePart attributes and all of its states with TemplateVisualState attributes. This information is consumed by tools such as Expression Blend to provide a design-time experience for parts and states.
  • This control expects two content presenters named OldContent and NewContent that represent the two pages involved in a navigation transition. The third part, ClientArea, is not used by this listing but is a requirement of the PhoneApplicationFrame base class.
  • This control defines three states in a group called NavigationStates. The control first transitions to GoingBack or GoingForward based on whether a back or forward navigation has occurred, and then it transitions to NavigationDone. As you’ll see in the next section, this gives control templates the opportunity to define any kind of animated transition they wish, and to distinguish between a backward animation and a forward animation.
  • The default style key is assigned so a default style can be given for the AnimatedFrame type.
  • To retrieve specially designated control parts, this listing overrides the OnApplyTemplate method inherited from FrameworkElement. This method is called any time a template is applied, so it gives you the opportunity to handle dynamic template changes gracefully. To retrieve the instances of elements inside the control template, GetTemplateChild is used. The null checks throughout this listing prevent unhandled exceptions from being thrown even if a control template is applied without elements named OldContent and NewContent.
  • Inside OnContentChanged, defined by the ContentControl base class, the old and new pieces of content are assigned to the appropriate content presenters and then the state changes are made. First, an instant state change is made to either GoingForward or GoingBack, based on the value captured in the frame’s Navigating event. Then, a state change is made to NavigationDone with transitions enabled. (Transitions are animations that can be defined by the control template.) Just as a control template may omit named parts, they may also omit states. The GoToState method returns false when the target state does not exist in the current control template.
  • Controls typically call GoToState in the following situations:
    • Inside OnApplyTemplate (with useTransitions=false
    • When the control first loads (with useTransitions=false)
    • Inside appropriate event handlers. In this example, there’s no ContentChanged event, but the OnContentChanged override is the sensible spot because the state change is designed to expose this situation.

There is no harm in calling GoToState when the destination state is the same as the current state. (When this is done, the call does nothing.)

Every state must have a unique name, even across different state groups!

Despite any partitioning into multiple state groups, a control must not have two states with the same name.This limitation can be surprising until you’ve implemented state transitions and realize that VisualStateManager’s GoToState method doesn’t have the concept of state groups. State groups are really just a documentation tool for understanding the behavior of a control’s states and the possible transitions. Therefore, it’s good to double-check the names of states used by your base classes, and to give your states very specific names (not names like Default or Normal that are likely already in use).

When a control loads, it should explicitly transition to the default state in every state group!

If a control does not explicitly transition to the default state(s), it introduces a subtle bug for consumers of the control. Before the initial transition for any state group, the control is not yet in any of those states.That means that the first transition to a non-default state will not invoke any transition from the default state that consumers may have defined. When you perform this initial transition, you should pass false for VisualStateManager.GoToState’s useTransitions parameter to make it happen instantaneously. AnimatedFrame does this, although it waits until the first call to OnContentChanged to perform that initial transition.

Do not call GoToState in a control’s constructor!

GoToState should not be called until all of a control’s visuals have been created and references to the parts obtained.Making the first call at the end of OnApplyTemplate is appropriate.

The AnimatedFrame Custom Control Default Style

Now we can look at the default style with a control template that leverages AnimatedFrame’s parts and states. First, let’s examine the outline of the control template, as it’s easy to get overwhelmed by the amount of XAML in the full listing. It is structured as follows:

[code]

<ControlTemplate TargetType=”local:AnimatedFrame”>
<Grid x:Name=”ClientArea”>
<!– The visual state groups –>
<VisualStateManager.VisualStateGroups>
<!– Only one group –>
<VisualStateGroup x:Name=”NavigationStates”>
<!– Specific (and optional) transitions between states –>
<VisualStateGroup.Transitions>
<!– Transition #1 –>
<VisualTransition From=”GoingForward” To=”NavigationDone”>
<Storyboard>…</Storyboard>
</VisualTransition>
<!– Transition #2 –>
<VisualTransition From=”GoingBack” To=”NavigationDone”>
<Storyboard>…</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<!– State #1 –>
<VisualState x:Name=”GoingForward”/>
<!– State #2 –>
<VisualState x:Name=”GoingBack”/>
<!– State #3 –>
<VisualState x:Name=”NavigationDone”/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!– The visual content –>
<ContentPresenter x:Name=”OldContent” …>

</ContentPresenter>
<ContentPresenter x:Name=”NewContent” …>

</ContentPresenter>
</Grid>
</ControlTemplate>

[/code]

To write a control template that takes advantage of states, you set the VisualStateManager.VisualStateGroups attachable property on the root element in the template’s visual tree to a collection of visual state groups, each of which has a collection of visual state children. In this case there’s only one group.

Each visual state can contain a storyboard that adjusts visual elements based on the state. These are often used like property setters in a style, “animating” a property to its final value with a duration of 0, and/or using discrete object animations to change the value of something that can’t be interpolated, such as an element’s visibility. You can do any sort of animation, however; even one that runs indefinitely while the control is in that specific state.

Each visual state group can also define a collection of visual transitions can automatically generate smooth animations when transitioning between states. Each VisualTransition object has To and From string properties that can be set to the names of the source and target states. You can omit both properties to make it apply to all transitions, specify only a To to make it apply to all transitions to that state, and so on. When transitioning from one state to another, the Visual State Manager chooses the most specific VisualTransition that matches the transition. The order of precedence is as follows:

  1. A VisualTransition with matching To and From
  2. A VisualTransition with a matching To and no explicit From
  3. A VisualTransition with a matching From and no explicit To
  4. The default VisualTransition, with no To or From specified

If VisualStateGroup’s Transitions property isn’t set, the default transition between any states is a zero-duration animation.

To specify the characteristics of a VisualTransition, you can set its GeneratedDuration property to control the duration of the generated linear animation. You can also set its GeneratedEasingFunction property to get a nonlinear animation between states. For the most customization, you can even set its Storyboard property to a Storyboard with arbitrary custom animations.

Listing 19.2 contains the complete default style for AnimatedFrame inside the project’s generic.xaml file.

LISTING 19.2 generic.xaml—The Generic Theme Dictionary Containing the Default Style for AnimatedFrame

[code]

<ResourceDictionary
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
xmlns:local=”clr-namespace:AdamNathanAppsLibrary”>
<!– AnimatedFrame default style –>
<Style TargetType=”local:AnimatedFrame”>
<Setter Property=”HorizontalContentAlignment” Value=”Stretch”/>
<Setter Property=”VerticalContentAlignment” Value=”Stretch”/>
<Setter Property=”Template”>
<Setter.Value>
<ControlTemplate TargetType=”local:AnimatedFrame”>
<Grid x:Name=”ClientArea”>
<!– The visual state groups –>
<VisualStateManager.VisualStateGroups>
<!– Only one group –>
<VisualStateGroup x:Name=”NavigationStates”>
<!– Specific (and optional) transitions between states –>
<VisualStateGroup.Transitions>
<!– Transition #1 –>
<VisualTransition From=”GoingForward” To=”NavigationDone”>
<Storyboard>
<!– Flip the old content (the current page)
out toward the screen –>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName=”OldContent”
Storyboard.TargetProperty=”(UIElement.Projection).(PlaneProjection.RotationY)”>
<LinearDoubleKeyFrame KeyTime=”0” Value=”0”/>
<EasingDoubleKeyFrame KeyTime=”0:0:.8” Value=”120”>
<EasingDoubleKeyFrame.EasingFunction>
<PowerEase Power=”5”/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<!– Hide the old content (the current page) –>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName=”OldContent”
Storyboard.TargetProperty=”Visibility”>
<DiscreteObjectKeyFrame KeyTime=”0” Value=”Visible”/>
<DiscreteObjectKeyFrame KeyTime=”0:0:.8”
Value=”Collapsed”/>
</ObjectAnimationUsingKeyFrames>
<!– Show the new content (the next page) –>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName=”NewContent”
Storyboard.TargetProperty=”Visibility”>
<DiscreteObjectKeyFrame KeyTime=”0” Value=”Collapsed”/>
<DiscreteObjectKeyFrame KeyTime=”0:0:.2” Value=”Visible”/>
</ObjectAnimationUsingKeyFrames>
<!– Flip the new content (the next page) in from behind –>
<DoubleAnimationUsingKeyFrames BeginTime=”0:0:.2”
Storyboard.TargetName=”NewContent”
Storyboard.TargetProperty=”(UIElement.Projection).(PlaneProjection.RotationY)”>
<LinearDoubleKeyFrame KeyTime=”0” Value=”-120”/>
<EasingDoubleKeyFrame KeyTime=”0:0:.6” Value=”0”>
<EasingDoubleKeyFrame.EasingFunction>
<PowerEase Power=”10”/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<!– Transition #2 –>
<VisualTransition From=”GoingBack” To=”NavigationDone”>
<Storyboard>
<!– Flip the old content (the current page)
out away from the screen –>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName=”OldContent”
Storyboard.TargetProperty=”(UIElement.Projection).(PlaneProjection.RotationY)”>
<LinearDoubleKeyFrame KeyTime=”0” Value=”0”/>
<EasingDoubleKeyFrame KeyTime=”0:0:.8” Value=”-120”>
<EasingDoubleKeyFrame.EasingFunction>
<PowerEase Power=”10”/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<!– Hide the old content (the current page) –>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName=”OldContent”
Storyboard.TargetProperty=”Visibility”>
<DiscreteObjectKeyFrame KeyTime=”0” Value=”Visible”/>
<DiscreteObjectKeyFrame KeyTime=”0:0:.8”
Value=”Collapsed”/>
</ObjectAnimationUsingKeyFrames>
<!– Show the new content (the previous page) –>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName=”NewContent”
Storyboard.TargetProperty=”Visibility”>
<DiscreteObjectKeyFrame KeyTime=”0” Value=”Collapsed”/>
<DiscreteObjectKeyFrame KeyTime=”0:0:.2” Value=”Visible”/>
</ObjectAnimationUsingKeyFrames>
<!– Flip the new content (the previous page) in
from in front of the screen –>
<DoubleAnimationUsingKeyFrames BeginTime=”0:0:.2”
Storyboard.TargetName=”NewContent”
Storyboard.TargetProperty=”(UIElement.Projection).(PlaneProjection.RotationY)”>
<LinearDoubleKeyFrame KeyTime=”0” Value=”120”/>
<EasingDoubleKeyFrame KeyTime=”0:0:.6” Value=”0”>
<EasingDoubleKeyFrame.EasingFunction>
<PowerEase Power=”10”/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<!– State #1 –>
<VisualState x:Name=”GoingForward”/>
<!– State #2 –>
<VisualState x:Name=”GoingBack”/>
<!– State #3 –>
<VisualState x:Name=”NavigationDone”/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!– The visual content –>
<ContentPresenter x:Name=”OldContent” CacheMode=”BitmapCache”
Visibility=”Collapsed”>
<ContentPresenter.Projection>
<PlaneProjection CenterOfRotationX=”0”/>
</ContentPresenter.Projection>
</ContentPresenter>
<ContentPresenter x:Name=”NewContent” CacheMode=”BitmapCache”>
<ContentPresenter.Projection>
<PlaneProjection CenterOfRotationX=”0”/>
</ContentPresenter.Projection>
</ContentPresenter>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!– PickerBox default style –>
<Style TargetType=”local:PickerBox”>

</Style>
</ResourceDictionary>

[/code]

Notes:

  • This control template opts for completely custom storyboards for the transitions from GoingForward to NavigationDone and GoingBack to NavigationDone, leaving all three states empty. This suppresses the usual automatically generated statechange animations (because there’s nothing defined in any state to interpolate), and is the best option here considering the complexity of the animations.
  • Each transition consists of four animations: one to flip out the old content, one to hide it when the flip is done, one to flip in the new content, and one to show it before the flip starts. The direction of the flip varies between the two transitions.
  • The actual content of the control template is simply the two content presenters, one on top of the other, inside a grid.
  • All default styles for custom controls in this assembly must be defined in this file, or in a resource dictionary merged into this file with resource dictionary’s MergedDictionaries property. Therefore, this file also contains the style for the PickerBox control.

Improving Rendering Performance with Bitmap Caching

Notice that both content presenters in Listing 19.2 are marked with CacheMode=”BitmapCache”. This is done to enable an important performance optimization that every Windows Phone developer should know about.

Vector graphics have a lot of advantages over bitmap-based graphics, but with those advantages come inherent scalability issues. In scenarios where there might be a rapid succession of redrawing, as with an animation, the cost of rendering can significantly impact the resulting user experience.

To use this feature, set the CacheMode property on any element you wish to cache. The type of the CacheMode property is the abstract CacheMode class, although BitmapCache is the only CacheMode subclass. Therefore, you can set it on a Grid as follows:

[code]

<Grid …>
<Grid.CacheMode>
<BitmapCache/>
</Grid.CacheMode>

</Grid>

[/code]

or, thanks to a type converter:

[code]

<Grid CacheMode=”BitmapCache”>

</Grid>

[/code]

When a cached element (including any of its children) is updated, BitmapCache automatically generates a new bitmap. This means that you never have to worry about a cached element becoming stale or losing its interactivity, but it also means that you should avoid caching a frequently changing element. Generating a new bitmap is an expensive operation. Fortunately, updates to any parents do not invalidate the cache, nor do updates to most of the element’s transforms or opacity.

Bitmap Caching and Animations

Only the following types of animations can run on the compositor thread, and only when the target element does not have a nonrectangular clip applied:

  • Translation
  • Rotation
  • Scaling (if the scale changes by less than 50%)
  • Plane projection
  • Opacity (if not using an opacity mask)

If the target of the animation isn’t already marked with BitmapCache, such an animation automatically creates a bitmap cache when the animation begins and discards it when the animation ends. Although this enables the improved performance of running on the compositor thread, this automatic caching can become costly if the animation runs repeatedly. Therefore, for the best results, relevant elements should be explicitly marked with BitmapCache.

That said, Silverlight for Windows Phone effectively marks some elements with BitmapCache automatically so you don’t have to:

  • ListBoxItem
  • ScrollViewer
  • MediaElement
  • Any element with a plane projection applied

Because bitmap caching applies to an element’s entire tree of subelements, the special treatment on ScrollViewer covers a lot of cases.

BitmapCache is most appropriate for static content that gets animated in one of the ways previously mentioned, to avoid creating a bottleneck in the rendering pipeline due to the CPU-bound work of repeated tessellation and rasterization on every frame. There is a tradeoff, however.The more you cache, the bigger the memory consumption will be on the GPU. For more detailed information about this and other performance considerations, as well as using performance debugging features such as redraw regions and cache visualizations, see http://bit.ly/perftips.

Rendering at a Custom Scale

BitmapCache has a RenderAtScale property (a double whose value is 1 by default) that can be used to customize the scale of an element when it is rendered to the cached bitmap.This property is especially interesting when you plan on changing the size of the element. If you zoom the element to a larger size, setting RenderAtScale to the final scale avoids a degraded result. Setting RenderAtScale to a smaller scale improves performance while sacrificing quality. For demonstration purposes, Figure 19.2 shows the use of RenderAtScale with four different values on an entire page, as follows:

[code]

<phone:PhoneApplicationPage …>
<phone:PhoneApplicationPage.CacheMode>
<BitmapCache RenderAtScale=”…”/>
</phone:PhoneApplicationPage.CacheMode>

</phone:PhoneApplicationPage>

[/code]

To set RenderAtScale, you must use property element syntax when setting CacheMode.Notice that the application bar is not affected in Figure 19.2, as it is not rendered by Silverlight.

FIGURE 19.2 Using RenderAtScale to reduce the resolution of the cached bitmap.
FIGURE 19.2 Using RenderAtScale to reduce the resolution of the cached bitmap.

The Finished Product

Animation Lab (Custom Controls & VSM)