My previous two blog posts presented a pair of universal apps named Contoso Cookbook and MyComix Reader. Universal apps are apps that run on Windows and Windows Phone and, in the future, on other devices such as Xbox Ones. When you create a universal app in Visual Studio, the resulting solution contains three projects: a Windows project, a Windows Phone project, and a shared project containing code and resources shared by the other two projects. Typically, the shared project holds source-code files containing components used in the other projects, images and other shared assets, and a shared App.xaml file (along with App.xaml.cs). The XAML files representing views (as well as the code-behind files accompanying the XAML files) are typically local to the Windows and Windows Phone project, enabling you to skin the app differently for different form factors.
Can XAML files representing views be placed in the shared project so they can be used on Windows and Windows Phone? Of course! Sometimes it makes a lot of sense to use the same view in both projects, especially if the UX presented on each platform doesn’t rely on controls that are unique to the platform. Case in point: the Unilife app pictured below. It’s a Windows/Windows Phone adaptation of John Conway’s game of life, in which you draw patterns of cells and then evolve those cells from one generation to the next using a simple set of genetic rules. A cell dies if it has too many or too few neighbors, and a dead comes alive in the next generation if it has just the right number of neighbors. The same logic drives both versions of the app. And the same XAML file, located in the shared project, defines the UX for both the Windows version and the Windows Phone version.
You can download a zip file containing the solution from my OneDrive. Open the solution in Visual Studio 2013 (you’ll need to install Update 2 if you haven’t already) and you’ll see that the app’s one page, MainPage.xaml, is located in the shared project, and that the Windows and Windows Phone projects contain no XAML:
MainPage.xaml includes little more than an empty Grid and a CommandBar:
- <Grid x:Name="CellGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
- </Grid>
- <Page.BottomAppBar>
- <CommandBar x:Name="MainAppBar">
- <AppBarButton x:Name="StartButton" Icon="Play" Label="Start" Click="StartSimulation" />
- <AppBarButton x:Name="StopButton" Icon="Pause" Label="Stop" Click="StopSimulation" IsEnabled="False" />
- <AppBarButton x:Name="StepButton" Icon="Next" Label="Step" Click="StepSimulation" />
- <AppBarButton x:Name="ClearButton" Icon="Delete" Label="Clear" Click="ClearCellGrid" />
- </CommandBar>
- </Page.BottomAppBar>
At run-time, code in MainPage.xaml.cs measures the available screen space by reading the Grid’s ActualWidth and ActualHeight properties and programmatically adds rows and columns to the Grid. Then it creates XAML Rectangles representing cells – one per Grid cell – and inserts them into the Grid:
- // Measure the available screen space
- var width = CellGrid.ActualWidth;
- var height = CellGrid.ActualHeight;
- // Compute number of rows and columns
- _cols = (int)(width / (_cellWidth + 2 * _margin));
- _rows = (int)(height / (_cellHeight + 2 * _margin));
- // Add rows and columns to the LayoutRoot Grid
- for (int i = 0; i < _cols; i++)
- CellGrid.ColumnDefinitions.Add(new ColumnDefinition());
- for (int j = 0; j < _rows; j++)
- CellGrid.RowDefinitions.Add(new RowDefinition());
- // Fill the Grid cells with Rectangles
- Brush fill = new SolidColorBrush(Colors.Blue);
- for (int i = 0; i < _cols; i++)
- {
- for (int j = 0; j < _rows; j++)
- {
- Rectangle rect = new Rectangle();
- rect.Margin = new Thickness(_margin);
- rect.Fill = fill;
- rect.Opacity = _opacity;
- rect.RadiusX = rect.RadiusY = 4.0;
- rect.SetValue(Grid.RowProperty, j);
- rect.SetValue(Grid.ColumnProperty, i);
- rect.PointerPressed += OnPointerPressed;
- rect.PointerEntered += OnPointerEntered;
- rect.Tag = ((j + (_extraRows / 2)) * (_cols + _extraCols)) + i + (_extraCols / 2);
- CellGrid.Children.Add(rect);
- }
- }
The result? You get a cell grid in which the number of rows and columns depends on the amount of screen real estate available. In other words, the UX adapts to the screen size and you get a decent-looking presentation on any device with any form factor.
MainPage.xaml.cs also contains shared process-lifetime management (PLM) code that restores the app to its previous state if the user switches away from it and the app is terminated while suspended. The Suspending event handler instantiates a class named PLMSettings, initializes it with app state, serializes it, and writes it to a file in LocalFolder. On startup, OnNavigatedTo deserializes the data if present and uses it to the restore the app’s state. The fact that the exact same code works identically on Windows and Windows Phone is a testament to the high degree of compatibility between the Windows Runtime (WinRT) and the Windows Phone Runtime (WinPRT).
If you haven’t taken the time to learn WinRT, now’s the time to do it because it’s the backbone of universal apps and the future of the Windows platform. Two resources you might find helpful are books written by colleagues of mine: Windows Runtime via C# by Jeffrey Richter and Maarten van de Bospoort, and the soon-to-be-published Programming the Windows Runtime by Example by Jeremy Likness and John Garland. You might also benefit from watching my video entitled Introduction to WinRT on WintellectNOW. If you’re not a WintellectNOW subscriber, sign up and enter the promo code JEFFPRO-2014 to enjoy two weeks of access for free.