Semitransparentes Panel

In einem Projekt kam ich auf die Idee, dass sich hier doch ein halbtransparentes Panel zur Auswahl von Items
recht nett machen würde. Da sich ein Panel nicht ohne weiteres semitransparent setzen lässt, entstand diese
kleine Klasse. Mit ein wenig cleverem Code lässt sich daraus auch ein vollständig flimmerfreies Auswahlwerkzeug
basteln.

Zunächst hat ein Panel erstmal keine Eigenschaft, wie z.B. eine Form, mit welcher sich die Durchlässigkeit setzen lässt.
Versucht man das Panel durch die Verwendung einiger Tricks semitransparent rendern zu lassen, ergibt sich das Problem,
dass dahinterliegende Controls nicht mehr gerendert werden.

Die Idee hinter diesem Beispiel ist es, den passenden Teil aus dem Hintergrundcontrol in Bildform auf das Panel zu rendern. Anschließend kann mit der Graphics-Klasse bequem ein halbtransparentes Overlay darüber gelegt werden.

Umsetzung [VB.net]

Zunächst erstelle ich eine neue Klasse und lasse diese von der Windows.Forms.Panel-Klasse erben. Darin erstelle ich eine Zusätzliche Property - "Opacity". Außerdem lege ich eine neue Variable ("imageCache") an. In dieser soll eine Kopie des "Parentcontrol-Bildes" abgelegt werden, um möglichst schnell darauf zugreifen zu können.

Public Class OverlayPanel
    Inherits Windows.Forms.Panel

    Public Property Opacity as Double
    Private imageCache As Drawing.Image = Nothing
End Class

Als nächstes überschreibe ich die die OnPaint-Methode der Basisklasse:

  Protected Overrides Sub OnPaint(e As PaintEventArgs)
    Me.PaintParentBackground(e)
    MyBase.OnPaint(e)
  End Sub

Bevor die Items etc. im Control gerendert werden rufe ich die Methode "drawBackground", welche es zu diesem Zeitpunkt noch nicht gibt, auf und übergebe die Eventargs. Diese enthalten eine Objektreferenz auf das zum zeichnen verwendete Graphics-Objekt.

In der drawBackground-Methode rendere ich nun den entsprechenden Hintegrund. Zunächst wird abgefragt, ob das Control "größer als 0" ist. Ist die Cache-Variable noch nicht gesetzt, blende ich das Panel azunächst aus, es soll ja nicht mitkopiert werden. Anschließend wird mithilfe der Control.DrawToBitmap()-Funktion ein Bild des Controls erzeugt. Danach wird das Panel wieder eingeblendet.
Im zweiten Teil wird nun der entsprechende Bereich des soeben geholten Bildes gerendert. Anschließend ein Rechteck mit der gewünschten Farbe und Transparenz darübergelegt.

Private Sub PaintParentBackground(ByVal e As PaintEventArgs)
  If Me.Width > 0 And Me.Height > 0 Then
    If IsNothing(imageCache) Then
      Me.Visible = False
      Me.imageCache = New Bitmap(Me.Parent.Width, Me.Parent.Height)
      Me.Parent.DrawToBitmap(Me.imageCache, New Rectangle(New Point(0, 0), Me.Parent.Size))
      Me.Visible = True
    End If

    Dim offsetX As Int32 = (Me.Parent.Width - Me.Parent.ClientSize.Width) / 2
    Dim offsetY As Int32 = Me.Parent.Height - Me.Parent.ClientSize.Height - offsetX
    Dim alpha As Byte = 255
    If Me._Opacity < 1 Then alpha = Me._Opacity * 255

    e.Graphics.DrawImage(Me.imageCache,
                         New Rectangle(New Point(0, 0), Me.Size),
                         New Rectangle(Me.Location + New Point(offsetX, offsetY), Me.Size),
                         GraphicsUnit.Pixel)

    e.Graphics.FillRectangle(New SolidBrush(Color.FromArgb(alpha, Me.BackColor)),
                             New Rectangle(New Point(0, 0), Me.Size))
  End If
End Sub

Dabei ist der entsprechende Offset der Positionsangabe für das eigene Control zu beachten! Beispielsweise wird bei einer Form beim Aufruf der DrawToBitmap()-Methode das komplette Control inklusive des Rahmens abgebildet. Die Positionsangabe bezieht sich jedoch auf die relative Position im inneren Bereich der Form. Folglich muss der passende Offset addiert werden.
Wenn man ein rahmenloses Control als Parent nutzt macht es natürlich Sinn die Offsetberechnung zu entfernen und so etwas Rechenzeit zu sparen!

Um das Ganze nicht zu sehr flimmern zu lassen können z.B. im Konstruktor noch folgende Optionen gesetzt werden:

Sub New()
  Me.SetStyle(ControlStyles.AllPaintingInWmPaint + ControlStyles.DoubleBuffer, True)
End Sub

Umsetzung [C#]

Der selbe Code, nur in C#:

using System;
using System.Drawing;
using System.Windows.Forms;
public class OverlayPanel : Panel
{
   
    public OverlayPanel()
    {
        this.SetStyle(ControlStyles.AllPaintingInWmPaint |
                      ControlStyles.OptimizedDoubleBuffer, true);
    }

    private double _opacity;
  
    public double Opacity
    {
        get { return this._opacity; }
        set {
            this._opacity = value;
            this.Refresh();
        }
    }

    private Bitmap imageCache = null;
    protected override void OnPaint(PaintEventArgs e)
    {
        this.PaintParentBackground(e);
        base.OnPaint(e)
     }

    private void PaintParentBackground(PaintEventArgs e)
    {

        if (this.Width > 0 && this.Height > 0)
        {
            if (imageCache == null)
            {
                this.Visible = false;
                this.imageCache = new Bitmap(this.Parent.Width, this.Parent.Height);
                this.Parent.DrawToBitmap(this.imageCache, new Rectangle(new Point(0, 0), 
                                         this.Parent.Size));
                this.Visible = true;
            }

            Int32 offsetX = (this.Parent.Width - this.Parent.ClientSize.Width) / 2;
            Int32 offsetY = this.Parent.Height - this.Parent.ClientSize.Height - offsetX;
            byte alpha = 255;
            if (this._opacity < 1) alpha = (byte) (this._opacity * 255);

            e.Graphics.DrawImage(this.imageCache, new Rectangle(new Point(0, 0), this.Size),
                                                  new Rectangle(new Point(offsetX + 
                                                  this.Location.X, offsetY + this.Location.Y),
                                                  this.Size), GraphicsUnit.Pixel);

            e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(alpha, this.BackColor)),
                                     new Rectangle(new Point(0, 0), this.Size));
        }
    }
}
2016 J. Weigelt - www.janelo.net