This article is Day #2 in a series called 31 Days of Windows 8. Each of the articles in this series will be published for both HTML5/JS and XAML/C#. You can find additional resources, downloads, and source code on our website.
Today we are going to talk about screen sizes, and why they are important to Windows 8 development. In the first part of this article, we will discuss orientation, and some simple ways we can make our application more useful based on the way our user holds their device. In the second part, we’re going to look at our application in a “snapped” state, and how we might change our interface to accommodate a much smaller screen size.
Orientation and snap are important because if you don’t consider them in your app, your app won’t be approved for the Windows Store.
If you look in the Windows 8 app certification requirements, in section 3.6, it reads:
Your app must support a snapped layout. In landscape orientation, your app’s functions must be fully accessible when the app’s display size is 1024 x 768. Your app must remain functional when the customer snaps and unsnaps the app.
What this says is that our application already needs to support three visual states, at a minimum:
- 1024 x 768 (minimum screen resolution & filled state)
- 320 x 768 (snapped)
- Your default resolution that you are planning for, generally 1366 x 768.
Here’s an example of a full screen application moving to a snapped state:
You can see that in this case, we have re-arranged our content to fill the smaller snapped state. There is also the opportunity to move your application to a “filled” state, which is represented by the light gray block to the right of our snapped view.
Thankfully, there are some simple ways to recognize which state our application is in, and the rest of this article will be dedicated to showing exactly how this is done.
Supporting Rotation
To get started let’s create our almost famous app from the blank template in Visual Studio 2012. Once you’ve created it, run your app in the simulator or on a remote machine (more on that later). You will see your awesome blank app and if you rotate it, the app will in fact automatically rotate as well. Why, How?
By default, all templates in Visual Studio are setup to support all rotations. Remember that package.appxmanifest file? In the Application UI tab you will find a section called Supported Rotations. By checking one or more orientation preference you are selecting which orientations your app will support. Again by default we support all.
Depending on your situation, this may be something of value to your apps overall experience. For example, if you’re building a game you might only want to support landscape mode.
Recognizing Orientation Change
Our first step in working with orientation is making sure that we can recognize when an orientation change has happened. Thankfully, the Windows 8 SDK has provided us with the SimpleOrientationSensor that has events for this exact purpose. For this example, we have again started with only a Blank App template in Visual Studio 2012.
First, we added a simple TextBox control to our MainPage.xaml file. Here is the entire set of code for that file currently:
<Page
x:Class="Day2_OrientationAndSnap.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Day2_OrientationAndSnap"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="No orientation reading."
x:Name="AlertBox"
FontSize="50"
TextAlignment="Center"
Margin="0,100,0,0" />
</Grid>
</Page>
Now, open the MainPage.xaml.cs file. We need to add some code in order to use this sensor.
First, we’ll add a new using statement: using Windows.Devices.Sensors;. Next, we will add a new instance of the SimpleOrientationSensor class, and add some event handlers to be able to actively recognize orientation change. Here’s the entirety of the code from my MainPage.xaml.cs file. We will explain the code afterwards. There are a few pieces of the C# code above that I should explain.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Devices.Sensors;
namespace Day2_OrientationAndSnap
{
public sealed partial class MainPage : Page
{
private SimpleOrientationSensor orientationSensor;
public MainPage()
{
this.InitializeComponent();
orientationSensor = SimpleOrientationSensor.GetDefault();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (orientationSensor != null)
orientationSensor.OrientationChanged += new TypedEventHandler<SimpleOrientationSensor, SimpleOrientationSensorOrientationChangedEventArgs>(orientationSensor_OrientationChanged);
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
if (orientationSensor != null)
orientationSensor.OrientationChanged -= orientationSensor_OrientationChanged;
base.OnNavigatingFrom(e);
}
async private void orientationSensor_OrientationChanged(SimpleOrientationSensor sender, SimpleOrientationSensorOrientationChangedEventArgs args)
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
ShowOrientationText(args.Orientation);
});
}
private void ShowOrientationText(SimpleOrientation simpleOrientation)
{
switch (simpleOrientation)
{
case SimpleOrientation.NotRotated:
AlertBox.Text = "Not Rotated";
break;
case SimpleOrientation.Rotated90DegreesCounterclockwise:
AlertBox.Text = "90 Degrees CounterClockwise";
break;
case SimpleOrientation.Rotated180DegreesCounterclockwise:
AlertBox.Text = "180 Degrees Rotated";
break;
case SimpleOrientation.Rotated270DegreesCounterclockwise:
AlertBox.Text = "270 Degrees Rotated CounterClockwise";
break;
case SimpleOrientation.Facedown:
AlertBox.Text = "Face Down";
break;
case SimpleOrientation.Faceup:
AlertBox.Text = "Face Up";
break;
default:
AlertBox.Text = "Unknown";
break;
}
}
}
}
First, we created a new SimpleOrientationSensor object, named orientationSensor. In the constructor method, MainPage(), we instantiate that object with the default orientation sensor on the device.
In the OnNavigatedTo() and OnNavigatingFrom() event handlers, we add and remove an OrientationChanged event to/from our new object. It is important to make sure that the object is not null, because on devices that don’t have this sensor, we will get an error.
Next, we have a new event handler named orientationSensor_OrientationChanged(). You should note that it is decorated with an async descriptor, and uses the await keyword when taking its action. This is ultimately done to avoid bottlenecks in our code that would otherwise hold up the application. (You can read more about async/await on MSDN.)
Once the data has been obtained, we call a new method, ShowOrientationText(), passing the Orientation data along.
Finally, ShowOrientationText() does a simple switch statement against all of the possible orientations that can exist: NotRotated, Rotated90DegreesCounterclockwise, Rotated180DegreesCounterclockwise, Rotated270DegreesCounterclockwise, Facedown, Faceup, or Unknown. I think it’s funny that they named one of them Rotated180DegreesCounterclockwise, as 180 degrees shouldn’t matter which direction they went.
Here’s a quick look at the application in its current state:
Remote Debugging
Now, if you’re anything like Clark and I, you like writing your code on a beefy quad-core desktop machine, maybe 8-12 GB of RAM, dual 27” monitors, mouse, keyboard, the whole 9 yards. Unfortunately, these machines are unlikely to have an orientation sensor, and picking up your monitor to change the orientation just isn’t going to work. In addition the SimpleOrientationSensor we’re using is not emulated by the Simulator, so we’ll need an actual device to make this happen.
Another important lesson we learned: It’s a simulator, not an emulator like Windows Phone. This means that it will only simulate the machine you’re currently working on, not act as a completely different, fully capable device. No orientation sensor? You need another device.
Thankfully, Microsoft has provided a way for us to make this happen on a remote secondary device, much like we do when building Windows Phone applications. Here’s the short story on how it works (MSDN has the longer, more thorough story):
Install the Remote Debugging Tools on the secondary device. I’m using a Samsung Series 7 Slate, but any Windows 8 device in a tablet form factor should suffice. You can download the Remote Debugging Tools here. Make sure you choose the appropriate flavor, x86, x64, or ARM, depending on your device.
Run the Remote Debugging Tools on the secondary device. You’ll see an icon that looks like this:
Once the Remote Debugger is running on your secondary device, go back to your primary machine and select “Remote Machine” as your target for deployment.
When you choose “Remote Machine” for the first time, you will be presented with a dialog box that looks like the image below. Remember, devices on your subnet will only appear if the Remote Debugger Tools have been installed and are currently running.
Later, when you want to switch devices, you’re going to struggle to find where this option is stored. I’m here, my dear readers, to save you that hassle. Open up your project properties (Alt + Enter), and choose the Debug tab. From there, you can change or remove your previous choice. If you remove the choice, the next time you choose remote debugging, you’ll get the dialog box from earlier.
Back to coding…
OK, so at this point, we have an application that recognizes that device orientation has changed. That’s all well and good for when we want to do something specific in code with orientation, but what if we just want to have our application re-orient itself to be readable/useable to our user? There’s a much easier way, thanks to VisualStateManager.
Go back to your project, and right-click to the get Add…New Item… dialog.
Add a new Basic Page, we named ours OrientationPage.xaml. If you take a look at the XAML for this page, you’ll discover that we’re working with an entirely different page object right out of the box. This is a LayoutAwarePage, and by default it already has enough structure to orient itself appropriately, and even provides a VisualState for the snapped view as well.
In addition, by using this type of page, the simulator also respects orientation changes.
This means that just by using this type of page, we automatically get an orientation and snap-aware page that has a static set of visual states that we can manipulate to make our page do what we’d like. To make this more obvious, I am going to modify my visual states so that each one has a different background color.
Here’s a look at the modified VisualState values from our OrientationPage.xaml:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="layoutRoot" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="Purple"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Filled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="layoutRoot" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="Orange"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="layoutRoot" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="DarkGreen"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitBackButtonStyle}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="layoutRoot" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="Blue"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedBackButtonStyle}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageTitle" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedPageHeaderTextStyle}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
As you can see, we’ve simply added a new node to each section that modifies the Background property of my layoutRoot Grid. What this new LayoutAwarePage allows us to do, instead of writing tons of code to manage our orientation, is to worry about the stuff that matters: our styling. By writing seperate styles for our page elements based on the visual state of our page, we simplify the entire process.
Summary
Today, we took a look at how we can determine the orientation of a user’s device, as well as how to use the new LayoutAwarePage item to manage the different visual states our app might encounter. There are tons of great examples on the web related to orientation and snap, but if there is ONE lesson you take away from today’s article, it’s this:
Your application MUST acknowledge the snapped state. Make sure you accomodate it.
To download the entire sample solution from this article, you can download it here:
Tomorrow, we’re going to look at the Splash Screen. It’s a valuable tool for loading your application, as well as monetizing it. We’ll dive head first into that tomorrow. See you then!
Leave a Reply