head.WriteLine()

Dienstag, April 04, 2006

Double Buffering in .NET 2.0

Ein Weg Performance-Probleme bei Zeichenoperationen zu umgehen ist Double Buffering. Hierbei wird die jeweilige Zeichenoperation nicht direkt auf dem Bildschirm ausgeführt, sondern zunächst im Speicher. Dieser Speicher wird daraufhin extrem schnell auf in den Bildschirmspeicher kopiert. Dies beschleunigt zwar nicht wirklich die Performance, vermeidet jedoch den altbekannten Flackereffekt.

Wollte man diese Technik unter .NET 1.x nutzen, so konnte man einen in der Control-Klasse eingebauten Mechanismus nutzen. Control bietet nämlich über die SetStyle()-Methode die Möglichkeit Double Buffering zu aktivieren. Das folgende Beispiel demonstriert dies:

this.SetStyle(
    ControlStyles.DoubleBuffer |
    ControlStyles.AllPaintingInWmPaint |
    ControlStyles.UserPaint |
    ControlStyles.ResizeRedraw,
    true);

Bei der Verwendung von Double Buffering sollten auch stets die Flags AllPaintingInWmPaint und UserPaint gesetzt werden. Sie weisen das Window an, keine WM_ERASEBKGND-Meldungen zu verarbeiten (Hintergrund löschen) und signalisiert, dass sich die Anwendung um das komplette Zeichnen der Oberfläche kümmert. Ist das Fenster oder Control resizable, so kann optional über ResizeRedraw ein automatisches Neuzeichnen bei Größenänderungen veranlasst werden.

In .NET 2.0 bietet die ControlStyles-Enumeration nun zusätzlich den Wert OptimizedDoubleBuffer. Dieser dient als Ersatz für DoubleBuffer und bietet in erster Linie eine bessere Performance. Die obere Anweisung könnte in .NET 2.0 somit wie folgt umgeschrieben werden.

this.SetStyle(
    ControlStyles.OptimizedDoubleBuffer |
    ControlStyles.AllPaintingInWmPaint |
    ControlStyles.UserPaint |
    ControlStyles.ResizeRedraw,
true);

Viele der im .NET-Framework 2.0 enthaltenen Komponenten nutzen übrigens diese Methode bereits standardmässig.

Double Buffering steuern
Darüber hinaus besteht nun auch die Möglichkeit dediziert in den Double Buffering-Prozess einzugreifen. Hierzu bietet der System.Drawing-Namespace die neuen Klassen BufferedGraphics, BufferedGraphicsContext und BufferedGraphicsManager. Das folgende Beispiel demonstriert die Verwendung:

// Grafikpuffer anlegen
Graphics gr = this.CreateGraphics();
BufferedGraphicsContext bufferedContext =
    BufferedGraphicsManager.Current;
bufferedContext.MaximumBuffer = this.Size;
BufferedGraphics bufferedGraphics =
    bufferedContext.Allocate(gr, this.ClientRectangle);

// Auf gepufferter Grafik zeichnen
this.DrawSomething(bufferedGraphics.Graphics);

// Grafik auf den Bildschirm rendern
bufferedGraphics.Render();


Hier wird zunächst ein Graphics-Objekt erzeugt, auf dem später gezeichnet werden soll. Daraufhin wird über BufferedGraphicsManager.Current ein BufferedGraphicsContext-Objekt ermittelt und dessen Puffer mit der Größe des Fensters initialisiert. Das Erzeugen des Speicherabbildes erfolgt die Allocate()-Methode, der das Graphics-Objekt, sowie eine Rectangle-Struktur mit Größe und Position des Zeichenbereichs übergeben wird. Allocate() gibt daraufhin ein BufferedGraphics-Objekt zurück, auf dem in der Folge gezeichnet werden kann. Es bietet alle Methoden und Eigenschaften wie Graphics, führt die Zeichenoperationen jedoch im Speicher und nicht auf dem Bildschirm aus. Für die Übertragung des Speicherabbildes auf den Bildschirm sorgt schließlich die Render()-Methode.

Das manuelle Double Buffering lohnt sich jedoch nur für extrem aufwendige Zeichenoperationen, bei denen eine sehr feine Steuerung der Bildschirmübertragung erforderlich ist. In der Regel reicht schon der oben gezeigte SetStyle()-Aufruf, um die Performance enorm zu verbessern.