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="https://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

  • Anonymous
    April 29, 2009
    PingBack from http://www.anith.com/?p=33578
  • Anonymous
    May 25, 2009
    Normal 0 false false false EN-US X-NONE X-NONE MicrosoftInternetExplorer4 /* Style Definitions */ table.MsoNormalTable
  • Anonymous
    September 13, 2010
    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
  • Anonymous
    May 08, 2011
    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?
  • Anonymous
    August 02, 2011
    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.
  • Anonymous
    August 07, 2011
    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
  • Anonymous
    October 19, 2011
    The comment has been removed