ObservableCollection içindeki Öğe değiştiğinde fark etmiyor (INotifyPropertyChanged ile bile)


167

Bu kodun neden çalışmadığını bilen var mı:

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

ViewModelBaseiçin containts herşey RaisePropertyChangedvb ve bu sorunun dışında her şey için çalışıyor ..


Yanıtlar:


119

Koleksiyon içindeki bir değeri değiştirdiğinizde ContentList'in Set yöntemi çağrılmaz, bunun yerine CollectionChanged olay tetiklemesini aramalısınız.

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

Tamam, bugün iki kez MSDN belgelerinin yanlış olması nedeniyle ısırıldım. Size verdiğim linkte şöyle diyor:

Bir öğe eklendiğinde, kaldırıldığında, değiştirildiğinde, taşındığında veya listenin tamamı yenilendiğinde oluşur.

Ama aslında değil bir öğe değiştirildiğinde ateş. O zaman daha bruteforce yöntemine ihtiyacınız olacak sanırım:

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

Eğer buna çok ihtiyacınız varsa, bir üye etkinliğini otomatik olarak tetiklediğinde olayı ObservableCollectiontetikleyen kendi alt sınıfınızı isteyebilirsiniz (belgelerde olması gerektiği gibi ...)CollectionChangedPropertyChanged


Üzgünüm Harris, ama ContentCollectionChanged'ın çağrılması için EntityViewModel'de hangi Olayı başlatmam gerekiyor?
Joseph jun. Melettukunnel

36
etkinlik yönetimini kendiniz uygulamak istemiyorsanız, <EntityViewModel> yerine ObservableCollection yerine bir BindingList <EntityViewModel> kullanabilirsiniz. Ardından EntityViewModel.PropertyChanged olaylarını otomatik olarak ListChangedType == ItemChanged olarak ListChanged olayları olarak iletir.
mjeanes

15
Bunların hepsi bu terimi anlamanıza bağlı değil changedmi? Bu, koleksiyondaki öğelerden birinin bir özelliğinin değiştiği (yani bunu yorumladığınızı düşünüyorum) veya koleksiyonun öğelerinden birinin farklı bir örnekle değiştirildiği anlamına gelebilir ( bu benim yorumumdur). Tamamen ikna olmadım - daha fazla bakmak zorunda kalacak.
belugabob

10
Çağırırsam ne olur _contentList.Clear()? Kimse abonelikten çıkmayacak PropertyChanged!
Paolo Moretti

2
@Paolo: Bu doğru, ContentCollectionChangedyalnızca Ekle / Kaldır'ı yönetir, Değiştir / Sıfırla'yı kullanmaz. Gönderiyi düzeltmeye ve düzeltmeye çalışacağım. Simon'un cevabında yaptığı yol doğrudur.
Mike Fuchs

178

İşte ObservableCollection alt sınıflarını sınıflandıran ve liste öğesindeki bir özellik değiştiğinde bir Sıfırla eylemini başlatan bir açılan sınıf. Uygulanması gereken tüm öğeleri uygular INotifyPropertyChanged.

Buradaki avantaj, veriyi bu sınıfa bağlayabilmeniz ve tüm bağlantılarınızın öğe özelliklerinizdeki değişikliklerle güncellenmesidir.

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

4
Bunun yerine NotifyCollectionChangedAction.Reset kullanmak yerine kendime benzer bir şey uygulamak için neden oldum.
Chris

2
Sorunuma harika bir çözüm - teşekkürler! ObservableCollection öğelerini bir Liste ile oluşturmuş olanlar için, tüm öğeleri olsa da yineleyen ve PropertyChanged ekleyen bir yapıcı eklemek isteyebilirsiniz.
Gavin

4
Burada potansiyel bir bellek sızıntısı var - Koleksiyon önemli ölçüde değiştirildiğinde, örneğin Temizle'de, bir Reset olayı meydana gelir. Bu gerçekleştiğinde INPC işleyicilerinizden hiçbiri aboneliğinizi iptal etmez.
Charles Mager

6
bu bir TAMAM uygulamasıdır, ancak büyük bir sorunu vardır - NotifyCollectionChangedAction.Replaceiyi bir fikir değildir, çünkü o zaman aslında bir öğenin değiştirilmesiyle bir öğe değişikliğinin neden olduğu olayı ayırt edemezsiniz. public event PropertyChangedEventHandler CollectionItemChanged;ItemPropertyChangedthis.CollectionItemChanged?.Invoke(sender, e);
Tanımlamak

4
Herkes bu sınıfın kullanımına bir örnek var mı?
Dekoder94

23

Diğer cevaplardaki teknikler de dahil olmak üzere oldukça sağlam bir çözüm olduğunu umduğum bir araya getirdim. Yeni bir sınıftır.ObservableCollection<>AradığımFullyObservableCollection<>

Aşağıdaki özelliklere sahiptir:

  • Yeni bir etkinlik ekler ItemPropertyChanged. Bunu kasten mevcut olandan ayrı tuttumCollectionChanged :
    • Geriye uyumluluğa yardımcı olmak.
    • Böylece ItemPropertyChangedEventArgsona eşlik eden yenide daha alakalı detaylar verilebilir : PropertyChangedEventArgskoleksiyondaki orijinal ve dizin.
  • Tüm kurucuları ObservableCollection<> .
  • Sıfırlanan listeyi doğru şekilde işler (ObservableCollection<>.Clear()Olası bir bellek sızıntısını önleyerek ) .
  • Etkinliğe OnCollectionChanged()yönelik kaynak yoğunluğu daha fazla olan bir abonelik yerine temel sınıfın geçersiz kılınır CollectionChanged.

kod

Tam .csdosya aşağıdadır. C # 6'nın birkaç özelliğinin kullanıldığını unutmayın, ancak onu geri döndürmek oldukça basit olmalıdır:

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

namespace Utilities
{
    public class FullyObservableCollection<T> : ObservableCollection<T>
        where T : INotifyPropertyChanged
    {
        /// <summary>
        /// Occurs when a property is changed within an item.
        /// </summary>
        public event EventHandler<ItemPropertyChangedEventArgs> ItemPropertyChanged;

        public FullyObservableCollection() : base()
        { }

        public FullyObservableCollection(List<T> list) : base(list)
        {
            ObserveAll();
        }

        public FullyObservableCollection(IEnumerable<T> enumerable) : base(enumerable)
        {
            ObserveAll();
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Remove ||
                e.Action == NotifyCollectionChangedAction.Replace)
            {
                foreach (T item in e.OldItems)
                    item.PropertyChanged -= ChildPropertyChanged;
            }

            if (e.Action == NotifyCollectionChangedAction.Add ||
                e.Action == NotifyCollectionChangedAction.Replace)
            {
                foreach (T item in e.NewItems)
                    item.PropertyChanged += ChildPropertyChanged;
            }

            base.OnCollectionChanged(e);
        }

        protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e)
        {
            ItemPropertyChanged?.Invoke(this, e);
        }

        protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e)
        {
            OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e));
        }

        protected override void ClearItems()
        {
            foreach (T item in Items)
                item.PropertyChanged -= ChildPropertyChanged;

            base.ClearItems();
        }

        private void ObserveAll()
        {
            foreach (T item in Items)
                item.PropertyChanged += ChildPropertyChanged;
        }

        private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            T typedSender = (T)sender;
            int i = Items.IndexOf(typedSender);

            if (i < 0)
                throw new ArgumentException("Received property notification from item not in collection");

            OnItemPropertyChanged(i, e);
        }
    }

    /// <summary>
    /// Provides data for the <see cref="FullyObservableCollection{T}.ItemPropertyChanged"/> event.
    /// </summary>
    public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs
    {
        /// <summary>
        /// Gets the index in the collection for which the property change has occurred.
        /// </summary>
        /// <value>
        /// Index in parent collection.
        /// </value>
        public int CollectionIndex { get; }

        /// <summary>
        /// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
        /// </summary>
        /// <param name="index">The index in the collection of changed item.</param>
        /// <param name="name">The name of the property that changed.</param>
        public ItemPropertyChangedEventArgs(int index, string name) : base(name)
        {
            CollectionIndex = index;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ItemPropertyChangedEventArgs"/> class.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <param name="args">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
        public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName)
        { }
    }
}

NUnit Testleri

Böylece, yapabileceğiniz değişiklikleri kontrol edebilirsiniz (ve ilk etapta ne test ettiğimi görebilirsiniz!), NUnit test sınıfımı da dahil ettim. Açıkçası, aşağıdaki kod sadece FullyObservableCollection<T>projenizde kullanmak için gerekli değildir .

NB Test sınıfı BindableBasePRISM'den uygulamak için kullanır INotifyPropertyChanged. Ana koddan PRISM'e bağımlılık yoktur.

using NUnit.Framework;
using Utilities;
using Microsoft.Practices.Prism.Mvvm;
using System.Collections.Specialized;
using System.Collections.Generic;

namespace Test_Utilities
{
    [TestFixture]
    public class Test_FullyObservableCollection : AssertionHelper
    {
        public class NotifyingTestClass : BindableBase
        {
            public int Id
            {
                get { return _Id; }
                set { SetProperty(ref _Id, value); }
            }
            private int _Id;

            public string Name
            {
                get { return _Name; }
                set { SetProperty(ref _Name, value); }
            }
            private string _Name;

        }

        FullyObservableCollection<NotifyingTestClass> TestCollection;
        NotifyingTestClass Fred;
        NotifyingTestClass Betty;
        List<NotifyCollectionChangedEventArgs> CollectionEventList;
        List<ItemPropertyChangedEventArgs> ItemEventList;

        [SetUp]
        public void Init()
        {
            Fred = new NotifyingTestClass() { Id = 1, Name = "Fred" };
            Betty = new NotifyingTestClass() { Id = 4, Name = "Betty" };

            TestCollection = new FullyObservableCollection<NotifyingTestClass>()
                {
                    Fred,
                    new NotifyingTestClass() {Id = 2, Name = "Barney" },
                    new NotifyingTestClass() {Id = 3, Name = "Wilma" }
                };

            CollectionEventList = new List<NotifyCollectionChangedEventArgs>();
            ItemEventList = new List<ItemPropertyChangedEventArgs>();
            TestCollection.CollectionChanged += (o, e) => CollectionEventList.Add(e);
            TestCollection.ItemPropertyChanged += (o, e) => ItemEventList.Add(e);
        }

        // Change existing member property: just ItemPropertyChanged(IPC) should fire
        [Test]
        public void DetectMemberPropertyChange()
        {
            TestCollection[0].Id = 7;

            Expect(CollectionEventList.Count, Is.EqualTo(0));

            Expect(ItemEventList.Count, Is.EqualTo(1), "IPC count");
            Expect(ItemEventList[0].PropertyName, Is.EqualTo(nameof(Fred.Id)), "Field Name");
            Expect(ItemEventList[0].CollectionIndex, Is.EqualTo(0), "Collection Index");
        }


        // Add new member, change property: CollectionPropertyChanged (CPC) and IPC should fire
        [Test]
        public void DetectNewMemberPropertyChange()
        {
            TestCollection.Add(Betty);

            Expect(TestCollection.Count, Is.EqualTo(4));
            Expect(TestCollection[3].Name, Is.EqualTo("Betty"));

            Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count");

            Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count");
            Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Add), "Action (add)");
            Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
            Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
            Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Betty), "NewItems[0] dereference");

            CollectionEventList.Clear();      // Empty for next operation
            ItemEventList.Clear();

            TestCollection[3].Id = 7;
            Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count");

            Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count");
            Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Betty), "Collection Index dereference");
        }


        // Remove member, change property: CPC should fire for removel, neither CPC nor IPC should fire for change
        [Test]
        public void CeaseListentingWhenMemberRemoved()
        {
            TestCollection.Remove(Fred);

            Expect(TestCollection.Count, Is.EqualTo(2));
            Expect(TestCollection.IndexOf(Fred), Is.Negative);

            Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");

            Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
            Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Remove), "Action (remove)");
            Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
            Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");
            Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");

            CollectionEventList.Clear();      // Empty for next operation
            ItemEventList.Clear();

            Fred.Id = 7;
            Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
            Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
        }


        // Move member in list, change property: CPC should fire for move, IPC should fire for change
        [Test]
        public void MoveMember()
        {
            TestCollection.Move(0, 1);

            Expect(TestCollection.Count, Is.EqualTo(3));
            Expect(TestCollection.IndexOf(Fred), Is.GreaterThan(0));

            Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");

            Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
            Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Move), "Action (move)");
            Expect(CollectionEventList[0].OldItems.Count, Is.EqualTo(1), "OldItems count");
            Expect(CollectionEventList[0].NewItems.Count, Is.EqualTo(1), "NewItems count");
            Expect(CollectionEventList[0].OldItems[0], Is.EqualTo(Fred), "OldItems[0] dereference");
            Expect(CollectionEventList[0].NewItems[0], Is.EqualTo(Fred), "NewItems[0] dereference");

            CollectionEventList.Clear();      // Empty for next operation
            ItemEventList.Clear();

            Fred.Id = 7;
            Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");

            Expect(ItemEventList.Count, Is.EqualTo(1), "Item Event count (post change)");
            Expect(TestCollection[ItemEventList[0].CollectionIndex], Is.EqualTo(Fred), "Collection Index dereference");
        }


        // Clear list, chnage property: only CPC should fire for clear and neither for property change
        [Test]
        public void ClearList()
        {
            TestCollection.Clear();

            Expect(TestCollection.Count, Is.EqualTo(0));

            Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (pre change)");

            Expect(CollectionEventList.Count, Is.EqualTo(1), "Collection Event count (pre change)");
            Expect(CollectionEventList[0].Action, Is.EqualTo(NotifyCollectionChangedAction.Reset), "Action (reset)");
            Expect(CollectionEventList[0].OldItems, Is.Null, "OldItems count");
            Expect(CollectionEventList[0].NewItems, Is.Null, "NewItems count");

            CollectionEventList.Clear();      // Empty for next operation
            ItemEventList.Clear();

            Fred.Id = 7;
            Expect(CollectionEventList.Count, Is.EqualTo(0), "Collection Event count (post change)");
            Expect(ItemEventList.Count, Is.EqualTo(0), "Item Event count (post change)");
        }
    }
}

1
Ne yaptığımı bilmiyorum, ama bu benim için işe yaramıyor. ListView koleksiyonunuza bağlıyorum ama içindeki öğelerin özelliklerini güncellediğimde, ListView, tüm olayları ateşleyen görebilirsiniz bile güncellenmez. Ayrıca PRISM kütüphanesini kullanıyorum ...
Renato Parreira

@Renato, yeni etkinlikle bir şey yaptın mı? olaylara ListViewcevap verecek CollectionChangedçünkü onlar hakkında bilgi sahibi olacak. ItemPropertyChangedstandart olmayan bir ektir, bu yüzden bunu öğretmeniz gerekir. Hızlı ve kirli düzeltme olarak, sadece ateş deneyebilirsiniz CollectionChangedsıra (hatta yerine) olarak olayı ItemPropertyChangediçinde OnItemPropertyChanged(). Cevapta belirtilen nedenlerden dolayı onları ayrı tuttum, ancak kullanım durumunuz için ihtiyacınız olanı yapabilir.
Bob Sammers

20

Bu, yukarıdaki fikirleri kullanır ancak türetilmiş bir 'daha hassas' koleksiyon haline getirir:

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

12

ObservableCollection, CollectionChanged olayları olarak tek tek öğe değişikliklerini yaymayacaktır. Her etkinliğe abone olmanız ve manuel olarak iletmeniz gerekir veya bunu sizin için yapacak BindingList [T] sınıfına göz atabilirsiniz .


Neden bundan bahseden tek kişisin? +1
Atizs

7

TruelyObservableCollection etkinliğine "ItemPropertyChanged" eklendi:

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
}

INotifyPropertyChanged uyguladığından PropertyChanged'i doğrudan ObservableCollection öğesinden kullanabilirsiniz.
Dieter Meemken

6

Jack OCyons'un cevabını kendi OC'imi uygulamak için kullandım, ancak çalışmasını sağlamak için yapmam gereken bir değişikliğe dikkat çekmek istiyorum. Onun yerine:

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

Bunu kullandım:

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

Görünüşe göre eylem .Remove ise "e.NewItems" null üretir.


Ben de e.Action == replace
jk

6

Sadece bu konuya 2 sent ekliyorum. Keçe TrulyObservableCollection, ObservableCollection ile bulunan diğer iki kurucuyu gerektirdi:

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

5

Bu partiye çok geç kaldığımı biliyorum, ama belki - birine yardım eder.

Burada ObservableCollectionEx uygulamamı bulabilirsiniz. Bazı özellikleri vardır:

  • ObservableCollection'dan her şeyi destekler
  • iş parçacığı için güvenli
  • ItemPropertyChanged olayını destekler (Item.PropertyChanged öğesi her tetiklendiğinde yükselir)
  • filtreleri destekler (böylece, ObservableCollectionEx oluşturabilir, Kaynak olarak başka bir koleksiyon geçirebilir ve basit yüklemli Filtre uygulayabilirsiniz. WPF'de çok yararlı, uygulamalarımda bu özelliği çok kullanıyorum). Daha da fazlası - INotifyPropertyChanged arabirimi aracılığıyla öğelerin değişikliklerini filtreleyin.

Tabii ki, herhangi bir yorum takdir;)


1
Большое спасибо! Bunu paylaştığın için çok teşekkürler! Kendi uygulamamı yazmak zorunda kalmadan beni saatlerce kurtardın! :)
Alexander

@Alexander çok hoş geldiniz :)
chopikadze

@ chopikadze, ben ObservableCollectionEx cs dosyasını indiremiyorum nazikçe düzeltebilirsiniz. Teşekkürler
Shax

Bağlantı öldü.

5

Eğer ObservableCollection sadece olayı eklediğimizde / sildiğimizde veya koleksiyonumuza taşıdığımızda olay yaparsam. Koleksiyon öğeleri koleksiyonundaki bazı özellikleri simly güncellediğimizde bu konuda sinyal vermiyoruz ve kullanıcı arayüzü güncellenmeyecek.

Model sınıfınıza INotifyPropertyChange uygulamasını biraz uygulayabilirsiniz . Koleksiyon öğesindeki bazı özellikleri güncellediğimizde, otomatik olarak kullanıcı arayüzünü güncelleyecektir.

public class Model:INotifyPropertyChange
{
//...
}

ve sonra

public ObservableCollection<Model> {get; set;}

Benim durumumda bu koleksiyon için ListView Bağlamak için kullanılan ve ItemTemplate Model özelliği için Binding set ve iyi çalışır.

İşte bazı snippet

Windows XAML:

<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <ListView 
        Margin="10"
        BorderBrush="Black"
        HorizontalAlignment="Center"
        SelectedItem="{Binding SelectedPerson}"
        ItemsSource="{Binding Persons}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Label Content="{Binding Name}"/>
                    <Label Content="-"/>
                    <Label Content="{Binding Age}"/>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    <Grid 
        Grid.Row="1"
        VerticalAlignment="Center"
        HorizontalAlignment="Center">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label 
            VerticalAlignment="Center"
            Content="Name:"/>
        <TextBox
            Text="{Binding SelectedPerson.Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
            Margin="10"
            Grid.Column="1" 
            Width="100"/>
        <Label 
            VerticalAlignment="Center"
            Grid.Row="1"
            Content="Age:"/>
        <TextBox
            Text="{Binding SelectedPerson.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
            Margin="10"
            Grid.Row="1"
            Grid.Column="1" 
            Width="100"/>


    </Grid>
</Grid>

Model kodu örneği:

public class PersonModel:INotifyPropertyChanged
{
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }

    public int Age
    {
        get => _age;
        set
        {
            _age = value;
            OnPropertyChanged();
        }
    }

    private string _name;
    private int _age;
    //INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Ve ViewModel uygulaması:

 public class ViewModel:INotifyPropertyChanged
{
    public ViewModel()
    {
        Persons = new ObservableCollection<PersonModel>
        {
            new PersonModel
            {
                Name = "Jack",
                Age = 30
            },
            new PersonModel
            {
                Name = "Jon",
                Age = 23
            },
            new PersonModel
            {
                Name = "Max",
                Age = 23
            },
        };
    }

    public ObservableCollection<PersonModel> Persons { get;}

    public PersonModel SelectedPerson
    {
        get => _selectedPerson;
        set
        {
            _selectedPerson = value;
            OnPropertyChanged();
        }
    }

    //INotifyPropertyChanged Implementation
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private PersonModel _selectedPerson;
}

2

Kullandığım standart gözlemlenebilir toplama için basit çözüm:

Mülkünüze EKLEMEYİN VEYA iç öğelerini DOĞRUDAN DEĞİŞTİRMEYİN, bunun gibi geçici bir koleksiyon oluşturun

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

tmpList'e öğe ekleyebilir veya değişiklik yapabilir,

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

ardından atama yoluyla gerçek mülkünüze iletin.

ContentList=tmpList;

bu, INotifyPropertyChanged'i istediğiniz gibi fark etmenizi sağlayan tüm özelliği değiştirir.


1

Bu çözümü deneyin, ancak koleksiyon değiştiğinde, her öğe ekleme veya değiştirme için tetiklenen bir RaisePropertyChange ("SourceGroupeGridView") gibi benim için çalışır.

Sorun şudur:

public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
     NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
    OnCollectionChanged(args);
}

NotifyCollectionChangedAction.Bu eylemin sıfırlanması, groupedgrid öğesindeki tüm öğelerin tam olarak yeniden bağlanmasını sağlar, RaisePropertyChanged'de eşdeğerdir. Kullandığınızda tüm gridview grupları yenilenir.

Yalnızca kullanıcı arayüzünde yeni öğenin grubunu yenilemek istiyorsanız, Eylemi sıfırla seçeneğini kullanmazsanız, itemproperty'de Ekle eylemini aşağıdaki gibi bir şeyle simüle etmeniz gerekir:

void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{         
    var index = this.IndexOf((T)sender);

    this.RemoveAt(index);
    this.Insert(index, (T)sender);

    var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
    OnCollectionChanged(a);
}

İngilizcem için üzgünüm ve temel kod için teşekkürler :), umarım bu birine yardımcı olur ^ _ ^

Enjoi !!


1

Yukarıdaki çözüm için bir genişletme yöntemi ...

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

Cevabı açıklamak isteyebilirsiniz
geedubb

1
Uzantı yöntemlerini açıklayan bir bağlantı. docs.microsoft.com/tr-tr/dotnet/csharp/programming-guide/…
LawMan

1

Bir ObservableCollection veya TrulyObservableCollection yerine, bir BindingList kullanmayı ve ResetBindings yöntemini çağırmayı düşünün.

Örneğin:

private BindingList<TfsFile> _tfsFiles;

public BindingList<TfsFile> TfsFiles
{
    get { return _tfsFiles; }
    set
    {
        _tfsFiles = value;
        NotifyPropertyChanged();
    }
}

Tıklama gibi bir etkinlik verildiğinde kodunuz şöyle görünür:

foreach (var file in TfsFiles)
{
    SelectedFile = file;
    file.Name = "Different Text";
    TfsFiles.ResetBindings();
}

Modelim şöyle görünüyordu:

namespace Models
{
    public class TfsFile 
    {
        public string ImagePath { get; set; }

        public string FullPath { get; set; }

        public string Name { get; set; }

        public string Text { get; set; }

    }
}

1
Bu yöntem hakkında iyi bilgi BindingList, ancak diğer yaklaşımların üstesinden geldiği bu yaklaşımda bir sınırlama vardır: bu teknik kodda değiştirilen değere ve bir çağrının nereye ResetBindings()eklenebileceğine dayanır . Listenin nesneleri değiştirilemez kod veya ikinci bir kontrole bağlanma gibi başka yollarla değiştirilirse diğer cevapların çoğu işe yarayacaktır.
Bob Sammers

1

Gözlemlenebilir Toplama Listesinde OnChange'i Tetiklemek

  1. Seçili Öğenin dizinini al
  2. Öğeyi Üst Öğeden Kaldırma
  3. Üst öğeye aynı dizindeki öğeyi ekle

Misal:

int index = NotificationDetails.IndexOf(notificationDetails);
NotificationDetails.Remove(notificationDetails);
NotificationDetails.Insert(index, notificationDetails);

0

İşte benim uygulama sürümü. Listedeki nesneler INotifyPropertyChanged'i uygulamazsa, kontrol eder ve bir hata atar, bu nedenle geliştirirken bu sorunu unutamaz. Dışarıda ListItemChanged Olayını kullanarak listenin veya liste öğesinin kendisinin değişip değişmediğini belirleyin.

public class SpecialObservableCollection<T> : ObservableCollection<T>
{
    public SpecialObservableCollection()
    {
        this.CollectionChanged += OnCollectionChanged;
    }

    void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        AddOrRemoveListToPropertyChanged(e.NewItems,true); 
        AddOrRemoveListToPropertyChanged(e.OldItems,false); 
    }

    private void AddOrRemoveListToPropertyChanged(IList list, Boolean add)
    {
        if (list == null) { return; }
        foreach (object item in list)
        {
            INotifyPropertyChanged o = item as INotifyPropertyChanged;
            if (o != null)
            {
                if (add)  { o.PropertyChanged += ListItemPropertyChanged; }
                if (!add) { o.PropertyChanged -= ListItemPropertyChanged; }
            }
            else
            {
                throw new Exception("INotifyPropertyChanged is required");
            }
        }
    }

    void ListItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        OnListItemChanged(this, e);
    }

    public delegate void ListItemChangedEventHandler(object sender, PropertyChangedEventArgs e);

    public event ListItemChangedEventHandler ListItemChanged;

    private void OnListItemChanged(Object sender, PropertyChangedEventArgs e)
    {
        if (ListItemChanged != null) { this.ListItemChanged(this, e); }
    }


}

0

2 satır kodda basit çözüm. Sadece kopya oluşturucuyu kullanın. TrulyObservableCollection vb. Yazmanıza gerek yok.

Misal:

        speakers.list[0].Status = "offline";
        speakers.list[0] = new Speaker(speakers.list[0]);

Kopya oluşturucu olmadan başka bir yöntem. Serileştirmeyi kullanabilirsiniz.

        speakers.list[0].Status = "offline";
        //speakers.list[0] = new Speaker(speakers.list[0]);
        var tmp  = JsonConvert.SerializeObject(speakers.list[0]);
        var tmp2 = JsonConvert.DeserializeObject<Speaker>(tmp);
        speakers.list[0] = tmp2;

0

Bu uzantı yöntemini, ilgili koleksiyonlardaki öğe özellik değişikliği için bir işleyiciyi kolayca kaydetmek için de kullanabilirsiniz. Bu yöntem, INotifyPropertyChanged uygulayan öğeleri tutan INotifyCollectionChanged uygulayan tüm koleksiyonlara otomatik olarak eklenir:

public static class ObservableCollectionEx
{
    public static void SetOnCollectionItemPropertyChanged<T>(this T _this, PropertyChangedEventHandler handler)
        where T : INotifyCollectionChanged, ICollection<INotifyPropertyChanged> 
    {
        _this.CollectionChanged += (sender,e)=> {
            if (e.NewItems != null)
            {
                foreach (Object item in e.NewItems)
                {
                    ((INotifyPropertyChanged)item).PropertyChanged += handler;
                }
            }
            if (e.OldItems != null)
            {
                foreach (Object item in e.OldItems)
                {
                    ((INotifyPropertyChanged)item).PropertyChanged -= handler;
                }
            }
        };
    }
}

Nasıl kullanılır:

public class Test
{
    public static void MyExtensionTest()
    {
        ObservableCollection<INotifyPropertyChanged> c = new ObservableCollection<INotifyPropertyChanged>();
        c.SetOnCollectionItemPropertyChanged((item, e) =>
        {
             //whatever you want to do on item change
        });
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.