共用方式為


Strategies for sharing code in XAML-based Universal apps

Universal apps are a great way to share code between your Windows and Windows Phone versions of your app.

Most devs have figured out how to share modules which are exactly the same, but sharing modules which are only almost the same is a bit less obvious.

The Shared project isn’t a normal, stand-alone project: it gets merged into the platform specific projects at build time, so you can treat the full project as all of the files from the platform specific project plus all of the files from the shared project. We can use this split in several ways to target specific code to specific targets.

Conditional Compilation

Ok. This one's obvious. For small differences conditional compilation works well, and this is demonstrated in Visual Studio’s templates:

 public sealed partial class App : Application 
   { 
 #if WINDOWS_PHONE_APP 
       private TransitionCollection transitions; 
 #endif

For larger differences conditional compilation can quickly become cumbersome, and conditional compilation isn’t possible at all in XAML, only in code.

Partial Classes

Partial Classes are a C# and VB.Net feature which allows a class to be implemented in multiple files by prepending the “partial” keyword before the class definition. The final class is the combination of the classes, fields, and properties defined in all versions. XAML apps use this feature to share the code-behind class (e.g. MainPage.xaml.cs) and the generated XAML classes (e.g. MainPage.g.cs and MainPage.g.i.cs ).

The app can split a class amongst files in the platform specific project and the shared project by marking each part as a partial class. When building and running both parts will be available in the class.

A common scenario would be to have mostly shared backing code but UI tuned differently for phones and for larger screens. The app could have different versions of MainPage.xaml with platform-specific layouts in the platform-specific projects and the MainPage.xaml.cs file with the bulk of the code in the Shared project. A button handler for a phone specific feature might be implemented in a partial MainPage class in the WindowsPhone project and stubbed out in a partial MainPage class in the Windows project.

Resource Dictionaries

XAML doesn’t support conditional compiling or partial classes, so it is more difficult to split out large layout differences without using separate files. However, styles, colors, and templates can be placed in an external ResourceDictionary. Depending on the app the primary XAML file could be platform specific for gross layout and include a shared styles dictionary for ItemTemplates, or the app could share a primary XAML file and include platform specific resource dictionaries to include platform specific colors (the app may use its own branded colors on Windows but the user’s theme colors on Windows Phone).

Here’s a quick example with a platform specific XAML file which uses a ListView in the phone and a GridView on Windows. The ItemTemplate comes from the same DataTemplate in the shared project. The highlight on the template uses the standard PhoneAccentBrush on the phone. Since PhoneAccentBrush doesn’t exist on Windows the app defines its own in the Windows project’s PlatformDictionary.xaml to draw Purple.

Both platforms share the same implementation of MainPage.cs to generate the same sample data. Button_Click handlers are in platform specific MainPage_Platform.cs files.

Shared & Platform specific file diagram

App101.Windows\MainPage.xaml: 

 <Page.BottomAppBar> 
     < CommandBar> 
         < AppBarButton Icon="Accept" Click="Button_Click" /> 
     </CommandBar> 
 < /Page.BottomAppBar>
  
 <Grid> 
     <GridView ItemsSource="{Binding}" ItemTemplate="{StaticResource DataTemplate1}">
     </GridView> 
 < /Grid>

App101.WindowsPhone\MainPage.xaml:

 <Page.BottomAppBar> 
     <CommandBar> 
         < AppBarButton Icon="Accept" Click="Button_Click" /> 
     </CommandBar> 
 < /Page.BottomAppBar>
  
 <Grid> 
     < ListView ItemsSource="{Binding}" ItemTemplate="{StaticResource DataTemplate1}">
     </ListView> 
 < /Grid> 

App101.Shared\App.Xaml 

 <Application.Resources> 
     < ResourceDictionary> 
         < ResourceDictionary.MergedDictionaries> 
             < ResourceDictionary Source="PlatformDictionary.xaml" /> 
         </ResourceDictionary.MergedDictionaries>
  
     <DataTemplate x:Key="DataTemplate1"> 
         <Border BorderBrush="{StaticResource TileHighlightBrush}" Margin="10"> 
             <Grid> 
                  <Grid.ColumnDefinitions> 
                     < ColumnDefinition Width="100" /> 
                     < ColumnDefinition Width="*" /> 
                 </Grid.ColumnDefinitions> 
                  <Image Source="{Binding ProfileImage}" Grid.Column="0"/> 
                  <StackPanel Grid.Column="1"> 
                     <TextBlock Text="{Binding Name}" Style="{StaticResource TitleTextBlockStyle}" /> 
                     < TextBlock Text="{Binding Description}" TextWrapping="WrapWholeWords" 
                          Width="300" Height="100"/> 
                 </StackPanel> 
             </Grid> 
         </Border> 
     </DataTemplate> 
     < /ResourceDictionary> 
 < /Application.Resources>

  App101.Windows\PlatformDictionary.xaml 

 <ResourceDictionary 
      xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:local="using:App101"> 
    < SolidColorBrush x:Key="PhoneAccentBrush" Color="Purple"/> 
 < /ResourceDictionary>
  

App101.WindowsPhone\PlatformDictionary.xaml

 <ResourceDictionary 
      xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:local="using:App101"> 
 </ResourceDictionary>

App101.Shared\MainPage.cs

 class DataItem 
  { 
     public string Name { get; set; } 
     public string Description { get; set; } 
     public Uri ProfileImage { get; set; } 
  } 
  /// <summary> 
  /// An empty page that can be used on its own or navigated to within a Frame. 
  /// </summary> 
  public sealed partial class MainPage : Page 
  { 
     ObservableCollection<DataItem> data; 
     public MainPage() 
      { 
         this.InitializeComponent();
  
         this.NavigationCacheMode = NavigationCacheMode.Required;
  
         data = new ObservableCollection<DataItem>(); 
         for(int i=0;i<25;i++) 
          { 
             data.Add(new DataItem() 
             { 
                  Name = "Item " + i, 
                 Description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum", 
                  ProfileImage = new Uri("ms-appx:///Assets/ProfilePic.png") 
             });
         }
         this.DataContext = new CollectionViewSource() { Source = data }; 
     } 

App101.Windows\MainPage_Platform.cs

 partial class MainPage 
  { 
     void Button_Click(object sender, RoutedEventArgs e) 
     { 
         Debug.WriteLine("Windows app clicked"); 
     } 
  } 

App101.WindowsPhone\MainPage_Platform.cs

 partial class MainPage 
 { 
     void Button_Click(object sender, RoutedEventArgs e) 
     { 
         Debug.WriteLine("Windows Phone app clicked"); 
     } 
  } 

I hope this blog entry gives you some ideas about ways to share common code across your Universal apps!

Don’t forget to follow the Windows Store Developer Solutions team on Twitter @wsdevsol. Comments are welcome, both below and on twitter.

- Rob

Comments

  • Anonymous
    November 12, 2014
    Very helpful, thanks a lot. XAML Conditional Compilation can be done using a nuget package called xcc (xcc.codeplex.com). Still in beta but I've already used it successfully in a couple of Universal Apps I'm working on.