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:
- TweetSearch – A Cross platform Metro UI WinRT
- Morten Nielsen’s blog series on WInRT vs. Silverlight differences
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.
Tags: cross-platform, ObservableCollection, WinRT


email: ceberhardt@scottlogic.co.uk
on Google+



[...] with an implementation! So to bind to collections, you need to build your own collection class or grab a shim (as I chose [...]
Hi,
I came up with a modified implementation of ObservableVector which allows me to add a property only in Windows 8, and to bind to that property. In Silverlight/WPF/Windows Phone, you just bind to the original ObservableCollection. I think it is a convenient way to do things while we wait for the release of the bug fix.
http://blog.galasoft.ch/archive/2012/02/19/solving-the-observablecollection-bug-on-windows-8.aspx
Cheers,
Laurent
Unfortunately, I wasn’t able to get either solution to work. The original post only works with a collection of objects, which avoids much of the point
The code below doesn’t quite get the object model correct — all sorts of things will get screwed up if someone manages to retrieve a handle to the shim collection and write to it, because it will allow that. However, updates to the underlying collection are handled appropriately, and this allows for the use of a strongly typed collection in the program code.
And it’s much much shorter
public class ObservableCollectionAdapter : IValueConverter
{
internal class ObservableVector : List, IObservableVector
{
internal class VectorChangedEventArgs : IVectorChangedEventArgs
{
public CollectionChange CollectionChange { get; set; }
public uint Index { get; set; }
internal VectorChangedEventArgs(CollectionChange change, int index)
{
CollectionChange = change;
Index = (uint)index;
}
}
private INotifyCollectionChanged notifyCollectionChanged;
private ICollection collection;
public ObservableVector(INotifyCollectionChanged notifyCollectionChanged, ICollection collection)
{
this.notifyCollectionChanged = notifyCollectionChanged;
this.collection = collection;
Refresh();
notifyCollectionChanged.CollectionChanged += new NotifyCollectionChangedEventHandler(OriginalCollectionChanged);
}
private void Refresh()
{
this.Clear();
this.AddRange(collection.Cast());
}
void OriginalCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
VectorChangedEventArgs converted;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
converted = new VectorChangedEventArgs(CollectionChange.ItemInserted, e.NewStartingIndex);
this.InsertRange(e.NewStartingIndex, e.NewItems.Cast());
break;
case NotifyCollectionChangedAction.Remove:
converted = new VectorChangedEventArgs(CollectionChange.ItemRemoved, e.OldStartingIndex);
for (int i = 0; i < e.OldItems.Count; i++)
{
this.RemoveAt(e.OldStartingIndex);
}
break;
case NotifyCollectionChangedAction.Replace:
converted = new VectorChangedEventArgs(CollectionChange.ItemChanged, e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Reset:
default:
converted = new VectorChangedEventArgs(CollectionChange.Reset, 0);
Refresh();
break;
}
VectorChangedEventHandler vectorChanged = VectorChanged;
if (vectorChanged != null)
{
vectorChanged(this, converted);
}
}
public event VectorChangedEventHandler VectorChanged;
}
public object Convert(object value, string typeName, object parameter, string language)
{
return new ObservableVector((INotifyCollectionChanged)value, (ICollection)value);
}
public object ConvertBack(object value, string typeName, object parameter, string language)
{
throw new NotImplementedException();
}
}
If you are interested I have an alternative implementation that I discuss at http://andyonwpf.blogspot.com/2011/12/observablevector-as-replacement-for.html. This is a native implementation for Metro-style applications so does not have any of the overhead of wrapping an ObservableCollection class.
Hi Andy – yes, useful stuff. However my intention here was to add a wrapper that makes it easier to write cross platform Silverlight / WinRT code. But I agree, if you are just using WinRT, your code is much better!
There is bug related to IObservableVector only working with . I tried using this code on some WCF proxies, but it didn’t work out of the box. I modified it below to convert to the working IObservable. This fixed my problem and was able to successfully bind to a list box with the proper notifications occuring. Hopefully this will help others with this problem:
///
/// Adapts an ObservableCollection to implement IObservableVector
///
public class ObservableCollectionShim : IObservableVector
{
private IList _adaptee;
public ObservableCollectionShim(ObservableCollection adaptee)
{
_adaptee = adaptee;
(_adaptee as ObservableCollection).CollectionChanged += Adaptee_CollectionChanged;
}
///
/// Handles and adapts CollectionChanged events
///
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);
}
#region IObservableVector interface
public event VectorChangedEventHandler VectorChanged;
protected void OnVectorChanged(IVectorChangedEventArgs args)
{
if (VectorChanged != null)
{
VectorChanged(this, args);
}
}
#endregion
#region IList implementation
public int IndexOf(object item)
{
return _adaptee.IndexOf(item);
}
public void Insert(int index, object item)
{
_adaptee.Insert(index, item);
}
public void RemoveAt(int index)
{
_adaptee.RemoveAt(index);
}
public object this[int index]
{
get
{
return _adaptee[index];
}
set
{
_adaptee[index] = value;
}
}
public void Add(object item)
{
_adaptee.Add(item);
}
public void Clear()
{
_adaptee.Clear();
}
public bool Contains(object item)
{
return _adaptee.Contains(item);
}
public void CopyTo(object[] array, int arrayIndex)
{
_adaptee.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _adaptee.Count; ; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(object item)
{
if (_adaptee.Contains(item))
{
_adaptee.Remove(item);
return true;
}
return false;
}
public IEnumerator GetEnumerator()
{
return _adaptee.GetEnumerator() as IEnumerator;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _adaptee.GetEnumerator();
}
#endregion
}
That’s great information ! Just saved me a lot of work… Thanks
It is stuff like this (and the fact that they crippled a significant portion of System.Collections.Generic) that makes me think the Xaml + C# is just going to be another Silverlight: A doppelganger framework and halfass wanna-be WPF. Sad stuff.