Development Tip – ObservableCollection doesn’t work


NOTE: This post applied in an the early preview of Win8. It’s no longer applicable because the bug in question no longer exists; ObservableCollection works just great 🙂

Original article below.

 

 

Just getting started with Win8 development, and will note what I learn in a series of posts.

 

I’m finding that ObservableCollection isn’t being honored by XAML elements in the //BUILD version of the tools. More specifically, either it’s not firing collection changed events, or it is but they’re being ignored. Seems this is a known limitation of the current dev drop, and there’s an ObservableVector<object> which does work. However, using that throughout your code hurts because of all the casting required; it will also be messy to change when the tools catch up.

Until then, I created this wrapper that derives from ObservableCollection<T>, but also implements IObservableVector<object>. When the tools are fixed, you should be able to do a simple search/replace with ObservableCollection.

Please note that this isn’t tested, it’s not serious work, it’s 5 minutes of Band-Aid which “seems to work on my machine”. Use at own risk, etc.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Windows.Foundation.Collections;
 
namespace W8Shared
{
    public class BindableObservableCollection<T> : ObservableCollection<T>, IObservableVector<object>
    {
        public event VectorChangedEventHandler<object> VectorChanged;
 
        public int IndexOf(object item)
        {
            return base.IndexOf((T)item);
        }
 
        public void Insert(int index, object item)
        {
            base.Insert(index, (T)item);
            RaiseChange(CollectionChange.ItemInserted, (uint)index);
        }
 
        private void RaiseChange(CollectionChange type, uint index)
        {
            if (VectorChanged != null)
                VectorChanged(this, new VectorChangedEventArgs(type, index));
        }
 
        public new object this[int index]
        {
            get
            {
                return (object)base[index];
            }
            set
            {
                base[index] = (T)value;
                RaiseChange(CollectionChange.ItemChanged, (uint)index);
            }
        }
 
        public void Add(object item)
        {
            base.Add((T)item);
            RaiseChange(CollectionChange.ItemInserted, (uint)(base.Count-1));
        }
 
        public bool Contains(object item)
        {
            return base.Contains((T)item);
        }
 
        public void CopyTo(object[] array, int arrayIndex)
        {
            throw new NotImplementedException();
        }
 
        public bool IsReadOnly
        {
            get { throw new NotImplementedException(); }
        }
 
        public bool Remove(object item)
        {
            int index = base.IndexOf((T)item);
            if (index >= 0)
            {
                var res = base.Remove((T)item);
                RaiseChange(CollectionChange.ItemRemoved, (uint)index);
                return res;
            }
 
            return false;
        }
 
        private VectorEnumerator<T> _Enum;
        public new IEnumerator<object> GetEnumerator()
        {
            if (_Enum == null)
            {
                _Enum = new VectorEnumerator<T>(this);
            }
 
            return _Enum;            
        }
    }
 
    public class VectorChangedEventArgs : IVectorChangedEventArgs
    {
        public VectorChangedEventArgs(CollectionChange type, uint index)
        {
            CollectionChange = type;
            Index = index;
        }
        public CollectionChange CollectionChange { get; set; }
        public uint Index { get; set; }
    }
 
    public class VectorEnumerator<T> : IEnumerator<object>
    {
        private IEnumerable<T> _Coll;
        private IEnumerator<T> _Enum;
 
        public VectorEnumerator(IEnumerable<T> internalColl)
        {
            _Coll = internalColl;
            _Enum = _Coll.GetEnumerator();
        }
 
        public object Current
        {
            get { return (object)_Enum.Current; }
        }
 
 
        public bool MoveNext()
        {
            return _Enum.MoveNext();
        }
 
        public void Reset()
        {
            _Enum.Reset();
        }
 
        public void Dispose()
        {
            _Enum.Dispose();
        }
    }
}
Comments (7)

  1. Jonathan ANTOINE says:

    Nice tips, thank you for sharing It !

    Jonathan ANTOINE

    http://www.jonathanantoine.com

  2. Salfurium says:

    thanks for sharing this.

    I just wanted to correct a little bug:

    the second line of BindableObservableCollection<T>.Remove should read

    if (index >= 0)

    otherwise you won't be able to remove the very last object in the collection.

  3. AviP says:

    Thanks, Salfurium – good catch! Fixed.

  4. Lynn Neir says:

    Hi thanks for code, I was using code above and having some issues.  First issue was the property changed notifications on properties of the collection were not getting pushed through and updated in the UI.  So, I added some code to hook up to property changes when items are added (and then unhook when properties are removed).   Second issue I had was the enumerator was giving me problems after following sequence events: delete item, add item then try to enumerate through the list.  

    Below is the code as I modified:

       public class BindableObservableCollection<T> : ObservableCollection<T>, IObservableVector<object>

       {

           protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)

           {

               base.OnPropertyChanged(e);

           }

           public event VectorChangedEventHandler<object> VectorChanged;

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

           public void Insert(int index, object item)

           {        

               base.Insert(index, (T)item);

               HookToPropertyChanged(item);

               RaiseChange(CollectionChange.ItemInserted, (uint)index);

           }

           void HookToPropertyChanged(object item)

           {

               if (item is INotifyPropertyChanged)

                   (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(BindableObservableCollection_PropertyChanged);

           }

           void BindableObservableCollection_PropertyChanged(object sender, PropertyChangedEventArgs e)

           {

               int index = IndexOf(sender);

               if (index >= 0)

                   RaiseChange(CollectionChange.ItemChanged, (uint) index);

           }

           void UnHookToPropertyChanged(object item)

           {

              if (item is INotifyPropertyChanged)

                   (item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(BindableObservableCollection_PropertyChanged);

           }

           private void RaiseChange(CollectionChange type, uint index)

           {

               if (VectorChanged != null)                

                   VectorChanged(this, new VectorChangedEventArgs(type, index));

           }

           public new object this[int index] { get { return (object)base[index]; } set { base[index] = (T)value; RaiseChange(CollectionChange.ItemChanged, (uint)index); } }

           public void Add(object item)

           {

               base.Add((T)item);

               HookToPropertyChanged(item);

               RaiseChange(CollectionChange.ItemInserted, (uint)(base.Count – 1));

           }

           public bool Contains(object item) { return base.Contains((T)item); }

           public void CopyTo(object[] array, int arrayIndex) { throw new NotImplementedException(); }

           public bool IsReadOnly { get { throw new NotImplementedException(); } }

           public bool Remove(object item)

           {

               int index = base.IndexOf((T)item);

               if (index >= 0)

               {

                   UnHookToPropertyChanged(item);

                   var res = base.Remove((T)item);

                   RaiseChange(CollectionChange.ItemRemoved, (uint)index);

                   return res;

               }

               return false;

           }

           private VectorEnumerator<T> _Enum;

           public new IEnumerator<object> GetEnumerator()

           {

               // ToDo: bug here, always need new enum, needs investigation, likely because we have observerable stored in observerable!

               //if (_Enum == null)

               //{

                   _Enum = new VectorEnumerator<T>(this);

               //}

               return _Enum;

           }

       }

  5. Lynn Neir says:

    Forgot to mention in my last post…that in my case I had observable collection nested within observable collection so that may have been why I had to modify the code as I posted.

  6. Lynn Neir says:

    Sorry forgot to post the code… here is the modified code.  I had two problems: 1. no property updates, so I added code to hook up to property changes when items are added (also unhook when deleted).  2. had problems with enumerator when I did the following actions: deleted item in collection, added item then tried to enumerator through he list; seems it needs new enumerator rather than reusing old one – needs investigation!

    Here is code:

       public class BindableObservableCollection<T> : ObservableCollection<T>, IObservableVector<object>

       {

           protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)

           {

               base.OnPropertyChanged(e);

           }

           public event VectorChangedEventHandler<object> VectorChanged;

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

           public void Insert(int index, object item)

           {        

               base.Insert(index, (T)item);

               HookToPropertyChanged(item);

               RaiseChange(CollectionChange.ItemInserted, (uint)index);

           }

           void HookToPropertyChanged(object item)

           {

               if (item is INotifyPropertyChanged)

                   (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(BindableObservableCollection_PropertyChanged);

           }

           void BindableObservableCollection_PropertyChanged(object sender, PropertyChangedEventArgs e)

           {

               int index = IndexOf(sender);

               if (index >= 0)

                   RaiseChange(CollectionChange.ItemChanged, (uint) index);

           }

           void UnHookToPropertyChanged(object item)

           {

              if (item is INotifyPropertyChanged)

                   (item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(BindableObservableCollection_PropertyChanged);

           }

           private void RaiseChange(CollectionChange type, uint index)

           {

               if (VectorChanged != null)                

                   VectorChanged(this, new VectorChangedEventArgs(type, index));

           }

           public new object this[int index] { get { return (object)base[index]; } set { base[index] = (T)value; RaiseChange(CollectionChange.ItemChanged, (uint)index); } }

           public void Add(object item)

           {

               base.Add((T)item);

               HookToPropertyChanged(item);

               RaiseChange(CollectionChange.ItemInserted, (uint)(base.Count – 1));

           }

           public bool Contains(object item) { return base.Contains((T)item); }

           public void CopyTo(object[] array, int arrayIndex) { throw new NotImplementedException(); }

           public bool IsReadOnly { get { throw new NotImplementedException(); } }

           public bool Remove(object item)

           {

               int index = base.IndexOf((T)item);

               if (index >= 0)

               {

                   UnHookToPropertyChanged(item);

                   var res = base.Remove((T)item);

                   RaiseChange(CollectionChange.ItemRemoved, (uint)index);

                   return res;

               }

               return false;

           }

           private VectorEnumerator<T> _Enum;

           public new IEnumerator<object> GetEnumerator()

           {

               // ToDo: bug here, always need new enum, needs investigation, likely because we have observerable stored in observerable!

               //if (_Enum == null)

               //{

                   _Enum = new VectorEnumerator<T>(this);

               //}

               return _Enum;

           }

       }

  7. Andrew Wilkinson says:

    If you are interested I have an alternative implementation that I discuss at andyonwpf.blogspot.com/…/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<T> class.

Skip to main content