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