[c#] Notify ObservableCollection when Item changes

I found on this link

ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)

some techniques to notify a Observablecollection that an item has changed. the TrulyObservableCollection in this link seems to be what i'm looking for.

public class TrulyObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
    public TrulyObservableCollection()
    : base()
    {
        CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
    }

    void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (Object item in e.NewItems)
            {
                (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
            }
        }
        if (e.OldItems != null)
        {
            foreach (Object item in e.OldItems)
            {
                (item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
            }
        }
    }

    void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
        OnCollectionChanged(a);
    }
}

But when I try to use it, I don't get notifications on the collection. I'm not sure how to correctly implement this in my C# Code:

XAML :

    <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyItemsSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <DataGrid.Columns>
            <DataGridCheckBoxColumn Binding="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        </DataGrid.Columns>
    </DataGrid>

ViewModel :

public class MyViewModel : ViewModelBase
{
    private TrulyObservableCollection<MyType> myItemsSource;
    public TrulyObservableCollection<MyType> MyItemsSource
    {
        get { return myItemsSource; }
        set 
        { 
            myItemsSource = value; 
            // Code to trig on item change...
            RaisePropertyChangedEvent("MyItemsSource");
        }
    }

    public MyViewModel()
    {
        MyItemsSource = new TrulyObservableCollection<MyType>()
        { 
            new MyType() { MyProperty = false },
            new MyType() { MyProperty = true },
            new MyType() { MyProperty = false }
        };
    }
}

public class MyType : ViewModelBase
{
    private bool myProperty;
    public bool MyProperty
    {
        get { return myProperty; }
        set 
        {
            myProperty = value;
            RaisePropertyChangedEvent("MyProperty");
        }
    }
}

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChangedEvent(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
            PropertyChanged(this, e);
        }
    }
}

When i run the program, i have the 3 checkbox to false, true, false as in the property initialisation. but when i change the state of one of the ckeckbox, the program go through item_PropertyChanged but never in MyItemsSource Property code.

The answer is


You could use an extension method to get notified about changed property of an item in a collection in a generic way.

public static class ObservableCollectionExtension
{
    public static void NotifyPropertyChanged<T>(this ObservableCollection<T> observableCollection, Action<T, PropertyChangedEventArgs> callBackAction)
        where T : INotifyPropertyChanged
    {
        observableCollection.CollectionChanged += (sender, args) =>
        {
            //Does not prevent garbage collection says: http://stackoverflow.com/questions/298261/do-event-handlers-stop-garbage-collection-from-occuring
            //publisher.SomeEvent += target.SomeHandler;
            //then "publisher" will keep "target" alive, but "target" will not keep "publisher" alive.
            if (args.NewItems == null) return;
            foreach (T item in args.NewItems)
            {
                item.PropertyChanged += (obj, eventArgs) =>
                {
                    callBackAction((T)obj, eventArgs);
                };
            }
        };
    }
}

public void ExampleUsage()
{
    var myObservableCollection = new ObservableCollection<MyTypeWithNotifyPropertyChanged>();
    myObservableCollection.NotifyPropertyChanged((obj, notifyPropertyChangedEventArgs) =>
    {
        //DO here what you want when a property of an item in the collection has changed.
    });
}

I solved this case by using static Action


public class CatalogoModel 
{
    private String _Id;
    private String _Descripcion;
    private Boolean _IsChecked;

    public String Id
    {
        get { return _Id; }
        set { _Id = value; }
    }
    public String Descripcion
    {
        get { return _Descripcion; }
        set { _Descripcion = value; }
    }
    public Boolean IsChecked
    {
        get { return _IsChecked; }
        set
        {
           _IsChecked = value;
            NotifyPropertyChanged("IsChecked");
            OnItemChecked.Invoke();
        }
    }

    public static Action OnItemChecked;
} 

public class ReglaViewModel : ViewModelBase
{
    private ObservableCollection<CatalogoModel> _origenes;

    CatalogoModel.OnItemChecked = () =>
            {
                var x = Origenes.Count;  //Entra cada vez que cambia algo en _origenes
            };
}

I know it's late, but maybe this helps others. I have created a class NotifyObservableCollection, that solves the problem of missing notification to item itself, when a property of the item changes. The usage is as simple as ObservableCollection.

public class NotifyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    private void Handle(object sender, PropertyChangedEventArgs args)
    {
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, null));
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null) {
            foreach (object t in e.NewItems) {
                ((T) t).PropertyChanged += Handle;
            }
        }
        if (e.OldItems != null) {
            foreach (object t in e.OldItems) {
                ((T) t).PropertyChanged -= Handle;
            }
        }
        base.OnCollectionChanged(e);
    }

While Items are added or removed the class forwards the items PropertyChanged event to the collections PropertyChanged event.

usage:

public abstract class ParameterBase : INotifyPropertyChanged
{
    protected readonly CultureInfo Ci = new CultureInfo("en-US");
    private string _value;

    public string Value {
        get { return _value; }
        set {
            if (value == _value) return;
            _value = value;
            OnPropertyChanged();
        }
    }
}

public class AItem {
    public NotifyObservableCollection<ParameterBase> Parameters {
        get { return _parameters; }
        set {
            NotifyCollectionChangedEventHandler cceh = (sender, args) => OnPropertyChanged();
            if (_parameters != null) _parameters.CollectionChanged -= cceh;
            _parameters = value;
            //needed for Binding to AItem at xaml directly
            _parameters.CollectionChanged += cceh; 
        }
    }

    public NotifyObservableCollection<ParameterBase> DefaultParameters {
        get { return _defaultParameters; }
        set {
            NotifyCollectionChangedEventHandler cceh = (sender, args) => OnPropertyChanged();
            if (_defaultParameters != null) _defaultParameters.CollectionChanged -= cceh;
            _defaultParameters = value;
            //needed for Binding to AItem at xaml directly
            _defaultParameters.CollectionChanged += cceh;
        }
    }


public class MyViewModel {
    public NotifyObservableCollection<AItem> DataItems { get; set; }
}

If now a property of an item in DataItems changes, the following xaml will get a notification, though it binds to Parameters[0] or to the item itself except to the changing property Value of the item (Converters at Triggers are called reliable on every change).

<DataGrid CanUserAddRows="False" AutoGenerateColumns="False" ItemsSource="{Binding DataItems}">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Parameters[0].Value}" Header="P1">
            <DataGridTextColumn.CellStyle>
                <Style TargetType="DataGridCell">
                    <Setter Property="Background" Value="Aqua" />
                    <Style.Triggers>
                        <DataTrigger Value="False">
                            <!-- Bind to Items with changing properties -->
                            <DataTrigger.Binding>
                                <MultiBinding Converter="{StaticResource ParameterCompareConverter}">
                                    <Binding Path="DefaultParameters[0]" />
                                    <Binding Path="Parameters[0]" />
                                </MultiBinding>
                            </DataTrigger.Binding>
                            <Setter Property="Background" Value="DeepPink" />
                        </DataTrigger>
                        <!-- Binds to AItem directly -->
                        <DataTrigger Value="True" Binding="{Binding Converter={StaticResource CheckParametersConverter}}">
                            <Setter Property="FontWeight" Value="ExtraBold" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.CellStyle>
        </DataGridTextColumn>

The ObservableCollection and its derivatives raises its property changes internally. The code in your setter should only be triggered if you assign a new TrulyObservableCollection<MyType> to the MyItemsSource property. That is, it should only happen once, from the constructor.

From that point forward, you'll get property change notifications from the collection, not from the setter in your viewmodel.


One simple solution to this is to replace the item being changed in the ObservableCollection which notifies the collection of the changed item. In the sample code snippet below Artists is the ObservableCollection and artist is an item of the type in the ObservableCollection:

    var index = Artists.IndexOf(artist);
    Artists.RemoveAt(index);
    artist.IsFollowed = true; // change something in the item
    Artists.Insert(index, artist);

A simple solution is to use BindingList<T> instead of ObservableCollection<T> . Indeed the BindingList relay item change notifications. So with a binding list, if the item implements the interface INotifyPropertyChanged then you can simply get notifications using the ListChanged event.

See also this SO answer.


All the solutions here are correct,but they are missing an important scenario in which the method Clear() is used, which doesn't provide OldItems in the NotifyCollectionChangedEventArgs object.

this is the perfect ObservableCollection .

public delegate void ListedItemPropertyChangedEventHandler(IList SourceList, object Item, PropertyChangedEventArgs e);
public class ObservableCollectionEX<T> : ObservableCollection<T>
{
    #region Constructors
    public ObservableCollectionEX() : base()
    {
        CollectionChanged += ObservableCollection_CollectionChanged;
    }
    public ObservableCollectionEX(IEnumerable<T> c) : base(c)
    {
        CollectionChanged += ObservableCollection_CollectionChanged;
    }
    public ObservableCollectionEX(List<T> l) : base(l)
    {
        CollectionChanged += ObservableCollection_CollectionChanged;
    }

    #endregion



    public new void Clear()
    {
        foreach (var item in this)            
            if (item is INotifyPropertyChanged i)                
                i.PropertyChanged -= Element_PropertyChanged;            
        base.Clear();
    }
    private void ObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.OldItems != null)
            foreach (var item in e.OldItems)                
                if (item != null && item is INotifyPropertyChanged i)                    
                    i.PropertyChanged -= Element_PropertyChanged;


        if (e.NewItems != null)
            foreach (var item in e.NewItems)                
                if (item != null && item is INotifyPropertyChanged i)
                {
                    i.PropertyChanged -= Element_PropertyChanged;
                    i.PropertyChanged += Element_PropertyChanged;
                }
            }
    }
    private void Element_PropertyChanged(object sender, PropertyChangedEventArgs e) => ItemPropertyChanged?.Invoke(this, sender, e);


    public ListedItemPropertyChangedEventHandler ItemPropertyChanged;

}

Examples related to c#

How can I convert this one line of ActionScript to C#? Microsoft Advertising SDK doesn't deliverer ads How to use a global array in C#? How to correctly write async method? C# - insert values from file into two arrays Uploading into folder in FTP? Are these methods thread safe? dotnet ef not found in .NET Core 3 HTTP Error 500.30 - ANCM In-Process Start Failure Best way to "push" into C# array

Examples related to wpf

Error: the entity type requires a primary key Reportviewer tool missing in visual studio 2017 RC Pass command parameter to method in ViewModel in WPF? Calling async method on button click Setting DataContext in XAML in WPF How to resolve this System.IO.FileNotFoundException System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll? Binding an Image in WPF MVVM How to bind DataTable to Datagrid Setting cursor at the end of any text of a textbox

Examples related to collections

Kotlin's List missing "add", "remove", Map missing "put", etc? How to unset (remove) a collection element after fetching it? How can I get a List from some class properties with Java 8 Stream? Java 8 stream map to list of keys sorted by values How to convert String into Hashmap in java How can I turn a List of Lists into a List in Java 8? MongoDB Show all contents from all collections Get nth character of a string in Swift programming language Java 8 Distinct by property Is there a typescript List<> and/or Map<> class/library?

Examples related to observablecollection

Sort ObservableCollection<string> through C# Notify ObservableCollection when Item changes What is the use of ObservableCollection in .net? How do I sort an observable collection? ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged) ObservableCollection Doesn't support AddRange method, so I get notified for each item added, besides what about INotifyCollectionChanging?

Examples related to inotifypropertychanged

Notify ObservableCollection when Item changes ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged) Implementing INotifyPropertyChanged - does a better way exist?