vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#

https://www.vbarchiv.net
Rubrik: Grafik und Font   |   VB-Versionen: VB.NET28.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 HaackBewertung:  Views:  17.602 
ohne HomepageSystem:  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.
 



Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Tipps & Tricks finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6
(einschl. Beispielprojekt!)

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
 
Copyright ©2000-2024 vb@rchiv Dieter OtterAlle Rechte vorbehalten.


Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.