Using the preprocessor to share incompatible XAML between SL and WPF – Part 1


One of the goals of Silverlight is to be a subset of WPF so that you can cross-compile the code between the two frameworks.  This works great when you adhere to the strict subset that Silverlight provides, but when you want to deviate from that to provide richer functionality in your WPF version, you’re left with messy workarounds.  One options is to make the changes in code using the preprocessor #if…#endif directives to conditionally include code, but what if you want to make the change in XAML?  The XAML processor doesn’t allow preprocessor commands such as #if…#endif, so currently the only workaround is to create two copies of the XAML and change them manually.  In doing this, you’d lose all the benefits of code sharing now that you’re maintaining two XAML files.


I’ve created a custom MSBuild targets file that will allow you to take full advantage of the C++ preprocessor to conditionally include code the way you’re used to doing it already.  In part one I’ll just give a high level overview of what it does and how to use it, and I’ll provide more detail, and better incremental build functionality.


To use this build tool, you must add the following paths to your environment PATH variable so that the compiler knows where to find the C++ preprocessor:


c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\
c:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\
NOTE: Your paths may be different.


After you’ve done that, just import PreprocessXaml.targets at the end of your csproj file (see attached example).  This works for Silverlight projects or WPF projects.  Once you do that, all your XAML pages will be preprocessed (except for App.xaml, we’ll add that in part 2) and compiled to Filename.i.xaml and from then on will be processed as normal.



   1: <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   2:   <!--
   3:     Insert the PreprocessXaml target into the build process
   4:   -->
   5:   <PropertyGroup>
   6:     <!-- For WPF projects -->
   7:     <MarkupCompilePass1DependsOn>
   8:       $(MarkupCompilePass1DependsOn)
   9:       PreprocessXaml;
  10:     </MarkupCompilePass1DependsOn>
  11:     <!-- For Silverlight projects -->
  12:     <CompileXamlDependsOn>
  13:       $(CompileXamlDependsOn)
  14:       PreprocessXaml;
  15:     </CompileXamlDependsOn>
  16:   </PropertyGroup>
  17:   <!--
  18:     Runs the C++ preprocessor over all XAML files before they are compiled and packaged.
  19:     This allows for conditional inclusion of XAML such as 
  20:     <TextBlock
  21:     #if SILVERLIGHT
  22:       Text="This is in Silverlight"
  23:     #else
  24:       Text="This is in WPF"
  25:     #endif
  26:     />
  27:     or use #define to define constants etc.
  28:   -->
  29:   <Target Name="PreprocessXaml">
  30:     <ItemGroup>
  31:       <!-- Create a list of pages post-preprocess -->
  32:       <PreprocessedPages Include="@(Page->'%(RelativeDir)%(Filename).i%(Extension)')" />
  33:       <!-- We don't want to include the unprocessed files as resources, include the preprocessed files instead -->
  34:       <Resource Remove="@(Page)" />
  35:       <Resource Include="@(PreprocessedPages)" />
  36:       <!-- Convert the DefineConstants property into an ItemGroup -->
  37:       <XamlConstants Include="$(DefineConstants)" />
  38:     </ItemGroup>
  39:     <PropertyGroup>
  40:       <!-- Convert the XamlConstants ItemGroup into a list command line switches for CL.exe -->
  41:       <CommandLineDefineConstants>@(XamlConstants->'/D%(Identity)',' ')</CommandLineDefineConstants>
  42:     </PropertyGroup>
  43:     <!-- Run the preprocessor -->
  44:     <Exec Command="CL.exe /nologo /EP $(CommandLineDefineConstants) &quot;%(Page.FullPath)&quot; &gt; %(RelativeDir)%(Filename).i%(Extension)" />
  45:     <!-- Replace the pages with the preprocessed pages so that subsequent targets will use the preprocessed files -->
  46:     <ItemGroup>
  47:       <Page Remove="**" />
  48:       <Page Include="@(PreprocessedPages)" />
  49:     </ItemGroup>
  50:   </Target>
  51: </Project>

PreprocessXaml.targets


Attached is a simple project that shows conditional compilation of XAML at work.

PreprocessorTest.zip

Comments (7)
  1. Anonymous says:

    Normal 0 false false false EN-US X-NONE X-NONE MicrosoftInternetExplorer4 /* Style Definitions */ table.MsoNormalTable

  2. Anonymous says:

    Error 1 The command "CL.exe /nologo /EP /DDEBUG /DTRACE "C:Documents and SettingseskrobMy DocumentsVisual Studio 2010ProjectsPreprocessorTestPreprocessorTestPreprocessorTestWPFWindow1.xaml" > Window1.i.xaml" exited with code 9009. PreprocessorTestWPF

  3. Anonymous says:

    i also tired this and got the "exited with code 9009" error at first, but after adding the correct direcotries to the PATH variable this goes away and everything compiles fine. Note that these directories are different than those listed above (but similar)  for visual studio 2010.

    after that was resolved i was able to get everything compiling correctly, but when i included the preprocessor targets in the project file, running the WPF app gave me the runtime error "Cannot locate resource 'mainwindow.xaml'. Removing the preprocessor targets from the project file causes this error to go away….

    thoughts?

  4. Anonymous says:

    That "Cannot locate resource 'MainWindow.xaml'" can be corrected by changing the reference to it in App.xaml to 'MainWindow.i.xaml'.

    The PreprocessXaml.targets project deletes the non-preprocessed XAML files from the obj directory.

    Has anyone tried a ClickOnce publish with the PreprocessXaml project imported?

    It doesn't produce the <project>.EXE for me so the publish fails. The EXE does appear for a regular build.

  5. Anonymous says:

    If you try this in a Windows Phone Silverlight project, you need to follow AV695's instructions, except instead of changing the reference in App.xaml from MainWindow.xaml to MainWindow.i.xaml, you will need to change the reference in WPAppMainfest.xml from MainPage.xaml to MainPage.i.xaml. WPDev WP7Dev

  6. Anonymous says:

    Hi Ben,

    crazy stuff! I have an improvement for the PreprocessorXaml target. Because of a strange differentiation of msbuild between files and linked files (aka "add as a link" in the "add existing item" dialog) in project files the line "<Page Remove="**" />" does not work as expected. The linked files are not removed from the Page item group. Use "<PageRemove="@(Page)" />" instead and it works!

    Greetings from germany!

Comments are closed.

Skip to main content