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;
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;
- 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.
- 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.
- 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;
- Has IsFocusable=True
- Has IsTabStop = True
- Has called Focus() to make sure it is focused
- OpenFileDialog works slightly differently between Silverlight and WPF when it comes to opening the file you want to read text from.
- 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.
- 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.
- 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.
- 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;
( 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;
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 );
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.