Orientation Strategies for Windows Phone 7
June 16, 2010
Roscoe, N.Y.
I've recently been thinking about ways to deal with orientation changes in Windows Phone 7. (That's when the user turns the phone sideways from portrait mode to landscape mode or back again.) I've also been working on the way-overlong chapter on data binding in my forthcoming book Programming Windows Phone 7, and it occurred to me that I might deal with orientation using data binding and data conversion techniques.
The sample program I'll be discussing is called ColorScroll, and it goes way back. The first version was named COLORSCR and appeared in the May 1987 issue of Microsoft Systems Journal (which later became MSDN Magazine):
This first version of the program implemented a rather crude (and heavily arithmetic) version of dynamic layout. To quote the article, "Whenever you resize the COLORSCR window, the sizes of the child windows change proportionally. This is particularly interesting for the scroll bars. Depending on the dimensions of COLORSCR's window, the scroll bars can be long and thin or short and stubby.
By the time I adapted the program for the Windows Presentation Foundation in my book Applications = Code + Markup, the dynamic layout was handled in the environment, but for a few brief moments I was hoping to implement the entire program in XAML using data bindings. Simple, right? First, give each ScrollBar a name, like so:
<ScrollBar Name=redScrollBar ... />
Then define a binding to the appropriate property of the Color object of the SolidColorBrush used to display the resultant color:
<SolidColorBrush>
<SolidColorBrush.Color>
<Color R="{Binding ElementName=redScrollBar, Path=Value} ... />
</SolidColorBrush.Color>
</SolidColorBrush>
Well that won't work. As we all know, the targets of data bindings must be dependency objects, so let's instead give the Color object a name:
<Color x:Name="color" />
And now define a binding on the ScrollBar that flips the data back to the binding source:
<ScrollBar Value="{Binding ElementName=color, Path=R, Mode=OneWayToSource}" ... />
And the problem with that is the Color structure, which provides no notification mechanism so nobody knows when one of its properties is changed. To make this work, I had to supply some code in the form of an IMultiValueConverter class that converted the red, green, and blue values from the ScrollBar into a single Color or SolidColorBrush. Although the program couldn't be done entirely in XAML, it redeemed itself by becoming a great example for WPF multibinding!
In the Silverlight book that no publisher ever asked me to write, I wasn't able to use a multibinding because Silverlight doesn't support them. Instead, I created a chunk of code I sometimes think of as a "binding server" — a class (called RgbColor in this case) that implements INotifyPropertyChanged and which exists solely to be accessed through data bindings and assist XAML with some crunching. Here's the ColorScroll for Silverlight source code from that hypothetical book, or just play with the program right here:
This program uses two features new in Silverlight 4: implicit styles and the always handy StringFormat option on Binding. Notice I've switched from ScrollBar controls to Slider, primarily because the Slider works better with keyboard navigation in Silverlight and it doesn't get as wide as its container.
When I started porting this program to Silverlight for Windows Phone (which doesn't support either of those two new Silverlight 4 featues), I immediately ran into a problem that never existed before. With Windows Phone 7 we're dealing with a fixed-size screen, and I thought the Slider controls should be positioned at the top like this:
Now what happens when the user tips the phone sideways? You have a couple choices. If you leave the SupportedOrientations property of PhoneApplicationPage to its default value, then your display doesn't change:
This seemed unsatisfactory to me because the text labels and — worse — the hexadecimal values are now sideways. Surely I want to set SupportedOrientations to support both Portrait and Landscape modes. But now look what happens:
For the Silverlight program, changing phone orientation is just like changing a window size. All the controls and panels adjust to the new size. But that's not what I wanted. What I really wanted was for everything to stay in approximately the same place:
And so I asked myself? What do I need to do to make this happen?
Well, one solution is simply to have parallel page definitions for portrait and landscape. But that seemed to me not the best solution. I wanted a single chunk of markup for both configurations. So certain things have to happen. For example, the Slider controls need to change from a horizonal orientation to a vertical orientation when the MainPage changes from a portrait to a landscape mode. To me, this implied a data binding. I gave the MainPage a name of this (a common technique when doing ElementName bindings in WPF and which I'm sure will become popular in Silverlight):
<phoneNavigation:PhoneApplicationPage
...
Name="this">
The PhoneApplicationPage has a property named Orientation that indicates if the orientation of the phone is portrait or landscape, and which way it's tilted. I bound this property to the Orientation property of the Slider with a converter:
<Slider ...
Orientation="{Binding ElementName=this,
Path=Orientation,
Converter={StaticResource orientationConverter},
ConverterParameter=False}" />
The PageOrientationToOrientation class referenced by that StaticResource converts the Orientation property of the PhoneApplicationPage to the Orientation property of the Slider. Here's the Convert method:
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool isAligned = true;
if (parameter is string)
Boolean.TryParse(parameter as string, out isAligned);
bool isPortrait = ((PageOrientation)value & PageOrientation.Portrait) != 0;
return isAligned ^ isPortrait ? Orientation.Horizontal : Orientation.Vertical;
}
I figured the common case would be Portrait mode converted to Vertical orientation, so that's what happens by default. Use a converter parameter of "False" to switch it as in this program.
I also wanted to switch the direction of the sliders when the phone was in landscape mode so another converter handled that:
<Slider ...
IsDirectionReversed="{Binding ElementName=this,
Path=Orientation,
Converter={StaticResource isLandscapeConverter}}"
... />
Of course the elephant in the room is the Grid. When the phone changes orientation, I want the Grid to basically swap all its rows and columns. Whatever was a RowDefinition now becomes a ColumnDefinition, and vice versa, and whatever was a Grid.Row attached property becomes and Grid.Column attached property. For some simple grids, I've seen this done in code but I wanted a more automated general-purpose solution. (The less code in the code-behind, the happier I am.)
Someday, perhaps, I will write an alternative Grid that performs this job in a very elegant manner. Right now, I have a kludge. It's called OrientableGrid and it derives from Grid and adds a property named SwapRowsAndColumns and whenever that property changes, it performs a brute force swap. This new property is, of course, another target of a data binding:
<petzold:OrientableGrid
DataContext="{Binding Source={StaticResource rgbColor}}"
SwapRowsAndColumns="{Binding ElementName=this,
Path=Orientation,
Converter={StaticResource isLandscapeConverter}}">
I don't know if the approach I've shown here is the "correct" way to handle orientation changes on the phone. But it seems like the start of what could be a useful general-purpose technique for optimizing space no matter how the user wants to use the device. Here's the ColorScroll for Windows Phone source code.
Programming Windows Phone 7 Series
Free ebook to be published later this year. Preview excerpts available now.