Using ObservableCollection with WinRT (via a little shim!)

WInRT introduces a new interface for collection change notification, IObservableVector, which means ObservableCollection no longer works with this framework. In this blog post I will show how you can use ObservableCollection, via a simple adapter, within you WInRT applications.

<ItemsControl ItemsSource="{Binding Path=MyCollection, Converter={StaticResource ObservableCollectionAdapter}}"/>

Developers are slowly starting to get their heads around the differences between the old (Silverlight, WPF, WP7) and the new - WinRT. Whilst the it has a familiar feel, with the UI defined in XAML and an API that is very similar to .NET, there are a great many differences. For an overview of some of these differences see:

A number of developers have discovered that while WinRT has the ObservableCollection class, when bound to the UI, lists / grids fail to update as items are added / removed. This is because while the same interfaces are being used for collections (ILIst, IEnumerable etc ...), there is a new interface for notification of collection changes, IObservableVector. Because ObservableCollection implements IEnumerable its contents will be rendered in the UI, however it implements the 'old' interface for change notification - INotifyCollectionChanged.

The WinRT SDK samples include classes that implement IObservableVector to demonstrate binding with collection change handling, however, what if you have some old code using ObservableCollection that you want to port over? Or, what if you want to code-share between Silverlight and WInRT?

A few days ago I saw a blog post by Avi Pilosof where he created a class that implements both the INotifyCollectionChanged and IObservableVector interfaces. This will certainly do the job, however there is a simpler way ...

Using the Gang of Four Adapter pattern, we can create a class which wraps ObservableCollection, to implement IObservableVector:

/// <summary>
/// Adapts an ObservableCollection to implement IObservableVector
/// </summary>
public class ObservableCollectionShim<T> : IObservableVector<T>
{
  private ObservableCollection<T> _adaptee;

  public ObservableCollectionShim(ObservableCollection<T> adaptee)
  {
    _adaptee = adaptee;
    _adaptee.CollectionChanged += Adaptee_CollectionChanged;
  }
  ...
}

IObservableVector extends various list interfaces (ILIst etc ...) so we have to implement these on ObservableCollectionShim via straight-through adapter methods ...

public int IndexOf(T item)
{
  return _adaptee.IndexOf(item);
}

public void Insert(int index, T item)
{
  _adaptee.Insert(index, item);
}

public void RemoveAt(int index)
{
  _adaptee.RemoveAt(index);
}

// etc ...

Now, the interesting part! The change notification is implemented by handling CollectionChanged events, and re-emitting them as VectorChanged events:

/// <summary>
/// Handles and adapts CollectionChanged events
/// </summary>
private void Adaptee_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
  VectorChangedEventArgs args = new VectorChangedEventArgs();

  switch (e.Action)
  {
    case NotifyCollectionChangedAction.Add:
      args.CollectionChange = CollectionChange.ItemInserted;
      args.Index = (uint)e.NewStartingIndex;
      break;

    case NotifyCollectionChangedAction.Remove:
      args.CollectionChange = CollectionChange.ItemRemoved;
      args.Index = (uint)e.OldStartingIndex;
      break;

    case NotifyCollectionChangedAction.Replace:
      args.CollectionChange = CollectionChange.ItemChanged;
      args.Index = (uint)e.NewStartingIndex;
      break;

    case NotifyCollectionChangedAction.Reset:
    case NotifyCollectionChangedAction.Move:
      args.CollectionChange = CollectionChange.Reset;
      break;
    }
  OnVectorChanged(args);
}

Note, there does not seem to be an equivalent for NotifyCollectionChangedAction.Move, however, I am pretty sure ObservableCollection never raises that change type anyway!

We can now make use of our 'shim' class within a view-model as follows:

private ObservableCollection<string> _items;

public IObservableVector<string> Items
{
  get
  {
    return  new ObservableCollectionShim<string>_items;
  }
}

However, if we were using this view-model within a cross-platform application, this would still cause problems, because IObservableCollection does not exist in Silveright / WPF. A more elegant solution is to wrap the ObservableCollection in the shim class within a value converter. Firstly, we need a way to create a shim without specifying the generic type argument (IValueConverter is not strongly typed, your bound values are presented as the type 'object'):

public static class ExtensionMethods
{
  /// <summary>
  /// Creates an ObservableCollectionShim that adapts this ObservableCollection.
  /// </summary>
  public static object ToObservableVector(this INotifyCollectionChanged collection)
  {
    Type genericItemType = collection.GetType().GenericTypeArguments[0];
    Type shimType = typeof(ObservableCollectionShim<>);
    Type genericShimType = shimType.MakeGenericType(new Type[] { genericItemType });
    return Activator.CreateInstance(genericShimType, new object[] { collection });
  }
}

This can then be used within a value converter as follows:

public class ObservableCollectionAdapter : IValueConverter
{
  public object Convert(object value, string typeName, object parameter, string language)
  {
    return ((INotifyCollectionChanged)value).ToObservableVector();
  }

  public object ConvertBack(object value, string typeName, object parameter, string language)
  {
    throw new NotImplementedException();
  }
}

This allows us to adapt an ObservableCollection for use with WinRT simply by applying a value-converter within the View:

<ItemsControl ItemsSource="{Binding Path=MyCollection, Converter={StaticResource ObservableCollectionAdapter}}"/>

A nice side-effect of this approach is that if we replace the ObservableCollection within our view-model with a new instance, the binding framework will take care of creating a new 'shim' via the value converter.

This technique allows us to write cross-platform view-models using ObservableCollection for our collections of objects.

You can download the sourcecode with a simple example here: ObservableCollectionShim.zip

Regards, Colin E.

MORE BY COLIN

gifbot - Building a GitHub App

blog comments powered by Disqus