Skip to content

Instantly share code, notes, and snippets.

@itajaja
Created November 17, 2013 00:04
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itajaja/7507120 to your computer and use it in GitHub Desktop.
Save itajaja/7507120 to your computer and use it in GitHub Desktop.
TrulyObservableCollection fires collectionchanged also when items change
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace Hylasoft.OrdersGui.Utils
{
public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public TrulyObservableCollection()
{
CollectionChanged += TrulyObservableCollection_CollectionChanged;
}
void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (Object item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += item_PropertyChanged;
}
}
if (e.OldItems != null)
{
foreach (Object item in e.OldItems)
{
(item as INotifyPropertyChanged).PropertyChanged -= item_PropertyChanged;
}
}
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(a);
}
}
}
@maurosampietro
Copy link

maurosampietro commented Nov 22, 2017

The implementation above suffers several drawbacks that does not make it suitable to use in a WPF MVVM scenario.

The problem is that, unluckily, there is not a 'NotifyCollectionChangedAction.ItemChanged' enum value to raise and we are forced to raise something else (Reset or Replace, etc) that does not really correspond to the actual change we made.

For example imagine you need to bind the ItemsSource of a TabControl to this implementation of TrulyObservableCollection.

Since a change on a value of an item of the collection equals to a change to the RESET of collection itself, the selection is lost on every change to the collection item and the selected tab is collapsed. There's no trickery you can do to avoid this.

My implementation helps in this scenario, introducing a new event to handle ItemChanges separately. I share in the hope it helps.

  public sealed class TrulyObservableCollection<T> : ObservableCollection<T>, ICollectionItemPropertyChanged<T>
     where T : INotifyPropertyChanged
    {
        public event EventHandler<ItemChangedEventArgs<T>> ItemChanged;

        public TrulyObservableCollection()
        {
            CollectionChanged += FullObservableCollectionCollectionChanged;
        }

        public TrulyObservableCollection( IEnumerable<T> pItems ) : this()
        {
            foreach( var item in pItems )
                this.Add( item );
        }
        
        private void FullObservableCollectionCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
        {
            if (e.NewItems != null)
            {
                foreach (Object item in e.NewItems)
                {
                    (item as INotifyPropertyChanged).PropertyChanged += item_PropertyChanged;
                }
            }
            if (e.OldItems != null)
            {
                foreach (Object item in e.OldItems)
                {
                    (item as INotifyPropertyChanged).PropertyChanged -= item_PropertyChanged;
                }
            }
        }

        private void ItemPropertyChanged( object sender, PropertyChangedEventArgs e )
        {
            var args = new ItemChangedEventArgs<T>( (T)sender, e.PropertyName );
            this.ItemChanged?.Invoke( this, args );
        }
    }

    internal interface ICollectionItemPropertyChanged<T>
    {
        event EventHandler<ItemChangedEventArgs<T>> ItemChanged;
    }

    public class ItemChangedEventArgs<T>
    {
        public T ChangedItem { get; }
        public string PropertyName { get; }

        public ItemChangedEventArgs( T item, string propertyName )
        {
            this.ChangedItem = item;
            this.PropertyName = propertyName;
        }
    }

@maztan
Copy link

maztan commented Apr 6, 2019

Your implementation is great and I did introduce new event too, at first. Then I discovered that this new event won't work with BindingOperations.EnableCollectionSynchronization(...), no queuing of those events on GUI thread would occur for example. This makes me feel that without some serious re-implementation the easiest solution is to inherit from NotifyCollectionChangedEventArgs and add "PropertyName" property. Next, use this new args class when rising CollectionChanged event and check for that type in the handlers.

    public class CollectionItemPropertyChangedEventArgs : NotifyCollectionChangedEventArgs
    {

        public CollectionItemPropertyChangedEventArgs(NotifyCollectionChangedAction action, object newItem, object oldItem, int index, string itemPropertyName)
            : base(action, newItem, oldItem, index)
        {
            PropertyName = itemPropertyName ?? throw new ArgumentNullException(nameof(itemPropertyName));
        }

        //
        // Summary:
        //     Gets the name of the collection item's property that changed.
        //
        // Returns:
        //     The name of the collection item's property that changed.
        public virtual string PropertyName { get; }
    }

And when rising the event:

private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    NotifyCollectionChangedEventArgs args = new CollectionItemPropertyChangedEventArgs(
        NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender), e.PropertyName);
    OnCollectionChanged(args);
}

And in handler:

private void MappingEntryModel_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
      if (e is CollectionItemPropertyChangedEventArgs e1)
      {
           if (e1.PropertyName == "SomeProperty")
           {
                //deal with item property change
           }
       }
}

@lgaudouen
Copy link

Do you have the latest version of this excellent source?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment