ObservableCollection không nhận thấy khi Item trong đó thay đổi (ngay cả với INotifyPropertyChanged)


167

Có ai biết tại sao mã này không hoạt động:

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

ViewModelBasechống lại mọi thứ cho RaisePropertyChangedvv và nó hoạt động cho mọi thứ khác ngoại trừ vấn đề này ..


Câu trả lời:


119

Phương thức Set của ContentList sẽ không được gọi khi bạn thay đổi một giá trị bên trong bộ sưu tập, thay vào đó bạn nên chú ý đến việc bắn sự kiện 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
    }
}

Được rồi, đó là hai lần hôm nay tôi đã bị cắn bởi tài liệu MSDN bị sai. Trong liên kết tôi đã cho bạn nó nói:

Xảy ra khi một mục được thêm, xóa, thay đổi, di chuyển hoặc toàn bộ danh sách được làm mới.

Nhưng nó thực sự không kích hoạt khi một vật phẩm được thay đổi. Tôi đoán bạn sẽ cần một phương pháp bruteforce hơn sau đó:

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

Nếu bạn sẽ cần điều này rất nhiều, bạn có thể muốn phân lớp riêng của mình ObservableCollectionđể kích hoạt CollectionChangedsự kiện khi một thành viên PropertyChangedtự động kích hoạt sự kiện của nó (giống như nó nói trong tài liệu này ...)


Xin lỗi Harris, nhưng sự kiện nào tôi phải kích hoạt trong EntityViewModel để ContentCollectionChanged được gọi?
Joseph tháng sáu. Melettukunnel

36
lưu ý rằng nếu bạn không muốn tự mình thực hiện quản lý sự kiện, bạn có thể sử dụng BindingList <EntityViewModel> thay cho ObservableCollection <EntityViewModel>. Sau đó, nó sẽ tự động chuyển tiếp các sự kiện EntityViewModel.PropertyChanged dưới dạng các sự kiện ListChanged trong đó ListChangedType == ItemChanged.
mjeanes

15
Có phải tất cả điều này phụ thuộc vào sự hiểu biết của bạn về thuật ngữ này changed? Điều này có thể có nghĩa là một thuộc tính của một trong các thành phần trong bộ sưu tập đã thay đổi (đó là cách tôi nghĩ bạn đang diễn giải nó) hoặc có thể có nghĩa là một trong các yếu tố của bộ sưu tập đã được thay đổi bằng cách thay thế nó bằng một phiên bản khác ( đây là cách giải thích của tôi) Mặc dù không hoàn toàn thuyết phục - sẽ phải xem xét thêm.
belugabob

10
Điều gì xảy ra nếu tôi gọi _contentList.Clear()? Không ai sẽ hủy đăng ký PropertyChanged!
Paolo Moretti

2
@Paolo: Đúng vậy, ContentCollectionChangedchỉ xử lý Thêm / Xóa chứ không thay thế / Đặt lại. Tôi sẽ cố gắng chỉnh sửa và sửa bài. Cách simon làm điều đó trong câu trả lời của ông là chính xác.
Mike Fuchs

178

Dưới đây là một lớp thả xuống mà các lớp con ObservableCollection và thực sự đưa ra một hành động Đặt lại khi một thuộc tính trên một mục danh sách thay đổi. Nó thực thi tất cả các mục để thực hiện INotifyPropertyChanged.

Lợi ích ở đây là bạn có thể dữ liệu liên kết với lớp này và tất cả các ràng buộc của bạn sẽ cập nhật với các thay đổi đối với các thuộc tính vật phẩm của bạn.

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
Tuy nhiên, tôi đã có lý do để thực hiện một cái gì đó tương tự, thay vì sử dụng NotifyCollectionChangedAction.Reset Tôi đã sử dụng .Replace: new NotifyCollectionChangedEventArss (NotifyCollectionChangedAction.Replace, item, item, IndexOf (item)).
Chris

2
Giải pháp tuyệt vời cho vấn đề của tôi - cảm ơn bạn! Đối với những người đã tạo ObservableCollection của họ bằng Danh sách, bạn có thể muốn thêm một hàm tạo cũng lặp lại mặc dù tất cả các mục và thêm PropertyChanged.
Gavin

4
Có một rò rỉ bộ nhớ tiềm năng ở đây - Một sự kiện Reset xảy ra khi bộ sưu tập bị thay đổi đáng kể, ví dụ như trên Clear. Không có trình xử lý INPC nào của bạn sẽ bị hủy đăng ký khi điều này xảy ra.
Charles Mager

6
Đây là một triển khai OK nhưng nó có một vấn đề lớn - đó NotifyCollectionChangedAction.Replacekhông phải là một ý tưởng hay, bởi vì sau đó bạn không thể phân biệt giữa một mục trong thực tế bị thay thế hoặc sự kiện do thay đổi mục. Sẽ tốt hơn nhiều khi bạn xác định public event PropertyChangedEventHandler CollectionItemChanged;và sau đó ItemPropertyChangedlàmthis.CollectionItemChanged?.Invoke(sender, e);
hyankov

4
Có ai có một ví dụ về việc sử dụng lớp này?
Bộ giải mã94

23

Tôi đã kết hợp những gì tôi hy vọng là một giải pháp khá mạnh mẽ, bao gồm một số kỹ thuật trong các câu trả lời khác. Đó là một lớp mới bắt nguồn từ ObservableCollection<>, mà tôi đang gọiFullyObservableCollection<>

Nó có các tính năng sau:

  • Nó thêm một sự kiện mới ItemPropertyChanged,. Tôi đã cố tình giữ điều này tách biệt với hiện tại CollectionChanged:
    • Để hỗ trợ tương thích ngược.
    • Vì vậy, chi tiết có liên quan hơn có thể được đưa ra trong cái mới ItemPropertyChangedEventArgsđi kèm với nó: bản gốc PropertyChangedEventArgsvà chỉ mục trong bộ sưu tập.
  • Nó sao chép tất cả các nhà xây dựng từ ObservableCollection<>.
  • Nó xử lý chính xác danh sách đang được đặt lại ( ObservableCollection<>.Clear()), tránh rò rỉ bộ nhớ.
  • Nó ghi đè lên lớp cơ sở OnCollectionChanged(), thay vì đăng ký nhiều tài nguyên hơn cho CollectionChangedsự kiện.

Các .cstập tin đầy đủ sau. Lưu ý rằng một vài tính năng của C # 6 đã được sử dụng, nhưng việc nhập lại nó khá đơn giản:

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

Xét nghiệm NUnit

Vì vậy, bạn có thể kiểm tra các thay đổi bạn có thể thực hiện (và xem những gì tôi đã kiểm tra ở vị trí đầu tiên!), Tôi cũng đã bao gồm lớp kiểm tra NUnit của mình. Rõ ràng, mã sau đây không cần thiết chỉ để sử dụng FullyObservableCollection<T>trong dự án của bạn.

NB Lớp kiểm tra sử dụng BindableBasetừ PRISM để thực hiện INotifyPropertyChanged. Không có sự phụ thuộc vào PRISM từ mã chính.

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
Tôi không biết những gì tôi đang làm sai, nhưng điều này không làm việc cho tôi. Tôi đang ràng buộc ListView của mình với bộ sưu tập của bạn nhưng khi tôi cập nhật các thuộc tính của các mục bên trong, ListView không cập nhật, thậm chí tho 'Tôi có thể thấy tất cả các sự kiện đang diễn ra. Tôi cũng đang sử dụng thư viện PRISM ...
Renato Parreira

@Renato, bạn đã làm gì với sự kiện mới chưa? ListViewsẽ trả lời CollectionChangedcác sự kiện vì nó biết về chúng. ItemPropertyChangedlà một bổ sung không chuẩn, vì vậy bạn cần dạy nó về điều đó. Là một sửa chữa nhanh chóng và bẩn thỉu, bạn có thể thử chỉ bắn các CollectionChangedsự kiện cũng như (hoặc thậm chí thay vì) ItemPropertyChangedvào OnItemPropertyChanged(). Tôi giữ chúng riêng biệt vì những lý do đã nêu trong câu trả lời, nhưng đối với trường hợp sử dụng của bạn, nó có thể chỉ làm những gì bạn cần.
Bob Sammer

20

Điều này sử dụng các ý tưởng trên nhưng làm cho nó trở thành một bộ sưu tập 'nhạy cảm hơn':

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 sẽ không truyền bá các thay đổi của từng mục dưới dạng các sự kiện CollectionChanged. Bạn sẽ cần phải đăng ký từng sự kiện và chuyển tiếp nó theo cách thủ công hoặc bạn có thể kiểm tra lớp BindingList [T] , sẽ thực hiện điều này cho bạn.


Tại sao bạn là người duy nhất đề cập đến điều này? +1
Atizs

7

Đã thêm vào sự kiện 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
}

Bạn có thể sử dụng PropertyChanged từ ObservableCollection trực tiếp, vì nó triển khai INotifyPropertyChanged.
Người ăn kiêng Meemken

6

Tôi đã sử dụng câu trả lời của Jack Kenyons để thực hiện OC của riêng mình, nhưng tôi muốn chỉ ra một thay đổi tôi phải thực hiện để làm cho nó hoạt động. Thay vì:

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

Tôi đã sử dụng điều này:

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

Có vẻ như "e.NewItems" tạo null nếu hành động là .Remove.


Tôi nghĩ nó cũng cần thay đổi hơn nữa nếu e.Action == thay thế
jk.

6

Chỉ cần thêm 2 xu của tôi vào chủ đề này. Cảm thấy TrulyObservableCollection yêu cầu hai hàm tạo khác như được tìm thấy với 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

Tôi biết rằng tôi đã quá muộn cho bữa tiệc này, nhưng có lẽ - nó sẽ giúp ích cho ai đó ..

Ở đây bạn có thể tìm thấy việc triển khai ObservableCollectionEx của tôi. Nó có một số tính năng:

  • nó hỗ trợ mọi thứ từ ObservableCollection
  • nó là chủ đề an toàn
  • nó hỗ trợ sự kiện ItemPropertyChanged (nó tăng lên mỗi khi mục Item.PropertyChanged được kích hoạt)
  • nó hỗ trợ các bộ lọc (vì vậy, bạn có thể tạo ObservableCollectionEx, chuyển một bộ sưu tập khác dưới dạng Nguồn cho nó và Bộ lọc với vị từ đơn giản. Rất hữu ích trong WPF, tôi sử dụng tính năng này rất nhiều trong các ứng dụng của mình). Thậm chí nhiều hơn - bộ lọc theo dõi các thay đổi của các mục thông qua giao diện INotifyPropertyChanged.

Tất nhiên, bất kỳ ý kiến ​​được đánh giá cao;)


1
Việt Nam! Rất cám ơn đã chia sẻ điều đó! Bạn đã tiết kiệm cho tôi nhiều giờ bằng cách không phải viết bài thực hiện của riêng tôi! :)
Alexander

@Alexander bạn rất được chào đón :)
chopikadze 7/07/13

@chopikadze, tôi không thể tải xuống tệp cs của ObservableCollectionEx, bạn có thể vui lòng sửa nó không. Cảm ơn
Shax

Liên kết đã chết.

5

Nếu tôi biết ObservableCollection chỉ tạo sự kiện khi chúng tôi thêm / xóa hoặc di chuyển các mục trong bộ sưu tập của mình. Khi chúng tôi cập nhật một cách nhanh chóng một số thuộc tính trong bộ sưu tập các mục bộ sưu tập không báo hiệu về nó và giao diện người dùng sẽ không được cập nhật.

Bạn có thể thực hiện mô phỏng INotifyPropertyChange trong lớp Model của mình. Và hơn là khi chúng tôi cập nhật một số tính thích hợp trong mục bộ sưu tập, nó sẽ tự động cập nhật UI.

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

và hơn

public ObservableCollection<Model> {get; set;}

Trong trường hợp của tôi, tôi đã sử dụng ListView to Bind cho bộ sưu tập này và trong ItemTemplate set Binding to Model và nó hoạt động tốt.

Đây là một số đoạn

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>

Ví dụ mã mẫu:

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

Và triển khai 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

Giải pháp đơn giản cho việc quan sát tiêu chuẩn mà tôi đã sử dụng:

KHÔNG THÊM vào tài sản của bạn HOẶC THAY ĐỔI các mục bên trong TRỰC TIẾP, thay vào đó, hãy tạo một số bộ sưu tập tạm thời như thế này

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

và thêm các mục hoặc thay đổi vào tmpList,

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

sau đó chuyển nó vào tài sản thực tế của bạn bằng cách chuyển nhượng.

ContentList=tmpList;

điều này sẽ thay đổi toàn bộ thuộc tính gây ra thông báo INotifyPropertyChanged khi bạn cần.


1

Tôi thử giải pháp này, nhưng chỉ hoạt động với tôi như RaisePropertyChange ("SourceGroupeGridView") khi bộ sưu tập thay đổi, được kích hoạt cho từng mục thêm hoặc thay đổi.

Vấn đề là ở:

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

NotifyCollectionChangedAction.Reset hành động này tạo ra một rebind hoàn chỉnh của tất cả các mục trong groupedgrid, tương đương tại RaisePropertyChanged. Khi bạn sử dụng nó, tất cả các nhóm của Gridview được làm mới.

NẾU bạn, chỉ muốn làm mới trong UI nhóm của mục mới, bạn không sử dụng Đặt lại hành động, bạn sẽ cần mô phỏng Thêm hành động trong itemproperty với nội dung như sau:

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

Xin lỗi bởi tiếng Anh của tôi và cảm ơn mã cơ sở :), tôi hy vọng điều này sẽ giúp được ai đó ^ _ ^

Enjoi !!


1

Đây là một phương pháp mở rộng cho giải pháp trên ...

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

Bạn có thể muốn giải thích câu trả lời
geedubb

1
Đây là một liên kết mô tả các phương pháp mở rộng. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/ từ
Lawman

1

Thay vì một ObservableCollection hoặc TrulyObservableCollection, hãy xem xét sử dụng BindingList và gọi phương thức ResetBindings.

Ví dụ:

private BindingList<TfsFile> _tfsFiles;

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

Đưa ra một sự kiện, chẳng hạn như nhấp chuột, mã của bạn sẽ trông như thế này:

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

Mô hình của tôi trông như thế này:

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
Thông tin tốt về phương pháp này BindingList, nhưng có một hạn chế đối với phương pháp này mà các câu trả lời khác đã khắc phục: kỹ thuật này dựa vào giá trị được thay đổi trong mã và nơi ResetBindings()có thể thêm lệnh gọi . Hầu hết các câu trả lời khác sẽ hoạt động nếu các đối tượng của danh sách bị thay đổi thông qua các phương tiện khác, chẳng hạn như mã không thể thay đổi hoặc từ một ràng buộc sang điều khiển thứ hai.
Bob Sammer

1

Để kích hoạt OnChange trong danh sách ObservableCollection

  1. Nhận chỉ mục của mục đã chọn
  2. Loại bỏ các mục từ cha mẹ
  3. Thêm các mục ở cùng một chỉ mục trong cha mẹ

Thí dụ:

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

0

Đây là phiên bản thực hiện của tôi. Nó kiểm tra và đưa ra lỗi, nếu các đối tượng trong danh sách không triển khai INotifyPropertyChanged, vì vậy không thể quên vấn đề đó trong khi phát triển. Ở bên ngoài, bạn sử dụng Sự kiện ListItemChanged để xác định xem danh sách hoặc mục danh sách có thay đổi hay không.

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

Giải pháp đơn giản trong 2 dòng mã. Chỉ cần sử dụng các constructor sao chép. Không cần phải viết TrulyObservableCollection, v.v.

Thí dụ:

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

Một phương pháp khác mà không cần xây dựng bản sao. Bạn có thể sử dụng tuần tự hóa.

        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

Bạn cũng có thể sử dụng phương thức mở rộng này để dễ dàng đăng ký một trình xử lý để thay đổi thuộc tính vật phẩm trong các bộ sưu tập có liên quan. Phương thức này được tự động thêm vào tất cả các bộ sưu tập triển khai INotifyCollectionChanged chứa các mục thực hiện 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;
                }
            }
        };
    }
}

Cách sử dụng:

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
        });
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.