Sample: Windows Phone 7 Example Application with Landscape Layout

I’ve had a lot of fun writing Windows Phone 7 applications and for this post I wanted to share some notes and sample source code to help you get started as well.  The latest build of the Windows Phone 7 tools now support Visual Studio 2010 in addition to the free Express SKU.  You can find everything you need for development here.

One of my favorite applications for my phone is a tip calculator which makes it easy to figure out your bill in a restaurant or for a cab ride.  This sample implements the following simple tip calculator:

image

The application has a built-in numeric key pad for entering the bill amount and controls which allow for adjustment of both the tip percentage as well as rounding the total bill to an even number.

Layout

Visual Studio makes it very easy to create an application including styling that matches the ‘Metro’ theme as explained in the Design and Interaction Guide.  The application is arranged in 3 pieces using grids:

image

Each grid is then placed in its corresponding row using the Grid.Row property.

Landscape Mode

When the user rotates the phone we want the layout to change to match the new dimensions.  This is also fairly straight forward to implement.  The first step is to have an additional column in the LayoutRoot which can accommodate the totals data:

image

When the phone is rotated into landscape mode, we will move the TotalsGrid into the second column adjacent to the NumbersGrid.  There are a few things required to make this work.  First LayoutRoot must needs to have 3 rows and 2 columns where content can be placed:

    1: <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneBackgroundBrush}">
    2:     <Grid.RowDefinitions>
    3:         <RowDefinition Height="Auto"/>
    4:         <RowDefinition Height="Auto"/>
    5:         <RowDefinition Height="*"/>
    6:     </Grid.RowDefinitions>
    7:     <Grid.ColumnDefinitions>
    8:         <ColumnDefinition Width="Auto" />
    9:         <ColumnDefinition Width="0" x:Name="LandscapeColumn" />
   10:     </Grid.ColumnDefinitions>

The first two RowDefinition elements have their Height set to “Auto” which means they will consume only as much room as the content they contain.  The final row will take up the open space.  The first column will also use “Auto” for its width.  The second column, LandscapeColumn, is given a width of “0” and will not be used in Portrait mode.  Each grid is placed in its own row by default.

To support landscape and portrait mode, the application must indicate it supports the setting and it must handle the change in orientation event as follows:

    1: SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;
    2:  
    3: this.OrientationChanged += new EventHandler<OrientationChangedEventArgs>(MainPage_OrientationChanged);

When the orientation changes we’ll first move the TotalsGrid into the second column adjacent to the NumbersGrid, then change the column width attributes accordingly:

    1: void MainPage_OrientationChanged(object sender, OrientationChangedEventArgs e)
    2: {
    3:     // In landscape mode, the totals grid is moved to the right on the screen
    4:     // which puts it in row 1, column 1.
    5:     if ((e.Orientation & PageOrientation.Landscape) != 0)
    6:     {
    7:         LandscapeColumn.Width = GridLength.Auto;
    8:         Grid.SetRow(TotalsGrid, 1);
    9:         Grid.SetColumn(TotalsGrid, 1);
   10:  
   11:         LayoutRoot.ColumnDefinitions[1].Width = GridLength.Auto;
   12:     }
   13:     // In portrait mode, the totals grid goes below the number pad at the
   14:     // bottom of the screen which is row 0, column 2.
   15:     else
   16:     {
   17:         LandscapeColumn.Width = new GridLength(0);
   18:         Grid.SetRow(TotalsGrid, 2);
   19:         Grid.SetColumn(TotalsGrid, 0);
   20:  
   21:         LayoutRoot.ColumnDefinitions[1].Width = new GridLength(0);
   22:     }
   23: }

The resulting display then looks like the following:

image

Button Handlers

The numeric button grid is created in xaml by placing all items into a 3 x 4 grid.  Each numeric button is given a name “button[x]” which corresponds to its value.  When a button is pressed, we want to add that new digit to the bill amount.   We could go through and add a handler manually using the IDE, but we can do a lot better by taking advantage of the C# anonymous delegates feature:

    1: private void InitButtonHandlers()
    2: {
    3:     for (int i = 0; i <= 9; i++)
    4:     {
    5:         string btnName = "button" + i.ToString();
    6:         System.Windows.Controls.Button btn = 
    7:             (System.Windows.Controls.Button)this.LayoutRoot.FindName(btnName);
    8:  
    9:         int j = i; // avoid local variable capture
   10:         btn.Click += new RoutedEventHandler(
   11:             (object sender, RoutedEventArgs e) => { TryUpdateBillAmount(textBlockBill.Text + j.ToString()); }
   12:         );
   13:     }
   14: }

The loop will find each button using its name and then add a new click handler.  The following is the most interesting piece of this code:

image

The code is defining a button handler for each button instance right in the loop.  The method definition starts with the argument list for a RoutedEventHandler:

(object sender, RoutedEventArgs e)

The lambda syntax, =>, then leads us to the method implementation:

{ TryUpdateBillAmount(textBlockBill.Text + j.ToString()); }

In each case the code takes the current view string value, appends the new digit to the end, and then invokes the TryUpdateBillAmount method which will decide if the update is valid.

The last interesting thing to examine is this line of code:

int j = i; // avoid local variable capture

The local variable “i” is considered an “outer” variable when it gets captured.  This means “i” gets allocated on the heap rather than on the stack and will not contain the per loop value (you will always get the value “10” in this example if you use “i” in the delegate).  In order to get around this gotcha, we declare the local variable “j” in the loop and capture the current value.  (Many thanks to Scott Wiltamuth, co-designer of the C# language specification, for helping me figure this one out!)

Debugging With the Emulator

Debugging is very simple, just press F5!  By default the emulator is your target:

image

As devices become available, you will be able to simply change this value to target your device connected to your computer through USB:

image

You can now test the application easily using the normal Visual Studio debugger and emulator controls.  For example, to test landscape mode simply select the direction you want to rotate the device:

image 

Summary

You can find the full source code for the example here.  The techniques used here are generic Silverlight and C# features which just helps demonstrate how your skill set can easily translate into new form factors like the Phone. 

Enjoy!