Windows Phone 7 supports multiple screen orientations, and the built-in sensors allow the system to adjust the orientation automatically when the user rotates or tilts the device, without manual interaction. For developers, supporting these orientations and orientation changes seems pretty simple at first, but on closer inspection more and more issues need to be resolved and planned ahead.
Supporting Multiple Orientations
A lot of applications or parts of applications will not benefit from supporting multiple orientations. Sometimes it even wouldn't make any sense to support orientation changes – this often is the case in games, when a gameplay concept is tailored to a certain screen format or supporting different formats would be way too expensive. However, in some situations it adds quite some value to the application and improves user experience. Consider a page that requires simple text entering functionality:
If your application offers to use this page in landscape mode, your users will immediately benefit from the option to use a larger on-screen keyboard (SIP), which makes entering text much easier and less error-prone:
This concept is implemented in a lot of the built-in applications already, for example in the messaging hub.
Other scenarios where this makes sense is when you need to present certain content like documents to the user. You can see an example for this in the built-in web browser:
Switching to landscape mode makes reading the content much more convenient (at the cost of reduced vertical content visibility), and shows more of the current location in the address bar (Mango only):
Considerations
All devices on the market today have a native screen resolution of 480x800 pixels. That is, when you look at the phone in portrait mode. When you switch to landscape mode, the horizontal and vertical dimensions obviously change places, and now you're working with a resolution of 800x480. The first conclusion drawn from this is that if you want to support screen orientation changes and multiple orientations, you cannot work with fixed layouts. A lot of developers simply assume a fixed resolution of 480x800 and set the sizes of the used elements on a page to fixed values in relation to that resolution, because this makes the overall layout design easier and probably even has a small performance advantage. With the support for different orientations also comes the need to work with dynamic layouts or relative sizes instead of absolute positioning – at least if you want to avoid to change every single value of your elements manually when an orientation change happens. Make sure that you take this into consideration early on in the development process, to avoid large adjustments and restructuring later.
The second thing that needs some thinking up front is the way your content should work with different orientations. The above screenshots of Internet Explorer actually show an interesting approach to this; you can see that the browser keeps the general layout of the displayed document exactly the same in both orientations – the only difference is that the content was enlarged. What spanned 480 pixels before now has a width of 800 pixels, which means nothing but scaling the whole document up proportionally - this is what ultimately improves readability. However, this is a decision that has been made very consciously, and it's not the only thinkable possibility. If you look at other places of the phone (again, for example, the messaging), you can see that an alternative would be to keep the text size as it is and do a reflow of the layout. The displayed page then would look pretty different. The benefit of improved readability would be gone, but much more of the content would be visible at the same time.
A general recommendation for this cannot be made – it really depends on your particular situation and application content, and as such, needs some thinking and planning before you start implementing this feature.
Orientations
The supported orientations on the phone are:
- Portrait (Up)
- Landscape Left
- Landscape Right
Portrait mode is the orientation when the phone is "standing upright", which means the height is larger than the width (480x800). Landscape modes are the horizontally aligned counterparts where the resolution is 800x480. The phone differentiates between the two possible landscape orientations. "Left" in that context means that if you look at the screen, the status bar is at the left side of the phone. "Right" consequently is the orientation where the status bar is at the right side. It's noteworthy that the enumeration for the device orientation actually differentiates between Portrait Up and Down modes, however the "Down" value is never used. No orientation change happens when you put the phone in that position.
You can specify the supported orientation on a page level. This allows you to make individual pages support multiple orientations, but not others, or to support different orientations on different pages. The value can be specified in code or as an attribute of the PhoneApplicationPage in XAML:
1: SupportedOrientations="PortraitOrLandscape"
The possible values for this property are:
- Portrait
- Landscape
- PortraitOrLandscape
As you can see, there is no way to specify that a page supports only Landscape Left or only Landscape Right. You have the possibility to support both or no landscape mode at all.
To detect changes of orientations that are triggered either by physically tilting the phone, or because the supported orientations value forces the phone into a certain orientation, you can use either the OrientationChanged event of the PhoneApplicationPage class, or override the OnOrientationChanged method if you're writing code in your page class. The event arguments provide information about the new orientation. That page orientation value (or the same value you can obtain from the Orientation property of the page) allows to check for bit flags, because its internal values are:
1: public enum PageOrientation
2: {
3: None = 0,
4: Portrait = 1,
5: Landscape = 2,
6: PortraitUp = 5,
7: PortraitDown = 9,
8: LandscapeLeft = 18,
9: LandscapeRight = 34,
10: }
So, instead of checking for e.g. both "LandscapeLeft" and "LandscapeRight", you can simply check for the "Landscape" bit:
1: if (orientation == PageOrientation.LandscapeLeft ||
2: orientation == PageOrientation.LandscapeRight)
3: {
4: ...
5: }
6:
7: // ... is the same as doing:
8: if ((orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
9: {
10: ...
11: }
The Emulator
The emulator has two buttons in its toolbar that allow you to rotate the virtual device. This rotation also triggers the orientation changes of the phone and the respective events in your applications, and hence allows you to test different orientations:
One obstacle you may run into is the following: you probably know that you can use your computer's keyboard for text input in the emulator instead of the need to click on the on-screen keyboard with your mouse. This is done by either using the Page Up/Down keys to show or hide the SIP, or toggled by using the Pause key. The problem is that when the computer's keyboard input is used and routed to the emulator, it stops processing orientation changes. So please remember that if you want to test different screen orientations and whether your application handles changes correctly, make sure that you do not use this input feature, or you may find yourself pulling your hair out asking why your code is not working.
Two General Approaches
Now that you know both how to specify the supported orientations and how to detect orientation changes, how do you make optimal use of this? When you search for available samples and recommendations on the topic, you will find two recurring patterns for handling orientation changes (for example on MSDN or in the App Hub education section). In the below parts I will discuss both of them.
The Scrolling Technique
This first approach is the simpler one, because ideally it doesn't require working with the OrientationChanged event mentioned above at all. The idea behind this is to create a dynamic layout for your application page that doesn't need to be touched when the orientation changes. This can be achieved by two things:
- Avoid fixed sizes of elements and instead make use of variable widths, and layout features of panels like star/auto sizing. This handles the horizontal sizing issue.
- Add scroll viewers for containers like the stack panel, or use elements that come with built-in scrolling capabilities. This accommodates for changes in the available vertical size.
Often this works best when you show lists of elements one after another on your page, or if you are using controls that inherently work with lists of elements like the ListBox. Consider the following code, for example:
1: <StackPanel x:Name="ContentPanel"
2: Grid.Row="1"
3: Margin="12,0,12,0">
4: <TextBlock x:Name="CurrentOrientation"
5: Text="Current Orientation: Portrait" />
6: <Button Content="Do something" />
7: <CheckBox Content="Option A" />
8: <ListBox x:Name="MyList">
9: <ListBox.ItemTemplate>
10: <DataTemplate>
11: <Grid>
12: <Grid.ColumnDefinitions>
13: <ColumnDefinition Width="Auto" />
14: <ColumnDefinition Width="*" />
15: </Grid.ColumnDefinitions>
16: <Ellipse Fill="Blue"
17: Width="50"
18: Height="50"
19: Margin="0 5 10 5"
20: VerticalAlignment="Top" />
21: <TextBlock Grid.Column="1"
22: Text="{Binding}"
23: TextWrapping="Wrap"/>
24: </Grid>
25: </DataTemplate>
26: </ListBox.ItemTemplate>
27: </ListBox>
28: </StackPanel>
As you can see, the item template of the list box uses a grid with auto/star sized columns. This ensures that the available horizontal space is optimally used for both portrait and landscape mode. And since the list box has a built-in scroll viewer that handles content which is too big for a single screen, we don't have to worry about the difference in the available vertical size for both modes either. When we run this code on a page with both portrait and landscape mode supported, we get something like this (I simply fed the list box with a list of "Lorem ipsum" strings from code behind):
If we change the screen orientation to landscape mode in the emulator, we see this:
The layout automatically adjusted to the changed resolution and makes use of the wider display. However, the larger width cannot fully compensate for the reduced vertical resolution, and we now need to scroll the list box to see all the "Lorem ipsum" items.
The Grid Layout Technique
When you look at the above example, you may get the hint that often the simple solution of combining dynamic sizing with scroll viewers does not result in the most pleasant experience for the viewer's eyes. Stretched buttons and especially those lathy list box items are not very nice to look at. In addition, the vertical size that remains for the list box makes scrolling through the list more difficult and confusing.
More complex approaches that try to reach the optimal result therefore often work with a more radical change of the layout when the screen orientation changes. Here the idea is to use more sophisticated layout containers like grids and then dynamically move around the objects to different cells to make best use of the available screen estate. The drawback of this is that his can hardly be automated. You need to handle the available events and determine what to do manually. The following XAML shows the changed layout for our above sample:
1: <Grid x:Name="ContentPanel"
2: Grid.Row="1"
3: Margin="12,0,12,0">
4: <Grid.ColumnDefinitions>
5: <ColumnDefinition Width="Auto" />
6: <ColumnDefinition Width="*" />
7: </Grid.ColumnDefinitions>
8: <Grid.RowDefinitions>
9: <RowDefinition Height="Auto" />
10: <RowDefinition Height="*" />
11: </Grid.RowDefinitions>
12:
13: <StackPanel x:Name="ControlsPanel"
14: Grid.Column="1"
15: Margin="0 0 20 0">
16: <TextBlock x:Name="CurrentOrientation"
17: Text="Current Orientation: Portrait" />
18: <Button Content="Do something" />
19: <CheckBox Content="Option A" />
20: </StackPanel>
21:
22: <Grid x:Name="ListGrid"
23: Grid.Row="1"
24: Grid.Column="1">
25: <ListBox x:Name="MyList">
26: <ListBox.ItemTemplate>
27: <DataTemplate>
28: <Grid>
29: <Grid.ColumnDefinitions>
30: <ColumnDefinition Width="Auto" />
31: <ColumnDefinition Width="*" />
32: </Grid.ColumnDefinitions>
33: <Ellipse Fill="Blue"
34: Width="50"
35: Height="50"
36: Margin="0 5 10 5"
37: VerticalAlignment="Top" />
38: <TextBlock Grid.Column="1"
39: Text="{Binding}"
40: TextWrapping="Wrap" />
41: </Grid>
42: </DataTemplate>
43: </ListBox.ItemTemplate>
44: </ListBox>
45: </Grid>
46: </Grid>
The basic setup now is a lot more complex. We are using a grid instead of a stack panel here, and that grid has two columns and two rows set up, even though we only use one of the defined columns at the moment (the star-sized one). This is the default setup for portrait mode and results in the same look that we had above. The difference is that we now have code that handles screen orientation changes; this should throw some light on the seemingly convoluted grid setup:
1: protected override void OnOrientationChanged(OrientationChangedEventArgs e)
2: {
3: // change the layout, based on the current orientation
4: if (e.Orientation == PageOrientation.PortraitUp)
5: {
6: // move the controls to the star-sized column
7: // and to the row above the list
8: Grid.SetColumn(ControlsPanel, 1);
9: Grid.SetRow(ControlsPanel, 0);
10: }
11: else
12: {
13: // for landscape mode, move the controls to the first column
14: // and to the star-sized row
15: Grid.SetColumn(ControlsPanel, 0);
16: Grid.SetRow(ControlsPanel, 1);
17: }
18: }
The following schematic view of the grid shows a bit better what we are doing here:
In portrait mode, both parts of the layout (the controls and the list box) are located in the star-sized column, one above the other. When we detect an orientation change to landscape mode, the controls are moved to the second row, and to the first column of the grid, which effectively turns the vertical layout we had in portrait mode into a horizontal layout that perfectly fits the landscape setup. This pattern (with an equal number of auto and star-sized columns and rows in a grid) works for most scenarios where you can split the page content into two parts that can be arranged either vertically or side by side, and can be adopted easily for your needs.
Here is what the final result for landscape mode looks like in the emulator (as mentioned portrait mode looks exactly the same as before):
Even though there's hardly more of the items visible compared to the previous solution, the result is actually much nicer for the list box handling, because now the user has more screen space available for the gesture-driven scrolling. But this solution also was much harder to set up. With some effort and the help of behaviors and/or attached properties you could make this a bit more generic and probably get rid of the code behind, but still you will often find yourself fine-tuning the result a lot until you're completely satisfied. Since the aspect ratio of the phone screen is not 2:1, your two blocks of content have less horizontal space available when arranged horizontally (800/2=400 instead of 480), which can make a huge difference for the layout. Also, all of a sudden you need to think about and adjust additional margin values to cleanly separate the content blocks in landscape mode – so, eventually this can become pretty detailed and involve manipulating a lot of properties.
Another drawback of this solution is the missing design-time support in Blend, for example. However, sometimes re-arranging the elements on your page in this sophisticated way is the only possibility to achieve a sufficiently good result to support different screen orientations.
Conclusion
I hope this article helped you understand the basic concepts of screen orientations on the phone, and the additional questions and problems that surface when you start implementing support for orientation changes. Based on the techniques shown here you can start working on more sophisticated techniques that couldn't be covered in the article, for example using visual states for the different orientations. You can download the sample code of the two demo projects shown above here:
Download source code
If you have any questions, feel free to contact me or post a comment below.