Explicación del problema
Usted pidió una explicación de lo que va mal, así como instrucciones de cómo solucionar eso. Hasta el momento, nadie ha explicado el problema. Lo haré.
En ListBox con un VirtualizingWrapPanel hay cinco estructuras separadas de datos que rastrean artículos, cada uno de diferentes maneras:
- ItemsSource: La colección original (en este caso ObservableCollection)
- CollectionView: Mantiene una lista separada de elementos ordenados/filtrados/agrupados (solo si alguna de estas funciones está en uso)
- ItemContainerGenerator: rastrea la asignación entre elementos y contenedores
- InternalChildren: rastrea los contenedores que actualmente están visib Le
- WrapPanelAbstraction: Pistas que aparecen contenedores en los que se alinean
Cuando un artículo es retirado de ItemsSource, esta eliminación debe propagarse a través de todas las estructuras de datos.Así es como funciona:
- Se llama a quitar() en el ItemsSource
- ItemsSource elimina el elemento y dispara su CollectionChanged el cual es manejado por el CollectionView
- CollectionView elimina el elemento (si la clasificación/filtrado/agrupación está en uso) y dispara sus CollectionChanged que es manejado por el ItemContainerGenerator
- ItemContainerGenerator actualiza su cartografía, dispara sus ItemsChanged que es manejado por VirtualizingPanel
- VirtualizingPanel llama a su método OnItemsChanged virtual whic h es implementado por VirtualizingWrapPanel
- VirtualizingWrapPanel, descarta su WrapPanelAbstraction por lo que se construirá, pero no se actualiza InternalChildren
Debido a esto, la colección InternalChildren está fuera de sincronización con los otros cuatro colecciones, lo que lleva a los errores que se experimentaron
Solución al problema
Para solucionar el problema, agregue el código siguiente en cualquier lugar dentro método OnItemsChanged de VirtualizingWrapPanel:
switch(args.Action)
{
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
break;
case NotifyCollectionChangedAction.Move:
RemoveInternalChildRange(args.OldPosition.Index, args.ItemUICount);
break;
}
Esto mantiene la colección InternalChildren en sincronía con las otras estructuras de datos.
Por qué AddInternalChild/InsertInternalChild no se llama aquí
Usted puede preguntarse por qué no hay llamadas a InsertInternalChild o AddInternalChild en el código anterior, y sobre todo por qué manejar y mover Reemplazar no nos obligan a añadir un nuevo elemento durante OnItemsChanged.
La clave para entender esto es en la forma en que funciona ItemContainerGenerator.
Cuando ItemContainerGenerator recibe un evento de quitarlo de inmediato se encarga de todo:
- ItemContainerGenerator elimina de inmediato el tema de sus propias estructuras de datos
- ItemContainerGenerator desencadena el evento ItemChanged. Se espera que el panel elimine inmediatamente el contenedor.
- ItemContainerGenerator "unprepares" el contenedor mediante la eliminación de su DataContext
Por otro lado, ItemContainerGenerator se entera de que un elemento se agrega todo lo que se suele diferido:
- ItemContainerGenerator añade de inmediato una "ranura" para el elemento en su estructura de datos pero no crea un contenedor
- ItemContainerGenerator activa el evento ItemChanged. El panel llama a InvalidateMeasure() [esto es hecho por la clase base - no tiene que hacerlo]
- Más tarde cuando se llama a MeasureOverride, Generator.StartAt/MoveNext se usa para generar los contenedores de elementos.Todos los contenedores generados recientemente se agregan a InternalChildren en ese momento.
Por lo tanto, todas las extracciones de la colección InternalChildren (incluyendo los que son parte de un movimiento o sustituir) deben realizarse dentro de OnItemsChanged, pero adiciones pueden (y deben) ser diferidos hasta el siguiente MeasureOverride.
Parece que Tom Goff dio el código necesario mientras escribía mi respuesta. Su respuesta también es correcta, y es esencialmente la misma que la mía sin la explicación detallada. –
Hola Rayo - Buen resumen, tienes mi voto. Un problema con su respuesta es que el problema no es que la "Colección de InternalChildren no esté sincronizada con las otras cuatro colecciones", sino que estoy seguro de que no ayuda. El problema subyacente es que los niños realizados no son "limpiados". Si quita el ítem en el índice 10, entonces el ítem en el índice 11 se moverá al índice 10. Cuando vaya a realizar el ítem en el índice 10 (que anteriormente estaba en 11), terminará con la afirmación "Niño equivocado" fue generado ", ya que el otro niño nunca se realizó. – CodeNaked