Series Introduction
In this three part series you see how to implement a XAML based game for the Universal Windows Platform (UWP). We explore some of the difference between WPF, Silverlight and UWP. You also see how to create a cross-platform Portable Class Library for your game logic, and learn about using semaphores to provide thread safety for awaitable code blocks. We look at how to take advantage of x:Bind and how to play sound effects using the MediaElement
control. Finally, you see how to use a SplitView
to provide a slide in menu for the game.
In the next series of articles (after this three part series) you see how to go cross-platform using Xamarin Forms.
Links to articles in this series:
In this Article
In part 1 we look at creating a cross-platform compatible class library in which to place game logic. We contrast some of the differences between file linking, shared projects, portable class libraries, and the new .NET standard. Finally you see how the Game
page is implemented, and we briefly look at the new x:Bind markup extension.
Background
It’s hard to believe that I wrote the first incarnation of this Sokoban game for WPF and Silverlight all the way back in 2007. A lot has changed. WPF is alive and well in the enterprise space and Silverlight is all but gone; yet its legacy lives on.
For the last few months I have been working with Xamarin Android, porting Surfy Browser to Android with the Xamarin Android tools. It’s been an interesting experience getting up close and personal with both the Window and Android platforms. By the way, if you’re interested you can take a look at Surfy Browser for Android beta over at
https://play.google.com/apps/testing/com.outcoder.browser
It was a pleasure working with ‘Windows XAML’ once again for this project. In my view you just can’t beat Visual Studio and Microsoft’s tooling story for Windows development.
The game logic for this project is described in detail in the
first article. I recommend skimming through that article first (it’s fairly short) to gain an understanding of the principles of the game.
The Sokoban game logic remains mostly the same for this series of articles. Here and there I have refreshed the core game code, replacing threadpool calls with async methods, and C# 6.0 property lambdas and so forth. But these changes are mostly cosmetic.
More substantial are the changes I’ve made to the main view. I’ve refactored it and moved most of the logic down into the Game
class. You can think of the Game
class in the Sokoban project as the ViewModel for the main view.
Cross-Platform Code Sharing with a PCL Project
When beginning this project I decided to cover both UWP and Xamarin Forms. For that reason I chose to move the game logic to a Portable Class Library (PCL). Although Xamarin Forms now supports UWP, my intent was to build out a ‘native’ UWP app and then port that to Xamarin Forms.
When choosing a cross-platform code-sharing strategy you have a number of options:
- File Linking
- Shared Projects
- Portable Class Libraries (PCLs)
- .NET Standard
We briefly take a look at each of these now.
File Linking
File linking has been with us for years. I can recall discovering the elusive little drop down button on the add button a decade ago. See Figure 1.
Figure 1. Adding a linked file.
The advantage of using file linking is its flexibility. You can include any file in your project and enable or disable lines of content within a code file using preprocessor directives.
The major disadvantage of file linking is that it can be laborious; you have to manually ensure that when adding or renaming a linked file, you update the link in every project that links to the file.
Back in the day there were some Visual Studio extensions that provided auto-linking based on file suffixes. They have not since been taken forward by the Visual Studio team.
Shared Projects
Shared Projects do not produce any build artifacts; there isn’t an assembly produced from a Shared Project. Shared Projects resemble an auto-linking feature. Whatever is in your shared project is merged into your referencing project. Shared projects are an undeniably useful tool; preprocessor directives are supported, so you can include or exclude platform specific code, and there is no laborious link maintenance.
The downside, however, is that a project riddled with preprocessor directives can become a maintainability headache. Minor API differences can lead to file bloat. Partial classes can help to alleviate that, however it still contributes to internal complexity.
Portable Class Libraries
Portable class libraries allow you to target multiple platforms using the same output assembly. From the get-go that was a pretty attractive offer. It does, however, come at a cost: the API surface is the intersection of the combined frameworks. The more platforms you target, the fewer APIs you have access to.
PCLs allow you to target a set of known frameworks. You can see from Figure 2 that the Xamarin Forms PCL project template gives you quite a broad set of platforms, including iOS, Android. Though it’s not listed, UWP is also supported.
Figure 2. PCL Supported Platforms
To create a new cross-platform compatible class library, select Class Library (Xamarin.Forms) from the Cross-Platform node in the New Project dialog; as shown in Figure 3.
Figure 3. Creating a new PCL project.
Targeting the .NET Standard
The previous sub-sections have led here. Microsoft recognized the pros and cons of PCLs, in particular each time a new .NET platform arrives it brings with it new combinatorial complexity.
The .NET Standard is an attempt to unify yet broaden the API surface so that you can use more APIs in your cross-platform libraries without excluding platforms that do not contain the set of APIs you need. It doesn’t force you to use the lowest common denominator like PCLs do.
You can find more information regarding the .NET Standard at:
Some may be dubious about attempting to unify the .NET framework forks by creating a new standard. But, I feel it addresses the limitations of PCLs in an effective way. It’s a promising approach that Microsoft is continuing to invest in.
Secondary Considerations when Choosing a Cross-Platform Code Sharing Strategy
Besides the obvious advantages and disadvantages of each code sharing approach, there are also tooling implications. In particular, I’ve found that Visual Studio (with Resharper) can become confused when using PCLs and Shared Project types. I occasionally find that symbols can’t be resolved, and while your solution may build, there can be a host of unrecognized symbol errors popping up in Visual Studio’s error list. Intellisense can fall flat too.
TIP: A workaround for clearing erroneous type resolution failures is to close your solution and delete the .suo file for the solution. Reopening the solution sees those pesky errors vanish, at least for a little while.
Why Choose PCL?
The reason I chose a PCL for Sokoban’s game logic, and not one of the other approaches, is I absolutely wanted to avoid preprocessor directives for readability sake. That ruled out shared projects and linking. The .NET Standard is undergoing some changes, and while it looks promising I found some issues with NuGet and converting PCLs over to the .NET Standard in a Xamarin Forms solution. I do, however, consider it to be the best option for creating .NET cross-platform apps going forward, and it is set to replace PCLs in that regard.
Implementing the Game Page in XAML
The MainPage.xaml layout consists of a top toolbar section, a central game and slide out menu section, which is implemented using a UWP SplitView
control. There is also an overlay that is used to partially hide the level upon completion and to present the user with a ‘press any key to continue’ message. See Listing 1.
Listing 1. MainPage.xaml content grid.
<Grid x:Name="rootGrid" Background="{ThemeResource GameBackgroundBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="topContentGrid"
HorizontalAlignment="Stretch"
Background="{ThemeResource ChromePrimaryBrush}"
Height="50" Padding="0,0,12,0">
...
</Grid>
<SplitView Grid.Row="1" x:Name="splitView"
DisplayMode="Overlay"
OpenPaneLength="320"
PaneBackground="{ThemeResource ApplicationPageBackgroundThemeBrush}"
IsTabStop="False">
<SplitView.Pane>
<StackPanel Background="{ThemeResource ChromeSecondaryBrush}">
...
</StackPanel>
</SplitView.Pane>
<SplitView.Content>
<Canvas x:Name="gameCanvas" Background="Transparent" />
</SplitView.Content>
</SplitView>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
PointerReleased="HandleFeedbackControlPointerReleased"
Background="{StaticResource GameShadeBrush}" Grid.RowSpan="3"
Visibility="{x:Bind Game.FeedbackVisible,
Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<TextBlock
Text="{x:Bind Game.FeedbackMessage, Mode=OneWay}"
FontSize="{StaticResource TextStyleExtraLargeFontSize}"
Foreground="White"
Visibility="{x:Bind Game.ContinuePromptVisible,
Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Grid>
</Grid>
Styling Your App with ThemeResources
Theme resources are a new XAML feature in UWP. Theme resources are distinct sets of styles, brushes and other UI elements that can be applied, switched out and reapplied at run-time. They are much like StaticResources. However, while StaticResources are evaluated only when XAML is first loaded by the app, a set of ThemeResources can be swapped for another any time.
There are three sets of ThemeResources
in the UWP. They are Light, Dark, and HighContrast. You can instruct your app to select a particular theme using the RequestedTheme
attribute of the Application
element, in your App.xaml file for your app; as shown:
<Application
x:Class="Outcoder.Sokoban.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Outcoder.Sokoban"
RequestedTheme="Light">
The RequestedTheme
may be either Light or Dark. Notice that there isn't a HighContrast value, as high contrast is applied by the OS if the user has configured high contrast settings via Settings > Ease of access > High contrast.
Resource dictionaries containing theme resources may be placed within the Application.Resources element. See the following example:
<Application.Resources>
<ResourceDictionary>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="ChromePrimaryBrush" Color="#ff9a00" />
...
You may also place a ResourceDictionary
elsewhere in your solution and reference it using the Source
attribute of the ResourceDictionary
, as shown:
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary Source="FooDictionary.xaml" x:Key="Light" />
In addition, the MergedDictionaries
property of the ResourceDictionary
allows you to conglomerate disparate resources from multiple files, like so:
<ResourceDictionary x:Key="Light">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="FooDictionary1.xaml" />
<ResourceDictionary Source="FooDictionary2.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Despite there appearing to be three supported theme types, there is, in fact, a fourth: the Default theme. If you have a resource that is the same across all your themes, you can place it is a ResourceDictionary
with its x:Key value set to Default; as shown:
<ResourceDictionary x:Key="Default">
If the app cannot locate a resource in the Light, Dark, or HighContrast themes, then it looks in your Default theme.
Use the ThemeResource
markup extension to reference a theme resource within a page, control or template, like so:
<Grid x:Name="rootGrid" Background="{ThemeResource GameBackgroundBrush}">
Visual Studio provides intellisence for ThemeResources
, just as it does with StaticResource
items. ThemeResources
provide an easy way to change the look and feel of your app inline with the user selected operating system theme.
NOTE: Do not refer to another ThemeResource
within a theme resource. Instead, use the StaticResources
markup extension. Resolution errors and circular dependencies may produce unpredictable results. There is one exception: it's okay to reference built-in theme resources.
Understanding x:Bind Markup Extension
Anyone familiar with WPF or Silverlight will tell you that data binding in XAML is done using the Binding markup extension. If you’re not familiar with data-binding in XAML, data-binding is a way to update the state of a UI element to the state of another object and vice versa, without having to write code to perform the update. Data binding is a key ingredient for creating highly maintainable UI in XAML and one of the things that makes XAML UI development so much fun.
The Binding markup extension uses runtime inspection to map between object properties. As it turns out, runtime inspection comes with a performance cost. In most situations the cost is negligable and unnoticable. However, when dealing with long lists of dynamically populating list items, fast buttery scrolling needs rapid population of item data. While virtualization—where population occurs only on visible items—can partially mitigate the cost, there are some scenarios where runtime inspection can cause your UI to lag noticeably.
For this reason, Microsoft has introduced the x:Bind markup extension, which is unique to UWP XAML apps. x:Bind causes code to be generated at development time, which alleviates the need for runtime inspection. It’s faster. It also comes with compile time binding validation and enables breakpoints with binding code, which is a big maintainability win.
The downside to x:Bind is you need to explicitly bind to a property or child property of your view. Despite this requirement, I recommend using x:Bind wherever you can.
Within MainPage.xaml.cs in the Sokoban.Launcher.Uwp project in the downloadable sample code, you see that the Sokoban Game
object is exposed as a public property of the page, as shown:
public Game Game { get; }
This allows the page to x:Bind directly to Game
properties, such as the level number, as demonstrated:
<TextBlock Text="{x:Bind Game.Level.LevelNumber, Mode=OneWay, FallbackValue=0}" />
NOTE: Unlike the Binding markup extension, whose Mode attribute varies from control to control, x:Bind’s default mode is OneTime. Try to keep this in mind when your binding expression appears not to be working. I personally would have prefered this value to be OneWay across the board, because I feel it's the most common scenario.
Conclusion
In this article you looked at creating a cross-platform compatible class library in which to place game logic. We contrasted some of the differences between file linking, shared projects, portable class libraries, and the new .NET standard. Finally you saw how the Game page is implemented, and we briefly looked at the new x:Bind markup extension.
In the
next part of this series we look at wiring up the the sokoban
Game
to the app’s main page. You see how to play sound effects using the UWP
MediaElement
control. Finally, you explore how the game grid is populated with custom cell controls.
I hope you find this project useful. If so, then please rate it and/or leave feedback below.
History