C# ObservableCollection 和 Item PropertyChanged

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/901921/
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 01:48:05  来源:igfitidea点击:

ObservableCollection and Item PropertyChanged

c#observablecollection

提问by Bill Campbell

I've seen lots of talk about this question but maybe I'm just too much of a newbie to get it. If I have an observable collection that is a collection of "PersonNames" as in the msdn example (http: //msdn.microsoft.com/en-us/library/ms748365.aspx), I get updates to my View if a PersonNameis added or removed, etc. I want to get an update to my View when I change a property in the PersonNameas well. Like if I change the first name. I can implement OnPropertyChangedfor each property and have this class derive from INotifyPropertyChangedand that seems to get called as expected.

我已经看到很多关于这个问题的讨论,但也许我只是一个新手,无法理解。如果我有一个可观察的集合,它是 msdn 示例中的“PersonNames”集合(http://msdn.microsoft.com/en-us/library/ms748365.aspx),如果 aPersonName是,我会更新我的视图添加或删除等。我也想在更改属性时更新我的​​视图PersonName。就像我改变名字一样。我可以OnPropertyChanged为每个属性实现并让这个类派生自INotifyPropertyChanged,并且似乎按预期调用。

My question is, how does the View get the updated data from the ObservableCollectionas the property changed does not cause any event for the ObservableCollection?

我的问题是,当属性更改不会导致任何事件时,视图如何从 获取更新的数据?ObservableCollectionObservableCollection

This is probably something really simple but why I can't seem to find an example surprises me. Can anyone shed any light on this for me or have any pointers to examples I would greatly appreciate it. We have this scenario in multiple places in our current WPF app and are struggling with figuring it out.

这可能真的很简单,但为什么我似乎找不到一个例子让我感到惊讶。任何人都可以为我阐明这一点,或者有任何指向示例的指示,我将不胜感激。我们在当前 WPF 应用程序的多个地方都有这种情况,并且正在努力解决这个问题。



"Generally, the code responsible for displaying the data adds a PropertyChangedevent handler to each object currently displayed onscreen."

“通常,负责显示数据的代码会为PropertyChanged当前显示在屏幕上的每个对象添加一个事件处理程序。”

Could someone please give me an example of what this means? My View binds to my ViewModelwhich has a ObservableCollection. This collection is made up of a RowViewModelwhich has properties that support the PropertiesChangedevent. But I can't figure out how to make the collection update itself so my view will be updated.

有人可以给我举个例子说明这是什么意思吗?我的视图绑定到ViewModel具有ObservableCollection. 此集合由RowViewModel具有支持PropertiesChanged事件的属性的 a组成。但我不知道如何让集合本身更新,所以我的视图会被更新。

回答by Jason Kresowaty

As you found out, there is no collection-level event that indicates that a property of an item in the collection has changed. Generally, the code responsible for displaying the data adds a PropertyChanged event handler to eachobject currently displayed onscreen.

正如您所发现的,没有集合级别的事件指示集合中项目的属性已更改。通常,负责显示数据的代码会向当前显示在屏幕上的每个对象添加一个 PropertyChanged 事件处理程序。

回答by chilltemp

Here is how you would attach/detach to each item's PropertyChanged event.

以下是您将如何附加/分离到每个项目的 PropertyChanged 事件。

ObservableCollection<INotifyPropertyChanged> items = new ObservableCollection<INotifyPropertyChanged>();
items.CollectionChanged += items_CollectionChanged;

static void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.OldItems != null)
    {
        foreach (INotifyPropertyChanged item in e.OldItems)
            item.PropertyChanged -= item_PropertyChanged;
    }
    if (e.NewItems != null)
    {
        foreach (INotifyPropertyChanged item in e.NewItems)
            item.PropertyChanged += item_PropertyChanged;
    }
}

static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    throw new NotImplementedException();
}

回答by Marc Ziss

Bill,

账单,

I'm sure that you have found a workaround or solution to your issue by now, but I posted this for anyone with this common issue. You can substitute this class for ObservableCollections that are collections of objects that implement INotifyPropertyChanged. It is kind of draconian, because it says that the list needs to Reset rather than find the one property/item that has changed, but for small lists the performance hit should be unoticable.

我确定您现在已经找到了解决问题的方法或解决方案,但我已将其发布给遇到此常见问题的任何人。您可以将此类替换为实现 INotifyPropertyChanged 的​​对象集合的 ObservableCollections。这有点严厉,因为它说列表需要重置而不是找到一个已更改的属性/项目,但对于小列表,性能影响应该是不明显的。

Marc

马克

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

namespace WCIOPublishing.Helpers
{
    public class ObservableCollectionWithItemNotify<T> : ObservableCollection<T> where T: INotifyPropertyChanged 
    {

        public ObservableCollectionWithItemNotify()
        {
            this.CollectionChanged += items_CollectionChanged;
        }


        public ObservableCollectionWithItemNotify(IEnumerable<T> collection) :base( collection)
        {
            this.CollectionChanged += items_CollectionChanged;
            foreach (INotifyPropertyChanged item in collection)
                item.PropertyChanged += item_PropertyChanged;

        }

        private void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if(e != null)
            {
                if(e.OldItems!=null)
                    foreach (INotifyPropertyChanged item in e.OldItems)
                        item.PropertyChanged -= item_PropertyChanged;

                if(e.NewItems!=null)
                    foreach (INotifyPropertyChanged item in e.NewItems)
                        item.PropertyChanged += item_PropertyChanged;
            }
        }

        private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var reset = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            this.OnCollectionChanged(reset);

        }

    }
}

回答by Johan Larsson

We wrote this in the WPF-chat:

我们在 WPF-chat 中写道:

public class OcPropertyChangedListener<T> : INotifyPropertyChanged where T : INotifyPropertyChanged
{
    private readonly ObservableCollection<T> _collection;
    private readonly string _propertyName;
    private readonly Dictionary<T, int> _items = new Dictionary<T, int>(new ObjectIdentityComparer());
    public OcPropertyChangedListener(ObservableCollection<T> collection, string propertyName = "")
    {
        _collection = collection;
        _propertyName = propertyName ?? "";
        AddRange(collection);
        CollectionChangedEventManager.AddHandler(collection, CollectionChanged);
    }

    private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                AddRange(e.NewItems.Cast<T>());
                break;
            case NotifyCollectionChangedAction.Remove:
                RemoveRange(e.OldItems.Cast<T>());
                break;
            case NotifyCollectionChangedAction.Replace:
                AddRange(e.NewItems.Cast<T>());
                RemoveRange(e.OldItems.Cast<T>());
                break;
            case NotifyCollectionChangedAction.Move:
                break;
            case NotifyCollectionChangedAction.Reset:
                Reset();
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

    }

    private void AddRange(IEnumerable<T> newItems)
    {
        foreach (T item in newItems)
        {
            if (_items.ContainsKey(item))
            {
                _items[item]++;
            }
            else
            {
                _items.Add(item, 1);
                PropertyChangedEventManager.AddHandler(item, ChildPropertyChanged, _propertyName);
            }
        }
    }

    private void RemoveRange(IEnumerable<T> oldItems)
    {
        foreach (T item in oldItems)
        {
            _items[item]--;
            if (_items[item] == 0)
            {
                _items.Remove(item);
                PropertyChangedEventManager.RemoveHandler(item, ChildPropertyChanged, _propertyName);
            }
        }
    }

    private void Reset()
    {
        foreach (T item in _items.Keys.ToList())
        {
            PropertyChangedEventManager.RemoveHandler(item, ChildPropertyChanged, _propertyName);
            _items.Remove(item);
        }
        AddRange(_collection);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(sender, e);
    }

    private class ObjectIdentityComparer : IEqualityComparer<T>
    {
        public bool Equals(T x, T y)
        {
            return object.ReferenceEquals(x, y);
        }
        public int GetHashCode(T obj)
        {
            return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj);
        }
    }
}

public static class OcPropertyChangedListener
{
    public static OcPropertyChangedListener<T> Create<T>(ObservableCollection<T> collection, string propertyName = "") where T : INotifyPropertyChanged
    {
        return new OcPropertyChangedListener<T>(collection, propertyName);
    }
}
  • Weak events
  • Keeps track of the same item being added multiple times to the collection
  • It ~bubbles~ up the property changed events of the children.
  • The static class is just for convenience.
  • 弱事件
  • 跟踪多次添加到集合中的同一项目
  • 它〜冒泡〜起来改变孩子们的事件。
  • 静态类只是为了方便。

Use it like this:

像这样使用它:

var listener = OcPropertyChangedListener.Create(yourCollection);
listener.PropertyChanged += (sender, args) => { //do you stuff}

回答by Stack

Instead of ObservableCollection simply use the BindingList<T>.
The following code shows a DataGrid binding to a List and to item's properties.

而不是 ObservableCollection 只需使用BindingList<T>
以下代码显示了绑定到列表和项目属性的 DataGrid。

<Window x:Class="WpfApplication1.MainWindow"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    Title="MainWindow" Height="350" Width="525">
    <DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" >
        <DataGrid.Columns>
            <DataGridTextColumn Header="Values" Binding="{Binding Value}" />
        </DataGrid.Columns>
    </DataGrid>
</Window>


using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Threading;

namespace WpfApplication1 {
    public partial class MainWindow : Window {
        public MainWindow() {
            var c = new BindingList<Data>();
            this.DataContext = c;
            // add new item to list on each timer tick
            var t = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(1) };
            t.Tick += (s, e) => {
                if (c.Count >= 10) t.Stop();
                c.Add(new Data());
            };
            t.Start();
        }
    }

    public class Data : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
        System.Timers.Timer t;
        static Random r = new Random();
        public Data() {
            // update value on each timer tick
            t = new System.Timers.Timer() { Interval = r.Next(500, 1000) };
            t.Elapsed += (s, e) => {
                Value = DateTime.Now.Ticks;
                this.PropertyChanged(this, new PropertyChangedEventArgs("Value"));
            };
            t.Start();
        }
        public long Value { get; private set; }
    }
}

回答by Abbas

Following is the code giving a simple explanation of answer by @Stackand showing how BindingListis observing if it has a item changed and shows ObservableCollectionwill not observe the change inside an item.

以下代码给出了@Stack对答案的简单解释,并显示了如何BindingList观察项目是否发生更改,并且显示ObservableCollection将不会观察项目内部的更改。

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace BindingListExample
{
    class Program
    {
        public ObservableCollection<MyStruct> oc = new ObservableCollection<MyStruct>();
        public System.ComponentModel.BindingList<MyStruct> bl = new BindingList<MyStruct>();

        public Program()
        {
            oc.Add(new MyStruct());
            oc.CollectionChanged += CollectionChanged;

            bl.Add(new MyStruct());
            bl.ListChanged += ListChanged;
        }

        void ListChanged(object sender, ListChangedEventArgs e)
        {
            //Observe when the IsActive value is changed this event is triggered.
            Console.WriteLine(e.ListChangedType.ToString());
        }

        void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            //Observe when the IsActive value is changed this event is not triggered.
            Console.WriteLine(e.Action.ToString());
        }

        static void Main(string[] args)
        {
            Program pm = new Program();
            pm.bl[0].IsActive = false;
        }
    }

    public class MyStruct : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private bool isactive;
        public bool IsActive
        {
            get { return isactive; }
            set
            {
                isactive = value;
                NotifyPropertyChanged("IsActive");
            }
        }

        private void NotifyPropertyChanged(String PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
    }
}