Tengo algo aquí que realmente me está tomando por sorpresa.
Tengo un ObservableCollection de T que está lleno de elementos. También tengo un controlador de eventos adjunto al evento CollectionChanged.
Cuando clara la colección que provoca un evento CollectionChanged con e.Action conjunto de NotifyCollectionChangedAction.Reset. Ok, eso es normal. Pero lo extraño es que ni e.OldItems ni e.NewItems contienen nada. Esperaría que e.OldItems se llene 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 solucionado?
Algunos antecedentes: estoy usando el evento CollectionChanged para adjuntar y separar de otro evento y, por lo tanto, si no obtengo ningún elemento en e.OldItems ... no podré desconectarme de ese evento.
ACLARACIÓN: Sé que la documentación no establece rotundamente 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 lo 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 del 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 };
}
}
fuente
Respuestas:
No pretende incluir los elementos antiguos, porque Restablecer no significa que la lista se haya borrado
Significa que ha ocurrido algo dramático, y el costo de resolver las adiciones / eliminaciones probablemente excedería el costo de simplemente volver a escanear la lista desde cero ... así que eso es lo que debe hacer.
MSDN sugiere un ejemplo de toda la colección que se reordena como candidata a reiniciarse.
Reiterar. Restablecer no significa que esté claro , significa que sus suposiciones sobre la lista ahora no son válidas. Trátelo como si fuera una lista completamente nueva . Clear pasa a ser un ejemplo de esto, pero bien podría haber otros.
Algunos ejemplos:
he tenido una lista como esta con muchos elementos en ella, y se ha vinculado a los datos de un WPF
ListView
para mostrar en pantalla.Si borra la lista y genera el
.Reset
evento, el rendimiento es prácticamente instantáneo, pero si, en cambio, genera muchos.Remove
eventos individuales , el rendimiento es terrible, ya que WPF elimina los elementos uno por uno. También lo he usado.Reset
en mi propio código para indicar que la lista ha sido reordenada, en lugar de emitir miles deMove
operaciones individuales . Al igual que con Clear, hay un gran impacto en el rendimiento cuando se generan muchos eventos individuales.fuente
OldItems
al limpiar (es solo copiar una lista), pero tal vez hubo algún escenario en el que esto fue demasiado caro. En cualquier caso, si desea una colección que le notifique todos los elementos eliminados, no sería difícil de hacer.Reset
es para indicar una operación costosa, es muy probable que se aplique el mismo razonamiento para copiar toda la lista aOldItems
.Reset
realidad significa "Se borró el contenido de la colección ". Ver msdn.microsoft.com/en-us/library/…Tuvimos el mismo problema aquí. La acción Restablecer en CollectionChanged no incluye OldItems. Tuvimos una solución: usamos en su lugar el siguiente método de extensión:
Terminamos no admitiendo la función Clear () y lanzando una NotSupportedException en el evento CollectionChanged para las acciones de restablecimiento. RemoveAll desencadenará una acción Eliminar en el evento CollectionChanged, con los OldItems adecuados.
fuente
Otra opción es reemplazar el evento Reset con un solo evento Remove que tenga todos los elementos borrados en su propiedad OldItems de la siguiente manera:
Ventajas:
No es necesario suscribirse a un evento adicional (como lo requiere 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 y OldItems en cualquier evento para agregar / eliminar controladores de eventos según sea necesario.
Desventajas:
Sin evento de reinicio
Sobrecarga pequeña (?) Creando una copia de la lista.
???
EDITAR 2012-02-23
Desafortunadamente, cuando se vincula a los controles basados en listas de WPF, borrar una colección ObservableCollectionNoReset con varios elementos dará como resultado una excepción "Acciones de rango no admitidas". Para usar con controles con esta limitación, cambié la clase ObservableCollectionNoReset a:
Esto no es tan eficiente cuando RangeActionsSupported es falso (el valor predeterminado) porque se genera una notificación de eliminación por cada objeto de la colección
fuente
Range actions are not supported.
, no sé por qué hace esto, pero ahora esto no deja otra opción que eliminar cada elemento de uno en uno ...foreach( NotifyCollectionChangedEventHandler handler in this.CollectionChanged )
Ifhandler.Target is CollectionView
, entonces puede disparar el controlador conAction.Reset
argumentos; de lo contrario, puede proporcionar los argumentos completos. Lo mejor de ambos mundos, manejador por manejador :). Algo así como lo que hay aquí: stackoverflow.com/a/3302917/529618De acuerdo, sé que esta es una pregunta muy antigua, pero se me ocurrió una buena solución al problema y pensé en compartirla. Esta solución se inspira en muchas de las excelentes respuestas aquí, pero tiene las siguientes ventajas:
Aquí está el código:
Este método de extensión simplemente toma un
Action
que se invocará antes de que se borre la colección.fuente
Encontré una solución que permite al usuario aprovechar la eficiencia de agregar o eliminar muchos elementos a la vez mientras solo se activa un evento, y satisfacer las necesidades de UIElements para obtener los argumentos del evento Action.Reset mientras que todos los demás usuarios lo harían como una lista de elementos agregados 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
argumentos cuando cambia más de un elemento, podemos seleccionarlos y dar a todos los demás argumentos de eventos adecuados que contengan la lista completa de elementos eliminados o agregados. A continuación se muestra la implementación.fuente
Ok, aunque todavía deseo que ObservableCollection se comporte como deseaba ... el siguiente código 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 este TrulyObservableCollection, utilizo este evento de compensación para recorrer los elementos que todavía están en la colección en ese punto para realizar la separación en el evento del que deseaba desconectarme.
Espero que este enfoque también ayude a otra persona.
fuente
BrokenObservableCollection
, noTrulyObservableCollection
, está malinterpretando lo que significa la acción de restablecimiento.ActuallyUsefulObservableCollection
. :)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. Comencé anulando el evento de cambio de colección y redirigiendo las acciones de restablecimiento a las acciones de eliminación con una lista de elementos. Todo esto salió mal porque estaba usando la colección observable como fuente de elementos para una vista de colección y obtuve "Acciones de rango no admitidas".
Finalmente creé un nuevo evento llamado CollectionChangedRange que actúa de la manera que esperaba que actuara la versión incorporada.
No puedo imaginar por qué se permitiría esta limitación y espero que esta publicación al menos evite que otros caigan en el callejón sin salida que yo hice.
fuente
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 agregados ) cuando la acción es Restablecer comparando su lista con ObservableCollection.
Otra opción es crear su propia clase que implemente IList e INotifyCollectionChanged, luego puede adjuntar y separar eventos dentro de esa clase (o establecer OldItems en Clear si lo desea); en realidad no es difícil, pero requiere mucho tipeo.
fuente
Para el escenario de adjuntar y desconectar controladores de eventos a los elementos de ObservableCollection, también existe una solución "del lado del cliente". En el código de manejo de eventos, puede verificar si el remitente está en ObservableCollection usando el método Contains. Ventaja: puede trabajar con cualquier ObservableCollection existente. Contras: el método Contains se ejecuta con O (n) donde n es el número de elementos en ObservableCollection. Entonces esta es una solución para pequeñas ObservableCollections.
Otra solución "del lado del cliente" es utilizar un controlador de eventos en el medio. Simplemente registre todos los eventos en el controlador de eventos en el medio. Este controlador de eventos, a su vez, notifica al controlador de eventos real 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 del anterior. Este enfoque también funciona para grandes ObservableCollections. Usé esto para el evento PropertyChanged (ver código a continuación).
fuente
Al observar 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 desencadena el evento, pero no registra los elementos eliminados y no invoca el código Eliminar en absoluto.
fuente
Bueno, decidí ensuciarme yo mismo.
Microsoft puso MUCHO trabajo para asegurarse siempre de que NotifyCollectionChangedEventArgs no tenga ningún dato al llamar a un reinicio. Supongo 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 dado que mis colecciones nunca tienen más de 100 elementos, no veo ningún problema con eso.
De todos modos creé una clase heredada con el siguiente método:
fuente
Tanto la interfaz ObservableCollection como la INotifyCollectionChanged están claramente escritas con un uso específico en mente: la construcción de la interfaz de usuario y sus características de rendimiento específicas.
Cuando desee notificaciones de cambios en la colección, generalmente solo le interesan los eventos Agregar y Eliminar.
Yo uso la siguiente interfaz:
También escribí mi propia sobrecarga de Colección donde:
Por supuesto, también se puede agregar AddRange.
fuente
Estaba revisando parte del código de gráficos en los kits de herramientas de Silverlight y WPF y noté que también resolvieron este problema (de una manera similar) ... y pensé en seguir adelante y publicar su solución.
Básicamente, también crearon un ObservableCollection derivado y anularon ClearItems, llamando a Remove en cada elemento que se borraba.
Aquí está el código:
fuente
Este es un tema candente ... porque en mi opinión, Microsoft no hizo su trabajo correctamente ... una vez más. No me malinterpretes, me gusta Microsoft, ¡pero no son perfectos!
Leí la mayoría de los comentarios anteriores. Estoy de acuerdo con todos los que piensan que Microsoft no programó Clear () correctamente.
En mi opinión, al menos, se necesita un argumento para que sea posible desprender objetos de un evento ... pero también entiendo el impacto que tiene. Entonces, pensé en esta solución propuesta.
Espero que haga felices a todos, o al menos a casi todos ...
Eric
fuente
Para simplificarlo, ¿por qué no anula el método ClearItem y hace lo que quiera allí, es decir, separar los elementos del evento?
Simple, limpio y contenido dentro del código de colección.
fuente
Tuve el mismo problema y esta fue mi solución. Parece funcionar. ¿Alguien ve algún problema potencial con este enfoque?
Aquí hay algunos otros métodos útiles en mi clase:
fuente
Encontré otra solución "simple" derivada de ObservableCollection, pero no es muy elegante porque usa Reflection ... Si te gusta aquí está mi solución:
Aquí guardo los elementos actuales en un campo de matriz en el método ClearItems, luego intercepto la llamada de OnCollectionChanged y sobrescribo el campo privado e._oldItems (a través de Reflections) antes de iniciar la base.
fuente
Puede anular el método ClearItems y generar un evento con la acción Eliminar y OldItems.
Parte de la
System.Collections.ObjectModel.ObservableCollection<T>
realización:fuente
http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(VS.95).aspx
Lea esta documentación con los ojos abiertos y el cerebro encendido. Microsoft hizo todo bien. Debes volver a escanear tu colección cuando te envíe una notificación de reinicio. Recibe una notificación de reinicio porque lanzar Agregar / Eliminar para cada elemento (que se elimina y se vuelve a agregar a la colección) es demasiado costoso.
Orion Edwards tiene toda la razón (respeto, hombre). Por favor piense más al leer la documentación.
fuente
Si
ObservableCollection
no se aclara, puede probar este código a continuación. puede ayudarte:fuente