Cosa causa un ListCollectionView di WPF che utilizza l'ordinamento personalizzato per riordinare i suoi elementi?

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

  •  03-07-2019
  •  | 
  •  

Domanda

Prendi in considerazione questo codice (digita nomi generici a scopo di esempio):

// 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();

Quando imposto CustomSort , la raccolta viene ordinata come mi aspetto.

Tuttavia, ho bisogno che i dati si riordinino in fase di esecuzione in risposta alla modifica delle proprietà su Item . La classe Item deriva da INotifyPropertyChanged e so che la proprietà si attiva correttamente poiché il mio modello di dati aggiorna i valori sullo schermo, non viene chiamata solo la logica di ordinamento.

Ho anche provato a sollevare INotifyPropertyChanged.PropertyChanged passando una stringa vuota, per vedere se una notifica generica provocherebbe l'avvio dell'ordinamento. Nessuna banana.

MODIFICA In risposta al suggerimento di Kent, ho pensato di sottolineare che l'ordinamento degli articoli usando questo ha lo stesso risultato, vale a dire che la raccolta ordina una volta ma non riordinare quando i dati cambiano:

_itemsView.SortDescriptions.Add(
    new SortDescription("PropertyName", ListSortDirection.Ascending));
È stato utile?

Soluzione

Ho trovato questo articolo del Dr . WPF che inizia con una risposta alla mia domanda, per poi passare a discutere dell'impatto sulle prestazioni della chiamata di Aggiorna . Alcuni estratti chiave:

  

Sfortunatamente, il metodo Refresh () provoca una rigenerazione completa della vista. Quando si verifica un aggiornamento () all'interno della vista, genera una notifica CollectionChanged e fornisce l'azione come " Reset " ;. ItemContainerGenerator per ListBox riceve questa notifica e risponde eliminando tutti gli elementi visivi esistenti per gli elementi. Quindi rigenera completamente nuovi contenitori e oggetti visivi.

Descrive quindi una soluzione caotica che migliora le prestazioni. Invece di chiamare Aggiorna, rimuovi, modifica, quindi aggiungi nuovamente l'elemento.

Avrei pensato che la vista elenco potesse tenere traccia di un elemento che cambia e sapere di riposizionarlo da solo all'interno della vista.

Un nuovo approccio è stato introdotto in .NET 3.5 SP1 che coinvolge l'interfaccia IEditableObject che fornisce modifiche transazionali tramite associazioni di dati al modello con i metodi BeginEdit () , CancelEdit () e EndEdit () . Leggi l'articolo per ulteriori informazioni.

MODIFICA Come sottolinea user346528 , IEditableObject non era in realtà una novità in 3.5SP1. In realtà sembra che sia stato nel framework dal 1.0 .

Altri suggerimenti

Come la risposta accettata mi indirizza a, sono in grado di forzare il riposizionamento di singoli elementi con il codice

IEditableCollectionView collectionView = DataGrid.Items;

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

Dove changedItem è il modello di visualizzazione (l'elemento nella raccolta ItemsSource ).

In questo modo non hai bisogno dei tuoi articoli per implementare interfacce come IEditableObject (che a mio avviso ha un contratto molto complesso e difficile da implementare in alcuni casi).

Saltare un vecchio post, ma basta creare una nuova classe di raccolta che eredita da ListViewCollection e Overrides OnPropertyChanged (per un IBindingList, gli eventi ListChanged conterranno la modifica della proprietà nel parametro ListChangedEventArgs). E assicurati che gli elementi all'interno della raccolta implementino e utilizzino INotifyPropertyChange ogni volta che una proprietà cambia (sollevata da te) o la raccolta non si legherà alle modifiche della proprietà.

Quindi, in questo metodo OnPropertyChanged, il mittente sarà l'elemento. Rimuovi l'elemento se - e solo se - una proprietà che provocherebbe la modifica di un resort, quindi aggiungilo nuovamente (inseriscilo in posizione ordinata se aggiungendolo non lo fa già). Lo spostamento di un elemento è preferibile se è disponibile anziché rimuoverlo / aggiungerlo. Allo stesso modo, ciò dovrebbe essere fatto anche con il filtro (controllo del predicato del filtro).

IEditableObject non è necessario! Se si desidera modificare diverse proprietà, quindi terminare la modifica (come la modifica di 3 proprietà e quindi la selezione su una riga diversa in WinForm DataGridView), quindi averne l'ordinamento, questo sarebbe il metodo corretto per farlo funzionare. Ma molte volte vorrai probabilmente ricorrere alla raccolta dopo aver modificato ciascuna proprietà senza dover chiamare manualmente BeginEdit / EndEdit. IEditableObject, tra l'altro, è presente nel framework .NET 2.0 e non è una novità di .NET 3.5 (se leggi l'articolo del Dr.).

Nota: i problemi possono verificarsi usando BeginEdit () e EndEdit () con più modifiche allo stesso elemento - a meno che non si incrementi (per true) / decrementi (per false) un numero intero anziché impostare un valore booleano! Ricorda di incrementare / decrementare un numero intero per sapere veramente quando la modifica è terminata.

Mantenere un elenco perennemente ordinato richiede tempo ed è soggetto a errori (e può rompere il contratto di inserimento se si forzano inserti ordinati) e dovrebbe essere utilizzato solo in determinati luoghi come ComboBox. Su qualsiasi griglia, questa è una pessima idea, poiché la modifica di una riga provocherà il ricorso a una posizione inferiore alla posizione corrente dell'utente.

Public Class ListView
  Inherits ListCollectionView

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

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

Il miglior esempio di una raccolta che è simile a ciò di cui hai bisogno è Jesse Johnsons ObjectListView, sebbene questo sia specifico di .NET 2.0 (IBindingList invece di INotifyCollectionChanged / ObservableCollection / ListCollectionView) e utilizza una licenza molto restrittiva. Il suo blog potrebbe essere ancora molto prezioso per come ha realizzato questo.

Modifica:

Hai dimenticato di aggiungere che il mittente sarà l'elemento che devi ricorrere e e.PropertyName è ciò che dovrai usare per determinare se è compreso in SortDescription. In caso contrario, una modifica a quella proprietà non comporterà la necessità di un resort. Se e.PropertyName è Nothing, allora è proprio come un aggiornamento, dove molte proprietà potrebbero essere cambiate e dovrebbe essere fatto il ricorso.

Per determinare se deve essere filtrato, basta eseguirlo attraverso FilterPredicate e rimuoverlo se necessario. Il filtro è molto meno costoso dell'ordinamento.

Speriamo utile,

TamusJRoyce

Dato che stai utilizzando l'ordinamento personalizzato, non è possibile per ListCollectionView sapere quali criteri dovrebbero attivare un aggiornamento. Pertanto, dovrai chiamare Aggiorna () nella vista raccolta da solo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top