Sharing Code between Windows, WinRT and Windows Phone

December 16, 2013

In recent times, I often find myself developing for more than one “Windows” platform – typically Windows Phone and Windows 8 Store and sometimes Windows (WPF) as well. In this post, I’d like to share some of the tools and techniques I’ve been using to ease code sharing.

Portable Class Libraries (PCLs)

PCLs came out in Visual Studio 2012 and provide an easy way to create a single project that can be referenced by multiple project types. When you create a PCL, you get the following dialog:

image

This dialog allows you to select multiple targeted platforms (at least 2 must be selected). This will determine the least common denominator types that will be supported in the PCL code.

The PCL is great for neutral object model that must be defined and used by multiple platforms. It supports things like generic collections, LINQ, XML classes, MVVM related types, and even WCF client classes.

Here’s an example of an ObservableObject class that can be defined entirely within a PCL:

public abstract class ObservableObject : INotifyPropertyChanged {

      public event PropertyChangedEventHandler PropertyChanged;

 

      protected virtual void OnPropertyChanged([CallerMemberName] string name = null) {

            var pc = PropertyChanged;

            if(pc != null)

                  pc(this, new PropertyChangedEventArgs(name));

      }

 

      protected virtual bool Set<T>(ref T field, T value, [CallerMemberName] string name = null) {

            if(!EqualityComparer<T>.Default.Equals(field, value)) {

                  field = value;

                  OnPropertyChanged(name);

                  return true;

            }

            return false;

      }

}

Notice the usage of CallerMemberName, which is a .NET 4.5 attribute but is supported on WinRT and Windows Phone.

A PCL typically is not enough, and sometimes you need a platform specific implementation. One way to get this is to create the base class (which may be abstract), and require the platform-specific assembly to implement it.

For instance, we can create an MVVM-style RelayCommand like so:

public class RelayCommand<T> : ICommand {

      static readonly Func<T, bool> _true = _ => true;

      readonly Action<T> _execute;

      readonly Func<T, bool> _canExecute;

      public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null) {

            _execute = execute;

            _canExecute = canExecute ?? _true;

      }

 

      public bool CanExecute(object parameter) {

            return _canExecute((T)parameter);

      }

 

      public virtual event EventHandler CanExecuteChanged;

 

      public virtual void Refresh() {

            var eh = CanExecuteChanged;

            if(eh != null)

                  eh(this, EventArgs.Empty);

      }

 

      public void Execute(object parameter) {

            _execute((T)parameter);

      }

}

 

The CanExecuteChanged event may need special implementation in some platform, such as WPF – this is why the event is declared virtual. In a WPF assembly referencing this one we can create a specific implementation like so:

class WindowsRelayCommand<T> : RelayCommand<T> {

      public WindowsRelayCommand(Action<T> execute, Func<T, bool> canExecute = null)

            : base(execute, canExecute) {

      }

 

      public override event EventHandler CanExecuteChanged {

            add { CommandManager.RequerySuggested += value; }

            remove { CommandManager.RequerySuggested -= value; }

      }

}

 

To make this even simpler, we can create a factory to abstract away the exact implementation. Here are examples for WPF and Windows Phone:

public static class CommandFactory {

      public static ICommand CreateRelayCommand<T>(Action<T> execute, Func<T, bool> canExecute = null) {

            return new WindowsRelayCommand<T>(execute, canExecute);

      }

}

 

public static class CommandFactory {

      public static ICommand CreateRelayCommand<T>(Action<T> execute, Func<T, bool> canExecute = null) {

            return new RelayCommand<T>(execute, canExecute);

      }

}

 

The problem with PCLs today is that its common surface is too small. For example, the FrameworkElement class exists in WPF, WinRT and Windows Phone. It doesn’t have the same set of members in all these platforms, but if I promise only to use members that exist in all these platforms, such as the Width property – this still won’t work – the PCL simply doesn’t support this kind of sharing.

Although I understand why it would be difficult to implement such a feature, I think it’s worth the effort. It would make PCLs super useful by today’s usage.

Sharing source code

Building PCLs will take you so far. Eventually, you’ll have to create platform-specific assemblies, adding references to any required PCLs.

Because WPF, WinRT and Silverlight (by that I mean Windows Phone too) share many commonalities, it’s desirable to have the same files shared across projects, and just build multiple times.

For example, let’s look at converters. Converters must implement the IValueConverter interface, which has the same structure in WPF and Silverlight, but slightly different in WinRT. For these differences we can use conditional compilation. Here’s an example:

public sealed class AngleConverter : IValueConverter {

      public double Ascendent { get; set; }

#if NETFX_CORE

      public object Convert(object value, Type targetType, object parameter, string culture) {

#else

      public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {

#endif

// code removed for clarity

 

#if NETFX_CORE

      public object ConvertBack(object value, Type targetType, object parameter, string culture) {

#else

      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {

#endif

            throw new NotImplementedException();

      }

}

The NETFX_CORE is defined by default in WinRT projects, SILVERLIGHT is defined in Silverlight and Windows Phone projects and WINDOWS_PHONE is defined in Windows Phone projects.

Another common example is using namespaces:

#if NETFX_CORE

using Windows.UI.Xaml.Controls;

using Windows.UI.Xaml;

using Windows.UI.Xaml.Media;

using Windows.UI.Xaml.Media.Imaging;

using Windows.Foundation;

#else

using System.Windows.Controls;

using System.Windows;

using System.Windows.Media;

using System.Windows.Media.Imaging;

#endif

For the actual sharing we can use the Add as a Link option in the Add Exiting Item Dialog:

image

After this, the linked file appears with a different icon in Solution Explorer:

image

Sharing requires some juggling at times. Conditional compilation can always resolve this, but it creates more convoluted code and a maintenance headache.

I typically try to make as much code as possible without conditional compilation.

For example, the SolidColorBrush class is supported on all platforms, but to specify a Blue brush for instance, WPF can use the Brushes.Blue static property. The Brushes class does not exist in Silverlight or WinRT, so there are two options here. Either add such a class to WinRT/Silverlight assemblies (with code sharing) or just use new SolidColorBrush(Colors.Blue) for all platforms.

XAML

XAML is a common feature of all the aforementioned platforms, so naturally there is a tendency to share it. Unfortunately, this is not easy. All platforms support XAML to different extents. Examples:

  • 1. Only WPF and Silverlight 5 support custom markup extensions.
  • 2. The syntax for adding an XML namespace is different in WinRT than in the other platforms.
  • 3. Only WPF supports bindings in style setters.
  • 4. The actual controls, even the common ones, have slightly different properties.

These, and the fact that XAML does not easily support conditional compilation, makes sharing XAML difficult if not impossible. Even if it could have been shared, the maintenance headache would probably be too great.

Sharing XAML comes up most frequently when creating user controls or custom controls. My approach is simply not to share the XAML, but to share the code behind file, since most of it can be shared even when defining dependency properties, setting code level bindings, etc.

Hopefully, in the future, all the various flavors of XAML would converge (hopefully again to the highest level, close to WPF), but until that time, direct XAML sharing is something I typically avoid.

Tip

With conditional compilation, some code will be grayed out, with intellisense not working for that. If you want to switch to the “other” platform, first close the document and then re-open it from the appropriate project (whether that file is really there or it’s just a link).

Happy sharing!

 

 

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*