c# - que - ObservableCollection no se da cuenta cuando el elemento en él cambia(incluso con INotifyPropertyChanged)
para que sirve observablecollection c# (16)
Agregado al evento 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
}
¿Alguien sabe por qué este código no funciona?
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
todo para RaisePropertyChanged
etc. y funciona para todo lo demás excepto este problema.
Aquí está mi versión de la implementación. Comprueba y arroja un error, si los objetos de la lista no implementan INotifyPropertyChanged, por lo que no puede olvidar ese problema mientras se desarrolla. En el exterior, use el evento ListItemChanged, determine si la lista o el elemento de la lista en sí ha cambiado.
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); }
}
}
Aquí hay un método de extensión para la solución anterior ...
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;
}
Aquí hay una clase adicional que subclasifica ObservableCollection y en realidad plantea una acción de Restablecer cuando una propiedad en un elemento de la lista cambia. INotifyPropertyChanged
todos los elementos para implementar INotifyPropertyChanged
.
El beneficio aquí es que puede enlazar datos a esta clase y todas sus vinculaciones se actualizarán con los cambios en las propiedades de sus elementos.
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);
}
}
El método Set de ContentList no se llamará cuando cambie un valor dentro de la colección, en su lugar debería estar atento a la activación del evento 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
}
}
De acuerdo, eso es dos veces hoy. Me ha mordido la documentación de MSDN que está equivocada. En el enlace que te di, dice:
Se produce cuando un elemento se agrega, elimina, cambia, mueve o la lista completa se actualiza.
Pero en realidad no se dispara cuando se cambia un elemento. Supongo que necesitarás un método más bruteforce entonces:
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
}
}
Si va a necesitar esto mucho, es posible que desee subclasificar su propio ObservableCollection
que desencadena el evento CollectionChanged
cuando un miembro desencadena automáticamente su evento PropertyChanged
(como dice que debe hacerlo en la documentación ...)
En lugar de una ObservableCollection o TrulyObservableCollection, considere usar BindingList y llamar al método ResetBindings.
Por ejemplo:
private BindingList<TfsFile> _tfsFiles;
public BindingList<TfsFile> TfsFiles
{
get { return _tfsFiles; }
set
{
_tfsFiles = value;
NotifyPropertyChanged();
}
}
Dado un evento, como un clic, su código se vería así:
foreach (var file in TfsFiles)
{
SelectedFile = file;
file.Name = "Different Text";
TfsFiles.ResetBindings();
}
Mi modelo se veía así:
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; }
}
}
Esto utiliza las ideas anteriores, pero lo convierte en una colección derivada ''más sensible'':
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);
}
}
}
He reunido lo que espero sea una solución bastante sólida, incluidas algunas de las técnicas en otras respuestas. Es una nueva clase derivada de ObservableCollection<>
, que llamo FullyObservableCollection<>
Tiene las siguientes características:
- Agrega un nuevo evento,
ItemPropertyChanged
. He mantenido esto deliberadamente separado deCollectionChanged
existente:- Para ayudar a la compatibilidad con versiones anteriores.
- De modo que se pueden dar más detalles relevantes en el nuevo
ItemPropertyChangedEventArgs
que lo acompaña: elPropertyChangedEventArgs
original y el índice dentro de la colección.
- Replica todos los constructores de
ObservableCollection<>
. - Maneja correctamente la lista que se restablece (
ObservableCollection<>.Clear()
), evitando una posible pérdida de memoria. -
OnCollectionChanged()
la clase baseOnCollectionChanged()
, en lugar de una suscripción más intensiva en recursos al eventoCollectionChanged
.
El archivo completo .cs
sigue. Tenga en cuenta que se han utilizado algunas características de C # 6, pero debería ser bastante sencillo respaldarlo:
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)
{ }
}
}
Para que pueda verificar los cambios que pueda realizar (¡y vea lo que probé en primer lugar!), También creé una clase de prueba NUnit para ello. Nota: utiliza BindableBase
de PRISM para implementar INotifyPropertyChanged
. Obviamente, el siguiente código no es necesario solo para usar la clase en su proyecto.
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)");
}
}
}
Intento esta solución, pero solo funciona para mí como RaisePropertyChange ("SourceGroupeGridView") cuando cambió la colección, que se activó para cada elemento agregar o cambiar.
El problema está en:
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
OnCollectionChanged(args);
}
NotifyCollectionChangedAction.Restablezca esta acción para hacer una revinculación completa de todos los elementos en groupedgrid, es equivalente en RaisePropertyChanged. Cuando lo usa, todos los grupos de gridview se actualizan.
Si solo quieres actualizar en la interfaz de usuario el grupo del nuevo elemento, no utilizas la acción Restablecer, necesitarás simular una acción Agregar en itemproperty con algo como esto:
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);
}
Lo siento por mi inglés, y gracias por el código base :), espero que esto ayude a alguien ^ _ ^
Enjoi !!
ObservableCollection no propagará cambios de elementos individuales como eventos CollectionChanged. Tendrá que suscribirse a cada evento y reenviarlo manualmente, o puede consultar la BindingList[T] , que lo hará por usted.
Sé que estoy demasiado tarde para esta fiesta, pero tal vez, ayudará a alguien ...
Here puede encontrar mi implementación de ObservableCollectionEx. Tiene algunas características:
- es compatible con todo, desde ObservableCollection
- es seguro para subprocesos
- admite el evento ItemPropertyChanged (aumenta cada vez que se activa el elemento Item.PropertyChanged)
- es compatible con filtros (por lo tanto, podría crear ObservableCollectionEx, pasar otra colección como Fuente a ella y Filtrar con predicado simple. Muy útil en WPF, uso mucho esta función en mis aplicaciones). Aún más: el filtro rastrea los cambios de elementos a través de la interfaz INotifyPropertyChanged.
Por supuesto, cualquier comentario es apreciado;)
Si sé que ObservableCollection solo se produce cuando agregamos / borramos o movemos elementos en nuestra colección. Cuando actualizamos de manera simple algunas propiedades de la colección de elementos de colección, no nos damos cuenta y la interfaz de usuario no se actualizará.
Simplemente puede implementar INotifyPropertyChange en su clase de modelo. Y que cuando actualicemos alguna propiedad en el ítem de colección automáticamente actualizará la IU.
public class Model:INotifyPropertyChange
{
//...
}
y que
public ObservableCollection<Model> {get; set;}
En mi caso, utilicé ListView to Bind para esta colección y en ItemTemplate fijé el enlace en la propiedad Modelo y funciona bien.
Aquí hay algunos fragmentos
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>
Ejemplo de código de modelo:
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));
}
}
Y la implementación de 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;
}
Solo agregué mis 2 centavos sobre este tema. Sentí que TrulyObservableCollection requería los otros dos constructores encontrados en 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);
}
Solución simple para la colección observable estándar que he usado:
NO AGREGUE a su propiedad O CAMBIE sus elementos internos DIRECTAMENTE; en su lugar, cree una colección temporal como esta
ObservableCollection<EntityViewModel> tmpList= new ObservableCollection<EntityViewModel>();
y agrega elementos o realiza cambios a tmpList,
tmpList.Add(new EntityViewModel(){IsRowChecked=false}); //Example
tmpList[0].IsRowChecked= true; //Example
...
luego pásalo a tu propiedad real por asignación.
ContentList=tmpList;
esto cambiará la propiedad completa, lo que provoca que se notifique INotifyPropertyChanged según lo necesite.
Usé la respuesta de Jack Kenyons para implementar mi OC, pero me gustaría señalar un cambio que tuve que hacer para que funcione. En lugar de:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.NewItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
Usé esto:
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach(T item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
Parece que "e.NewItems" produce null si la acción es .Remove.
Solución simple en 2 líneas de código. Solo usa el constructor de copia. No es necesario escribir TrulyObservableCollection, etc.
Ejemplo:
speakers.list[0].Status = "offline";
speakers.list[0] = new Speaker(speakers.list[0]);
Otro método sin el constructor de copia. Puedes usar la serialización.
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;