web-dev-qa-db-fra.com

ObservableCollection et Item PropertyChanged

J'ai vu beaucoup de discussions sur cette question, mais je suis peut-être juste trop novice pour l'obtenir. Si j'ai une collection observable qui est une collection de "PersonNames" comme dans l'exemple msdn ( http: //msdn.Microsoft.com/en-us/library/ms748365.aspx ), j'obtiens Mises à jour de ma vue si un PersonName est ajouté ou supprimé, etc. Je souhaite également obtenir une mise à jour de ma vue lorsque je modifie une propriété dans le PersonName. Comme si je change le prénom. Je peux implémenter OnPropertyChanged pour chaque propriété et faire dériver cette classe de INotifyPropertyChanged et cela semble être appelé comme prévu. Ma question est de savoir comment la vue obtient les données mises à jour à partir de ObservableCollection car la propriété modifiée ne provoque aucun événement pour ObservableCollection. C'est probablement quelque chose de vraiment simple, mais pourquoi je n'arrive pas à trouver un exemple me surprend. Quelqu'un peut-il nous éclairer à ce sujet ou avoir des indications sur des exemples que j'apprécierais beaucoup? Nous avons ce scénario à plusieurs endroits dans notre application WPF actuelle et nous avons du mal à le comprendre.


"Généralement, le code responsable de l'affichage des données ajoute un gestionnaire d'événements PropertyChanged à chaque objet actuellement affiché à l'écran."

Quelqu'un pourrait-il me donner un exemple de ce que cela signifie? Ma vue se lie à mon ViewModel qui a un ObservableCollection. Cette collection est composée d'un RowViewModel qui a des propriétés qui prennent en charge l'événement PropertiesChanged. Mais je ne sais pas comment faire la mise à jour de la collection elle-même, donc ma vue sera mise à jour.

44
Bill Campbell

Voici comment vous pouvez attacher/détacher l'événement PropertyChanged de chaque élément.

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();
}
64
chilltemp

Nous avons écrit ceci dans le 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);
    }
}
  • Evénements faibles
  • Garde la trace du même élément ajouté plusieurs fois à la collection
  • Il ~ bouillonne ~ la propriété a changé les événements des enfants.
  • La classe statique est juste pour plus de commodité.

Utilisez-le comme ceci:

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

Facture,

Je suis sûr que vous avez trouvé une solution de contournement ou une solution à votre problème à l'heure actuelle, mais j'ai posté cela pour toute personne ayant ce problème commun. Vous pouvez remplacer cette classe par ObservableCollections qui sont des collections d'objets qui implémentent INotifyPropertyChanged. C'est un peu draconien, car il dit que la liste doit être réinitialisée plutôt que de trouver la propriété/l'élément qui a changé, mais pour les petites listes, les performances ne devraient pas être remarquables.

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

        }

    }
}
13
Marc Ziss

Comme vous l'avez découvert, aucun événement au niveau de la collection n'indique qu'une propriété d'un élément de la collection a changé. Généralement, le code responsable de l'affichage des données ajoute un gestionnaire d'événements PropertyChanged à l'objet each actuellement affiché à l'écran.

4
Jason Kresowaty

Au lieu de ObservableCollection, utilisez simplement BindingList <T> .
Le code suivant montre une liaison DataGrid à une liste et aux propriétés d'un élément.

<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; }
    }
}
2
Stack

Voici le code donnant une explication simple de la réponse par @ Stack et montrant comment BindingList observe s'il a un élément changé et montre que ObservableCollection n'observera pas le changement à l'intérieur d'un élément.

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