जब आइटम में परिवर्तन होता है, तब भी अवलोकन योग्य नहीं होता है (INotifyPropertyChanged के साथ)


167

क्या किसी को पता है कि यह कोड काम क्यों नहीं करता है:

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

ViewModelBasecontaints के लिए सब कुछ RaisePropertyChangedआदि और यह इस समस्या को छोड़कर बाकी सब के लिए काम कर रहा है ..


जवाबों:


119

जब आप संग्रह के अंदर कोई मूल्य बदलते हैं, तो ContentList की सेट विधि को कॉल नहीं किया जाएगा, इसके बजाय आपको CollectionChanged इवेंट फायरिंग के लिए बाहर देखना चाहिए ।

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

ठीक है, आज दो बार मैं MSDN प्रलेखन द्वारा गलत किया जा रहा है काट लिया गया है। मैंने आपको जो लिंक दिया है, उसमें लिखा है:

तब होता है जब कोई आइटम जोड़ा जाता है, निकाला जाता है, बदला जाता है, स्थानांतरित किया जाता है, या पूरी सूची को ताज़ा किया जाता है।

लेकिन यह वास्तव में आग नहीं है जब एक आइटम बदल जाता है। मुझे लगता है कि आपको एक और अधिक bruteforce विधि की आवश्यकता होगी:

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

यदि आपको इसकी बहुत आवश्यकता है, तो आप अपने स्वयं के उपसंहार करना चाहते हैं ObservableCollectionजो उस CollectionChangedघटना को ट्रिगर करता है जब कोई सदस्य अपने PropertyChangedईवेंट को स्वचालित रूप से चलाता है (जैसे कि यह कहता है कि इसे दस्तावेज़ में होना चाहिए ...)


क्षमा करें हैरिस, लेकिन मुझे EntityViewModel में किस ईवेंट को फायर करना है ताकि ContentCollectionChanged को कॉल किया जाए?
जोसेफ जून मेलेट्टुकनेल

36
ध्यान दें कि यदि आप ईवेंट प्रबंधन को स्वयं लागू नहीं करना चाहते हैं, तो आप ऑब्ज़र्वेबल कॉलेलेशन <EntityViewModel> के स्थान पर एक बाइंडिंगलिस्ट <EntityViewModel> का उपयोग कर सकते हैं। यह तब स्वतः EntityViewModel.PropertyChanged घटनाओं को ListChanged इवेंट्स के रूप में अग्रेषित करेगा जहाँ ListChangedType == ItemChanged।
बजे

15
क्या यह सब आपके कार्यकाल की समझ पर निर्भर नहीं करता है changed? इसका मतलब यह हो सकता है कि संग्रह में तत्वों में से एक की संपत्ति बदल गई है (जो है कि मुझे लगता है कि आप इसे व्याख्या कर रहे हैं) या इसका मतलब यह हो सकता है कि संग्रह के तत्वों में से एक को एक अलग उदाहरण के साथ बदल दिया गया है ( यह मेरी व्याख्या है)। हालांकि पूरी तरह से आश्वस्त नहीं हैं - आगे इस पर गौर करना होगा।
बेलुगबोब

10
अगर मैं आह्वान _contentList.Clear()करूँ तो क्या होगा ? किसी से भी सदस्यता समाप्त नहीं होगी PropertyChanged!
पाओलो मोराती 11

2
@ पाओलो: यह सही है, ContentCollectionChangedकेवल जोड़ें / निकालें, और बदलें / रीसेट को नहीं संभालता है। मैं पोस्ट को संपादित करने और ठीक करने का प्रयास करूंगा। जिस तरह से सिमोन अपने जवाब में करते हैं वह सही है।
माइक फुच्स

178

यहां एक ड्रॉप-इन वर्ग है जो उप-कक्षाओं ऑब्जर्वेबलकॉलेक्शन और वास्तव में एक रीसेट आइटम को उठाता है जब एक सूची आइटम पर एक संपत्ति बदल जाती है। यह लागू करने के लिए सभी वस्तुओं को लागू करता है INotifyPropertyChanged

यहां लाभ यह है कि आप इस वर्ग के लिए डेटा बाइंड कर सकते हैं और आपके सभी बाइंडिंग आपके आइटम गुणों में परिवर्तन के साथ अपडेट होंगे।

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
मेरे पास स्वयं के समान कुछ लागू करने का कारण है, हालांकि इसके बजाय NotifyCollectionChangedAction.Reset का उपयोग करने के बजाय मैंने उपयोग किया है।
क्रिस

2
मेरी समस्या का भयानक समाधान - धन्यवाद! उन लोगों के लिए, जिन्होंने एक सूची के साथ अपने ऑब्जर्वेबल कॉलेक्शन को बनाया है, आप एक कंस्ट्रक्टर जोड़ना चाह सकते हैं जो सभी वस्तुओं को जोड़ देता है और प्रॉपर्टीचेंज जोड़ता है।
गैविन

4
यहां एक संभावित मेमोरी लीक है - एक रीसेट घटना तब होती है जब संग्रह में काफी बदलाव होता है, उदाहरण के लिए क्लीयर। ऐसा होने पर आपका कोई भी INPC हैंडलबार बंद नहीं किया जाएगा।
चार्ल्स मैगर

6
यह एक ठीक कार्यान्वयन है, लेकिन इसका एक प्रमुख मुद्दा है - NotifyCollectionChangedAction.Replaceयह एक अच्छा विचार नहीं है, क्योंकि तब आप वास्तव में किसी आइटम के बीच अंतर नहीं कर सकते हैं जिसे प्रतिस्थापित किया जा रहा है या आइटम परिवर्तन के कारण घटना हो सकती है। यह बहुत बेहतर हो जाता है जब आप परिभाषित करते हैं public event PropertyChangedEventHandler CollectionItemChanged;और फिर ItemPropertyChangedकरते हैंthis.CollectionItemChanged?.Invoke(sender, e);
hyankov

4
क्या किसी को इस वर्ग के उपयोग का उदाहरण मिला है?
डिकोडर 94

23

मैंने एक साथ रखा है जो मुझे आशा है कि एक बहुत मजबूत समाधान है, जिसमें अन्य उत्तरों में कुछ तकनीकें भी शामिल हैं। यह एक नया वर्ग है ObservableCollection<>, जिसे मैं बुला रहा हूँFullyObservableCollection<>

इसकी निम्नलिखित विशेषताएं हैं:

  • यह एक नई घटना कहते हैं ItemPropertyChanged। मैंने इसे जानबूझकर मौजूदा से अलग रखा है CollectionChanged:
    • पिछड़े अनुकूलता की सहायता करना।
    • तो ItemPropertyChangedEventArgsइसके साथ आने वाले नए में अधिक प्रासंगिक विवरण दिया जा सकता है: PropertyChangedEventArgsसंग्रह के भीतर मूल और सूचकांक।
  • यह सभी कंस्ट्रक्टरों की नकल करता है ObservableCollection<>
  • यह ObservableCollection<>.Clear()संभावित मेमोरी लीक से बचने के लिए सूची को सही तरीके से रीसेट ( ) कर रहा है ।
  • यह बेस क्लास के ओवरराइड करता है OnCollectionChanged()CollectionChanged घटना के लिए एक अधिक संसाधन-गहन सदस्यता के बजाय, ।

कोड

पूरी .csफाइल इस प्रकार है। ध्यान दें कि C # 6 की कुछ विशेषताओं का उपयोग किया गया है, लेकिन इसे वापस करने के लिए यह काफी सरल होना चाहिए:

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 टेस्ट

इसलिए आप अपने द्वारा किए जा सकने वाले परिवर्तनों की जांच कर सकते हैं (और देखें कि मैंने पहली जगह में क्या परीक्षण किया है!), मैंने अपना NUnit परीक्षण वर्ग भी शामिल किया है। जाहिर है, निम्नलिखित कोड केवल FullyObservableCollection<T>अपनी परियोजना में उपयोग करने के लिए आवश्यक नहीं है

NB परीक्षण वर्ग BindableBasePRISM से लागू करने के लिए उपयोग करता है INotifyPropertyChanged। मुख्य कोड से PRISM पर निर्भरता नहीं है।

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
मुझे नहीं पता कि मैं क्या गलत कर रहा हूं, लेकिन यह मेरे लिए काम नहीं करता है। मैं अपने सूची में अपना सूची दृश्य बाँध रहा हूँ, लेकिन जब मैं अंदर की वस्तुओं के गुणों को अद्यतन करता हूँ, तो सूची दृश्य अपडेट नहीं करता है, यहाँ तक कि 'मैं सभी घटनाओं को देख सकता हूँ। मैं भी प्रिज्म पुस्तकालय का उपयोग कर रहा ...
रेनाटो Parreira

@ रेनाटो, क्या आपने नई घटना के साथ कुछ किया है? घटनाओं का ListViewजवाब देंगे CollectionChangedक्योंकि यह उनके बारे में जानता है। ItemPropertyChangedएक गैर-मानक जोड़ है, इसलिए आपको इसके बारे में सिखाने की जरूरत है। एक त्वरित और गंदे तय के रूप में, आप बस फायरिंग की CollectionChangedघटना के साथ ही (या यहां तक ​​कि इसके बजाय) ItemPropertyChangedमें कोशिश कर सकते हैं OnItemPropertyChanged()। मैंने उत्तर में बताए गए कारणों के लिए उन्हें अलग रखा, लेकिन आपके उपयोग-मामले के लिए यह वही हो सकता है जो आपको चाहिए।
बॉब सैमर्स

20

यह उपरोक्त विचारों का उपयोग करता है, लेकिन यह एक व्युत्पन्न 'अधिक संवेदनशील' संग्रह बनाता है:

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 व्यक्तिगत आइटम परिवर्तनों को संग्रहित घटनाओं के रूप में प्रचारित नहीं करेगा। आपको या तो प्रत्येक ईवेंट को सब्सक्राइब करना होगा और इसे मैन्युअल रूप से फॉरवर्ड करना होगा, या आप बाइंडिंगलिस्ट [टी] वर्ग की जांच कर सकते हैं , जो आपके लिए ऐसा करेगा।


आप केवल यही क्यों उल्लेख करते हैं? +1
Atizs

7

TruelyObservableCollection घटना में जोड़ा गया "ItemPropertyChanged":

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 लागू करता है, आप सीधे ObservableCollection से PropertyChanged का उपयोग कर सकते हैं।
डाइटर मीमकेन

6

मैंने अपने स्वयं के ओसी को लागू करने के लिए जैक केनियन्स उत्तर का उपयोग किया, लेकिन मैं एक बदलाव की ओर इशारा करना चाहता हूं जो मुझे इसे बनाने के लिए करना था। के बजाय:

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

मैंने इसका उपयोग किया:

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

ऐसा लगता है कि अगर कार्रवाई है तो "e.NewItems" शून्य पैदा करता है।


मुझे लगता है कि इसे और बदलावों की जरूरत है अगर e.Action == प्रतिस्थापित
jk।

6

बस इस विषय पर मेरे 2 सेंट जोड़ने। TrulyObservableCollection को देखने के लिए दो अन्य कंस्ट्रक्टर्स की आवश्यकता होती है, जैसा कि ऑब्जर्वेबलCollection के साथ पाया जाता है:

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

मुझे पता है कि मुझे इस पार्टी के लिए बहुत देर हो चुकी है, लेकिन शायद - यह किसी को मदद करेगा ।।

यहाँ आप ऑब्जर्वेबल का चयन कर सकते हैं। इसकी कुछ विशेषताएं हैं:

  • यह ऑब्जर्वेबल कॉलेक्शन से सब कुछ सपोर्ट करता है
  • यह सुरक्षित है
  • यह ItemPropertyChanged इवेंट का समर्थन करता है (यह हर बार तब होता है जब Item.PropertyChanged आइटम निकाल दिया जाता है)
  • यह फ़िल्टर का समर्थन करता है (इसलिए, आप ऑब्जर्वेबल कोलेलिशन एक्स बना सकते हैं, इसे स्रोत के रूप में एक और संग्रह पास करें, और सरल विधेय के साथ फ़िल्टर करें। WPF में बहुत उपयोगी है, मैं अपने अनुप्रयोगों में इस सुविधा का बहुत उपयोग करता हूं)। और भी - फ़िल्टर इनोटिफ़प्रोपर्टी चेंजिंग इंटरफ़ेस के माध्यम से आइटम के परिवर्तन को ट्रैक करता है।

बेशक, किसी भी टिप्पणी की सराहना की है;)


1
आगे बढ़ें! साझा करने के लिए बहुत धन्यवाद! आपने अपना कार्यान्वयन नहीं लिखने के कारण मुझे कई घंटे बचाए! :)
अलेक्जेंडर


@chopikadze, मैं आपके ऑब्ज़र्वेबल कोलेजिनेशन की सीएस फ़ाइल डाउनलोड करने में असमर्थ हूँ। क्या आप इसे ठीक कर सकते हैं। धन्यवाद
Shax

लिंक मर चुका है।

5

अगर मुझे पता है कि ऑब्जर्वेबल कॉलेक्शन हमें इवेंट तभी बनाते हैं, जब हम अपने कलेक्शन में आइटम जोड़ते / हटाते या हटाते हैं। जब हम संग्रह आइटमों में कुछ गुणों को समान रूप से अपडेट करते हैं, तो इसके बारे में संकेत नहीं करते हैं और UI अपडेट नहीं किया जाएगा।

आप simly लागू कर सकते हैं INotifyPropertyChange अपने मॉडल वर्ग में। और जब हम संग्रह आइटम में कुछ उचित अपडेट करते हैं तो यह स्वतः ही यूआई को अपडेट कर देगा।

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

और से

public ObservableCollection<Model> {get; set;}

मेरे मामले में मैंने इस संग्रह के लिए बाइंड को ListView का उपयोग किया और ItemTemplate में बाइंडिंग टू मॉडल प्रॉपर्टी और यह अच्छा काम करता है।

यहाँ कुछ स्निपेट है

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>

मॉडल कोड उदाहरण:

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

और ViewModel कार्यान्वयन:

 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

मेरे द्वारा उपयोग किए जाने वाले मानक वेधनीय रंग के लिए सरल समाधान:

अपनी संपत्ति में शामिल न करें या इसे आंतरिक आइटम बदलें, इसके बजाय, इस तरह से कुछ अस्थायी संग्रह बनाएं

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

और आइटम जोड़ें या tmpList में परिवर्तन करें,

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

फिर इसे असाइन करके अपनी वास्तविक प्रॉपर्टी पर भेज दें।

ContentList=tmpList;

यह पूरी संपत्ति को बदल देगा जिसके कारण आपकी आवश्यकता के अनुसार INotifyPropertyChanged नोटिस होता है।


1

मैं इस समाधान की कोशिश करता हूं, लेकिन केवल मेरे लिए काम करता है जैसे कि RaisePropertyChange ("SourceGroupeGridView") जब संग्रह बदल जाता है, तो प्रत्येक आइटम जोड़ने या बदलने के लिए निकाल दिया जाता है।

समस्या यह है:

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

NotifyCollectionChangedAction.eset इस क्रिया को समूहीकृत में सभी वस्तुओं का पूर्ण रीबाइंड करें, RaisePropertyChanged के बराबर है। जब आप इसका उपयोग करते हैं तो ग्रिडव्यू के सभी समूह ताज़ा हो जाते हैं।

यदि आप, केवल यूआई में नए आइटम के समूह को रीफ्रेश करना चाहते हैं, तो आप रीसेट एक्शन का उपयोग नहीं करते हैं, आपको कुछ इस तरह से आइटमप्रॉपर्टी में ऐड एक्शन का अनुकरण करने की आवश्यकता होगी:

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

मेरे अंग्रेजी द्वारा क्षमा करें, और आधार कोड के लिए धन्यवाद :), मुझे आशा है कि इससे किसी को मदद मिलेगी ^ _ ^

Enjoi !!


1

यहाँ उपरोक्त समाधान के लिए एक विस्तार विधि है ...

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

आप उत्तर की व्याख्या करना चाह सकते हैं
geedubb

1
यहां एक लिंक दिया गया है जो विस्तार विधियों का वर्णन करता है। docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
LawMan

1

ऑब्जर्व करने योग्य कॉलेक्शन या ट्रूलीऑब्जर्वेबल कॉलेलेशन के बजाय, एक बाइंडिंगलिस्ट का उपयोग करने और रीसेटबिंडिंग विधि को कॉल करने पर विचार करें।

उदाहरण के लिए:

private BindingList<TfsFile> _tfsFiles;

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

किसी घटना को देखते हुए, जैसे एक क्लिक पर आपका कोड इस तरह दिखाई देगा:

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

मेरा मॉडल इस तरह देखा:

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
इस पद्धति के बारे में अच्छी जानकारी है BindingList, लेकिन इस दृष्टिकोण के लिए एक सीमा है कि अन्य उत्तर दूर हो जाते हैं: यह तकनीक कोड में बदले जा रहे मूल्य पर निर्भर करती है और जहां एक कॉल ResetBindings()जोड़ा जा सकता है। यदि सूची की वस्तुएं अन्य साधनों के माध्यम से बदल दी जाती हैं, जैसे कि असंगत कोड या एक बंधन से दूसरे नियंत्रण तक।
बॉब सैमर्स

1

प्रेक्षण योग्य सूची में ट्रिगर OnChange करने के लिए

  1. चयनित आइटम का सूचकांक प्राप्त करें
  2. माता-पिता से आइटम निकालें
  3. मूल में समान इंडेक्स पर आइटम जोड़ें

उदाहरण:

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

0

यहाँ कार्यान्वयन का मेरा संस्करण है। यह जाँचता है और एक त्रुटि फेंकता है, अगर सूची में ऑब्जेक्ट INotifyPropertyChanged को लागू नहीं करते हैं, तो उस मुद्दे को विकसित करने के लिए नहीं भूल सकते। बाहर की ओर आप ListItemChanged Event का उपयोग करते हैं, यह निर्धारित करते हैं कि सूची या सूची आइटम स्वयं बदल गया है या नहीं।

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 लाइनों में सरल समाधान। बस कॉपी कंस्ट्रक्टर का उपयोग करें। TrulyObservableCollection आदि लिखने की आवश्यकता नहीं है।

उदाहरण:

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

कॉपी कंस्ट्रक्टर के बिना एक और तरीका। आप क्रमांकन का उपयोग कर सकते हैं।

        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

आप प्रासंगिक संग्रह में आइटम संपत्ति परिवर्तन के लिए आसानी से एक हैंडलर को पंजीकृत करने के लिए इस विस्तार विधि का उपयोग कर सकते हैं। यह विधि स्वचालित रूप से INOTifyCollectionChanged को लागू करने वाले सभी संग्रहों में जोड़ दी जाती है, जो कि INotifyPropertyChanged को लागू करने वाले आइटम रखती हैं:

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

कैसे इस्तेमाल करे:

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
        });
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.