ObservableCollection и Item PropertyChanged

Я видел много разговоров по этому вопросу, но, может быть, я слишком много новичка, чтобы понять это. Если у меня есть наблюдаемая коллекция, которая представляет собой коллекцию "PersonNames", как в примере с msdn ( http://msdn.microsoft.com/en-us/library/ms748365.aspx), я получаю обновления для своего View, если PersonName добавлено или удалено и т. д. Я хочу получить обновление для своего просмотра, когда я изменяю свойство в PersonName также. Например, если я поменяю имя. Я могу реализовать OnPropertyChanged для каждого свойства и этот класс наследуется от INotifyPropertyChanged и это, кажется, называют как ожидалось. Мой вопрос: как View получает обновленные данные из ObservableCollection так как свойство изменилось, не вызывает никакого события для ObservableCollection, Это, наверное, что-то действительно простое, но почему я не могу найти пример, удивляет меня. Может кто-нибудь пролить свет на это для меня или есть ссылки на примеры, я был бы очень признателен. У нас есть этот сценарий в нескольких местах в нашем текущем приложении WPF, и мы пытаемся понять его.


"Как правило, код, отвечающий за отображение данных, добавляет PropertyChanged обработчик событий для каждого объекта, отображаемого в данный момент на экране."

Может ли кто-нибудь дать мне пример того, что это значит? Мой вид привязывается к моему ViewModel который имеет ObservableCollection, Эта коллекция состоит из RowViewModel который имеет свойства, которые поддерживают PropertiesChanged событие. Но я не могу понять, как сделать обновление самой коллекции, чтобы мой взгляд был обновлен.

6 ответов

Вот как вы можете прикрепить / отсоединить событие каждого элемента 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-чате:

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}

Билл,

Я уверен, что вы уже нашли обходной путь или решение вашей проблемы, но я опубликовал это для всех, у кого есть эта общая проблема. Вы можете заменить этот класс 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);

        }

    }
}

Как вы выяснили, нет события уровня коллекции, которое бы указывало на то, что свойство элемента в коллекции изменилось. Как правило, код, отвечающий за отображение данных, добавляет обработчик события PropertyChanged к каждому объекту, отображаемому в данный момент на экране.

Вместо ObservableCollection просто используйте BindingList;.
В следующем коде показана привязка 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; }
    }
}

Ниже приведен код, дающий простое объяснение ответа @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));
            }
        }
    }
}
Другие вопросы по тегам