Rubrik: Grafik und Font | VB-Versionen: VB.NET | 28.03.07 |
AutoRedraw-Eigenschaft in .NET 2.0 nachbauen Ein UserControl für VB.NET, das sich verhält wie ein VB6-Control mit AutoRedraw=True | ||
Autor: Andreas Haack | Bewertung: | Views: 17.602 |
ohne Homepage | System: WinNT, Win2k, WinXP, Win7, Win8, Win10, Win11 | Beispielprojekt auf CD |
In VB 6 gibt es die Möglichkeit, bei einer Form, einer PictureBox oder einem UserControl, die Eigenschaft AutoRedraw auf True zu setzen. Dies führt dazu, dass die Controls bzw. die Form alles, was auf ihnen gezeichnet wird, puffern und automatisch neu zeichnen, sobald dies nötig wird.
In VB.NET gibt es weder für Forms, noch für PictureBoxen und auch nicht für UserControls diese AutoRedraw-Eigenschaft bzw. das dementsprechende Verhalten. Im Allgemeinen sollen alle Grafikoperationen im Paint-Event bzw. in der korrespondierenden OnPaint- oder in OnPaintBackground-Methode ausgeführt werden.
Nun mag man sich nicht mit dem Umstand abfinden wollen, jeden bereits gezeichneten Inhalt in Paint erneut zu zeichnen, sondern möchte jede Grafikaktion nur einmal durchführen und das Neuzeichnen dem Control überlassen.
Als erster Lösungsansatz, der auch im Internet rasch zu finden ist, bietet es sich an, eine Bitmap zu verwenden, sie einem Control wie einer PictureBox zuzuweisen und alle Grafikoperationen auf der Bitmap vorzunehmen. Die Bitmap besorgt dann gewissermaßen die Pufferung des gezeichneten Inhalts und man ist die lästige Aufgabe los, im Paint-Event das Neuzeichnen selbst erledigen müssen.
Dieser Ansatz erscheint auf dem ersten Blick auch recht praktikabel. Im praktischen Einsatz zeigte sich jedoch der Einsatz einer Bitmap als sehr rechenintensiv. Müssen viele Grafikoperationen im Vollbildmodus durchgeführt werden, schnellt die Prozessorlast auf nicht mehr vertretbare Größen hoch. Dies scheint daran zu liegen, dass GDI+ offenbar nicht sonderlich effektiv auf Bitmaps zeichnen kann, wie eine kurze Suche im Internet mit Stichworten wie "gdi+ draw bitmap slow" zeigt.
Wir müssen also einen anderen Lösungsansatz wählen. Das Framework 2.0. stellt uns die dafür notwendigen Klassen auch schon bereit.
BufferedGraphicsContext und BufferedGraphics
Die Lösung zur Nachprogrammierung des AutoRedraw-Verhaltens liegt in den Klassen BufferedGraphicsContext und BufferedGraphics aus dem System.Drawing-Namespace. Mit ihnen kann ein benutzerdefinierter Grafikpuffer erstellt werden. In ihm kann man dann in aller Ruhe zeichnen. Der Inhalt wird bei erst Bedarf in das eigentliche Graphics-Objekt gerendert. Informationen zu beiden Klassen befinden sich im SDK im Kapitel Verwenden der doppelten Pufferung (online derzeit, d.h. im März 2007, unter
http://msdn2.microsoft.com/de-de/library/ms229622(vs.80).aspx).
Da die grafischen Vorgänge in einem Puffer ausgeführt werden, ist die Darstellung darüber hinaus flimmerfrei.
Das BufferedDraw-Control
Beim folgenden Codebeispiel habe ich ein Windows-Steuerelement mit dem Name BufferedDraw angelegt. Ich habe jedoch den Code aus der automatisch vom Visual Studio erzeugten Datei BufferedDraw.Designer.vb ausgeschnitten und in die eigentliche Arbeitsdatei BufferedDraw.vb eingefügt und anschließend die designergenerierte Datei gelöscht. Diesen Schritt müssen Sie nachvollziehen, wenn Sie den Code ausprobieren möchten.
Imports System.Drawing.Drawing2D Public Class BufferedDraw Inherits System.Windows.Forms.UserControl Public Sub New() InitializeComponent() Me.DoubleBuffered = True Me.CreateGraphicBuffer() End Sub ' Wird vom Windows Form-Designer benötigt. Private components As System.ComponentModel.IContainer <System.Diagnostics.DebuggerStepThrough()> _ Private Sub InitializeComponent() components = New System.ComponentModel.Container() Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font End Sub Protected Overrides Sub Dispose(ByVal disposing As Boolean) Try If disposing Then If components IsNot Nothing Then components.Dispose() End If If _buffGraphics IsNot Nothing Then _buffGraphics.Dispose() End If If _buffContext IsNot Nothing Then _buffContext.Dispose() End If End If Catch ex As Exception ' Wenn wir das Projekt Debuggen, sollen uns Fehler im Dispose ' auf jeden Fall erreichen. #If DEBUG Then MsgBox(ex.ToString) #End If Finally MyBase.Dispose(disposing) End Try End Sub Private _buffContext As BufferedGraphicsContext Protected ReadOnly Property BufferContext() As BufferedGraphicsContext Get Return _buffContext End Get End Property Private _buffGraphics As BufferedGraphics Protected ReadOnly Property BufferedGraphics() As BufferedGraphics Get Return _buffGraphics End Get End Property <System.ComponentModel.Browsable(False)> _ Public ReadOnly Property Graphics() As Graphics Get If _buffGraphics IsNot Nothing Then Return _buffGraphics.Graphics End If Return Nothing End Get End Property Private _lastSize As Size Protected Overrides Sub OnResize(ByVal e As System.EventArgs) ' Testen, ob wir auch in der Laufzeit sind If Me.DesignMode Then MyBase.OnResize(e) Return End If ' Testen, ob Form auf dem das ' Control ist gerade mimimiert ist If Me.Size.Width <= 0 Or Me.Size.Height <= 0 Then MyBase.OnResize(e) Return End If ' wir merken uns die Größe If Not _lastSize.Equals(Me.Size) Then _lastSize = Me.Size ' und erzeugen die Buffer-Objekte Me.CreateGraphicBuffer() End If ' und rufen die Basisklasse auf, ' damit der Resize-Event geworfen wird MyBase.OnResize(e) End Sub Protected Overridable Sub CreateGraphicBuffer() Try ' neuen BufferContext erzeugen Dim newCtx As BufferedGraphicsContext newCtx = New BufferedGraphicsContext ' Größe festlegen newCtx.MaximumBuffer = New Size( _ Me.DisplayRectangle.Width + 1, _ Me.DisplayRectangle.Height + 1) ' neue BufferedGraphics erzeugen Dim newBuff As BufferedGraphics newBuff = newCtx.Allocate(Me.CreateGraphics, _ Me.DisplayRectangle) ' Die Hintergrundfarbe zuweisen newBuff.Graphics.Clear(Me.BackColor) ' Todo: eine bestehende Hintergrundgrafik in ' Buffer blitten. ' Wenn schon ein Grafik-Puffer da ist -> ' in neuen Puffer rendern und freigeben If _buffGraphics IsNot Nothing Then ' Die alte Transformations-Matrix übertragen newBuff.Graphics.Transform = _ _buffGraphics.Graphics.Transform.Clone _buffGraphics.Render(newBuff.Graphics) _buffGraphics.Dispose() End If ' alten BufferedGraphicsContext ggf. freigeben If _buffContext IsNot Nothing Then _buffContext.Dispose() End If ' Context und Puffer an Member zuweisen _buffContext = newCtx _buffGraphics = newBuff Catch ex As Exception Debug.WriteLine(ex.TargetSite) Throw End Try End Sub Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) ' Debug.WriteLine("OnPaint") ' den Buffer nur Rendern, wenn auch tatächlich einer ' vorhanden ist. If Me.DesignMode OrElse Me.BufferedGraphics Is Nothing Then Debug.WriteLine("DesignMode od. kein Buffer vorhanden") MyBase.OnPaint(e) Return End If Try Me.BufferedGraphics.Render(e.Graphics) Catch ex As Exception Debug.WriteLine(ex.ToString) Throw Finally ' Zum Schluß sollten wir das Paint-Ereignis ' auslösen. Dies machen wir aber nicht mit ' MyBase.OnPaint(e), da dies sonst ' wieder über unseren gerenderten Inhalt ' zeichnen würde. RaisePaintEvent(Nothing, e) End Try End Sub End Class
Test des Controls
Platzieren Sie das Control nun auf einer Form und testen Sie es.
Private Sub btnTest_Click(ByVal sender As System.Object, ByVal e As EventArgs) _ Handles btnTest.Click If Not Me.BufferedDraw1.Graphics Is Nothing Then Me.BufferedDraw1.Graphics.DrawLine(Pens.Blue, 0, 0, 100, 100) Me.BufferedDraw1.Invalidate() ' oder Me.BufferedDraw1.Refresh() End If End Sub
Minimieren Sie die From und maximieren Sie sie anschließend wieder. Überdecken Sie das Control mit einem anderen Fenster und legen Sie da Control anschließend wieder frei. Wie Sie sehen, verhält das Control ganz wie in VB6, wenn AutoRedraw auf True gesetzt wurde. Der Unterschied besteht auf den ersten Blick darin, dass Sie hier im Anschluss an eine Grafikoperationen Control.Invalidate (bzw. Control.Refresh) aufrufen müssen, damit der Puffer gerendert und das Ergebnis des Zeichnens sichtbar wird.
Bei einem VB6-Control müssten Sie dies nicht machen, insofern Sie die unsäglichen VB6-eigenen Grafik-Funktionen wie Line oder Circle verwendeten. Würden Sie in VB6 allerdings API-Funktionen zum Zeichnen auf einem Control mit Auto-Redraw = True verwenden, so müssten Sie am Ende des Zeichnenvorgangs ebenfalls Control.Refresh aufrufen, damit das Ergebnis sichtbar wird. Insofern ist das Verhalten des BufferedDraw mit dem eines VB6-Control identisch.