次の方法で共有


Using ObservableCollection with the Portable Library Tools CTP

One of the main goals of the Portable Library Tools is to be able to implement your ViewModel in the Portable Library such that you can share it between your UI in your Silverlight, Windows Phone, or even WPF project.  Unfortunately, this is a bit of a challenge with the Portable Library Tools CTP, because it does not include ObservableCollection<T> or INotifyCollectionChanged.

The reason for this is simple.  The CTP doesn’t have references for System.Windows, and both ObservableCollection<T> and INotifyCollectionChanged are defined in System.Windows.  That they are defined in this assembly isn’t the best in terms of layering, and we have been discussing what to do about that.  But they are there, and we’re looking to address this issue in a future release of the Portable Library Tools.  But that doesn’t help you now, does it?

In fact, ObservableCollection is one of a few common classes that you may find you would like to be able to address from your portable code.  The other one that I’ve run into is HttpUtility.UrlEncode/UrlDecode, so I’ll handle that here as well.

There are two ways to handle this, in general:

  1. If you have a common class or interface that is available to all callers, you can key off of you can use a service locator pattern or some kind of dependency injection (ISomething impl = Factory.GetInstance<ISomething>();)
  2. If you don’t have a common class or interface, you can create a class that allows you to define the specific operations you’re interested in

In the case of ObservableCollection, we have case (2).  Because ObservableCollection/INotifyCollectionChanged aren’t available as part of the Portable Library Tools references.  So there is no way to do the following:

  public class MyViewModel {
        private ObservableCollection<string> _strings = new ObservableCollection<string>();
        public ObservableCollection<string> Strings {
            get {
                return _strings;
            }
        }
    }

This is an interesting case where a Dependency Injection strategy won’t work.

However, if we look closer at ObservableCollection, we see that it derives from IList, which has the methods that we care about (Add, Remove, etc.).  But what we really care about this is consumers of our ViewModel – specifically Silverlight’s databinding engine.   Fortunately, the databinding engine uses reflection to check for INotifyCollectionChanged on the collection itself rather than the object being used as the DataContext.  So it doesn’t matter what the type is on our ViewModel, it matters what type that object actually is.

With this knowledge in hand, our strategy is simple, and we implement it as follows.

First, we type our ViewModel collection property as IList:

  public class PortableViewModel : INotifyPropertyChanged {
        private IList<string> _stringList;
        public IList<string> Strings {
            get {                
                return _stringList;
            }
        }
}

Now, we define a class that is abstract in our Portable Library, but will be implemented in the application projects.  The idea is to keep this as simple as possible:

     public abstract class PortabilityFactory {
        private static PortabilityFactory _factory;

        public static PortabilityFactory Current {
            get {
                if (_factory == null) {
                    throw new InvalidOperationException();
                }
                return _factory;
            }
            set {
                _factory = value;
            }
        }

        protected PortabilityFactory() {
           // note we auto-set current here,
           // which saves an extra step for the caller
            Current = this;            
        }

        public virtual IList<T> CreateList<T>() {
            return new List<T>();
        }

        public abstract string UrlEncode(string url);
        public abstract string UrlDecode(string url);
    }

We go back to our ViewModel, and specify the right call:

  public IList<string> Strings {
       get {
           if (_stringList == null) {
               _stringList = PortabilityFactory.Current.CreateList<string>();
           }
           return _stringList;
        }
}

Now, we go to our project and implement.  Here’s the Phone example, and the Silverlight example, respectively.  The best place to create this is in App.xaml.cs so it happens at app start up.

 namespace WindowsPhoneApplication1 {
    public partial class App : Application {

        private PortabilityFactory _pf = new PhonePortabilityFactory();

        private class PhonePortabilityFactory : PortabilityFactory {
            public override IList<T> CreateList<T>() {
                return new ObservableCollection<T>();
            }

            public override string UrlDecode(string url) {
                return System.Net.HttpUtility.UrlDecode(url);
            }

            public override string UrlEncode(string url) {
                return System.Net.HttpUtility.UrlEncode(url);
            } 
}

 

 namespace SilverlightApplication1
{
    public partial class App : Application
    {
        private PortabilityFactory _pf = new SilverlightPortabilityFactory();

        private class SilverlightPortabilityFactory : PortabilityFactory {
            public override IList<T> CreateList<T>() {
                return new ObservableCollection<T>();
            }

            public override string UrlDecode(string url) {
                return System.Windows.Browser.HttpUtility.UrlDecode(url);
            }

            public override string UrlEncode(string url) {
                return System.Windows.Browser.HttpUtility.UrlEncode(url);
            }
        }
}

Now you’ve done all you need to, and the rest of the system will work properly, and you can use the ViewModel directly as a datacontext:

 public MainPage()
{
      InitializeComponent();
      this._vm = new PortableObservableCollection.PortableViewModel();
      this.DataContext = _vm;
}

Even when Portable Library Tools reaches it’s version 1, there will still be things that aren’t in the portable subset.  You can use variations of the above techniques to handle those types as well.  For some more complex features, you can use more of a Service Locator or Dependency Injection pattern.   An example of this in the CTP is IsolatedStorage, which is not currently in the subset.   For this you can imagine creating an IFileStorage interface and having it’s implementation plumb through to the Isolated Storage APIs, but use it generically in your portable library code.  We are looking to minimize these situations as time goes on.

I’ve attached the project from which the code snippets came.  Note you’ll need to install the Visual Studio 2010 SP1 Beta, and the Portable Library Tools CTP.

PortableObservableCollection.zip

Comments

  • Anonymous
    April 06, 2011
    As of 3/31/2011, the method for "faking" ObservableCollections is obsolete, as least as long you're only targeting Silverlight and Silverlight for Windows Phone 7 - the namespace System.Windows is available now inside libraries targeting only those platforms.