¿Qué causa un WPF ListCollectionView que utiliza una ordenación personalizada para reorganizar sus elementos?

StackOverflow https://stackoverflow.com/questions/601864

  •  03-07-2019
  •  | 
  •  

Pregunta

Considere este código (nombres de tipos genéricos para fines de ejemplo):

// Bound to ListBox.ItemsSource
_items = new ObservableCollection<Item>();

// ...Items are added here ...

// Specify custom IComparer for this collection view
_itemsView = CollectionViewSource.GetDefaultView(_items)
((ListCollectionView)_itemsView).CustomSort = new ItemComparer();

Cuando configuro CustomSort , la colección se ordena como espero.

Sin embargo, requiero que los datos se vuelvan a ordenar en tiempo de ejecución en respuesta al cambio de las propiedades en Item . La clase Item deriva de INotifyPropertyChanged y sé que la propiedad se activa correctamente a medida que mi plantilla de datos actualiza los valores en pantalla, solo que no se llama a la lógica de clasificación.

También intenté elevar INotifyPropertyChanged.PropertyChanged pasando una cadena vacía, para ver si una notificación genérica haría que se iniciara la clasificación. Sin plátanos.

EDITAR En respuesta a la sugerencia de Kent, pensé en señalar que ordenar los elementos usando esto tiene el mismo resultado, es decir, que la colección se ordena una vez pero no reordenar a medida que cambian los datos:

_itemsView.SortDescriptions.Add(
    new SortDescription("PropertyName", ListSortDirection.Ascending));
¿Fue útil?

Solución

Encontré este artículo del Dr. . WPF que comienza con una respuesta a mi pregunta, luego pasa a discutir el impacto en el rendimiento de llamar a Refresh . Algunos extractos clave:

  

Desafortunadamente, el método Refresh () resulta en una regeneración completa de la vista. Cuando se produce una actualización () dentro de la vista, genera una notificación CollectionChanged y proporciona la acción como "Restablecer". ItemContainerGenerator para ListBox recibe esta notificación y responde descartando todos los elementos visuales existentes para los elementos. Luego regenera completamente nuevos contenedores de elementos y elementos visuales.

Luego describe una solución alternativa que mejora el rendimiento. En lugar de llamar a Actualizar, elimine, cambie y vuelva a agregar el elemento.

Hubiera creído posible que la vista de lista pudiera rastrear un elemento que cambia y saber reubicar ese elemento solo dentro de la vista.

Se introdujo un nuevo enfoque en .NET 3.5 SP1 que involucra la interfaz IEditableObject que proporciona edición transaccional a través de enlaces de datos a la plantilla con métodos BeginEdit () , CancelEdit () y EndEdit () . Lea el artículo para obtener más información.

EDITAR como user346528 señala , IEditableObject no era nuevo en 3.5SP1. En realidad, parece que ha estado en el marco desde 1.0 .

Otros consejos

Como me indica la respuesta aceptada, puedo forzar la reposición de un solo elemento con código

IEditableCollectionView collectionView = DataGrid.Items;

collectionView.EditItem(changedItem);
collectionView.CommitEdit();

Donde changedItem es el modelo de vista (el elemento en la colección ItemsSource ).

De esta manera, no necesita sus elementos para implementar ninguna interfaz como IEditableObject (que en mi opinión tiene contratos muy complejos y difíciles de implementar en algunos casos).

Poner una publicación anterior, pero solo crea una nueva clase de colección que herede de ListViewCollection y Overrides OnPropertyChanged (para un IBindingList, los eventos ListChanged contendrán el cambio de propiedad en el parámetro ListChangedEventArgs). Y asegúrese de que los elementos dentro de la colección implementen y usen INotifyPropertyChange cada vez que cambie una propiedad (planteada por usted), o la colección no se unirá a los cambios de propiedad.

Luego, en este método OnPropertyChanged, el remitente será el elemento. Elimine el elemento si, y solo si, se cambia una propiedad que causaría un recurso, luego vuelva a agregarlo (insértelo en una posición ordenada si agregarlo aún no lo hace). Mover un elemento es preferible si está disponible en lugar de eliminarlo / agregarlo. Del mismo modo, esto también debe hacerse con el filtrado (comprobar el predicado del filtro).

¡IEditableObject no es necesario! Si desea editar varias propiedades, luego termine de editar (como editar 3 propiedades y luego seleccionar en una fila diferente en WinForm DataGridView), luego ordenarlo, este sería el método adecuado para que esto funcione. Pero muchas veces es probable que desee que la colección recurra después de cambiar cada propiedad sin tener que llamar manualmente a BeginEdit / EndEdit. IEditableObject, por cierto, está presente en el marco .NET 2.0 y no es nuevo en .NET 3.5 (si lee el artículo del Dr.).

Nota: Los problemas pueden ocurrir usando BeginEdit () y EndEdit () con múltiples ediciones en el mismo elemento, a menos que incremente (para verdadero) / disminuya (para falso) un entero en lugar de establecer un Booleano. Recuerde aumentar / disminuir un número entero para saber realmente cuándo finaliza la edición.

Mantener una lista ordenada perpetuamente lleva mucho tiempo y es propenso a errores (y puede romper el contrato de inserción si fuerza las inserciones ordenadas), y solo debe usarse en ciertos lugares como ComboBoxes. En cualquier cuadrícula, esta es una muy mala idea, ya que cambiar una fila hará que se salga de la posición actual de los usuarios.

Public Class ListView
  Inherits ListCollectionView

  Protected Overrides Sub OnPropertyChanged(sender As Object, e As PropertyChangedEventArgs)

    ' Add resorting/filtering logic here.
  End Sub
End Class

El mejor ejemplo en una colección que hace algo similar a lo que necesita es Jesse Johnsons ObjectListView, aunque es específico de .NET 2.0 (IBindingList en lugar de INotifyCollectionChanged / ObservableCollection / ListCollectionView) y utiliza una licencia muy restrictiva. Su blog aún puede ser muy valioso en cómo logró esto.

Editar:

Se olvidó de agregar que el remitente será el elemento que necesita recurrir, y e.PropertyName es lo que deberá usar para determinar si está dentro de las descripciones de clasificación. Si no es así, un cambio en esa propiedad no resultará en la necesidad de un resort. Si e.PropertyName es Nothing, entonces es como una actualización, donde muchas propiedades pueden haber cambiado y se debe recurrir.

Para determinar si necesita filtrado, simplemente ejecútelo a través de FilterPredicate y elimínelo si es necesario. Filtrar es mucho menos costoso que ordenar.

Con suerte útil,

TamusJRoyce

Dado que está utilizando una ordenación personalizada, no hay forma de que ListCollectionView sepa qué criterios deberían desencadenar una actualización. Por lo tanto, deberá llamar a Refresh () en la vista de colección usted mismo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top