Zeichnen eines Steuerelements (Windows Forms .NET)

Das benutzerdefinierte Zeichnen von Steuerelementen ist eine der vielen komplizierten Aufgaben, die durch Windows Forms vereinfacht werden kann. Wenn ein benutzerdefiniertes Steuerelement erstellt wird, gibt es viele Optionen, wie Sie die grafische Darstellung Ihres Steuerelements beeinflussen können. Wenn Sie ein benutzerdefiniertes Steuerelement erstellen, also ein Steuerelement, das von Control erbt, müssen Sie Code bereitstellen, um seine grafische Darstellung zu rendern.

Wichtig

Der Desktopleitfaden zu .NET 7 und .NET 6 ist in Bearbeitung.

Wenn Sie ein zusammengesetztes Steuerelement erstellen, also ein Steuerelement, das von UserControl oder einem der vorhandenen Windows Forms-Steuerelemente erbt, können Sie die grafische Standarddarstellung überschreiben und eigenen Grafikcode bereitstellen.

Wenn Sie benutzerdefiniertes Rendering für ein vorhandenes Steuerelement anwenden möchten, ohne ein neues Steuerelement zu erstellen, sind die verfügbaren Optionen eingeschränkter. Dennoch gibt es weiterhin ein große Bandbreite grafischer Möglichkeiten für Ihre Steuerelemente und Anwendungen.

Beim Rendern von Steuerelementen sind die folgenden Elemente beteiligt:

  • Die von der Basisklasse System.Windows.Forms.Control bereitgestellte Funktion zum Zeichnen
  • Die essentiellen Elemente der GDI-Grafikbibliothek
  • Die Geometrie der Zeichnungsregion
  • Die Prozedur für das Freigeben von Grafikressourcen

Vom Steuerelement bereitgestellte Funktionen zum Zeichnen

Die Basisklasse Control stellt Funktionen zum Zeichnen über das dazugehörige Paint-Ereignis bereit. Ein Steuerelement löst das Paint-Ereignis aus, sobald die dazugehörige Anzeige aktualisiert werden muss. Weitere Informationen zu Ereignissen in .NET finden Sie unter Behandeln und Auslösen von Ereignissen.

Die Ereignisdatenklasse für das Paint-Ereignis, PaintEventArgs, enthält die Daten, die für das Zeichnen eines Steuerelements erforderlich sind – ein Handle für ein Grafikobjekt und ein Rechteck, das die Region darstellt, in der gezeichnet wird.

public class PaintEventArgs : EventArgs, IDisposable
{

    public System.Drawing.Rectangle ClipRectangle {get;}
    public System.Drawing.Graphics Graphics {get;}

    // Other properties and methods.
}
Public Class PaintEventArgs
    Inherits EventArgs
    Implements IDisposable

    Public ReadOnly Property ClipRectangle As System.Drawing.Rectangle
    Public ReadOnly Property Graphics As System.Drawing.Graphics

    ' Other properties and methods.
End Class

Graphics ist eine verwaltete Klasse, die die Funktion zum Zeichnen kapselt. Weitere Informationen finden Sie in der GDI-Diskussion später in diesem Artikel. Bei ClipRectangle handelt es sich um eine Instanz der Rectangle-Struktur. Hierdurch wird der verfügbare Bereich definiert, in dem ein Steuerelement zeichnen kann. Der Entwickler eines Steuerelements kann ClipRectangle mit der ClipRectangle-Eigenschaft eines Steuerelements berechnen. Weitere Informationen finde Sie in der Geometriediskussion später in diesem Artikel.

OnPaint

Ein Steuerelement muss Renderinglogik bereitstellen, indem die OnPaint-Methode überschrieben wird, die es von Control erbt. OnPaint erhält über die übergebenen Eigenschaften Graphics und ClipRectangle der Instanz PaintEventArgs Zugriff auf ein Grafikobjekt und ein Rechteck, in dem gezeichnet werden kann.

Im folgenden Code wird der System.Drawing-Namespace verwendet:

protected override void OnPaint(PaintEventArgs e)
{
    // Call the OnPaint method of the base class.
    base.OnPaint(e);

    // Declare and instantiate a new pen that will be disposed of at the end of the method.
    using var myPen = new Pen(Color.Aqua);

    // Create a rectangle that represents the size of the control, minus 1 pixel.
    var area = new Rectangle(new Point(0, 0), new Size(this.Size.Width - 1, this.Size.Height - 1));

    // Draw an aqua rectangle in the rectangle represented by the control.
    e.Graphics.DrawRectangle(myPen, area);
}
Protected Overrides Sub OnPaint(e As PaintEventArgs)
    MyBase.OnPaint(e)

    ' Declare and instantiate a drawing pen.
    Using myPen = New System.Drawing.Pen(Color.Aqua)

        ' Create a rectangle that represents the size of the control, minus 1 pixel.
        Dim area = New Rectangle(New Point(0, 0), New Size(Me.Size.Width - 1, Me.Size.Height - 1))

        ' Draw an aqua rectangle in the rectangle represented by the control.
        e.Graphics.DrawRectangle(myPen, area)

    End Using
End Sub

Die OnPaint-Methode der Control-Basisklasse implementiert keine Zeichnungsfunktionen, ruft jedoch die Ereignisdelegaten auf, die für das Paint-Ereignis registriert sind. Wenn Sie OnPaint überschreiben, sollten Sie in der Regel die OnPaint-Methode der Basisklasse aufrufen, damit registrierte Delegaten das Paint-Ereignis empfangen. Steuerelemente, deren gesamte Oberfläche gezeichnet wird, sollten nicht die OnPaint-Methode der Basisklasse aufrufen, da dies zu Flimmern führen kann.

Hinweis

Rufen Sie OnPaint nicht direkt in Ihrem Steuerelement auf. Rufen Sie stattdessen die Invalidate-Methode (von Control geerbt) oder eine andere Methode auf, die Invalidate aufruft. Die Invalidate-Methode wiederum ruft OnPaint auf. Die Invalidate-Methode wird überladen und zeichnet je nach für Invalidatee bereitgestellten Argumenten entweder Teile oder den gesamten Bildschirmbereich neu.

Der Code in der OnPaint-Methode Ihres Steuerelements wird ausgeführt, wenn das Steuerelement erstmalig gezeichnet wird, und bei jeder Aktualisierung. Damit Ihr Steuerelement bei jeder Größenanpassung neu gezeichnet wird, fügen Sie die folgende Zeile im Konstruktor Ihres Steuerelements hinzu:

SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.ResizeRedraw, True)

OnPaintBackground

Die Control-Basisklasse definiert eine weitere Methode, die hilfreich für das Zeichnen ist: die OnPaintBackground-Methode.

protected virtual void OnPaintBackground(PaintEventArgs e);
Protected Overridable Sub OnPaintBackground(e As PaintEventArgs)

OnPaintBackground zeichnet den Hintergrund (und so auch die Form) des Fensters. Dies erfolgt sehr schnell. OnPaint hingegen zeichnet die Details und ist deshalb möglicherweise langsamer, da einzelne Zeichnungsanforderungen in ein Paint-Ereignis kombiniert werden, das alle Bereiche abdeckt, die neu gezeichnet werden müssen. Sie sollten OnPaintBackground aufrufen, wenn Sie beispielsweise ein Hintergrund mit Farbverlauf für Ihr Steuerelement zeichnen möchten.

Während OnPaintBackground eine ereignisähnliche Bezeichnung aufweist und dasselbe Argument wie die OnPaint-Methode annimmt, handelt es sich bei OnPaintBackground um keine tatsächliche Ereignismethode. Es gibt kein PaintBackground-Ereignis, und OnPaintBackground ruft keine Ereignisdelegaten auf. Wenn die OnPaintBackground-Methode überschrieben wird, ist keine abgeleitete Klasse erforderlich, um die OnPaintBackground-Methode der dazugehörigen Basisklasse aufzurufen.

GDI+-Grundlagen

Die Graphics-Klasse bietet Methoden für das Zeichnen verschiedener Formen wie Kreise, Dreiecke, Bögen und Ellipsen sowie Methoden für das Anzeigen von Text. Der System.Drawing-Namespace enthält Namespaces und Klassen, die grafische Elemente wie Formen (z. B. Kreise, Rechtecke und Bögen), Farben, Schriftarten und Pinsel kapseln.

Geometrie der Zeichnungsregion

Die ClientRectangle-Eigenschaft eines Steuerelements gibt die rechteckige Region an, die für das Steuerelement auf der Anzeige für den Benutzer verfügbar ist. Die ClipRectangle-Eigenschaft von PaintEventArgs gibt dahingegen den Bereich an, der gezeichnet wird. Ein Steuerelement muss möglicherweise nur einen Teil des verfügbaren Bereichs zeichnen, z. B. wenn ein kleiner Bereich der Anzeige des Steuerelements geändert wird. In solchen Situationen muss der Entwickler eines Steuerelements das tatsächliche Rechteck berechnen, in dem gezeichnet werden soll, und dieses an Invalidate übergeben. Die überladenen Versionen von Invalidate, die Rectangle oder Region als Argument akzeptieren, verwenden dieses Argument, um die ClipRectangle-Eigenschaft von PaintEventArgs zu generieren.

Freigeben von Grafikressourcen

Grafikobjekte sind teuer, da sie Systemressourcen verwenden. Zu solchen Objekten gehören Instanzen der System.Drawing.Graphics-Klasse und Instanzen von System.Drawing.Brush, System.Drawing.Pen sowie weitere Grafikklassen. Es ist wichtig, dass Sie nur eine Grafikressource zeichnen, wenn Sie eine benötigen, und dass Sie sie freigeben, sobald Sie die Verwendung beendet haben. Wenn Sie eine Instanz eines Typs erstellen, der die IDisposable-Schnittstelle implementiert, rufen Sie die dazugehörige Dispose-Methode auf, sobald Sie das Freigeben der Ressourcen abgeschlossen haben.

Siehe auch