2010-07-28 9 views
8

No hay muchas opciones para un panel de virtualización de wrap para su uso en WPF. Por una razón u otra, MS decidió no enviar uno en la biblioteca estándar.Virtualización de WPF Wrap Panel Edición

Si alguien pudiera ser tan atrevido como para dar una respuesta fuente multitud (y explicación) al primer elemento de trabajo en el siguiente proyecto de CodePlex, que sería de gran aprecio:

http://virtualwrappanel.codeplex.com/workitem/1

Gracias!


Resumen de emisión:

He intentado recientemente utilizando la virtualización de WrapPanel de este proyecto y he encontrado un error.

Pasos para reproducir:

  1. Crear cuadro de lista.
  2. Establezca el panel de virtualización como el elemento host en una plantilla listpanel.
  3. Enlace el elemento del cuadro de lista a una colección observable.
  4. Eliminar un elemento de la colección observable de respaldo.

El Debug.Assert falla (Debug.Assert (niño == _children [childIndex], "niño incorrecto se generó");) en MeasureOverride, y resultados de la ejecución continuó en una excepción nula en el método de limpieza [ver captura de pantalla adjunta].

Háganme saber si puede corregir esto.

Gracias,

AO


Código:

http://virtualwrappanel.codeplex.com/SourceControl/list/changesets#

alt text http://virtualwrappanel.codeplex.com/Project/Download/AttachmentDownload.ashx?ProjectName=virtualwrappanel&WorkItemId=1&FileAttachmentId=138959

Respuesta

4

El método OnItemsChanged necesita manejar adecuadamente los parámetros args. Consulte esto question para obtener más información. Copiando el código de esa pregunta, usted tendría que actualizar OnItemsChanged así:

protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args) { 
    base.OnItemsChanged(sender, args); 
    _abstractPanel = null; 
    ResetScrollInfo(); 

    // ...ADD THIS... 
    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; 
    } 
} 
0

En primer lugar, ten en cuenta que, en general, si va a quitar un objeto de una colección y no tiene su referencia, eso el objeto está muerto en el punto de eliminación. Por lo menos, la llamada a RemoveInternalChildRange es ilegal después de la eliminación, pero ese no es el problema central.

En segundo lugar, es posible que tenga una pequeña condición de carrera, incluso si no es estrictamente de subprocesos múltiples. Tiene que verificar (con un punto de interrupción) si ese controlador de eventos está reaccionando con demasiada impaciencia: no quiere que el controlador de eventos se ejecute mientras todavía está en el medio de una eliminación, incluso si se trata de un único elemento.

En tercer lugar, comprobar NULL después:

UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement; 

y para el primer ensayo cambiar el código para tener una salida elegante, que en este caso significa gracefull continuar - se han de utilizar para el bucle y los incrementos en el bucle poder hacer continuar en absoluto.

También compruebe InternalChildren cuando vea ese nulo para ver si esa ruta de acceso da el mismo resultado que sus _children (como en tamaño, datos internos, nulo en el mismo lugar).

Si solo se omite un nulo (se representa sin excepciones), deténgalo en el depurador inmediatamente después y compruebe si estas matrices/colecciones se resolvieron (no hay nulos en el interior).

Además, publique el proyecto de ejemplo totalmente compilable que proporciona el repro (como un archivo zip) en algún lugar: reduce las suposiciones aleatorias y permite a las personas simplemente construir/ejecutar y ver.

Hablando de suposiciones, compruebe qué hace su "colección observable". Si está eliminando un elemento de una colección, cualquiera y cada iterador/enumerador de un estado previo de esa colección tiene el derecho de arrojar o dar nulos y en una interfaz de usuario que intenta ser demasiado inteligente, puede tener un iterador obsoleto fácilmente.

8

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:

  1. ItemsSource: La colección original (en este caso ObservableCollection)
  2. CollectionView: Mantiene una lista separada de elementos ordenados/filtrados/agrupados (solo si alguna de estas funciones está en uso)
  3. ItemContainerGenerator: rastrea la asignación entre elementos y contenedores
  4. InternalChildren: rastrea los contenedores que actualmente están visib Le
  5. 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:

  1. Se llama a quitar() en el ItemsSource
  2. ItemsSource elimina el elemento y dispara su CollectionChanged el cual es manejado por el CollectionView
  3. CollectionView elimina el elemento (si la clasificación/filtrado/agrupación está en uso) y dispara sus CollectionChanged que es manejado por el ItemContainerGenerator
  4. ItemContainerGenerator actualiza su cartografía, dispara sus ItemsChanged que es manejado por VirtualizingPanel
  5. VirtualizingPanel llama a su método OnItemsChanged virtual whic h es implementado por VirtualizingWrapPanel
  6. 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:

  1. ItemContainerGenerator elimina de inmediato el tema de sus propias estructuras de datos
  2. ItemContainerGenerator desencadena el evento ItemChanged. Se espera que el panel elimine inmediatamente el contenedor.
  3. 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:

  1. ItemContainerGenerator añade de inmediato una "ranura" para el elemento en su estructura de datos pero no crea un contenedor
  2. ItemContainerGenerator activa el evento ItemChanged. El panel llama a InvalidateMeasure() [esto es hecho por la clase base - no tiene que hacerlo]
  3. 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.

+0

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. –

+0

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