WPF: Pong

Following up on yesterday’s amazing breakthrough with “Silverlight:Pong” 🙂 I thought I’d follow a bit of this XAML Continuum myself and move my Silverlight Pong to WPF.

I wanted to maintain a single code-base as much as possible. It’s fairly easy to cross-compile C# code by using conditional compilation where necessary for WPF/Silverlight ( Silverlight projects already define the SILVERLIGHT constant which is useful ) but I wanted to also try and have a single lump of XAML so that pretty much all the files in my WPF project were just linked from the Silverlight project.

I started to ponder about using the C++ preprocessor in order to do some pre-processing on my XAML when Marc pointed out that Ben had already done that in an MSBuild style manner;

Using the preprocessor to share incompatible XAML between SL and WPF

and that also links up a little with this post from David;

A custom preprocessor for sharing XAML between Silverlight and WPF

although I didn’t actually use that second technique but I did use Ben’s preprocessor stuff from MSBuild. Having said that, I found that I had to hack at Ben’s .targets file a little in order to make it do what I want – more on that in a second. Here’s my projects in Visual Studio side-by-side. The first one is the Silverlight project and the second one is the WPF project;

image image

You’ll notice that almost every file in the WPF project is simply a link to the same file in the Silverlight project. There are one or two exceptions;

  • Window1.xaml – this is the main UI for the WPF version and simply contains a user of the MainPage control which comes linked from the Silverlight project.
  • GlassHelper.cs – I use this ( originally from Adam Nathan ) to bring Glass into my WPF application.
  • App.xaml – this is the default App.xaml that VS gave me.
  • WPFdefaultUI.xaml – this one takes a bit more explanation. In my original game I made the UI replaceable. In Silverlight, the default UI is loaded from a resource called defaultUI.xaml and inserted into the tree at runtime and it can also be replaced later on. In WPF I want to do the same thing but ( because of namespace clashes ) I can’t use the very same file unless I pre-process it but this file isn’t actually part of the build process ( it’s a resource ) and I got into a pickle with MSBuild if I tried to pre-process it so I felt it was easier just to have 2 versions ( they are 95% the same anyway and only differ in that they use a Viewbox which lives in different places in WPF and Silverlight ).

As I say, my Window1.xaml ends up looking like this;

<Window x:Class="WPFPong.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Pong"
    Title="Mikes Great Pong Game" Height="600" Width="800">
    <local:MainPage>       
    </local:MainPage>
</Window>

and then in a few places ( specifically MainPage.xaml ) I’ve got XAML with conditional compilation as in;

<UserControl
    x:Class="Pong.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
#if SILVERLIGHT
    xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"   
    KeyDown="OnKeyDown">
#else
    xmlns:controls="clr-namespace:System.Windows.Controls;assembly=PresentationFramework"
    Focusable="True"
    IsTabStop="True"
    KeyDown="OnKeyDown">
#endif   

and code with conditional compilation as in ( note – there are places where I should have not muddied up a particular routine with conditional compilation but should, instead, have created 2 separate routines with one for Silverlight and one for WPF but I was being “quick/lazy” );

#if SILVERLIGHT
    static string DefaultUIResourceName = "defaultUI.xaml";
#else
    static string DefaultUIResourceName = "WPFDefaultUI.xaml";
#endif

So…what issues did I encounter along the way here;

  1. Namespaces and assemblies. It’s a bit painful when the same control lives in one namespace/assembly in Silverlight and another in WPF. Viewbox was my prime example here.
  2. In Silverlight I had left Canvas.Left unset on a Rectangle which seemed to result in a value of 0 whereas in WPF it seemed to result in NaN which foxed me for quite a while.
  3. I have a UserControl which handles KeyDown. This was fine in Silverlight but caused me grief in WPF in that I didn’t get the KeyDown event unless I was very careful about making sure that my UserControl;
    1. Has IsFocusable=True
    2. Has IsTabStop = True
    3. Has called Focus() to make sure it is focused
  4. OpenFileDialog works slightly differently between Silverlight and WPF when it comes to opening the file you want to read text from.
  5. XamlReader works slightly differently between Silverlight and WPF when it comes to loading some XAML UI from a string – Silverlight makes it "easier” with less code to write.
  6. In my original Silverlight application I had embedded some MP3 files to play as sounds. MediaElement in Silverlight will play sounds embedded as resources. It will not do so in WPF. Also, I needed to play around a little with a leading “/” to flick between URI’s for sounds with Silverlight/WPF although I daresay I could have fixed that one.
  7. In my original Silverlight application I had put a MediaElement into a UserControl.Resources dictionary. It played sounds just fine from there. In WPF, it did not. I spent quite a while before I realised that the MediaElement actually needed to be added to the UI definition ( rather than just in a resource dictionary ) in order to get it to play sounds in WPF. Makes sense and I’m not 100% sure why I had put it in the resource dictionary in the first place but I puzzled over it for quite some time.
  8. Fonts – I’m not 100% sure on this one but I’d embedded a font in my Silverlight application and I had to tweak the syntax slightly from a WPF perspective to in order to get my fonts to display.

So, with that all done I can run up my WPF Pong version;

image

( don’t worry too much about the penguins – they’re on my desktop )

and, just like I could in my Silverlight version I can hit the “L” key in order to load up a replacement UI;

image

and find myself a new XAML file ( very similar to the one that I used on the previous post to replace the Silverlight’s version UI – in fact, just one changed around the namespace of Viewbox );

image

and I’m “in business”.

You can run the WPF version via ClickOnce from here if you have .NET Framework V3.5 Service Pack 1 and here are the projects for download if you want to play with the source-code. As Ben said, you’ll need to tweak your PATH variable to pick up the VC\Bin folder for CL.EXE.

Just one more note. I did find that I was having some trouble with the original PreprocessXaml.targets file that Ben had and it seemed to be because I was linking to MainPage.xaml in a separate (Silverlight) project rather than in my project. What I was finding was that the pre-processed XAML file ( MainPage.i.xaml ) was being dumped out in the Silverlight project’s folders rather than in my project’s folders and this caused a bit of a muddle. I hacked the .targets file to suit my purposes and it’s included with the source code below but bear in mind that this was really just a quick hack to fix my problem and you’re 99.9% likely to be better off with Ben’s original file than my version.