Zeichnen eines Gitters von Bildern mit WPF

7

Ich versuche, ein Raster aus Bildern / Symbolen mit WPF zu zeichnen. Die Rastermaße variieren, liegen aber typischerweise zwischen 10x10 und 200x200. Der Benutzer sollte in der Lage sein, auf Zellen zu klicken, und einige Zellen müssen 10-20 Mal pro Sekunde aktualisieren (Bild ändern). Das Gitter sollte in allen vier Richtungen wachsen und schrumpfen können, und es sollte in der Lage sein, zu einem anderen "Schnitt" der 3D-Struktur zu wechseln, die es darstellt. Mein Ziel ist es, eine entsprechend effiziente Methode zum Zeichnen des Gitters zu finden, die diese Anforderungen erfüllt.

Meine aktuelle Implementierung verwendet einen WPF Grid . Ich erzeuge Zeilen- und Spaltendefinitionen zur Laufzeit und fülle das Gitter mit Line (für die Gitternetzlinien) und Border (für die Zellen, da sie gerade gerade an / aus sind) in die entsprechende Zeile / Spalte. (Die Line -Objekte erstrecken sich über den ganzen Weg.)

Beim Erweitern des Gitters (mit Num6) habe ich festgestellt, dass es zu langsam gezeichnet wird, um es bei jeder Operation neu zu zeichnen, also ändere ich es so, dass einfach ein neues ColumnDefinition , Line und jedes Border -Objekt hinzugefügt wird Säule des Wachstums. Das löste mein Wachstumsproblem, und eine ähnliche Taktik konnte verwendet werden, um auch schnell zu schrumpfen. Zum Aktualisieren einzelner Zellen während der Simulation könnte ich einfach Referenzen auf die Zellobjekte speichern und das angezeigte Bild ändern. Sogar der Wechsel zu einer neuen Z-Ebene könnte verbessert werden, indem nur Zelleninhalte aktualisiert werden, anstatt das gesamte Gitter neu zu erstellen.

Bevor ich jedoch all diese Optimierungen machen konnte, stieß ich auf ein anderes Problem. Immer wenn ich mit der Maus über das Gitter fahre (sogar bei langsamen / normalen Geschwindigkeiten), nimmt die CPU-Auslastung der Anwendung zu. Ich habe alle Event-Handler aus den untergeordneten Elementen des Gitters entfernt, aber das hatte keine Auswirkungen. Schließlich bestand die einzige Möglichkeit, die CPU-Auslastung zu kontrollieren, darin, IsHitTestVisible = false für Grid festzulegen. (Dies für jedes untergeordnete Element von Grid zu setzen, hat nichts bewirkt!)

Ich glaube, dass die Verwendung einzelner Steuerelemente zum Erstellen meines Rasters für diese Anwendung zu intensiv und ungeeignet ist und dass die Verwendung von WPFs 2D-Zeichnungsmechanismen effizienter sein könnte. Ich bin jedoch ein Anfänger für WPF, also suche ich Ratschläge, wie ich das am besten erreichen kann. Von dem Wenigen, das ich gelesen habe, kann ich ein DrawingGroup verwenden, um das Bild jeder Zelle zusammen auf ein einzelnes Bild zur Anzeige zu bringen. Ich könnte dann einen Click-Event-Handler für das gesamte Bild verwenden und die Koordinaten der angeklickten Zelle anhand der Mausposition berechnen. Das scheint chaotisch zu sein, und ich weiß nur nicht, ob es einen besseren Weg gibt.

Gedanken?

Update 1:

Ich habe den Rat eines Freundes genommen und bin dazu übergegangen, für jede Zelle ein Canvas mit einem Rectangle zu verwenden. Wenn ich zuerst das Gitter zeichne, speichere ich Referenzen auf alle Rectangle in einem zweidimensionalen Array, und wenn ich dann den Gitterinhalt aktualisiere, greife ich einfach auf diese Referenzen zu.

%Vor%

Das Zeichnen des Gitters scheint anfangs schneller zu sein, und nachfolgende Aktualisierungen sind definitiv schneller, aber es gibt immer noch ein paar Probleme.

  • Egal, wie klein der Bereich ist, in dem ich die Maus übergebe, die CPU-Auslastung steigt immer noch an, wenn ich mit mehr als ein paar hundert Zellen über das Gitter fahre.

  • Updates sind immer noch zu langsam. Wenn ich also die Pfeiltaste nach oben drücke, um die Z-Ebene zu ändern (ein häufiger Anwendungsfall), bleibt das Programm für jeweils Sekunden stehen und springt dann um 50 Z-Stufen sofort.

  • Sobald das Grid ~ 5000 Zellen enthält, dauert die Aktualisierung in der Größenordnung von einer Sekunde. Dies ist prohibitiv langsam und 5000 Zellen passen in typische Anwendungsfälle.

Ich habe den UniformGrid -Ansatz noch nicht ausprobiert, weil ich denke, dass er die gleichen Probleme aufweist, die ich bereits festgestellt habe. Ich könnte es versuchen, sobald ich ein paar mehr Möglichkeiten erschöpft habe.

    
Henry Merriam 14.04.2011, 18:54
quelle

7 Antworten

17

Ihre Frage

Lass uns deine Frage umformulieren. Dies sind Ihre Problembedingungen:

  1. Sie möchten ein Raster mit dynamischen Größen zeichnen
  2. Jede Zelle wird schnell ein- / ausgeschaltet
  3. Rastergrößen ändern sich schnell
  4. Es gibt eine große Anzahl von Zellen (d. h. die Gitterdimensionen sind nicht trivial)
  5. Sie möchten, dass all diese Änderungen mit einer schnellen Bildrate (z. B. 30 Bildern pro Sekunde) durchgeführt werden.
  6. Die Positionierung und das Layout des Gitters und der Zellen sind deterministisch, einfach und nicht sehr interaktiv

Wenn Sie diese Einschränkungen beachten, können Sie sofort erkennen, dass Sie den falschen Ansatz verwenden.

Anforderung: Schnelle Aktualisierung von deterministischen Positionen mit geringer Interaktivität

Schnelle Bildwiederholrate + viele Änderungen pro Bild + große Anzahl von Zellen + ein WPF-Objekt pro Zelle = Disaster.

Wenn Sie nicht über sehr schnelle Grafikhardware und eine sehr schnelle CPU verfügen, leidet Ihre Bildrate immer unter der Vergrößerung der Rastermaße.

Was Ihr Problem vorschreibt, ist eher ein Videospiel oder ein CAD-Zeichenprogramm mit dynamischem Zoom. Es ist weniger wie eine normale Desktop-Anwendung.

Immediate Mode vs. Retained Mode Zeichnung

Mit anderen Worten, Sie möchten "Sofortmodus" -Zeichnung, nicht "beibehaltene Modus" -Zeichnung (WPF ist beibehaltener Modus). Dies liegt daran, dass Ihre Integritätsbedingungen nicht viel von den Funktionen erfordern, die durch die Behandlung jeder Zelle als separates WPF-Objekt bereitgestellt werden.

Sie benötigen beispielsweise keine Layout-Unterstützung, da die Position jeder Zelle deterministisch ist. Sie werden keine Hit-Testing-Unterstützung benötigen, da wiederum Positionen deterministisch sind. Sie benötigen keine Containerunterstützung, da jede Zelle ein einfaches Rechteck (oder ein Bild) ist. Sie benötigen keine komplexe Formatierungsunterstützung (z. B. Transparenz, abgerundete Rahmen usw.), da sich nichts überschneidet. Mit anderen Worten, es gibt keinen Vorteil, ein Grid (oder UniformGrid) und ein WPF-Objekt pro Zelle zu verwenden.

Konzept des Immediate Mode Drawing to Buffer Bitmap

Um die gewünschte Bildrate zu erreichen, werden Sie im Wesentlichen zu einer großen Bitmap (die den gesamten Bildschirm abdeckt) oder einem "Bildschirmpuffer" gezeichnet. Für Ihre Zellen zeichnen Sie einfach zu diesem Bitmap / Puffer (vielleicht mit GDI). Hit-Tests sind einfach, da die Positionen der Zellen alle deterministisch sind.

Diese Methode ist schnell, da nur ein Objekt vorhanden ist (die Bildschirmpuffer-Bitmap). Sie können entweder die gesamte Bitmap für jedes Bild aktualisieren oder nur die sich ändernden Bildschirmpositionen oder eine intelligente Kombination dieser aktualisieren.

Beachten Sie, dass Sie, obwohl Sie hier ein "Gitter" zeichnen, kein "Gitter" -Element verwenden. Wählen Sie Ihren Algorithmus und Ihre Datenstrukturen basierend auf Ihren Problemeinschränkungen, nicht so, wie es aussieht, um die offensichtliche Lösung zu sein - mit anderen Worten, ein "Raster" ist möglicherweise nicht die richtige Lösung zum Zeichnen ein "Gitter".

Immediate Mode Zeichnung in WPF

WPF basiert auf DirectX, daher verwendet es im Grunde bereits eine Bildschirmpuffer-Bitmap (die als Puffer bezeichnet wird) hinter der Szene.

Die Art und Weise, wie Sie das Zeichnen im WFP-Modus im unmittelbaren Modus verwenden, besteht darin, die Zellen als GeometryDrawing-Objekte zu erstellen (nicht Shape's, das beibehalten wird). GemoetryDrawing ist normalerweise extrem schnell, da GemoetryDrawing-Objekte direkt auf DirectX-Primitive abgebildet werden. Sie werden nicht einzeln als Framework-Elemente angelegt und verfolgt, so dass sie sehr leicht sind - Sie können eine große Anzahl von ihnen haben, ohne die Leistung zu beeinträchtigen.

Wählen Sie das GeometryDrawing in ein DrawingImage (das ist im Wesentlichen Ihr Back-Buffer) und Sie erhalten ein sich schnell änderndes Bild für Ihren Bildschirm. Hinter der Szene macht WPF genau das, was Sie von ihm erwarten - d. H. Zeichnen Sie jedes Rechteck, das sich im Bildpuffer ändert.

Auch hier sollten Sie Shape's nicht verwenden - dies sind Framework-Elemente und wird erhebliche Overheads verursachen, wenn sie am Layout teilnehmen. Verwenden Sie beispielsweise NICHT Rechteck , verwenden Sie stattdessen RechteckGeometrie .

Optimierungen

Mehrere weitere Optimierungen, die Sie in Betracht ziehen sollten:

  1. GeometryDrawing-Objekte wiederverwenden - einfach Position und Größe ändern
  2. Wenn das Raster eine maximale Größe hat, erstellen Sie die Objekte
  3. vor
  4. Ändern Sie nur die GeometryDrawing-Objekte, die sich geändert haben - WPF aktualisiert sie nicht unnötigerweise
  5. Füllen Sie die Bitmap in "Stufen", dh für verschiedene Zoomstufen, immer auf ein Gitter, das viel größer als das vorherige ist, und verwenden Sie Skalierung, um es zurück zu skalieren. Bewegen Sie sich beispielsweise von einem 10x10-Gitter direkt zu einem 20x20-Gitter, aber skalieren Sie es um 55% zurück, um 11x11-Felder anzuzeigen. Auf diese Weise werden Ihre GeometryDrawing-Objekte nie geändert, wenn Sie von 11x11 auf 20x20 zoomen. nur die Skalierung der Bitmap wird geändert, wodurch die Aktualisierung extrem schnell wird.

BEARBEITEN: Frame-für-Bild-Rendering durchführen

Überschreibe OnRender , wie in der Antwort vorgeschlagen, um das Kopfgeld für diese Frage zu vergeben.Dann zeichnest du die gesamte Szene auf einer Leinwand.

Verwenden Sie DirectX für absolute Kontrolle

Alternativ können Sie Raw DirectX verwenden, wenn Sie absolute Kontrolle über jeden Frame haben möchten.

    
Stephen Chung 26.04.2011, 02:22
quelle
3

Sie können Ihr eigenes benutzerdefiniertes Steuerelement (basierend auf Canvas, Panel usw.) schreiben und OnRender wie folgt überschreiben:

%Vor%

Ich kann ein 400x400 Gitter mit diesem auf meinem Laptop (keine Konkurrenzmaschine ...) erfolgreich zeichnen und seine Größe ändern.

Es gibt mehr raffinierte und bessere Möglichkeiten, dies zu tun (mit StreamGeometry im DrawingContext), aber das ist zumindest eine gute Testumgebung.

Natürlich müssen Sie die HitTestXXX-Methoden überschreiben.

    
Simon Mourier 26.04.2011 21:19
quelle
2

Wenn Sie mit dem Canvas -Ansatz fortfahren, sieht es so aus, als ob Sie die Gitterlinien schnell zeichnen könnten. Sie könnten alle leeren Quadrate auslassen und die Gesamtanzahl der Elemente auf dem Bildschirm drastisch reduzieren, abhängig von der Dichte Ihrer Arbeit . Wie auch immer, um die Gitterlinien schnell zu zeichnen, können Sie DrawingBrush wie folgt verwenden:

%Vor%

was zu diesem Effekt führt:

    
Rick Sladkey 24.04.2011 07:03
quelle
2

Ich denke, Sie werden es schwer haben, mit so vielen Elementen umzugehen. Wenn nur eine kleine Anzahl sichtbar war, wurde das Steuerelement für die Steuerung virtualisiert hier könnte helfen, aber das hilft nur beim Scrollen. Um so viele Zellen gleichzeitig sichtbar zu haben, müssen Sie wahrscheinlich auf die eine oder andere Weise zu einer Bitmap zeichnen.

Hier ist ein Beispiel, in dem ein VisualBrush einer Zelle gekachelt wird und dann jede Zelle mit einer OpacityMask getoggelt wird. Der folgende Ansatz ist ziemlich ordentlich, da nur ein Pixel pro Zelle benötigt wird; Die Elemente können beliebig groß sein, und Sie benötigen keinen komplexen Code, der den Inhalt der Zelle in eine Bitmap verwandelt.

Das Beispiel erstellt ein 1000 * 1000-Gitter und es gibt 3 Zelltypen. Wenn Sie nur zwei benötigen, könnte der Code weiter vereinfacht und viele Schleifen entfernt werden. Updates waren schnell (3ms für 200 * 200, 100ms für 1k * 1k), Scrollen funktioniert wie erwartet und das Hinzufügen von Zoom sollte nicht allzu schwierig sein.

%Vor%


%Vor%     
Kris 26.04.2011 01:47
quelle
1

Wenn Sie möchten, dass die Zellen die gleiche Größe haben, könnte das UniformGrid am besten passen. Auf diese Weise müssen Sie sich keine Gedanken über die Größeneinstellung im Code machen.

Wenn Sie implementieren, wäre ich sehr an den Ergebnissen interessiert.

    
CodeWarrior 14.04.2011 19:06
quelle
0

Ich schlage vor, dass Sie dafür ein benutzerdefiniertes Panel schreiben, das Schreiben ist einfach, da Sie nur die Methoden MeasureOverride und ArrangeOverride überschreiben müssen. Basierend auf der Anzahl von Zeilen / Spalten Sie können jeder Zelle die verfügbare Größe zuweisen. Dies sollte Ihnen eine bessere Leistung als das Grid bieten, auch wenn Sie es noch weiter optimieren möchten, können Sie auch die Virtualisierung im Panel implementieren.

Ich habe es so gemacht, als ich eine Roll-Matrix erstellen musste, die anstelle von Bildern und Anzahl der Zeilen / Spalten verschiedene Textinformationen anzeigen musste. Hier ist ein Beispiel, wie Sie ein benutzerdefiniertes Panel schreiben können

Ссылка

Lassen Sie es mich wissen, wenn Sie möchten, dass ich den Code, den ich mit Ihnen geschrieben habe, teile.

    
NeroBrain 25.04.2011 18:30
quelle
0

Gehen Sie hier ein paar Vermutungen durch:

  1. Verwenden Sie den Canvas-Ansatz.
  2. Deaktivieren Sie den Treffertest im Canvas auf halte Mouseover CPU davon ab, verrückt zu werden.
  3. Verfolgen Sie Ihre Änderungen separat von die Benutzeroberfläche. Ändern Sie nur die Füllung Eigenschaft auf Elementen, die seit dem letzten Update geändert. Ich bin Raten, dass die langsamen Updates sind aufgrund der Aktualisierung von Tausenden von UI Elemente und die nachfolgenden Neu-Rendering von allem.
RandomEngy 25.04.2011 19:54
quelle

Tags und Links