【问题标题】:ObservableCollection and Item PropertyChangedObservableCollection 和 Item PropertyChanged
【发布时间】:2010-10-28 11:10:05
【问题描述】:

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

我的问题是,视图如何从ObservableCollection 获取更新的数据,因为属性更改不会导致ObservableCollection 发生任何事件?

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


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

谁能给我一个例子来说明这意味着什么?我的视图绑定到我的ViewModel,它有一个ObservableCollection。此集合由 RowViewModel 组成,它具有支持 PropertiesChanged 事件的属性。但我不知道如何让集合自行更新,以便更新我的视图。

【问题讨论】:

    标签: c# observablecollection


    【解决方案1】:

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

    【讨论】:

    • 谢谢。我正在使用 WPF 并且有一个 DataGrid,其 ItemsSource 在 XAML 中绑定到 ObservableCollection。因此,我需要在 ViewModel 中的某处添加代码来处理 PropertyChanged 事件,以便 View 知道更新 DataGrid?然后我是否必须删除该项目并将其添加到集合中才能使其视图更新它?这似乎违反直觉(但这并不意味着它不正确:)
    • 如果 ObservableCollection 中的元素实现 INotifyPropertyChanged(或者是 DependencyObjects),DataGrid 会自动执行此操作。
    【解决方案2】:

    这是您将如何附加/分离到每个项目的 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();
    }
    

    【讨论】:

    • 这很漂亮。我一直在寻找这个,它真的帮助了我。非常感谢。
    • 为什么两个改过的函数都是静态的?
    • 已经有一段时间了,我找不到我为这个示例抽象的来源。我想我使用的是静态的 WPF 依赖属性。我看不出为什么这些函数不能是每个实例的。
    • 无需检查 e.OldItems != null 和 e.NewItems != null 吗?
    • 我认为这在重置事件时不起作用。因为它们不会填充旧项目。
    【解决方案3】:

    比尔,

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

    马克

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

    【讨论】:

    • 这很好。但幸运的是,您不必重置集合,您也可以替换为: var replace = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, this.Items.IndexOf((T)sender)); this.OnCollectionChanged(replace);
    • @Marc-Ziss 这是一个不错的解决方案。
    • @infografnet 我无法让它与 Replace 一起使用。可能是因为 oldItem 和 newItem 都是发件人吗?我确保属性更改不会检查值是否与以前相同。
    • @BobbyJ,是的,你是对的。在这种情况下,oldItem 将与 newItem 相同。但这不应该打扰。只需尝试在回调函数中跳过检查 oldItem 即可。 if (e.Action == NotifyCollectionChangedAction.Replace) { 对 e.NewItems 做一些事情,不要检查 e.OldItems; }
    【解决方案4】:

    我们在 WPF 聊天中写道:

    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);
        }
    }
    
    • 弱事件
    • 跟踪多次添加到集合中的同一项目
    • 它~冒泡~了孩子们的财产改变事件。
    • 静态类只是为了方便。

    像这样使用它:

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

    【讨论】:

    • 非常好 - 我特别喜欢它使用弱事件来跟踪项目,因为这消除了退订的许多复杂性并使其更有用。
    • 鉴于所有不同的解决方案,我认为这是迄今为止最好的实现。干得好,约翰。
    • 迟到了。适用于父模型但不适用于导航属性,例如作为实现 INotifyPropertyChanged 的​​ List 的模型属性
    • 我替换了 CollectionChangedEventManager 和 PropertyChangedEventManager,因为它在使用 DependencyInjection (Unity) 时不起作用,而不是使用常规 += ChildPropertyChanged; 确实起作用
    【解决方案5】:

    只需使用 BindingList<T> 代替 ObservableCollection。
    以下代码显示了与 List 和项目属性的 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; }
        }
    }
    

    【讨论】:

    • 我不知道有一个 DispatcherTimer 允许您更新 UI 线程。谢谢
    【解决方案6】:

    下面的代码给出了@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));
                }
            }
        }
    }
    

    【讨论】:

    • 这对我不起作用。与可观察集合相同。我错过了什么吗?
    猜你喜欢
    • 1970-01-01
    • 2010-11-03
    • 1970-01-01
    • 1970-01-01
    • 2012-01-19
    • 2013-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多