项目更改时通知ObservableCollection
我在这个链接find
ObservableCollection在其中的项目更改时不会注意到(即使使用INotifyPropertyChanged)
一些技巧通知Observablecollection项目已经改变。 在这个链接TrulyObservableCollection似乎是我在找什么。
public class TrulyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged { 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); }
}
但是当我尝试使用它时,我没有收到有关收集的通知。 我不知道如何在我的C#代码中正确实现这个:
XAML:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyItemsSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> <DataGrid.Columns> <DataGridCheckBoxColumn Binding="{Binding MyProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> </DataGrid.Columns> </DataGrid>
ViewModel:
public class MyViewModel : ViewModelBase { private TrulyObservableCollection<MyType> myItemsSource; public TrulyObservableCollection<MyType> MyItemsSource { get { return myItemsSource; } set { myItemsSource = value; // Code to trig on item change... RaisePropertyChangedEvent("MyItemsSource"); } } public MyViewModel() { MyItemsSource = new TrulyObservableCollection<MyType>() { new MyType() { MyProperty = false }, new MyType() { MyProperty = true }, new MyType() { MyProperty = false } }; } } public class MyType : ViewModelBase { private bool myProperty; public bool MyProperty { get { return myProperty; } set { myProperty = value; RaisePropertyChangedEvent("MyProperty"); } } } public class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void RaisePropertyChangedEvent(string propertyName) { if (PropertyChanged != null) { PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName); PropertyChanged(this, e); } } }
当我运行程序时,我有3checkbox,如属性初始化为false,true,false。 但是当我改变其中一个ckeckbox的状态,程序通过item_PropertyChanged,但从来没有在MyItemsSource属性代码。
只有在集合对象发生变化时(例如,当它被设置为新对象或设置为null)时,才会触发您已注释为// Code to trig on item change...
触发的位置。
使用您当前的TrulyObservableCollection实现来处理您的集合的属性已更改的事件,将事件注册到MyItemsSource
的CollectionChanged
事件
public MyViewModel() { MyItemsSource = new TrulyObservableCollection<MyType>(); MyItemsSource.CollectionChanged += MyItemsSource_CollectionChanged; MyItemsSource.Add(new MyType() { MyProperty = false }); MyItemsSource.Add(new MyType() { MyProperty = true}); MyItemsSource.Add(new MyType() { MyProperty = false }); } void MyItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { // Handle here }
我个人真的不喜欢这个实现。 您正在引发一个说明整个集合已被重置的CollectionChanged
事件,只要属性发生更改。 当然,只要集合中的某个项目发生更改,就会使用户界面更新,但是我发现这样做对性能不利,而且似乎没有办法确定哪些属性发生了更改,哪些是关键信息之一我通常需要在PropertyChanged
上做一些事情。
我更喜欢使用常规的ObservableCollection
,并将PropertyChanged
事件连接到CollectionChanged
上的项目。 提供您的UI绑定正确的ObservableCollection
的项目,您不应该告诉用户界面更新集合中的项目属性更改时。
public MyViewModel() { MyItemsSource = new ObservableCollection<MyType>(); MyItemsSource.CollectionChanged += MyItemsSource_CollectionChanged; MyItemsSource.Add(new MyType() { MyProperty = false }); MyItemsSource.Add(new MyType() { MyProperty = true}); MyItemsSource.Add(new MyType() { MyProperty = false }); } void MyItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) foreach(MyType item in e.NewItems) item.PropertyChanged += MyType_PropertyChanged; if (e.OldItems != null) foreach(MyType item in e.OldItems) item.PropertyChanged -= MyType_PropertyChanged; } void MyType_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "MyProperty") DoWork(); }
我通过使用静态Action解决了这个问题
public class CatalogoModel { private String _Id; private String _Descripcion; private Boolean _IsChecked; public String Id { get { return _Id; } set { _Id = value; } } public String Descripcion { get { return _Descripcion; } set { _Descripcion = value; } } public Boolean IsChecked { get { return _IsChecked; } set { _IsChecked = value; NotifyPropertyChanged("IsChecked"); OnItemChecked.Invoke(); } } public static Action OnItemChecked; } public class ReglaViewModel : ViewModelBase { private ObservableCollection<CatalogoModel> _origenes; CatalogoModel.OnItemChecked = () => { var x = Origenes.Count; //Entra cada vez que cambia algo en _origenes }; }
ObservableCollection
及其衍生产品在内部引发属性更改。 如果您将新的TrulyObservableCollection<MyType>
分配给MyItemsSource
属性,则只能触发setter中的代码。 也就是说,它只能从构造函数中发生一次。
从这一点开始,您将从集合中获取属性更改通知,而不是来自视图模型中的setter。
我知道这很晚了,但也许这有助于别人。 我已经创build了一个NotifyObservableCollection
类,它解决了物品属性发生变化时丢失物品本身通知的问题。 用法和ObservableCollection
一样简单。
public class NotifyObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged { private void Handle(object sender, PropertyChangedEventArgs args) { OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, null)); } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (object t in e.NewItems) { ((T) t).PropertyChanged += Handle; } } if (e.OldItems != null) { foreach (object t in e.OldItems) { ((T) t).PropertyChanged -= Handle; } } base.OnCollectionChanged(e); }
在添加或删除项目时,类将项目PropertyChanged
事件转发到集合PropertyChanged
事件。
用法:
public abstract class ParameterBase : INotifyPropertyChanged { protected readonly CultureInfo Ci = new CultureInfo("en-US"); private string _value; public string Value { get { return _value; } set { if (value == _value) return; _value = value; OnPropertyChanged(); } } } public class AItem { public NotifyObservableCollection<ParameterBase> Parameters { get { return _parameters; } set { NotifyCollectionChangedEventHandler cceh = (sender, args) => OnPropertyChanged(); if (_parameters != null) _parameters.CollectionChanged -= cceh; _parameters = value; //needed for Binding to AItem at xaml directly _parameters.CollectionChanged += cceh; } } public NotifyObservableCollection<ParameterBase> DefaultParameters { get { return _defaultParameters; } set { NotifyCollectionChangedEventHandler cceh = (sender, args) => OnPropertyChanged(); if (_defaultParameters != null) _defaultParameters.CollectionChanged -= cceh; _defaultParameters = value; //needed for Binding to AItem at xaml directly _defaultParameters.CollectionChanged += cceh; } } public class MyViewModel { public NotifyObservableCollection<AItem> DataItems { get; set; } }
如果现在DataItems
中一个项目的属性发生变化,下面的xaml会得到一个通知,虽然它绑定到Parameters[0]
或者项目本身,除了项目的变化属性Value
(触发器的转换器在每个更改)。
<DataGrid CanUserAddRows="False" AutoGenerateColumns="False" ItemsSource="{Binding DataItems}"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding Parameters[0].Value}" Header="P1"> <DataGridTextColumn.CellStyle> <Style TargetType="DataGridCell"> <Setter Property="Background" Value="Aqua" /> <Style.Triggers> <DataTrigger Value="False"> <!-- Bind to Items with changing properties --> <DataTrigger.Binding> <MultiBinding Converter="{StaticResource ParameterCompareConverter}"> <Binding Path="DefaultParameters[0]" /> <Binding Path="Parameters[0]" /> </MultiBinding> </DataTrigger.Binding> <Setter Property="Background" Value="DeepPink" /> </DataTrigger> <!-- Binds to AItem directly --> <DataTrigger Value="True" Binding="{Binding Converter={StaticResource CheckParametersConverter}}"> <Setter Property="FontWeight" Value="ExtraBold" /> </DataTrigger> </Style.Triggers> </Style> </DataGridTextColumn.CellStyle> </DataGridTextColumn>
您可以使用扩展方法以通用方式通知集合中项目的已更改属性。
public static class ObservableCollectionExtension { public static void NotifyPropertyChanged<T>(this ObservableCollection<T> observableCollection, Action<T, PropertyChangedEventArgs> callBackAction) where T : INotifyPropertyChanged { observableCollection.CollectionChanged += (sender, args) => { //Does not prevent garbage collection says: http://stackoverflow.com/questions/298261/do-event-handlers-stop-garbage-collection-from-occuring //publisher.SomeEvent += target.SomeHandler; //then "publisher" will keep "target" alive, but "target" will not keep "publisher" alive. if (args.NewItems == null) return; foreach (T item in args.NewItems) { item.PropertyChanged += (obj, eventArgs) => { callBackAction((T)obj, eventArgs); }; } }; } } public void ExampleUsage() { var myObservableCollection = new ObservableCollection<MyTypeWithNotifyPropertyChanged>(); myObservableCollection.NotifyPropertyChanged((obj, notifyPropertyChangedEventArgs) => { //DO here what you want when a property of an item in the collection has changed. }); }
这里的所有解决scheme都是正确的,但是它们缺less使用Clear()方法的重要场景,该方法不会在NotifyCollectionChangedEventArgs
对象中提供OldItems
。
这是完美的ObservableCollection
。
public class ObservableCollectionEX<T> : ObservableCollection<T> { #region Constructors public ObservableCollectionEX() : base() { CollectionChanged += ObservableCollection_CollectionChanged; } public ObservableCollectionEX(IEnumerable<T> c) : base(c) { CollectionChanged += ObservableCollection_CollectionChanged; } public ObservableCollectionEX(List<T> l) : base(l) { CollectionChanged += ObservableCollection_CollectionChanged; } #endregion public new void Clear() { foreach (var item in this) { if (item is INotifyPropertyChanged i) { if (i != null) i.PropertyChanged -= Element_PropertyChanged; } } base.Clear(); } private void ObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) foreach (var item in e.OldItems) { if (item != null && item is INotifyPropertyChanged i) { i.PropertyChanged -= Element_PropertyChanged; } } if (e.NewItems != null) foreach (var item in e.NewItems) { if (item != null && item is INotifyPropertyChanged i) { i.PropertyChanged -= Element_PropertyChanged; i.PropertyChanged += Element_PropertyChanged; } } } private void Element_PropertyChanged(object sender, PropertyChangedEventArgs e) { //raise the event ItemPropertyChanged?.Invoke(sender, e); } /// <summary> /// the sender is the Item /// </summary> public PropertyChangedEventHandler ItemPropertyChanged; }
你甚至可以加倍努力,改变ItemPropertyChanged来提供像这样的所有者列表
在一些名字空间的课外:
public delegate void ListedItemPropertyChangedEventHandler(IList SourceList, object Item, PropertyChangedEventArgs e);
在课堂上改变这些:
private void Element_PropertyChanged(object sender, PropertyChangedEventArgs e) { //raise the event ItemPropertyChanged?.Invoke(this,sender, e); } public ListedItemPropertyChangedEventHandler ItemPropertyChanged;