首页 文章

ObservableCollection没有注意到它中的Item何时发生变化(即使使用INotifyPropertyChanged)

提问于
浏览
148

有谁知道为什么这段代码不起作用:

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

ViewModelBase 包含 RaisePropertyChanged 等的所有内容,除了这个问题之外,它还可以用于其他所有内容 .

16 回答

  • 107

    当您更改集合中的值时,不会调用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 ,当一个成员自动触发 PropertyChanged 事件时触发 CollectionChanged 事件(就像它在文档中所说的那样......)

  • 6

    这是一个子类ObservableCollection的drop-in类,当列表项的属性发生更改时,实际上会引发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);
        }
    }
    
  • 1

    这使用了上述想法,但使其成为派生的“更敏感”的集合:

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

    我把我希望的一个非常强大的解决方案放在一起,包括其他答案中的一些技巧 . 这是一个派生自 ObservableCollection<> 的新类,我称之为 FullyObservableCollection<>

    它具有以下功能:

    • 它添加了一个新事件 ItemPropertyChanged . 我故意把它与现有的_2374132分开:

    • 以帮助向后兼容 .

    • 因此可以在随附的新 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测试类 . 显然, the following code is not necessary 只是在你的项目中使用 FullyObservableCollection<T> .

    NB 测试类使用PRISM中的 BindableBase 来实现 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

    ObservableCollection不会将单个项目更改传播为CollectionChanged事件 . 您需要订阅每个事件并手动转发,或者您可以查看BindingList[T]类,它将为您执行此操作 .

  • 169

    添加到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
    }
    
  • 7

    我使用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”产生null .

  • 15

    只需在这个主题上加上我的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);
        }
    
  • 1

    我知道我为这个派对来得太晚了,但也许 - 这会对某人有所帮助..

    Here你可以找到我的ObservableCollectionEx实现 . 它有一些功能:

    • 它支持ObservableCollection中的所有内容

    • 它是线程安全的

    • 它支持ItemPropertyChanged事件(每次触发Item.PropertyChanged项时都会引发)

    • 它支持过滤器(因此,您可以创建ObservableCollectionEx,将另一个集合作为Source传递给它,使用简单谓词过滤 . 在WPF中非常有用,我在我的应用程序中使用了很多这个功能) . 更多 - 通过INotifyPropertyChanged界面过滤跟踪项目的更改 .

    当然,任何评论都表示赞赏;)

  • 0

    如果我知道ObservableCollection只在我们添加/删除或移动我们的集合中的项目时才发生事件 . 当我们简单地更新集合项集合中的一些属性时,不要发出信号,并且不会更新UI .

    你可以模拟在Model类中实现 INotifyPropertyChange . 而且当我们更新集合项目中的某些属性时,它会自动更新UI .

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

    然后

    public ObservableCollection<Model> {get; set;}
    

    在我的情况下,我使用ListView绑定此集合,并在ItemTemplate中设置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>
    

    Model code example:

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

    And ViewModel implementation:

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

    我使用的标准observablecollection的简单解决方案:

    不要添加到您的 property 或直接更改它的内部项目,而是创建一些像这样的临时集合

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

    并添加项目或对tmpList进行更改,

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

    然后通过作业将它传递给你的实际 property .

    ContentList=tmpList;
    

    这将改变整个属性,导致您根据需要注意INotifyPropertyChanged .

  • 5

    我尝试这个解决方案,但只有当我更改集合时,像RaisePropertyChange(“SourceGroupeGridView”)一样对我有效,为每个项目添加或更改触发 .

    问题在于:

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

    NotifyCollectionChangedAction.Reset此操作对groupsgrid中的所有项进行完全重新绑定,在RaisePropertyChanged中等效 . 当您使用它时,所有gridview组都会刷新 .

    如果您只想在UI中刷新新项目的组,则不使用重置操作,您需要在itemproperty中模拟添加操作,如下所示:

    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!

  • 2

    这是上述解决方案的扩展方法......

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

    而不是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; }
    
        }
    }
    
  • 19

    这是我的实现版本 . 如果列表中的对象没有实现INotifyPropertyChanged,它会检查并抛出错误,因此在开发时不能忘记该问题 . 在外部,您使用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;
    

相关问题