当其中的 Item 发生更改时,Observer ableCollection 不会注意到(即使使用 INotifyPropertyChanged 也是如此)

有人知道为什么这个代码不起作用吗:

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等等的所有内容,除了这个问题,它还能处理其他所有内容。

218045 次浏览

更改集合中的值时,不会调用 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事件时,该 ObservableCollection将触发 CollectionChanged事件(就像文档中所说的那样... ...)

Observer ableCollection 不会将单个项更改作为 CollectionChanged 事件传播。您可能需要订阅每个事件并手动转发它,或者您可以签出 BindingList [ T ]类,它将为您完成这项工作。

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

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' - I don't know, why
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
}
}

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

如果操作是,“ e.NewItems”似乎生成 null。

下面是一个下拉类,它子类 Observer ableCollection,并在列表项的属性发生更改时实际引发一个 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);
}
}

添加到 TruelyObserver ableCollection 事件“ 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
}

我知道我已经来不及参加这个派对了,但也许,也许这对某些人有帮助。

你可以在 给你中找到我实现的 Observatory ableCollectionEx,它有一些特性:

  • 它支持所有来自 Observer ableCollection 的内容
  • 线程安全
  • 它支持 ItemPropertyChanged 事件(每次激发 Item.PropertyChanged 项时都会引发该事件)
  • 它支持过滤器(因此,您可以创建 Observer ableCollectionEx,将另一个集合作为 Source 传递给它,并使用简单谓词 Filter。在 WPF 中非常有用,我在应用程序中经常使用这个特性)。甚至 more-filter 通过 INotifyPropertyChanged 接口跟踪项的更改。

当然,欢迎提出任何意见;)

只是在这个话题上加上一点我的意见。觉得 TrulyObservatory-Collection 需要另外两个构造函数,就像在 Observatory-Collection 中找到的那样:

public TrulyObservableCollection()
: base()
{
HookupCollectionChangedEvent();
}


public TrulyObservableCollection(IEnumerable<T> collection)
: base(collection)
{
foreach (T item in collection)
item.PropertyChanged += ItemPropertyChanged;


HookupCollectionChangedEvent();
}


public TrulyObservableCollection(List<T> list)
: base(list)
{
list.ForEach(item => item.PropertyChanged += ItemPropertyChanged);


HookupCollectionChangedEvent();
}


private void HookupCollectionChangedEvent()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollectionChanged);
}

简单的解决方案,标准的观察收集,我已经使用:

不要直接添加属性或更改其内部项,而是创建一些临时集合,如下所示

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

并添加项或更改 tmpList,

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

然后通过赋值将其传递给您的实际属性。

ContentList=tmpList;

这将更改整个属性,从而根据需要通知 INotifyPropertyChanged。

下面是上述解决方案的扩展方法..。

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

我尝试了这个解决方案,但是只有在集合发生更改时,才能像 RaisePropertyChange (“ SourceGroupeGridView”)那样工作,该工作针对每个项目的添加或更改而触发。

问题在于:

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

NotifyCollectionChangedAction.重置此操作使得 grouppedgrid 中的所有项目完全重新绑定,在 RaisePropertyChanged 中是等效的。当您使用它时,所有组的网格视图都会刷新。

如果你只想在 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);
}
对不起,我的英语不好,谢谢你的基本代码:) , 我希望这对某人有所帮助

玩得开心!

下面是我的实现版本。如果 list 中的对象没有实现 INotifyPropertyChanged,那么它将检查并抛出一个错误,因此在开发时不能忘记这个问题。在外部,您使用 ListItemChanged Event 确定列表或列表项本身是否已更改。

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


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


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


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


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


public event ListItemChangedEventHandler ListItemChanged;


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




}

我已经把我希望是一个非常健壮的解决方案放在一起,包括其他答案中的一些技巧。它是从 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>

测试类使用来自 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)");
}
}
}

可以考虑使用 BindingList 并调用 ResetBindings 方法,而不使用 Observer ableCollection 或 TrulyObserver ableCollection。

例如:

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


}
}

如果我知道 Observer ableCollection 只有在添加/删除或移动集合中的项时才会发生 make 事件。当我们简单地更新集合项集合中的一些属性时,不要标记它,UI 也不会被更新。

您可以简单地在 Model 类中实现 INotifyPropertyChange。 当我们更新集合项中的一些属性时,它会自动更新 UI
public class Model:INotifyPropertyChange
{
//...
}

然后

public ObservableCollection<Model> {get; set;}

在我的例子中,我为这个集合使用了 ListView to Bind,并且在 ItemTemplate 中设置了 Binding to Model 属性,它工作得很好。

这里有一些片段

视窗 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;
}
简单的解决方案只需要两行代码,只需要使用复制建构子。 不需要编写 TrulyObservatory ableCollection 等

例如:

        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;

还可以使用此扩展方法轻松地为相关集合中的项属性更改注册处理程序。此方法将自动添加到实现 INotifyCollectionChanged 的所有集合中,这些集合保存实现 INotifyPropertyChanged 的项:

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

使用方法:

public class Test
{
public static void MyExtensionTest()
{
ObservableCollection<INotifyPropertyChanged> c = new ObservableCollection<INotifyPropertyChanged>();
c.SetOnCollectionItemPropertyChanged((item, e) =>
{
//whatever you want to do on item change
});
}
}

触发可观测收集列表中的更改

  1. 获取所选项的索引
  2. 从父级中删除该项
  3. 在父级中添加位于相同索引处的项

例如:

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

我在这里看到的大多数示例都将 INotifyPropertyChanged 约束放置在泛型类型上,该泛型类型强制模型实现 INotifyPropertyChanged。

如果您遵循在模型上放置 INotifyPropertyChanged 约束的示例,那么它就像在您的模型中实现 INotifyPropertyChanged 一样好,并允许 Observer ableCollection 处理 Update 属性更改。

但是如果您不希望您的模型实现 INotifyPropertyChanged,您可以尝试这样做。

自定义观察收集

 public class CustomObservableCollection<T> : ObservableCollection<T>
{


public void Refresh(T item)
{
var index = IndexOf(item);


RemoveAt(index);
Insert(index, item);


OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, IndexOf(item)));
}
}

模特

public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}

ViewModel

 public class PersonViewModel
{


public PersonViewModel(){


People=new CustomObservableCollection<Person>();


}
    

private void AddPerson(){


People.Add(new Person(){
FirstName="John",
LastName="Doe",
Age=20
});


}
    

private void UpdatePerson(){


var person=People.Where(...).FirstOrDefault();
person.Age=25;


People.Refresh(person);
}


public CustomObservableCollection<Person> People { get; set; }
    

}

对于我来说,帮助这个诀窍-RemoveAt 和插入替换项目,在这个“更改”-事件正常上升。

private ObservableCollection<CollectionItem> collection = new ObservableCollection<CollectionItem>();


public void Update(CollectionItem newItem, CollectionItem old ) {
int j = collection.IndexOf(old);
collection.RemoveAt(j);
collection.Insert(j, newComplexCondition);
}

遇到了同样的问题,我想我应该使用一个派生自 Observer ableCollection 的类来发布我的解决方案。它并没有给上面的类似实现增加很多东西,但是它确实使用了 PropertyChangedEventManager,它有两个优点: 1。它使用弱事件,因此没有解除挂钩事件和任何结果的内存泄漏的问题被删除。2.它允许指定在更改时将触发 CollectionChangedEvent 的特定属性。

此外,对于那些像@Martin Harris 这样的人,如果他们对 Observer ableCollection 的行为感到困惑,我可以向他们推荐 这篇优秀的文章吗。

/// <summary>
/// Implements an ObservableCollection that raises a CollectionChanged (Reset) event if an item in the collection raises PropertyChanged
/// The property name or names mey be optionally specified.
/// Note, could have performance issues if used on a large collection.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class ObservableCollectionResetOnItemChange<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public IEnumerable<string> PropertyNames { get; private set; }


public ObservableCollectionResetOnItemChange(IEnumerable<string> propertyNames = null)
{
PropertyNames = propertyNames?? new List<string>();
CollectionChanged += OnCollectionChanged;
}


public ObservableCollectionResetOnItemChange(string propertyName = null) :
this(propertyName is null ? null : new List<string>() { propertyName } )
{
}


public ObservableCollectionResetOnItemChange(IEnumerable<T> items, IEnumerable<string> propertyNames = null) :
this(propertyNames)
{
foreach (T item in items)
{
{
Add(item);
}
}
}


public ObservableCollectionResetOnItemChange(IEnumerable<T> items, string propertyName = null) :
this(items, propertyName is null ? null : new List<string>() { propertyName })
{
}


private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (T item in e.NewItems)
{
if (PropertyNames.Any())
{
foreach (string name in PropertyNames)
{
PropertyChangedEventManager.AddHandler(item, ItemPropertyChanged, name);
}
}
else
{
PropertyChangedEventManager.AddHandler(item, ItemPropertyChanged, string.Empty);
}
}
}
}


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