DrawNode Event wird bei jeder aktion ausgelöst
-
Hallo,
ich habe in meiner Anwendung die DrawNode()-Methode des TreeViews überschrieben. Das Zeichnen klappt auch ganz gut, das Problem ist nur das, dass DrawNode()_Ereignis jedesmal ausgelöst wird. Beim Schliessen der Anwendung zeichnet er zuerst den ganzen TreeView neu bevor er beendet, und auch wenn ich mit der Maus über die +/- icons fahre wird neu gezeichnet.
Das ist sehr nervig da das Zeichnen sehr aufwendig ist und daher viel Zeit für das erneute Zeichnen verloren geht.
Leider gibt es überhaupt keine Lösungsansätze über dieses Problem im Netz. Ich hoffe Ihr könnt mir helfen.
Die DrawMode ist OwnerDrawText:
myTreeView.DrawMode = TreeViewDrawMode.OwnerDrawText;gruß
gast53
-
Ja, es muss halt neugezeichnet werden, wenn sich was ändert.
Wie du das in den Griff bekommst, musst du selber wissen. Ansätze wären z.B., das ganze in Bitmaps zu cachen und nur die Bitmaps drüberzuzeichnen, wenn du weißt, dass sich nichts geändert hat. Oder sonst irgendwelche Informationen cachen, damit das Zeichnen dann doch nicht so aufwändig wird.
-
Die Frage wäre woher ich die Information bekomme, ob sich etwas geändert hat?
Das mit den Bitmap-caching werde ich mir mal ansehen, vielen Dank.
-
Ob sich etwas geändert hat, musst du doch wissen. Was zeichnest du denn und warum ist es kompliziert?
z.B., wenn du Subversion Status Icons zeichnen willst und es deswegen aufwändig ist, weil du erst den Status abfragen musst, dann wäre der Ansatz, sich den Status zu merken und beim Zeichnen nicht mehr abzufragen.
-
gast53 schrieb:
Die Frage wäre woher ich die Information bekomme, ob sich etwas geändert hat?
Wann das Control sich neu zeichnen muss weiß es doch selbst am besten und ruft dann eben Deine überschriebene Methode auf. Das ist nicht nur wenn sich tatsächlich was geändert hat, vielleicht war kurzzeitig einfach nur ein anderes Fenster im Vordergrund.
gast53 schrieb:
Das mit den Bitmap-caching werde ich mir mal ansehen, vielen Dank.
Du lädst aber hoffentlich nicht jedesmal die Bilder neu von der Festplatte oder?
Was genau passiert beim neuzeichnen und warum dauert das so lange?
Das Treeview-Control bietet leider keinen VirtualMode an um nur das zu laden/zeichnen was tatsächlich sichtbar ist, aber das könnte man von Hand nachbauen.
-
ich zeichne für jeden Knoten (DrawNode() er wird also für jeden Knoten aufgerufen) einen Balken in den hintergrund(jeden Knotens) der Visuell anzeigt wieviel Festplattenspeicher durch diesen Knoten relativ zur jeweiligen Partition belegt ist.
Rechnerintensiv wird es dadurch das ich um die Größe eines Verzeichnisses zu erhalten, diesen und seine Unterordner durchlaufen muss. Bei Ordnern mit wenigen unterordnern ist das kein Problem, da sieht man nur ein kurzes flickern.
Bei größeren Systemordnern, oder wenn man eine Partition lädt, kann das bis zu einer minute dauern. Wenn jetzt jedesmal neu gezeichnet wird könnt Ihr euch denken was mein Problem ist. Ich habe die Durchlaufenen Verzeichnisse ausgeben lassen es sind ~55000.
Hilfreich wäre es zu erfahren wie Windows das umsetzt. Doch da kann ich auch nichts finden.
Man müsste vergleichen können ob sich etwas geändert hat. Der DrawTreeNodeEventArgs e parameter hat auch keine hilfreichen eigenschaften oder Methoden dafür.
-
ich habe hier die funktion DrawToBitmap() gefunden. Und man könnte auch die OnPrint() Methode überschreiben.
Geht das in die richtige Richtung?
-
es scheint so als ob das Bitmap nicht mehr als ein Bild ist, eine art Screenshot des TreeViews. Das würde mir dann nicht viel helfen, der Nutzer müsste nämlich auch die Knoten expandieren können.
-
Hi,
das Problem liegt nicht am zeichnen selbst und daran wirst Du nicht viel optimieren können. Der Aufwand ist das Auslesen der Verzeichnisse.
Wenn Du im Explorer die Eigenschaften des Windows-Ordners anzeigen lässt, dauert es sekundenlang bis die Anzeige der Größe zum Stillstand kommt. Trotzdem reagiert das Dialogfenster noch auf Eingaben (Verschieben etc) und genau das ist Dein Ansatz. Lagere das Auslesen der Verzeichnisse aus in einen anderen Thread und aktualisiere die GUI regemäßig. Schau Dir mal die Klasse BackgroundWorker (*) an.
Man müsste vergleichen können ob sich etwas geändert hat.
Ich weiß nicht genau warum Du über Änderungen informiert werden willst. Geht es darum die Anzeige auch aktuell zu halten, nachdem sie geladen wurde? Dazu kannst Du nach dem vollständigen Auslesen der Verzeichnisse den FileSystemWatcher (**) verwenden. Das ändert aber nichts an Deinem ersten Problem, dass es Zeitaufwändig ist, den Baum erstmalig zu konstruieren, was in einem Thread stattfinden sollte.
* http://msdn.microsoft.com/de-de/library/system.componentmodel.backgroundworker(v=vs.110).aspx
** http://msdn.microsoft.com/de-de/library/system.io.filesystemwatcher.aspx
-
Ich weiß nicht genau warum Du über Änderungen informiert werden willst. Geht es darum die Anzeige auch aktuell zu halten, nachdem sie geladen wurde? Dazu kannst Du nach dem vollständigen Auslesen der Verzeichnisse den FileSystemWatcher (**) verwenden. Das ändert aber nichts an Deinem ersten Problem, dass es Zeitaufwändig ist, den Baum erstmalig zu konstruieren, was in einem Thread stattfinden sollte.
Beim erstmaligen Konstruieren des Baums sehe ich auch kein Problem auch nicht das es etwas länger dauert, es geschiet ja nur einmal. Das Problem liegt beim zeichnen, wo auch Verzeichnisse durchlaufen werden, dass ist auch nicht schlimm wenn das länger dauert. Nur ist es nicht nötig den Baum erneut zu zeichnen(intern nochmals zu durchlaufen) wenn sich an ihm nichts geändert hat. Er wurde ja nicht vom Nutzer aktualisiert. Die Methode zeichnet den alten Baum nochmals mit dem gleichen Zeitaufwand. Und das jedesmal auch wenn ich die Anwendung schliesse, wird nochmals das Paint() ereignis ausgelöst, was einfach nicht sinnvoll ist.
Die Idee war deshalb den Baum solange nicht neu zu zeichnen bis der Nutzer einen anderen Pfad lädt. Also bis auf diesen Punkt alle Paint() ereignisse abzufangen, und zu verhindern das die Verzeichnisse nochmals durchlaufen werden, um die Größe zu ermitteln. Der Zustand müsste irgendwie gespeichert werden. Dann wäre auch nicht unbedingt ein BackgroundWorker erforderlich. Aber ich hatte auch schon daran gedacht.
-
Mechanics schrieb:
dann wäre der Ansatz, sich den Status zu merken und beim Zeichnen nicht mehr abzufragen.
Ich glaube in diese Richtung wird es gehen, am auslösen der Events wird man nicht viel ändern können.
Nur wie kann ich das umsetzen das DrawNode() wird ja für jeden Knoten einzeln aufgerufen. Ich könnte die errechneten Größen und den Namen in ein Array speichern, dannach den Namen des Knotenpunktes mit den Namen in den Arrays vergleichen. Ich frage mich ob es schneller wäre das Array mit den 55000 Verzeichnissen zu durchlaufen?
-
Konstruiere dir beim Erzeugen der einzelnen Nodes einfach jeweils ein Objekt einer eigenen Klasse (welche die Informationen über das Verzeichnis und/oder Datei enthält) und hänge dieses Objekt an die TreeNode.Tag-Eigenschaft, welche du dann im DrawNode-Ereignis benutzen kannst.
Noch besser ist es den TreeView dynamisch aufzubauen, d.h. nicht zu Beginn schon alle Unterverzeichnisse auszulesen, sondern zuerst nur die erste Ebene (und evtl. zweite Ebene, damit der Anwender sieht ob es darunter dann Unterverzeichnisse gibt - durch Anzeige des Plus-Icons). Durch Anlegen eines Dummy-Nodes kannst du dann beim TreeView.BeforeExpand-Ereignis entscheiden, ob der Subtree schonmal angezeigt wurde oder ob die Verzeichnis-Daten erst nachgeladen werden müssen.
Kurz und knapp gibt es unter File Explorer using Treeview controller in C# 2.0 ein ähnliches Beispielprojekt.