Virtualisierung des WPF-Wrap-Panel-Problems

8

Es gibt nicht viele Optionen für ein virtualisierendes Wrap-Panel zur Verwendung in WPF. Aus dem einen oder anderen Grund hat sich MS entschieden, kein Exemplar in die Standardbibliothek zu liefern.

Wenn jemand so mutig sein könnte, dem ersten Workitem des folgenden Codeplex-Projekts eine Crowd Source-Antwort (und Erklärung) zu geben, würde ich das sehr schätzen:

Ссылка

Danke!

Zusammenfassung des Problems:

Ich habe kürzlich versucht, das Wrapper für die Virtualisierung aus diesem Projekt zu verwenden und habe einen Fehler gefunden.

Schritte zum Reproduzieren:

  1. Erstellen Sie ein Listenfeld.
  2. Legen Sie das virtualisierte Wrapper als itemhost in einer Listboxpanel-Vorlage fest.
  3. Binden Sie die Objektquelle der Listbox an eine beobachtbare Sammlung.
  4. Entfernen Sie ein Objekt aus der beobachtbaren Objektgruppe.

Das Debug.Assert schlägt fehl (Debug.Assert (child == _children [childIndex], "Falsches Kind wurde generiert");) in MeasureOverride, und die weitere Ausführung führt zu einer Null-Ausnahme in der Cleanup-Methode [siehe angehängter Screenshot] .

Bitte lassen Sie mich wissen, wenn Sie dies korrigieren können.

Danke,

AO

Code:

Ссылка

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

    
Albert Oldfield 28.07.2010, 18:30
quelle

3 Antworten

4

Die OnItemsChanged-Methode muss die args-Parameter ordnungsgemäß verarbeiten. Weitere Informationen finden Sie in dieser Frage . Wenn Sie den Code aus dieser Frage kopieren, müssen Sie OnItemsChanged wie folgt aktualisieren:

%Vor%     
CodeNaked 18.08.2010, 19:35
quelle
8

Erläuterung des Problems

Sie haben nach einer Erklärung für das, was schief geht, und nach Anweisungen zur Fehlerbehebung gefragt. Bisher hat niemand das Problem erklärt. Ich werde es tun.

In ListBox mit einem VirtualizingWrapPanel gibt es fünf separate Datenstrukturen, die Elemente auf verschiedene Arten verfolgen:

  1. ItemsSource: Die ursprüngliche Sammlung (in diesem Fall ObservableCollection)
  2. CollectionView: Behält eine separate Liste von sortierten / gefilterten / gruppierten Elementen bei (nur wenn eines dieser Features verwendet wird)
  3. ItemContainerGenerator: Verfolgt die Zuordnung zwischen Elementen und Containern
  4. InternalChildren: Verfolgt aktuell sichtbare Container
  5. WrapPanelAbstraction: Verfolgt, welche Container in welcher Zeile angezeigt werden

Wenn ein Element aus ItemsSource entfernt wird, muss diese Entfernung durch alle Datenstrukturen weitergegeben werden. So funktioniert es:

  1. Sie rufen Remove () auf der ItemsSource
  2. auf
  3. ItemsSource entfernt das Element und löst sein CollectionChanged aus, das von der CollectionView
  4. verarbeitet wird
  5. CollectionView entfernt das Objekt (wenn Sortierung / Filterung / Gruppierung verwendet wird) und feuert sein CollectionChanged, das vom ItemContainerGenerator
  6. verarbeitet wird
  7. ItemContainerGenerator aktualisiert seine Zuordnung, feuert ItemsChanged, was von VirtualizingPanel
  8. gehandhabt wird
  9. VirtualizingPanel ruft seine virtuelle OnItemsChanged-Methode auf, die von VirtualizingWrapPanel
  10. implementiert wird
  11. VirtualizingWrapPanel verwirft seine WrapPanelAbstraction, sodass es erstellt wird, aber es aktualisiert InternalChildren nicht

Aus diesem Grund ist die InternalChildren-Sammlung nicht mit den anderen vier Sammlungen synchronisiert, was zu den Fehlern geführt hat, die aufgetreten sind.

Lösung des Problems

Um das Problem zu beheben, fügen Sie den folgenden Code an beliebiger Stelle innerhalb der OnItemsChanged-Methode von VirtualizingWrapPanel hinzu:

%Vor%

Dadurch wird die InternalChildren-Sammlung mit den anderen Datenstrukturen synchronisiert.

Warum AddInternalChild / InsertInternalChild hier nicht aufgerufen wird

Sie fragen sich vielleicht, warum im obigen Code keine Aufrufe von InsertInternalChild oder AddInternalChild enthalten sind, und insbesondere, warum wir bei der Verwendung von Ersetzen und Verschieben kein neues Element während OnItemsChanged hinzufügen müssen.

Der Schlüssel zum Verständnis ist die Funktionsweise von ItemContainerGenerator.

Wenn ItemContainerGenerator ein remove-Ereignis empfängt, behandelt es alles sofort:

  1. ItemContainerGenerator entfernt das Element sofort aus seinen eigenen Datenstrukturen
  2. ItemContainerGenerator löst das ItemChanged-Ereignis aus. Das Panel wird erwartet, den Container sofort zu entfernen.
  3. ItemContainerGenerator "prepariert" den Container durch Entfernen seines DataContext

Auf der anderen Seite lernt ItemContainerGenerator, dass ein Element hinzugefügt wird, alles wird normalerweise zurückgestellt:

  1. ItemContainerGenerator fügt sofort einen "Slot" für das Element in seiner Datenstruktur hinzu, erstellt jedoch keinen Container
  2. ItemContainerGenerator löst das ItemChanged-Ereignis aus. Das Panel ruft InvalidateMeasure () auf [dies wird von der Basisklasse ausgeführt - Sie müssen es nicht tun]
  3. Später, wenn MeasureOverride aufgerufen wird, wird Generator.StartAt / MoveNext verwendet, um die Elementcontainer zu generieren. Alle neu erzeugten Container werden zu diesem Zeitpunkt zu InternalChildren hinzugefügt.

Daher müssen alle Entfernungen von der InternalChildren-Sammlung (einschließlich derjenigen, die Teil eines Move oder Replace sind) innerhalb von OnItemsChanged ausgeführt werden, aber Zusätze können (und sollten) bis zum nächsten MeasureOverride zurückgestellt werden.

    
Ray Burns 18.08.2010 21:09
quelle
0

Beachten Sie zuerst, dass im Allgemeinen, wenn Sie ein Objekt aus einer Sammlung entfernen und Sie es nicht als Referenz haben, dieses Objekt zum Zeitpunkt des Entfernens tot ist. Zumindest RemoveInternalChildRange Aufruf ist nach der Entfernung illegal, aber das ist nicht das Kernproblem.

Zweitens könnte es sein, dass Sie eine kleine Race-Bedingung haben, auch wenn es nicht streng Multi-Threading ist. Überprüfen Sie (mit Haltepunkt), ob dieser Ereignishandler zu eifrig reagiert - Sie möchten nicht, dass der Ereignishandler ausgeführt wird, während Sie sich gerade in der Mitte einer Entfernung befinden, selbst wenn es sich um ein einzelnes Element handelt.

Drittens überprüfen Sie nach:

%Vor%

und für den ersten Versuch ändern Sie den Code zu einem graceful exit, was in diesem Fall bedeutet, dass gracefull fortfahren - for-Schleife und Inkremente in der Schleife verwenden müssen, um überhaupt fortfahren zu können.

Überprüfen Sie auch InternalChildren, wenn Sie NULL sehen, um zu sehen, ob dieser Zugriffspfad das gleiche Ergebnis wie Ihre _children liefert (wie Größe, interne Daten, null an der gleichen Stelle).

Wenn nur eine Null übersprungen wird (ohne Ausnahme rendert), dann stoppe sie im Debugger und überprüfe, ob diese Arrays / Collections gesetzt sind (keine Nullen innerhalb).

Veröffentlichen Sie auch das vollständig kompilierbare Beispielprojekt, das die Repro (als Zip-Datei) irgendwo gibt - reduziert zufällige Annahmen und ermöglicht es ppl, nur zu erstellen / auszuführen und zu sehen.

Apropos Annahmen - überprüfen Sie, was Ihre "beobachtbare Sammlung" macht. Wenn Sie ein Objekt aus einer Objektgruppe entfernen, hat jeder Iterator / Enumerator aus einem früheren Zustand dieser Sammlung das Recht, null zu geben oder zu geben und in einer Benutzeroberfläche, die zu schlau zu sein versucht ein veralteter Iterator kann leicht passieren.

    
ZXX 17.08.2010 22:12
quelle

Tags und Links