36

I have a ObservableCollection, I can add and remove item from the collection. But I can't replace an existing item in the collection. There is a way to replace an item and reflect that on my bound components.

System.Collections.Specialized.NotifyCollectionChangedAction.Replace

Can anyone please show me how to accomplish this?

Kamran Ahmed
  • 7,661
  • 4
  • 30
  • 55
Taufiq Abdur Rahman
  • 1,348
  • 4
  • 24
  • 44
  • possible duplicate of [How do I update a single item in an ObservableCollection class?](http://stackoverflow.com/questions/6781192/how-do-i-update-a-single-item-in-an-observablecollection-class) – KyleMit May 28 '14 at 15:58

3 Answers3

73
collection[someIndex] = newItem;
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • I did achive this by implementing this class public class MyObservableCollection : ObservableCollection { public MyObservableCollection() { } public MyObservableCollection(List collection) { MyObservableCollection mb = new MyObservableCollection(); for (int x = 0; x < collection.Count; x++) this.Add(collection[x]); } public void Replace(int index, T item) { base.SetItem(index, item); } } – Taufiq Abdur Rahman Jul 30 '11 at 08:56
  • 6
    You don't need to make your own class. You can just write `collection[someIndex] = newItem`. – SLaks Jul 31 '11 at 01:49
  • This answer doesn't work when I try it. This answer does work https://stackoverflow.com/questions/6781192/how-do-i-update-a-single-item-in-an-observablecollection-class. Is this something to do with the way the object is referenced? – Chucky Dec 17 '18 at 12:24
  • 1
    Replacing an item by using an index doesn't update the UI for me. – Chucky Dec 18 '18 at 10:36
5

Updated: Indexer uses overridden SetItem and notifies about changes.

I think the answer about using indexer may be wrong, because the question was about replace and notify.

Just to clarify: ObservableCollection<T> uses indexer from its base Collection<T> class, which in turn is a wrapper of List<T>, which is a wrapper of simple array of T. And there's no override for indexer method in ObservableCollection implementation.

So when you use indexer to replace an item in ObservableCollection it invokes the following code from Collection class:

public T this[int index] {
        get { return items[index]; }
        set {
            if( items.IsReadOnly) {
                ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
            }

            if (index < 0 || index >= items.Count) {
                ThrowHelper.ThrowArgumentOutOfRangeException();
            }

            SetItem(index, value);
        }

It just checks boundaries and calls SetItem that uses indexer of underlying List class:

protected virtual void SetItem(int index, T item) {
        items[index] = item;
    }

During assignment there is no call to the CollectionChanged event, because underlying collections know nothing of it.

But when you use SetItem method, it is called from ObservableCollection class:

protected override void SetItem(int index, T item)
    {
        CheckReentrancy();
        T originalItem = this[index];
        base.SetItem(index, item);

        OnPropertyChanged(IndexerName);
        OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, item, index);
    }

After assignment it calls OnCollectionChanged method, which fires CollectionChanged event with NotifyCollectionChangedAction.Replace action parameter.

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

As a conclusion: the idea with custom class inherited from ObservableCollection and Replace method that calls base.SetItem() worth a try.

Mikhail Tumashenko
  • 1,683
  • 2
  • 21
  • 28
0

Simple extension method for replace item in observable collection:

public static void ReplaceItem<T>(this ObservableCollection<T> items, Func<T, bool> predicate, T newItem)
{
    for (int i = 0; i < items.Count; i++)
    {
        if (predicate(items[i]))
        {
            items[i] = newItem;
            break;
        }
    }
}
BorisSh
  • 531
  • 4
  • 3