C# ObservableCollection 不会注意到其中的 Item 何时更改(即使使用 INotifyPropertyChanged)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1427471/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 16:25:19  来源:igfitidea点击:

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

c#observablecollectioninotifypropertychanged

提问by Joseph jun. Melettukunnel

Does anyone know why this code doesn't work:

有谁知道为什么这段代码不起作用:

public class CollectionViewModel : ViewModelBase {  
    public ObservableCollection<EntityViewModel> ContentList
    {
        get { return _contentList; }
        set 
        { 
            _contentList = value; 
            RaisePropertyChanged("ContentList"); 
            //I want to be notified here when something changes..?
            //debugger doesn't stop here when IsRowChecked is toggled
        }
     }
}

public class EntityViewModel : ViewModelBase
{

    private bool _isRowChecked;

    public bool IsRowChecked
    {
        get { return _isRowChecked; }
        set { _isRowChecked = value; RaisePropertyChanged("IsRowChecked"); }
    }
}

ViewModelBasecontaints everything for RaisePropertyChangedetc. and it's working for everything else except this problem..

ViewModelBase包含RaisePropertyChanged等的所有内容,它适用于除此问题之外的所有其他内容..

采纳答案by Martin Harris

The ContentList's Set method will not get called when you change a value inside the collection, instead you should be looking out for the CollectionChangedevent firing.

当您更改集合内的值时,将不会调用 ContentList 的 Set 方法,相反您应该注意CollectionChanged事件触发。

public class CollectionViewModel : ViewModelBase
{          
    public ObservableCollection<EntityViewModel> ContentList
    {
        get { return _contentList; }
    }

    public CollectionViewModel()
    {
         _contentList = new ObservableCollection<EntityViewModel>();
         _contentList.CollectionChanged += ContentCollectionChanged;
    }

    public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        //This will get called when the collection is changed
    }
}


Okay, that's twice today I've been bitten by the MSDN documentation being wrong. In the link I gave you it says:

好的,这是今天两次我被 MSDN 文档错误所困扰。在我给你的链接中,它说:

Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.

在添加、删除、更改、移动项目或刷新整个列表时发生。

But it actually doesn'tfire when an item is changed. I guess you'll need a more bruteforce method then:

但是当一个项目改变时它实际上不会触发。我想你需要一个更暴力的方法:

public class CollectionViewModel : ViewModelBase
{          
    public ObservableCollection<EntityViewModel> ContentList
    {
        get { return _contentList; }
    }

    public CollectionViewModel()
    {
         _contentList = new ObservableCollection<EntityViewModel>();
         _contentList.CollectionChanged += ContentCollectionChanged;
    }

    public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            foreach(EntityViewModel item in e.OldItems)
            {
                //Removed items
                item.PropertyChanged -= EntityViewModelPropertyChanged;
            }
        }
        else if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach(EntityViewModel item in e.NewItems)
            {
                //Added items
                item.PropertyChanged += EntityViewModelPropertyChanged;
            }     
        }       
    }

    public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        //This will get called when the property of an object inside the collection changes
    }
}

If you are going to need this a lot you may want to subclass your own ObservableCollectionthat triggers the CollectionChangedevent when a member triggers its PropertyChangedevent automatically (like it says it should in the documentation...)

如果你非常需要这个,你可能想要子类化你自己的子类ObservableCollectionCollectionChanged当成员PropertyChanged自动触发它的事件时触发事件(就像它在文档中说的那样......)

回答by mjeanes

ObservableCollection will not propagate individual item changes as CollectionChanged events. You will either need to subscribe to each event and forward it manually, or you can check out the BindingList[T]class, which will do this for you.

ObservableCollection 不会将单个项目更改作为 CollectionChanged 事件传播。您需要订阅每个事件并手动转发它,或者您可以查看BindingList[T]类,它会为您执行此操作。

回答by Hyman Kenyon

This uses the above ideas but makes it a derived 'more sensitive' collection:

这使用了上述想法,但使其成为派生的“更敏感”的集合:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;

namespace somethingelse
{
    public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
    {
        // this collection also reacts to changes in its components' properties

        public ObservableCollectionEx() : base()
        {
            this.CollectionChanged +=new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ObservableCollectionEx_CollectionChanged);
        }

        void ObservableCollectionEx_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach(T item in e.OldItems)
                {
                    //Removed items
                    item.PropertyChanged -= EntityViewModelPropertyChanged;
                }
            }
            else if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach(T item in e.NewItems)
                {
                    //Added items
                    item.PropertyChanged += EntityViewModelPropertyChanged;
                }     
            }       
        }

        public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            //This will get called when the property of an object inside the collection changes - note you must make it a 'reset' - dunno why
            NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            OnCollectionChanged(args);
        }
    }
}

回答by triazotan

I used Hyman Kenyons answer to implement my own OC, but I'd like to point out one change i had to make to make it work. Instead of:

我使用 Hyman Kenyons 的答案来实现我自己的 OC,但我想指出我必须做出的一项更改才能使其工作。代替:

    if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        foreach(T item in e.NewItems)
        {
            //Removed items
            item.PropertyChanged -= EntityViewModelPropertyChanged;
        }
    }

I used this:

我用过这个:

    if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        foreach(T item in e.OldItems)
        {
            //Removed items
            item.PropertyChanged -= EntityViewModelPropertyChanged;
        }
    }

It seems that the "e.NewItems" produces null if action is .Remove.

如果 action 是 .Remove,则“e.NewItems”似乎会产生 null。

回答by simon

Here is a drop-in class that sub-classes ObservableCollection and actually raises a Reset action when a property on a list item changes. It enforces all items to implement INotifyPropertyChanged.

这是一个插入类,它是 ObservableCollection 的子类,并在列表项上的属性更改时实际引发重置操作。它强制执行所有项目INotifyPropertyChanged

The benefit here is that you can data bind to this class and all of your bindings will update with changes to your item properties.

这里的好处是您可以将数据绑定到此类,并且您的所有绑定都将随着项目属性的更改而更新。

public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
    where T : INotifyPropertyChanged
{
    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)
            {
                ((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
            }
        }
        if (e.OldItems != null)
        {
            foreach (Object item in e.OldItems)
            {
                ((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
            }
        }
    }

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

回答by Swab.Jat

Added to TruelyObservableCollection event "ItemPropertyChanged":

添加到 TruelyObservableCollection 事件“ItemPropertyChanged”:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; // ObservableCollection
using System.ComponentModel; // INotifyPropertyChanged
using System.Collections.Specialized; // NotifyCollectionChangedEventHandler
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ObservableCollectionTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // ATTN: Please note it's a "TrulyObservableCollection" that's instantiated. Otherwise, "Trades[0].Qty = 999" will NOT trigger event handler "Trades_CollectionChanged" in main.
            // REF: http://stackoverflow.com/questions/8490533/notify-observablecollection-when-item-changes
            TrulyObservableCollection<Trade> Trades = new TrulyObservableCollection<Trade>();
            Trades.Add(new Trade { Symbol = "APPL", Qty = 123 });
            Trades.Add(new Trade { Symbol = "IBM", Qty = 456});
            Trades.Add(new Trade { Symbol = "CSCO", Qty = 789 });

            Trades.CollectionChanged += Trades_CollectionChanged;
            Trades.ItemPropertyChanged += PropertyChangedHandler;
            Trades.RemoveAt(2);

            Trades[0].Qty = 999;

            Console.WriteLine("Hit any key to exit");
            Console.ReadLine();

            return;
        }

        static void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine(DateTime.Now.ToString() + ", Property changed: " + e.PropertyName + ", Symbol: " + ((Trade) sender).Symbol + ", Qty: " + ((Trade) sender).Qty);
            return;
        }

        static void Trades_CollectionChanged(object sender, EventArgs e)
        {
            Console.WriteLine(DateTime.Now.ToString() + ", Collection changed");
            return;
        }
    }

    #region TrulyObservableCollection
    public class TrulyObservableCollection<T> : ObservableCollection<T>
        where T : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler ItemPropertyChanged;

        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);

            if (ItemPropertyChanged != null)
            {
                ItemPropertyChanged(sender, e);
            }
        }
    }
    #endregion

    #region Sample entity
    class Trade : INotifyPropertyChanged
    {
        protected string _Symbol;
        protected int _Qty = 0;
        protected DateTime _OrderPlaced = DateTime.Now;

        public DateTime OrderPlaced
        {
            get { return _OrderPlaced; }
        }

        public string Symbol
        {
            get
            {
                return _Symbol;
            }
            set
            {
                _Symbol = value;
                NotifyPropertyChanged("Symbol");
            }
        }

        public int Qty
        {
            get
            {
                return _Qty;
            }
            set
            {
                _Qty = value;
                NotifyPropertyChanged("Qty");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
#endregion
}

回答by chopikadze

I know that I'm too late for this party, but maybe - it will help to someone..

我知道我参加这个聚会为时已晚,但也许 - 它会对某人有所帮助..

Hereyou can find my implementation of ObservableCollectionEx. It has some features:

在这里你可以找到我对 ObservableCollectionEx 的实现。它有一些特点:

  • it supports everything from ObservableCollection
  • it's thread safe
  • it supports ItemPropertyChanged event (it raises each time when Item.PropertyChanged item is fired)
  • it supports filters (so, you could create ObservableCollectionEx, pass another collection as Source to it, and Filter with simple predicate. Very useful in WPF, I use this feature a lot in my applications). Even more - filter tracks changes of items via INotifyPropertyChanged interface.
  • 它支持 ObservableCollection 中的所有内容
  • 它是线程安全的
  • 它支持 ItemPropertyChanged 事件(它在每次 Item.PropertyChanged 项目被触发时引发)
  • 它支持过滤器(因此,您可以创建 ObservableCollectionEx,将另一个集合作为 Source 传递给它,并使用简单的谓词过滤。在 WPF 中非常有用,我在我的应用程序中经常使用此功能)。甚至更多 - 过滤器通过 INotifyPropertyChanged 接口跟踪项目的变化。

Of course, any comments are appreciated ;)

当然,任何意见表示赞赏;)

回答by pjdupreez

Just adding my 2 cents on this topic. Felt the TrulyObservableCollection required the two other constructors as found with ObservableCollection:

只需在此主题上添加我的 2 美分。感觉 TrulyObservableCollection 需要使用 ObservableCollection 发现的另外两个构造函数:

public TrulyObservableCollection()
        : base()
    {
        HookupCollectionChangedEvent();
    }

    public TrulyObservableCollection(IEnumerable<T> collection)
        : base(collection)
    {
        foreach (T item in collection)
            item.PropertyChanged += ItemPropertyChanged;

        HookupCollectionChangedEvent();
    }

    public TrulyObservableCollection(List<T> list)
        : base(list)
    {
        list.ForEach(item => item.PropertyChanged += ItemPropertyChanged);

        HookupCollectionChangedEvent();
    }

    private void HookupCollectionChangedEvent()
    {
        CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollectionChanged);
    }

回答by thinksomid

Simple solution for standard observablecollection that I've used:

我使用过的标准 observablecollection 的简单解决方案:

DO NOT ADD to your property OR CHANGE it's inner items DIRECTLY, instead, create some temp collection like this

不要添加到您的财产或直接更改它的内部项目,而是创建一些像这样的临时集合

ObservableCollection<EntityViewModel> tmpList= new ObservableCollection<EntityViewModel>();

and add items or make changes to tmpList,

并添加项目或更改 tmpList,

tmpList.Add(new EntityViewModel(){IsRowChecked=false}); //Example
tmpList[0].IsRowChecked= true; //Example
...

then pass it to your actual property by assignment.

然后通过分配将其传递给您的实际财产。

ContentList=tmpList;

this will change whole property which causes notice the INotifyPropertyChanged as you need.

这将更改整个属性,从而根据需要通知 INotifyPropertyChanged。

回答by LawMan

Here's an extension method for the above solution...

这是上述解决方案的扩展方法...

public static TrulyObservableCollection<T> ToTrulyObservableCollection<T>(this List<T> list)
     where T : INotifyPropertyChanged
{
    var newList = new TrulyObservableCollection<T>();

    if (list != null)
    {
        list.ForEach(o => newList.Add(o));
    }

    return newList;
}