spiegazione del problema
Avete chiesto una spiegazione di ciò che sta andando male, così come le istruzioni su come risolvere il problema esso. Finora nessuno ha spiegato il problema. Lo farò.
In ListBox con un VirtualizingWrapPanel ci sono cinque strutture separate di dati che tracciano gli elementi, ognuno in modo diverso:
- ItemsSource: La collezione originale (in questo caso ObservableCollection)
- CollectionView: Mantiene una lista separata di elementi ordinati/filtrati/raggruppati (solo se una di queste funzioni è in uso)
- ItemContainerGenerator: traccia la mappatura tra elementi e contenitori
- InternalChildren: traccia i contenitori che sono attualmente visibili Le
- WrapPanelAbstraction: canzoni che appaiono contenitori su cui si allineano
Quando un elemento viene rimosso da ItemsSource, tale rimozione deve essere propagata attraverso tutte le strutture di dati.Ecco come funziona:
- Si chiama Remove() sul ItemsSource
- ItemsSource rimuove l'elemento e spara la sua CollectionChanged che viene gestita dal CollectionView
- CollectionView rimuove la voce (se l'ordinamento/filtraggio/raggruppamento è in uso) e spara suo CollectionChanged che viene gestita dal ItemContainerGenerator
- ItemContainerGenerator aggiorna la sua mappatura, incendi sua ItemsChanged che è gestito da VirtualizingPanel
- VirtualizingPanel chiama il metodo OnItemsChanged virtuale whic h è attuato da VirtualizingWrapPanel
- VirtualizingWrapPanel scarta il WrapPanelAbstraction in modo che sarà costruito, ma non è mai aggiorna InternalChildren
A causa di questo, la collezione InternalChildren non è sincronizzato con le altre quattro collezioni, portando a gli errori che sono stati vissuti.
soluzione al problema
Per risolvere il problema, aggiungere il seguente codice in qualsiasi punto all'interno metodo OnItemsChanged di 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;
}
questo mantiene la collezione InternalChildren in sincronia con le altre strutture di dati.
Perché AddInternalChild/InsertInternalChild non è chiamato qui
si potrebbe chiedere perché non ci sono chiamate a InsertInternalChild o AddInternalChild nel codice sopra, e soprattutto perché la manipolazione Sostituire e Move non ci richiedono di aggiungere un nuovo elemento durante OnItemsChanged.
La chiave per capire questo è nel modo in cui funziona ItemContainerGenerator.
Quando ItemContainerGenerator riceve un evento elimina gestisce tutto e subito:
- ItemContainerGenerator rimuove immediatamente la voce dalle proprie strutture di dati
- ItemContainerGenerator genera l'evento ItemChanged. Il pannello dovrebbe rimuovere immediatamente il contenitore.
- ItemContainerGenerator "unprepares" contenitore rimuovendo il DataContext
D'altra parte, ItemContainerGenerator apprende che un elemento viene aggiunto tutto è tipicamente differito:
- ItemContainerGenerator aggiunge immediatamente una "slot" per l'elemento nella sua struttura dati ma non crea un contenitore
- ItemContainerGenerator attiva l'evento ItemChanged. Il pannello chiama InvalidateMeasure() [questo viene fatto dalla classe base - non è necessario farlo]
- Successivamente, quando viene chiamato MeasureOverride, Generator.StartAt/MoveNext viene utilizzato per generare i contenitori dell'elemento.Ogni contenitore appena generato viene aggiunto a InternalChildren in quel momento.
Così, tutte le rimozioni della collezione InternalChildren (compresi quelli che fanno parte di un movimento o sostituire) deve essere fatto all'interno OnItemsChanged, ma aggiunte possono (e dovrebbero) essere differiti fino al prossimo MeasureOverride.
Sembra che Tom Goff abbia dato il codice necessario mentre scrivevo la mia risposta. Anche la sua risposta è corretta, ed è essenzialmente uguale alla mia senza la spiegazione dettagliata. –
Ciao Ray - Bel riassunto, hai avuto il mio voto. Un problema con la tua risposta è che il problema non è proprio il fatto che "la collezione InternalChildren non sia sincronizzata con le altre quattro raccolte", ma sono sicuro che non aiuta. Il problema di fondo è che i bambini realizzati non vengono "ripuliti". Se rimuovi l'oggetto dall'indice 10, l'elemento all'indice 11 verrà spostato nell'indice 10. Quando vai a realizzare l'oggetto all'indice 10 (che in precedenza era 11), ti ritroverai con l'affermazione "Bambino sbagliato è stato generato ", poiché l'altro bambino non è mai stato realizzato. – CodeNaked