wpf - que - Al borrar una ObservableCollection, no hay elementos en e.OldItems
observablecollection xamarin (20)
Tengo algo aquí que realmente me está tomando por sorpresa.
Tengo una ObservableCollection de T que está llena de elementos. También tengo un controlador de eventos adjunto al evento CollectionChanged.
Cuando borra la colección, causa un evento CollectionChanged con e.Action establecido en NotifyCollectionChangedAction.Reset. Ok, eso es normal. Pero lo extraño es que ni e.OldItems ni e.NewItems tienen nada. Esperaría que e.OldItems se llenara con todos los elementos que se eliminaron de la colección.
¿Alguien más ha visto esto? Y si es así, ¿cómo lo han conseguido?
Algunos antecedentes: estoy usando el evento CollectionChanged para adjuntar y separar de otro evento y, por lo tanto, si no recibo ningún elemento en e.OldItems ... no podré separarme de ese evento.
ACLARACIÓN: Sé que la documentación no declara abiertamente que tiene que comportarse de esta manera. Pero para cualquier otra acción, me está notificando lo que ha hecho. Entonces, mi suposición es que me diría ... en el caso de Clear / Reset también.
A continuación se muestra el código de muestra si desea reproducirlo usted mismo. Primero fuera de la xaml:
<Window
x:Class="ObservableCollection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300"
>
<StackPanel>
<Button x:Name="addButton" Content="Add" Width="100" Height="25" Margin="10" Click="addButton_Click"/>
<Button x:Name="moveButton" Content="Move" Width="100" Height="25" Margin="10" Click="moveButton_Click"/>
<Button x:Name="removeButton" Content="Remove" Width="100" Height="25" Margin="10" Click="removeButton_Click"/>
<Button x:Name="replaceButton" Content="Replace" Width="100" Height="25" Margin="10" Click="replaceButton_Click"/>
<Button x:Name="resetButton" Content="Reset" Width="100" Height="25" Margin="10" Click="resetButton_Click"/>
</StackPanel>
</Window>
A continuación, el código detrás:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace ObservableCollection
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
_integerObservableCollection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_integerObservableCollection_CollectionChanged);
}
private void _integerObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
break;
default:
break;
}
}
private void addButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.Add(25);
}
private void moveButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.Move(0, 19);
}
private void removeButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.RemoveAt(0);
}
private void replaceButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection[0] = 50;
}
private void resetButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.Clear();
}
private ObservableCollection<int> _integerObservableCollection = new ObservableCollection<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
}
}
Abordé este de una manera ligeramente diferente ya que quería registrarme en un evento y manejar todas las adiciones y eliminaciones en el controlador de eventos. Empecé por anular el evento de cambio de colección y redirigir las acciones de reinicio a las acciones de eliminación con una lista de elementos. Todo esto salió mal ya que estaba usando la colección observable como una fuente de elementos para una vista de colección y obtuve "Acciones de rango no compatibles".
Finalmente creé un nuevo evento llamado CollectionChangedRange que actúa de la manera que esperaba que actuara la versión incorporada.
No puedo imaginarme por qué esta limitación estaría permitida y espero que este post por lo menos impida que otros pasen por el callejón sin salida que lo hice.
/// <summary>
/// An observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ObservableCollectionRange<T> : ObservableCollection<T>
{
private bool _addingRange;
[field: NonSerialized]
public event NotifyCollectionChangedEventHandler CollectionChangedRange;
protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs e)
{
if ((CollectionChangedRange == null) || _addingRange) return;
using (BlockReentrancy())
{
CollectionChangedRange(this, e);
}
}
public void AddRange(IEnumerable<T> collection)
{
CheckReentrancy();
var newItems = new List<T>();
if ((collection == null) || (Items == null)) return;
using (var enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
_addingRange = true;
Add(enumerator.Current);
_addingRange = false;
newItems.Add(enumerator.Current);
}
}
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems));
}
protected override void ClearItems()
{
CheckReentrancy();
var oldItems = new List<T>(this);
base.ClearItems();
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems));
}
protected override void InsertItem(int index, T item)
{
CheckReentrancy();
base.InsertItem(index, item);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
protected override void MoveItem(int oldIndex, int newIndex)
{
CheckReentrancy();
var item = base[oldIndex];
base.MoveItem(oldIndex, newIndex);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex));
}
protected override void RemoveItem(int index)
{
CheckReentrancy();
var item = base[index];
base.RemoveItem(index);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
}
protected override void SetItem(int index, T item)
{
CheckReentrancy();
var oldItem = base[index];
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, oldItem, item, index));
}
}
/// <summary>
/// A read only observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ReadOnlyObservableCollectionRange<T> : ReadOnlyObservableCollection<T>
{
[field: NonSerialized]
public event NotifyCollectionChangedEventHandler CollectionChangedRange;
public ReadOnlyObservableCollectionRange(ObservableCollectionRange<T> list) : base(list)
{
list.CollectionChangedRange += HandleCollectionChangedRange;
}
private void HandleCollectionChangedRange(object sender, NotifyCollectionChangedEventArgs e)
{
OnCollectionChangedRange(e);
}
protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs args)
{
if (CollectionChangedRange != null)
{
CollectionChangedRange(this, args);
}
}
}
Así es como funciona ObservableCollection, puede solucionar esto manteniendo su propia lista fuera de ObservableCollection (agregando a la lista cuando la acción es Agregar, eliminar cuando la acción es Eliminar, etc.) luego puede obtener todos los elementos eliminados (o elementos añadidos) ) cuando la acción se restablece al comparar su lista con la ObservableCollection.
Otra opción es crear su propia clase que implemente IList e INotifyCollectionChanged, luego puede adjuntar y separar eventos de esa clase (o establecer OldItems en Clear si lo desea): no es realmente difícil, pero es una gran cantidad de tipeo.
Bueno, decidí ensuciarme con eso yo mismo.
Microsoft puso mucho trabajo en asegurarse siempre de que NotifyCollectionChangedEventArgs no tenga datos cuando llama a un reinicio. Estoy asumiendo que esta fue una decisión de rendimiento / memoria. Si está restableciendo una colección con 100,000 elementos, supongo que no querían duplicar todos esos elementos.
Pero como mis colecciones nunca tienen más de 100 elementos, no veo ningún problema.
De todos modos, he creado una clase heredada con el siguiente método:
protected override void ClearItems()
{
CheckReentrancy();
List<TItem> oldItems = new List<TItem>(Items);
Items.Clear();
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
NotifyCollectionChangedEventArgs e =
new NotifyCollectionChangedEventArgs
(
NotifyCollectionChangedAction.Reset
);
FieldInfo field =
e.GetType().GetField
(
"_oldItems",
BindingFlags.Instance | BindingFlags.NonPublic
);
field.SetValue(e, oldItems);
OnCollectionChanged(e);
}
De acuerdo, sé que esta es una pregunta muy antigua, pero se me ocurrió una buena solución al problema y pensé que podría compartirla. Esta solución se inspira en muchas de las excelentes respuestas aquí, pero tiene las siguientes ventajas:
- No es necesario crear una nueva clase y anular los métodos de ObservableCollection
- No altera el funcionamiento de NotifyCollectionChanged (así que no te equivoques con Reset)
- No hace uso de la reflexión
Aquí está el código:
public static void Clear<T>(this ObservableCollection<T> collection, Action<ObservableCollection<T>> unhookAction)
{
unhookAction.Invoke(collection);
collection.Clear();
}
Este método de extensión simplemente toma una Action
que se invocará antes de que se elimine la colección.
En cuanto a NotifyCollectionChangedEventArgs , parece que OldItems solo contiene elementos modificados como resultado de la acción Reemplazar, Eliminar o Mover. No indica que contendrá nada en Clear. Sospecho que Clear activa el evento, pero no registra los elementos eliminados y no invoca el código Remove en absoluto.
Encontré una solución que permite al usuario capitalizar la eficiencia de agregar o eliminar muchos elementos a la vez y solo activar un evento, y satisfacer las necesidades de los elementos de UIE para obtener los argumentos de evento Action.Reset mientras que todos los demás usuarios lo harían. como una lista de elementos añadidos y eliminados.
Esta solución implica anular el evento CollectionChanged. Cuando vamos a disparar este evento, podemos mirar el objetivo de cada controlador registrado y determinar su tipo. Dado que solo las clases ICollectionView requieren NotifyCollectionChangedAction.Reset
args cuando hay más de un elemento modificado, podemos seleccionarlos y proporcionar a los demás argumentos de eventos adecuados que contengan la lista completa de elementos eliminados o agregados. A continuación está la implementación.
public class BaseObservableCollection<T> : ObservableCollection<T>
{
//Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) and Clear()
private bool _SuppressCollectionChanged = false;
/// Overridden so that we may manually call registered handlers and differentiate between those that do and don''t require Action.Reset args.
public override event NotifyCollectionChangedEventHandler CollectionChanged;
public BaseObservableCollection() : base(){}
public BaseObservableCollection(IEnumerable<T> data) : base(data){}
#region Event Handlers
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if( !_SuppressCollectionChanged )
{
base.OnCollectionChanged(e);
if( CollectionChanged != null )
CollectionChanged.Invoke(this, e);
}
}
//CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than
//one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable
//for applications in code, so we actually check the type we''re notifying on and pass a customized event args.
protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
if( handlers != null )
foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() )
handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
#endregion
#region Extended Collection Methods
protected override void ClearItems()
{
if( this.Count == 0 ) return;
List<T> removed = new List<T>(this);
_SuppressCollectionChanged = true;
base.ClearItems();
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
public void Add(IEnumerable<T> toAdd)
{
if( this == toAdd )
throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");
_SuppressCollectionChanged = true;
foreach( T item in toAdd )
Add(item);
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(toAdd)));
}
public void Remove(IEnumerable<T> toRemove)
{
if( this == toRemove )
throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");
_SuppressCollectionChanged = true;
foreach( T item in toRemove )
Remove(item);
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(toRemove)));
}
#endregion
}
Estaba revisando algunos de los códigos de gráficos en los kits de herramientas de Silverlight y WPF y me di cuenta de que también resolvieron este problema (de una manera similar) ... y pensé que seguiría adelante y publicaría su solución.
Básicamente, también crearon una ObservableCollection derivada y anularon ClearItems, llamando a Remove en cada artículo que se borra.
Aquí está el código:
/// <summary>
/// An observable collection that cannot be reset. When clear is called
/// items are removed individually, giving listeners the chance to detect
/// each remove event and perform operations such as unhooking event
/// handlers.
/// </summary>
/// <typeparam name="T">The type of item in the collection.</typeparam>
public class NoResetObservableCollection<T> : ObservableCollection<T>
{
public NoResetObservableCollection()
{
}
/// <summary>
/// Clears all items in the collection by removing them individually.
/// </summary>
protected override void ClearItems()
{
IList<T> items = new List<T>(this);
foreach (T item in items)
{
Remove(item);
}
}
}
Este es un tema candente ... porque en mi opinión, Microsoft no hizo su trabajo correctamente ... una vez más. No me malinterpreten, me gusta Microsoft, ¡pero no son perfectos!
Leí la mayoría de los comentarios anteriores. Estoy de acuerdo con todos aquellos que piensan que Microsoft no programó Clear () correctamente.
En mi opinión, al menos, se necesita un argumento para poder separar los objetos de un evento ... pero también entiendo su impacto. Entonces, pensé en esta solución propuesta.
Espero que todos sean felices, o al menos, la mayoría de todos ...
Eric
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reflection;
namespace WpfUtil.Collections
{
public static class ObservableCollectionExtension
{
public static void RemoveAllOneByOne<T>(this ObservableCollection<T> obsColl)
{
foreach (T item in obsColl)
{
while (obsColl.Count > 0)
{
obsColl.RemoveAt(0);
}
}
}
public static void RemoveAll<T>(this ObservableCollection<T> obsColl)
{
if (obsColl.Count > 0)
{
List<T> removedItems = new List<T>(obsColl);
obsColl.Clear();
NotifyCollectionChangedEventArgs e =
new NotifyCollectionChangedEventArgs
(
NotifyCollectionChangedAction.Remove,
removedItems
);
var eventInfo =
obsColl.GetType().GetField
(
"CollectionChanged",
BindingFlags.Instance | BindingFlags.NonPublic
);
if (eventInfo != null)
{
var eventMember = eventInfo.GetValue(obsColl);
// note: if eventMember is null
// nobody registered to the event, you can''t call it.
if (eventMember != null)
eventMember.GetType().GetMethod("Invoke").
Invoke(eventMember, new object[] { obsColl, e });
}
}
}
}
}
No pretende incluir los elementos antiguos, porque Restablecer no significa que la lista se ha eliminado
Significa que algo dramático ha tenido lugar, y el costo de calcular el add / remove probablemente exceda el costo de volver a escanear la lista desde cero ... así que eso es lo que debes hacer.
MSDN sugiere un ejemplo de la colección completa que se vuelve a clasificar como candidata para restablecer.
Reiterar. Restablecer no significa claro , significa que sus suposiciones sobre la lista ahora no son válidas. Trátelo como si fuera una lista completamente nueva . Claro pasa a ser una instancia de esto, pero bien podría haber otros.
Algunos ejemplos:
He tenido una lista como esta con muchos elementos, y se ha unido a un WPF ListView
para mostrar en la pantalla.
Si borra la lista y .Reset
evento .Reset
, el rendimiento es prácticamente instantáneo, pero si en su lugar .Remove
muchos eventos .Remove
, el rendimiento es terrible, ya que WPF elimina los elementos uno por uno. También he usado. .Reset
en mi propio código para indicar que la lista ha sido reordenada, en lugar de emitir miles de operaciones de Move
individuales. Al igual que con Clear, hay un gran impacto de rendimiento cuando se generan muchos eventos individuales.
Ok, a pesar de que todavía deseo que ObservableCollection se comportó como yo deseaba ... el código siguiente es lo que terminé haciendo. Básicamente, creé una nueva colección de T llamada TrulyObservableCollection y anulé el método ClearItems que luego usé para generar un evento de compensación.
En el código que usa esta TrulyObservableCollection, utilizo este evento Clearing para recorrer los elementos que todavía están en la colección en ese punto para separar el evento del que deseaba desprenderme.
Espero que este enfoque ayude a alguien más también.
public class TrulyObservableCollection<T> : ObservableCollection<T>
{
public event EventHandler<EventArgs> Clearing;
protected virtual void OnClearing(EventArgs e)
{
if (Clearing != null)
Clearing(this, e);
}
protected override void ClearItems()
{
OnClearing(EventArgs.Empty);
base.ClearItems();
}
}
Otra opción es reemplazar el evento Restablecer con un único evento Eliminar que tenga todos los elementos eliminados en su propiedad OldItems de la siguiente manera:
public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
protected override void ClearItems()
{
List<T> removed = new List<T>(this);
base.ClearItems();
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Reset)
base.OnCollectionChanged(e);
}
// Constructors omitted
...
}
Ventajas:
No es necesario suscribirse a un evento adicional (según lo requerido por la respuesta aceptada)
No genera un evento para cada objeto eliminado (algunas otras soluciones propuestas dan como resultado múltiples eventos eliminados).
El suscriptor solo necesita verificar NewItems & OldItems en cualquier evento para agregar / eliminar manejadores de eventos según sea necesario.
Desventajas:
Sin evento de reinicio
Pequeño (?) Por encima de la creación de copia de la lista.
???
EDITAR 2012-02-23
Desafortunadamente, cuando se vincula a los controles basados en listas de WPF, la eliminación de una colección de ObservableCollectionNoReset con múltiples elementos dará como resultado una excepción "Acciones de rango no admitidas". Para ser utilizado con controles con esta limitación, cambié la clase ObservableCollectionNoReset a:
public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
// Some CollectionChanged listeners don''t support range actions.
public Boolean RangeActionsSupported { get; set; }
protected override void ClearItems()
{
if (RangeActionsSupported)
{
List<T> removed = new List<T>(this);
base.ClearItems();
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
else
{
while (Count > 0 )
base.RemoveAt(Count - 1);
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Reset)
base.OnCollectionChanged(e);
}
public ObservableCollectionNoReset(Boolean rangeActionsSupported = false)
{
RangeActionsSupported = rangeActionsSupported;
}
// Additional constructors omitted.
}
Esto no es tan eficiente cuando RangeActionsSupported es falso (el valor predeterminado) porque se genera una notificación de eliminación por objeto en la colección
Para el escenario de conectar y separar controladores de eventos a los elementos de ObservableCollection, también hay una solución "del lado del cliente". En el código de manejo de eventos, puede verificar si el emisor está en ObservableCollection usando el método Contiene. Pro: puedes trabajar con cualquier ObservableCollection existente. Contras: el método Contiene ejecuta con O (n) donde n es el número de elementos en la ObservableCollection. Entonces esta es una solución para pequeños ObservableCollections.
Otra solución "del lado del cliente" es usar un controlador de eventos en el medio. Simplemente registre todos los eventos al controlador de eventos en el medio. Este controlador de eventos a su vez notifica al controlador de eventos reales a través de una devolución de llamada o un evento. Si se produce una acción de reinicio, elimine la devolución de llamada o el evento, cree un nuevo controlador de eventos en el medio y olvídese de la anterior. Este enfoque también funciona para grandes ObservableCollections. Lo usé para el evento PropertyChanged (vea el código a continuación).
/// <summary>
/// Helper class that allows to "detach" all current Eventhandlers by setting
/// DelegateHandler to null.
/// </summary>
public class PropertyChangedDelegator
{
/// <summary>
/// Callback to the real event handling code.
/// </summary>
public PropertyChangedEventHandler DelegateHandler;
/// <summary>
/// Eventhandler that is registered by the elements.
/// </summary>
/// <param name="sender">the element that has been changed.</param>
/// <param name="e">the event arguments</param>
public void PropertyChangedHandler(Object sender, PropertyChangedEventArgs e)
{
if (DelegateHandler != null)
{
DelegateHandler(sender, e);
}
else
{
INotifyPropertyChanged s = sender as INotifyPropertyChanged;
if (s != null)
s.PropertyChanged -= PropertyChangedHandler;
}
}
}
Tanto la ObservableCollection como la interfaz INotifyCollectionChanged están escritas con un uso específico en mente: la construcción de UI y sus características de rendimiento específicas.
Cuando desee notificaciones de cambios en la recopilación, generalmente solo le interesan los eventos Agregar y Eliminar.
Yo uso la siguiente interfaz:
using System;
using System.Collections.Generic;
/// <summary>
/// Notifies listeners of the following situations:
/// <list type="bullet">
/// <item>Elements have been added.</item>
/// <item>Elements are about to be removed.</item>
/// </list>
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
interface INotifyCollection<T>
{
/// <summary>
/// Occurs when elements have been added.
/// </summary>
event EventHandler<NotifyCollectionEventArgs<T>> Added;
/// <summary>
/// Occurs when elements are about to be removed.
/// </summary>
event EventHandler<NotifyCollectionEventArgs<T>> Removing;
}
/// <summary>
/// Provides data for the NotifyCollection event.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
public class NotifyCollectionEventArgs<T> : EventArgs
{
/// <summary>
/// Gets or sets the elements.
/// </summary>
/// <value>The elements.</value>
public IEnumerable<T> Items
{
get;
set;
}
}
También escribí mi propia sobrecarga de Colección donde:
- ClearItems plantea la eliminación
- InsertItem aumenta Agregado
- RemoveItem plantea la eliminación
- SetItem plantea la eliminación y agregado
Por supuesto, AddRange se puede agregar también.
Tuvimos el mismo problema aquí. La acción Restablecer en CollectionChanged no incluye OldItems. Tuvimos una solución alternativa: en su lugar, utilizamos el siguiente método de extensión:
public static void RemoveAll(this IList list)
{
while (list.Count > 0)
{
list.RemoveAt(list.Count - 1);
}
}
Terminamos no admitiendo la función Clear () y lanzando una NotSupportedException en el evento CollectionChanged para restablecer acciones. RemoveAll activará una acción Eliminar en el evento CollectionChanged, con los OldItems adecuados.
Please read this documentation with your eyes open and your brain turned on. Microsoft did everything right. You must re-scan your collection when it throws a Reset notification for you. You get a Reset notification because throwing Add/Remove for each item (being removed from and added back to collection) is too expensive.
Orion Edwards is completely right (respect, man). Please think wider when reading the documentation.
I found another "simple" solution deriving from ObservableCollection, but it is not very elegant because it uses Reflection... If you like it here is my solution:
public class ObservableCollectionClearable<T> : ObservableCollection<T>
{
private T[] ClearingItems = null;
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
if (this.ClearingItems != null)
{
ReplaceOldItems(e, this.ClearingItems);
this.ClearingItems = null;
}
break;
}
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
this.ClearingItems = this.ToArray();
base.ClearItems();
}
private static void ReplaceOldItems(System.Collections.Specialized.NotifyCollectionChangedEventArgs e, T[] olditems)
{
Type t = e.GetType();
System.Reflection.FieldInfo foldItems = t.GetField("_oldItems", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (foldItems != null)
{
foldItems.SetValue(e, olditems);
}
}
}
Here I save the current elements in an array field in the ClearItems method, then I intercept the call of OnCollectionChanged and overwrite the e._oldItems private field (through Reflections) before launching base.OnCollectionChanged
I had the same issue, and this was my solution. It seems to work. Does anyone see any potential problems with this approach?
// overriden so that we can call GetInvocationList
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
if (collectionChanged != null)
{
lock (collectionChanged)
{
foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
{
try
{
handler(this, e);
}
catch (NotSupportedException ex)
{
// this will occur if this collection is used as an ItemsControl.ItemsSource
if (ex.Message == "Range actions are not supported.")
{
handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
else
{
throw ex;
}
}
}
}
}
}
Here are some other useful methods in my class:
public void SetItems(IEnumerable<T> newItems)
{
Items.Clear();
foreach (T newItem in newItems)
{
Items.Add(newItem);
}
NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void AddRange(IEnumerable<T> newItems)
{
int index = Count;
foreach (T item in newItems)
{
Items.Add(item);
}
NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(newItems), index);
NotifyCollectionChanged(e);
}
public void RemoveRange(int startingIndex, int count)
{
IList<T> oldItems = new List<T>();
for (int i = 0; i < count; i++)
{
oldItems.Add(Items[startingIndex]);
Items.RemoveAt(startingIndex);
}
NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(oldItems), startingIndex);
NotifyCollectionChanged(e);
}
// this needs to be overridden to avoid raising a NotifyCollectionChangedEvent with NotifyCollectionChangedAction.Reset, which our other lists don''t support
new public void Clear()
{
RemoveRange(0, Count);
}
public void RemoveWhere(Func<T, bool> criterion)
{
List<T> removedItems = null;
int startingIndex = default(int);
int contiguousCount = default(int);
for (int i = 0; i < Count; i++)
{
T item = Items[i];
if (criterion(item))
{
if (removedItems == null)
{
removedItems = new List<T>();
startingIndex = i;
contiguousCount = 0;
}
Items.RemoveAt(i);
removedItems.Add(item);
contiguousCount++;
}
else if (removedItems != null)
{
NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex));
removedItems = null;
i = startingIndex;
}
}
if (removedItems != null)
{
NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex));
}
}
private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(e);
}
If your ObservableCollection
is not getting clear, then you may try this below code. it may help you:
private TestEntities context; // This is your context
context.Refresh(System.Data.Objects.RefreshMode.StoreWins, context.UserTables); // to refresh the object context
To keep it simple why don''t you override the ClearItem method and do whatever you want there ie Detach the items from the event.
public class PeopleAttributeList : ObservableCollection<PeopleAttributeDto>, {
{
protected override void ClearItems()
{
Do what ever you want
base.ClearItems();
}
rest of the code omitted
}
Simple, clean, and contain within the collection code.
You can override ClearItems method and raise event with Remove action and OldItems .
public class ObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>
{
protected override void ClearItems()
{
CheckReentrancy();
var items = Items.ToList();
base.ClearItems();
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items, -1));
}
}
Part of System.Collections.ObjectModel.ObservableCollection<T>
realization:
public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
protected override void ClearItems()
{
CheckReentrancy();
base.ClearItems();
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnCollectionReset();
}
private void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
private void OnCollectionReset()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private const string CountString = "Count";
private const string IndexerName = "Item[]";
}