Ask a questionAsk a question
 

AnswerUsage of INotifyCollectionChanged

  • Sunday, February 11, 2007 5:45 PMBillr17 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    I have a need to implement INotifyCollectionChanged myself, and cannot seem to get it working. With INotifyPropertyChanged it is a matter of implementing the event and making sure the binding has UpdateSourceTrigger=PropertyChanged set. I believe I have correctly implemented the event properly for INotifyCollectionChanged , but am missing a flag that needs set. What is the proper way to go about implementing INotifyCollectionChanged (Any links to examples would be appreciated, the SDK seems to not have much on this subject). Thanks.

Answers

  • Monday, February 12, 2007 3:41 PMKarl HulmeModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Hi Bill,

    Sorry I jumped the gun a bit there.  You're original post is very clear.  I understand the problem to be that the consumer of your custom collection (e.g. a listbox/combobox) isn't attaching to the CollectionChanged event.

    In order for a custom collection to be consumed it must implement the IEnumerable interface, which means you'll have to provide an implementation for IEnumerator as well.  The control binding will check for the IEnumerable interface BEFORE it looks for an implementation INotifyCollectionChanged which explains the behaviour you're seeing.  If it doesn't find the IEnumerable interface it won't go any further.  You can prove this to yourself using the following (slightly questionable!) code...

        public partial class Window1 : System.Windows.Window
        {

            public Window1()
            {
                InitializeComponent();
            }

            private void ButtonClick(Object sender, RoutedEventArgs e)
            {
                // debug method to see if the event is raised
                Model.GetItems().Add("Hello");
            }
        }

        public static class Model
        {
            private static MyCustomCollection col = new MyCustomCollection();

            public static MyCustomCollection GetItems()
            {
                return col;
            }
        }

        public class MyCustomCollection : INotifyCollectionChanged, IEnumerable, IEnumerator
        {
            public event NotifyCollectionChangedEventHandler CollectionChanged;

            protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
            {
                if (CollectionChanged != null)
                {
                    CollectionChanged(this, e);
                }
            }

            public void Add(Object o)
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, o));
            }

            public void Remove(Object o)
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, 0));
            }

            public void Clear()
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }

            public void Move(Object o, Int32 newIndex)
            {
                Int32 oldIndex = 0; // can get the old index position using collection.IndexOf(o);
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move,
                    o, newIndex, oldIndex));
            }

            public Object this[Int32 index]
            {
                get
                {
                    return null; // return collection[index];
                }
                set
                {
                    Object oldValue = null;  // get old value using collection[index];
                    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
                        value, oldValue));
                }
            }

            #region IEnumerable Members

            public IEnumerator GetEnumerator()
            {
                return this;
            }

            #endregion

            #region IEnumerator Members

            public object Current
            {
                get { return null; }
            }

            public bool MoveNext()
            {
                return false;
            }

            public void Reset()
            {
                return;
            }

            #endregion
        }

    First is the partial class definition which just defines a button click event handler which attempts to add something to the collection.  The collection won't honour this but it will attempt to raise the event.  Next, I've defined a static model class which exposes the custom collection.  Notice this now implements both INotifyCollectionChanged AND IEnumerator.  You can implement IEnumerable in a separate class if you prefer, if you don't you should probably implement it explicitly.  Stick a break point inside the CollectionChanged event and you should see that an eventhandler has been attached.  To test it you can use the xaml below...

    <Window x:Class="wpfforum02.Window1"
        xmlns="
    http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="
    http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:this="clr-namespace:wpfforum02"
        Title="wpfforum02" Height="300" Width="300"
        >
      <Window.Resources>
        <ObjectDataProvider x:Key="myCollection" ObjectType="{x:Type this:Model}" MethodName="GetItems" />
      </Window.Resources>
     
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="0.2*" />
          <RowDefinition />
        </Grid.RowDefinitions>

        <Button Grid.Row="0" Content="Test Event" Click="ButtonClick" />
        <ListBox Grid.Row="1" ItemsSource="{Binding Source={StaticResource myCollection}}">
          <ListBox.ItemTemplate>
            <DataTemplate>
              <TextBlock Text="Item Found !!" />
            </DataTemplate>
          </ListBox.ItemTemplate>
        </ListBox>
      </Grid>
    </Window>

    Hope this helps.

All Replies

  • Monday, February 12, 2007 1:37 PMKarl HulmeModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    Hi Bill,

    Not sure exactly what you're after from your question.  I think it would help if you explained why you need to re-implement INotifyCollectionChanged, as this is provided by the ObservableCollection class and can be subclassed or encapsulated into other classes as desired.

    If you have to implement INotifyCollectionChanged yourself then you're after something along the following lines...

        public class MyCustomCollection : INotifyCollectionChanged
        {
            public event NotifyCollectionChangedEventHandler CollectionChanged;

            protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
            {
                if (CollectionChanged != null)
                {
                    CollectionChanged(this, e);
                }
            }

            public void Add(Object o)
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, o));
            }

            public void Remove(Object o)
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, 0));
            }

            public void Clear()
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }

            public void Move(Object o, Int32 newIndex)
            {
                Int32 oldIndex = 0; // can get the old index position using collection.IndexOf(o);
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move,
                    o, newIndex, oldIndex));
            }

            public Object this[Int32 index]
            {
                get
                {
                    return null; // return collection[index];
                }
                set
                {
                    Object oldValue = null;  // get old value using collection[index];
                    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
                        value, oldValue));
                }
            }
        }

    In blue I've implemented the only member of the INotifyCollectionChanged interface and provided a virtual method to call it - standard stuff.  Then below in black is a set of example public methods that a consumer might call, all of which result in the CollectionChanged event being raised.  Notice that you need to use a different overloaded constructor of the NotifyCollectionChangedEventArgs object depending on the type of change that has occurred within the collection.  I've put in comments how you might get the necessary values if you had another collection to work with.

    Since I don't know what you're backing store is, it's difficult to provide anything really concrete.  Hopefully this will get you started or perhaps you could explain exactly where you're getting stuck?

  • Monday, February 12, 2007 2:24 PMBillr17 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    The reason I need to roll my own thing, is that I need to wrap an existing piece of data for data binding that I do not have access to the source code. Therefore I simply want to wrap the data structure so that I get add/delete data binding functionality.

    More simply, I had actually tried something very similar to what you posted above. The problem seems to be that the CollectionChanged event will always remain null. Therefore nothing receives the triggered events. I noticed that with INotifyPropertyChanged, the same thing occurs if the UpdateSourceTrigger is not set to PropertyChanged. I was thinking that perhaps there was something similar that needed set for ICollectionChanged as well?

     

  • Monday, February 12, 2007 3:41 PMKarl HulmeModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    Hi Bill,

    Sorry I jumped the gun a bit there.  You're original post is very clear.  I understand the problem to be that the consumer of your custom collection (e.g. a listbox/combobox) isn't attaching to the CollectionChanged event.

    In order for a custom collection to be consumed it must implement the IEnumerable interface, which means you'll have to provide an implementation for IEnumerator as well.  The control binding will check for the IEnumerable interface BEFORE it looks for an implementation INotifyCollectionChanged which explains the behaviour you're seeing.  If it doesn't find the IEnumerable interface it won't go any further.  You can prove this to yourself using the following (slightly questionable!) code...

        public partial class Window1 : System.Windows.Window
        {

            public Window1()
            {
                InitializeComponent();
            }

            private void ButtonClick(Object sender, RoutedEventArgs e)
            {
                // debug method to see if the event is raised
                Model.GetItems().Add("Hello");
            }
        }

        public static class Model
        {
            private static MyCustomCollection col = new MyCustomCollection();

            public static MyCustomCollection GetItems()
            {
                return col;
            }
        }

        public class MyCustomCollection : INotifyCollectionChanged, IEnumerable, IEnumerator
        {
            public event NotifyCollectionChangedEventHandler CollectionChanged;

            protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
            {
                if (CollectionChanged != null)
                {
                    CollectionChanged(this, e);
                }
            }

            public void Add(Object o)
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, o));
            }

            public void Remove(Object o)
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, 0));
            }

            public void Clear()
            {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }

            public void Move(Object o, Int32 newIndex)
            {
                Int32 oldIndex = 0; // can get the old index position using collection.IndexOf(o);
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move,
                    o, newIndex, oldIndex));
            }

            public Object this[Int32 index]
            {
                get
                {
                    return null; // return collection[index];
                }
                set
                {
                    Object oldValue = null;  // get old value using collection[index];
                    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
                        value, oldValue));
                }
            }

            #region IEnumerable Members

            public IEnumerator GetEnumerator()
            {
                return this;
            }

            #endregion

            #region IEnumerator Members

            public object Current
            {
                get { return null; }
            }

            public bool MoveNext()
            {
                return false;
            }

            public void Reset()
            {
                return;
            }

            #endregion
        }

    First is the partial class definition which just defines a button click event handler which attempts to add something to the collection.  The collection won't honour this but it will attempt to raise the event.  Next, I've defined a static model class which exposes the custom collection.  Notice this now implements both INotifyCollectionChanged AND IEnumerator.  You can implement IEnumerable in a separate class if you prefer, if you don't you should probably implement it explicitly.  Stick a break point inside the CollectionChanged event and you should see that an eventhandler has been attached.  To test it you can use the xaml below...

    <Window x:Class="wpfforum02.Window1"
        xmlns="
    http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="
    http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:this="clr-namespace:wpfforum02"
        Title="wpfforum02" Height="300" Width="300"
        >
      <Window.Resources>
        <ObjectDataProvider x:Key="myCollection" ObjectType="{x:Type this:Model}" MethodName="GetItems" />
      </Window.Resources>
     
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="0.2*" />
          <RowDefinition />
        </Grid.RowDefinitions>

        <Button Grid.Row="0" Content="Test Event" Click="ButtonClick" />
        <ListBox Grid.Row="1" ItemsSource="{Binding Source={StaticResource myCollection}}">
          <ListBox.ItemTemplate>
            <DataTemplate>
              <TextBlock Text="Item Found !!" />
            </DataTemplate>
          </ListBox.ItemTemplate>
        </ListBox>
      </Grid>
    </Window>

    Hope this helps.

  • Monday, February 12, 2007 4:37 PMBillr17 Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Thanks, thats what I was looking for. Not much documentation on this. Either I over looked the fact I need IEnumerator or it wasn't documented. Thanks for the examples.
  • Tuesday, October 14, 2008 4:31 PMmurki Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi Karl,

    I seem to be running into the same problem. But actually I'm inheriting from a class that already implements the IEnumerable<T> interface, and also I'm implementing the INotifyCollectionChanged interface. I'm raising my CollectionChanged event appropriately but is always null. I'm binding to a ListBox's ItemsSource property. So, would you have any idea as why this could be happening?

    The class I'm inheriting from is already implementing its own Add and Remove methods, and is also raising its own custom events, I'm just subscribing to these events and raising the INotifyCollectionChanged equivalents in order to notify the UI automatically.

    Any tips would be greatly appreciated,

    Thanks and Regards,
    -Miguel Juarez
  • Tuesday, December 02, 2008 12:21 AMDavid Veeneman Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Thanks Karl--this post was very helpful to me in creating an ObservableSet for use with NHibernate and WPF. I basically subclassed HashSet<T> to implement INotifyCollectionChanged.
  • Tuesday, December 02, 2008 7:29 PMJonathanPugh Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Did you get anywhere with this?  I am having similar problems :(
    -Jonathan
  • Tuesday, December 02, 2008 7:38 PMDavid Veeneman Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Not much luck subclassing HashSet<T>, so I'm looking at an implementation using the NHibernate IUserCollectionType interface. Here are a couple of links that may be helpful:

    http://happynomad121.blogspot.com/2007/12/collections-for-wpf-and-nhibernate.html

    http://analog-man.blogspot.com/2007/01/bridge-gap-between-your-nhibernate.html
  • Monday, February 02, 2009 11:39 AMwherewerewe Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Proposed AnswerHas Code
    I'm not sure if my suggestion will help. But I have somewhat managed to get my itemscontrol that is bound to a observablecollection to rebind when the collection has changed. You'll need to make sure that the UpdateSourceTrigger property is set.

    ItemsSource="{Binding YourObservableCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 

    The most painful part - was to clear the ObservableCollection and re-add the items.

                _yourObservableCollection.Clear(); 
                foreach (YourObject yourObj in _yourObjects) 
                { _yourObservableCollection.Add(yourObj); } 

    This may look and probably sound stupid - but I found that working with WPF - you have to think slightly differently to get things working.


    • Proposed As Answer bywherewerewe Monday, February 02, 2009 2:09 PM
    •  
  • Wednesday, October 21, 2009 2:41 PMburton Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Just a note for anyone who is still having problems with this:  ObservableCollection implements BOTH INotifyCollectionChanged and INotifyPropertyChanged.  Whenever the CollectionChanged event is raised, it also raises PropertyChanged events for Count and Item[].