This blog post is for WinRT XAML development on Windows 8. One of the key features of Windows 8 is its various orientation states for Windows Store apps. Basically, there are four states:

  • Full – used when the app is in full screen landscape orientation.

  • Filled – used when the user has another app snapped to one side of the screen.

  • Portrait – used when the app is rotated from landscape to portrait orientation.

  • Snapped – used when the app is snapped to one side of the screen. Snapped state is the smaller version of Filled state.


Usually, apps are designed to be viewed full screen in landscape orientation. The VisualStateManager class is used to help transition between different views. The VisualStateManager also enables us to create fully XAML solutions to handle orientation changes. You can, however, use code to detect and handle changes. A coded example is shown at the end.

Defining VisualStates


In this example, I will use the C1Calendar control and show how it can be modified for each of the different view states. Here I have defined a VisualStateGroup for my orientation states, and I’ve added a VisualState for each possible orientation.


<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled"/>
<VisualState x:Name="FullScreenPortrait"/>
<VisualState x:Name="Snapped"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>


Next we add a StoryBoard to each VisualState. The StoryBoards will include all of the animations needed to adjust the UI to the desired state. For example, when C1Calendar is displayed in Portrait orientation we may want to change the DayOfWeekFormat property to display shorter week names like “Wed” rather than “Wednesday.” The following XAML is a complete example with following explanation and screen captures.


<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<!--App Orientation States-->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Thickness>80</Thickness>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(C1Calendar.DayOfWeekFormat)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0" Value="ddd"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Thickness>100</Thickness>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(C1Calendar.DayOfWeekFormat)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0" Value="ddd"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(C1Calendar.ShowWeekNumbers)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0" Value="false"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(C1Calendar.Height)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0" Value="600"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(C1Calendar.VerticalAlignment)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0" Value="Top"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Thickness>10</Thickness>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(C1Calendar.ShowWeekNumbers)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0" Value="false"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(C1Calendar.DayOfWeekFormat)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0" Value="d"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(C1Calendar.Height)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0" Value="300"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(C1Calendar.VerticalAlignment)" Storyboard.TargetName="Calendar">
<DiscreteObjectKeyFrame KeyTime="0" Value="Top"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<c1:C1Calendar x:Name="Calendar" Margin="120" DayOfWeekFormat="dddd" MaxSelectionCount="21" ShowWeekNumbers="true" />
</Grid>

Full Landscape Orientation


Notice there is no StoryBoard defined for the Full state. This is because it’s our default UI that is defined in XAML. We have a margin set to 120, ShowWeekNumers is True, and our DayOfWeekFormat is “dddd” which is the fully spelled format.


Portrait Orientation


Portrait orientation is used when the user rotates the app from landscape to portrait. When in portrait orientation you should consider that users will naturally want to scroll up/down as opposed to left/right. In this example we set the ShowWeekNumbers property to False to give us more horizontal space, as well as abbreviate the week day names of the C1Calendar control. We make the margin 100 which is the conventional value for this orientation.



Also, since C1Calendar is best viewed as a square I have set an explicit height and VerticalAlignment. If I had more content in the app I would position it below the calendar.

Filled Landscape Orientation


Filled state is used when the user snaps another app, and the C1Calendar app is taking up the larger portion of the screen. It’s not quite as wide as Full, but not as vertically long as Portrait. In this state we reduce the margin of C1Calendar from 120 to 80, as well as change the DayOfWeekFormat to abbreviated names.



Snapped Orientation


Snapped orientation is used when the user snaps your app to the side of the screen. This state has the smallest screen real estate, so you will typically make the most drastic UI changes here. Like portrait, snapped view has more vertical space compared to horizontal so users will expect to scroll up/down. Here we set the margin of C1Calendar to 10. We also abbreviate week day names to one letter, hide the week numbers and specify an exact height.


Handling View State Changes


Implementing the StoryBoards in XAML is just the fun part. You also have to listen to the SizeChanged event of the current Window and apply the appropriate state. You can use the static VisualStateManager.GoToState method to do this. For example:


// register size changed event
Window.Current.SizeChanged += Current_SizeChanged;

void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
switch (ApplicationView.Value)
{
case ApplicationViewState.Filled:
VisualStateManager.GoToState(this, "Filled", false);
break;

case ApplicationViewState.FullScreenLandscape:
VisualStateManager.GoToState(this, "FullScreenLandscape", false);
break;

case ApplicationViewState.Snapped:
VisualStateManager.GoToState(this, "Snapped", false);
break;

case ApplicationViewState.FullScreenPortrait:
VisualStateManager.GoToState(this, "FullScreenPortrait", false);
break;

default:
return;
}
}


That’s it for a XAML-based approach to handling orientation changes. You can also, of course, just use the code-behind only and do your UI changes there. The downside is that you have to consider putting things back the way they were in the default UI (full landscape orientation), which is something automatically handled with the VisualStateGroup approach.

You can download the sample used above, and the C1Calendar control which is part of ComponentOne Studio WinRT Edition.