항목이 변경 될 때 ObservableCollection이 알리지 않음 (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"); }
    }
}

ViewModelBaseRaisePropertyChanged등을 위해 모든 것을 담아 두고이 문제를 제외한 다른 모든 것을 위해 노력하고 있습니다.


답변:


119

컬렉션 내에서 값을 변경할 때 ContentList의 Set 메서드가 호출되지 않고 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 문서가 잘못되어 물 렸습니다. 내가 당신에게 준 링크에서 그것은 말합니다 :

항목이 추가, 제거, 변경, 이동되거나 전체 목록이 새로 고쳐질 때 발생합니다.

그러나 실제로 항목이 변경되면 실행 되지 않습니다 . 그렇다면 더 무차별 한 방법이 필요할 것 같습니다.

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를 트리거하는 자체 서브 클래스를 작성하고 싶을 수도 있습니다 (문서에 있어야한다고 ...)CollectionChangedPropertyChanged


죄송하지만 Harris, EntityViewModel에서 어떤 이벤트를 발생시켜 ContentCollectionChanged를 호출해야합니까?
조셉 준. Melettukunnel

36
이벤트 관리를 직접 구현하지 않으려는 경우 ObservableCollection <EntityViewModel> 대신 BindingList <EntityViewModel>을 사용할 수 있습니다. 그런 다음 EntityViewModel.PropertyChanged 이벤트를 ListChangedType == ItemChanged 인 ListChanged 이벤트로 자동 전달합니다.
mjeanes

15
이 모든 것이 용어에 대한 당신의 이해에 달려 있지 changed않습니까? 이것은 컬렉션의 요소 중 하나의 속성이 변경되었음을 의미하거나 (이것이 해석하는 방식이라고 생각합니다) 컬렉션의 요소 중 하나가 다른 인스턴스로 교체되어 변경되었음을 의미 할 수 있습니다 ( 이것은 나의 해석이다). 그러나 완전히 확신하지는 못했습니다. 더 자세히 조사해야 할 것입니다.
belugabob

10
호출하면 어떻게 _contentList.Clear()됩니까? 아무도 구독을 취소하지 않습니다 PropertyChanged!
Paolo Moretti

2
@Paolo : 맞습니다. ContentCollectionChangedAdd / Remove 만 처리하고 Replace / Reset은 처리하지 않습니다. 게시물을 수정하고 수정하려고합니다. 사이먼이 답을하는 방식이 맞습니다.
Mike Fuchs

178

다음은 ObservableCollection을 서브 클래 싱하고 실제로 목록 항목의 속성이 변경 될 때 Reset 액션을 발생시키는 드롭 인 클래스입니다. 모든 항목을 구현하도록 강요합니다 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을 사용하는 대신 비슷한 것을 직접 구현 해야하는 원인이 있었지만 대신 .Replace를 사용했습니다 .new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Replace, item, item, IndexOf (item)).
Chris

2
내 문제에 대한 멋진 해결책-감사합니다! List를 사용하여 ObservableCollection을 만든 사용자의 경우 모든 항목을 종료하고 PropertyChanged를 추가하는 생성자를 추가 할 수 있습니다.
Gavin

4
여기에 메모리 누수가 발생할 수 있습니다. 컬렉션이 크게 변경되면 (예 : 지우기) 재설정 이벤트가 발생합니다. 이런 일이 발생하면 INPC 핸들러 중 어느 것도 탈퇴하지 않습니다.
Charles Mager

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
내가 뭘 잘못하고 있는지 모르겠지만 이것은 효과가 없습니다. 내 ListView를 컬렉션에 바인딩하고 있지만 내부 항목의 속성을 업데이트하면 ListView가 업데이트되지 않으며 모든 이벤트가 발생하는 것을 볼 수 있습니다. 나는 또한 PRISM 라이브러리를 사용하고 있습니다 ...
Renato Parreira

@Renato, 새로운 이벤트로 무엇을 했습니까? 이벤트에 대해 알고 있기 때문에 이벤트에 ListView응답 CollectionChanged합니다. ItemPropertyChanged비표준 추가이므로 그것에 대해 가르쳐야합니다. 빠르고 더러운 해결책으로에서 CollectionChanged이벤트를 시작하거나 ItemPropertyChanged에서 대신 시도해 볼 수 OnItemPropertyChanged()있습니다. 나는 대답에 명시된 이유로 분리하여 보관했지만 사용 사례의 경우 필요한 것을 수행 할 수 있습니다.
Bob Sammers

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은 개별 항목 변경 사항을 CollectionChanged 이벤트로 전파하지 않습니다. 각 이벤트를 구독하고 수동으로 전달하거나 BindingList [T] 클래스를 확인하면됩니다.


왜 당신이 이것을 언급하는 유일한 사람입니까? +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를 사용할 수 있습니다.
Dieter Meemken 2018 년

6

Jack Kenyons의 답변을 사용하여 내 OC를 구현했지만 작동하도록하기 위해 변경해야 할 사항 하나를 지적하고 싶습니다. 대신에:

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

action이 .Remove 인 경우 "e.NewItems"는 널을 생성하는 것으로 보입니다.


e.Action == replace
jk 인

6

이 주제에 2 센트 만 추가하면됩니다. TrulyObservableCollection을 느꼈다면 ObservableCollection에서 찾은 두 개의 다른 생성자가 필요했습니다.

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

나는이 파티에 너무 늦었다는 것을 알고 있지만 어쩌면 누군가에게 도움이 될 것이다 ..

여기 에서 ObservableCollectionEx의 구현을 찾을 수 있습니다. 몇 가지 기능이 있습니다.

  • 그것은 ObservableCollection에서 모든 것을 지원합니다
  • 스레드 안전
  • ItemPropertyChanged 이벤트를 지원합니다 (Item.PropertyChanged 항목이 실행될 때마다 발생 함).
  • 필터를 지원합니다 (따라서 ObservableCollectionEx를 생성하고 다른 컬렉션을 소스로 전달하고 간단한 술어로 필터링 할 수 있습니다. WPF에서 매우 유용합니다.이 기능은 내 응용 프로그램에서 많이 사용합니다). 훨씬 더-필터는 INotifyPropertyChanged 인터페이스를 통해 항목의 변경 사항을 추적합니다.

물론, 모든 의견을 부탁드립니다;)


1
Большое спасибо! 공유해 주셔서 감사합니다! 내 구현을 작성할 필요가 없어서 많은 시간을 절약했습니다! :)
Alexander

@Alexander 당신은 매우 환영합니다 :)
chopikadze

@chopikadze, ObservableCollectionEx의 cs 파일을 다운로드 할 수 없습니다 친절하게 해결할 수 있습니다. 감사합니다
Shax

연결이 끊어졌습니다.

5

ObservableCollection을 알고 있다면 컬렉션에서 항목을 추가 / 삭제하거나 이동할 때만 이벤트를 만듭니다. 컬렉션 아이템 컬렉션에서 일부 속성을 간단하게 업데이트 할 때이를 알리지 않고 UI가 업데이트되지 않습니다.

Model 클래스에서 INotifyPropertyChange 를 간단하게 구현할 수 있습니다 . 컬렉션 항목에서 일부 속성을 업데이트 할 때보 다 자동으로 UI가 업데이트됩니다.

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

그리고보다

public ObservableCollection<Model> {get; set;}

제 경우에는 ListView를 사용 하여이 컬렉션과 BindTemplate에서 Binding to Model 속성을 설정했습니다.

여기 스 니펫이 있습니다.

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.Reset이 조치는 groupedgrid의 모든 항목을 완전히 리 바인드하며 RaisePropertyChanged와 동일합니다. 사용하면 모든 gridview 그룹이 새로 고쳐졌습니다.

UI에서 새 항목의 그룹 만 새로 고치려면 재설정 작업을 사용하지 않고 다음과 같이 항목 속성에서 추가 작업을 시뮬레이션해야합니다.

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

내 영어로 죄송하지만 기본 코드에 감사드립니다 :), 이것이 누군가를 도울 수 있기를 바랍니다 ^ _ ^

엔조이 !!


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/ko-kr/dotnet/csharp/programming-guide/…
LawMan

1

ObservableCollection 또는 TrulyObservableCollection 대신 BindingList를 사용하고 ResetBindings 메서드를 호출하는 것이 좋습니다.

예를 들면 다음과 같습니다.

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()추가 할 수있는 위치에 의존합니다 . 목록의 개체가 변경 불가능한 코드와 같은 다른 방법이나 두 번째 컨트롤에 대한 바인딩과 같은 다른 수단을 통해 변경되면 다른 답변의 대부분이 작동합니다.
밥 Sammers

1

ObservableCollection 목록에서 OnChange를 트리거하려면

  1. 선택된 아이템의 인덱스 가져 오기
  2. 부모에서 항목을 제거하십시오
  3. 부모의 동일한 색인에 항목을 추가하십시오.

예:

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

0

여기 내 버전의 구현이 있습니다. 목록의 개체가 INotifyPropertyChanged를 구현하지 않으면 오류를 확인하고 throw하므로 개발 중에 해당 문제를 잊을 수 없습니다. 외부에서는 ListItemChanged 이벤트를 사용하여 목록 또는 목록 항목 자체가 변경되었는지 확인하십시오.

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

이 확장 메소드를 사용하여 관련 콜렉션에서 항목 특성 변경을위한 핸들러를 쉽게 등록 할 수도 있습니다. 이 메소드는 INotifyPropertyChanged를 구현하는 항목을 보유하는 INotifyCollectionChanged를 구현하는 모든 콜렉션에 자동으로 추가됩니다.

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
        });
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.