Motivation

Das PictureBox-Steuerelement, genauer das bei dessen Paint-Ereignis oder in der Methode OnPaint() übergebene Graphics-Objekt (Feld im Parameter PaintEventArgs), ist über geometrische Transformationen (TranslateTransform() und weitere, Matrix) in der Lage eine Skalierung vorzunehmen. Hierbei werden jedoch auch die Breite das Zeichen-Objekt Pen bei Vergrößerungen (ScaleTransform()) mit transformiert. Wenn man eine echte Vergrößerung der Grafik möchte, ist dies das korrekte Verhalten. Hat man aber z.B. eine Messwert-Kurve, möchte man zwar bei einer Vergrößerung die X- und Y-Achse strecken, aber nicht die Liniendicke der Messkurve. Gleiches gilt für Markierungen (z.B. kleine Quadrate, die man an den Linien anbringt. Und zuletzt vergesse ich immer wieder, wie das mit der Skalierung funktioniert ...

Das Steuerelement UrsScaledPictureBox  schafft Abhilfe, wie die folgenden Grafik zeigt:

Unterschiedliche Grafikausgabe   Die Box hat einen Vergrößerungsfaktor von 50. Beide Linien sind mit einem Pen der Dicke 3 gezeichnet. Bei der grünen Linie wurde Graphics.ScaleTransform() verwandt und anschließend mit Graphics.DrawLine() gezeichnet. Sie wurde mit vergrößert.

Die rote Linie wurde mit UrsScaledPictureBox .Drawline() gezeichnet und hat die Liniendicke behalten.

Ein anderes Beispiel zeigt die folgende Bilderfolge. Die Achsen wurden skaliert ohne die Strichdicke oder die Größe der Markierungen zu ändern.

   

Download

Verwendung

Im Wesentlichen handelt es sich bei der UrsScaledPictureBox  um eine von PictureBox abgeleitete und erweiterte Klasse. Zur Steuerung der Skalierung wurden folgende Eigenschaften hinzugefügt:

Eigenschaft Funktion Anmerkung
Origin 
As PointF
Ruft den Ursprung des skalierten Koordinatensystems ab oder legt ihn fest. Änderungen lösen das Ereignis ScaleChanged aus.
ScaledSize 
As SizeF
Ruft die skalierte Größe der UrsScaledPictureBox  ab oder legt sie fest. Änderungen lösen das Ereignis ScaleChanged aus.
ScaledRight 
As Single
Ruft die X-Koordinate des rechten oberen Eckpunkts ab. Es handelt sich um die skalierte Koordinate.
ScaledTop
As Single
Ruft die Y-Koordinate des rechten oberen Eckpunkts ab. Es handelt sich um die skalierte Koordinate.
TransformMatrix
As Drawing2D.Matrix
Ruft eine Kopie der geometrischen globalen Transformationsmatrix ab, die für die Skalierung bei den in dieser Klasse definierten Zeichenfunktionen genutzt wird. ReadOnly
DrawCoordinates 
As Boolean
Ruf einen Wert ab, der angibt, ob im Design-Modus die Koordinaten der Eckpunkte eingeblendet werden, oder legt ihn fest. True, wenn die Koordinaten eingeblendet werden sollen, ansonsten False.
ScalingMode as ScalingModes Legt das Verhalten bei Größenänderungen fest. Zoom: Die skalierte Größe bleibt unverändert. Die Zeichnung wird vergrößert oder verkleinert.
Extend: Die skalierte Größe ändert sich proportional zur Größe der Box. Die Zeichnung behält ihre Größe. Die Wertebereich der Achsen der Box vergrößert bzw. verkleinert sich.

 

Die Funktion von DrawCoordinates:
(X und Y sind unterschiedlich skaliert)
  Die Funktion von DrawCoordinates.

Folgendes Ereignis wurde hinzugefügt:

Ereignis Funktion
ScaleChanged
As EventHandler
Wird ausgelöst, wenn sich der Wert der ScaledSize- oder der Origin-Eigenschaft ändert.

Die implementierten Zeichenmethoden, jeweils analog zu den entsprechenden Methoden der Graphics-Klasse, sind:

Methode Wirkung Anmerkung
DrawLine Zeichnet eine verbindende Linie zwischen zwei Point-Strukturen. Beide Punkte werden transformiert.
DrawLines Zeichnet eine Reihe von Liniensegmenten, die ein Array von Point-Strukturen verbinden. Sämtliche Punkte werden transformiert.
DrawCurve Zeichnet eine Cardinal-Splinekurve durch ein angegebenes Array von Point-Strukturen. Sämtliche Punkte werden transformiert.
DrawRectangle Zeichnet ein Rechteck, das durch ein Koordinatenpaar, eine Breiten- und eine Höhenangabe angegeben ist. Der Ursprung wird transformiert, Breite und Höhe bleiben erhalten.
FillRectangle Füllt das Innere eines Rechtecks, das durch ein Koordinatenpaar, eine Höhen- und eine Breitenangabe angegeben ist.
Der Ursprung wird transformiert, Breite und Höhe bleiben erhalten.
DrawEllipse Zeichnet eine Ellipse, die durch ein umschließendes Rechteck definiert ist, das durch Koordinaten für die obere linke Ecke des Rechtecks, eine Höhen- und eine Breitenangabe angegeben ist. Der Ursprung wird transformiert, Breite und Höhe bleiben erhalten.
FillEllipse Füllt das Innere einer Ellipse, die durch ein umschließendes Rechteck definiert ist, das durch ein Koordinatenpaar, eine Höhen- und eine Breitenangabe angegeben ist. Der Ursprung wird transformiert, Breite und Höhe bleiben erhalten.

Weitere Methoden lassen sich relativ einfach ergänzen (siehe unten).

Detail zur Programmierung

Zur Implementierung der beschrieben Funktionen überschreibt UrsScaledPictureBox  die OnPaint-Methode der Basis-Klasse PictureBox. Dort wird

 - Code verbergen | + Code anzeigen 

''' <summary>
''' Löst das Paint-Ereignis aus.
''' </summary>
''' <param name="pe">Ein PaintEventArgs, das die Ereignisdaten enthält. </param>
''' <seealso cref="PictureBox.OnPaint(PaintEventArgs)"/>
Protected Overrides Sub OnPaint(pe As PaintEventArgs)
   If Not DesignMode Then 'macht problem im Designmode
      ComputeTransformMatrix()
   End If
   mGraphics = pe.Graphics

   MyBase.OnPaint(pe)

   pe.Graphics.ResetTransform()

   ' Koordinaten im Design-Modus einzeichnen
   If DesignMode AndAlso DrawCoordinates Then
      mGraphics.DrawString(Origin.ToString, SystemFonts.DialogFont, Brushes.Brown, 5, Height - 20)

      Dim s As String = "{Width=" & mScaledSize.Width & "}"
      Dim sz = mGraphics.MeasureString(s.ToString, SystemFonts.DialogFont)
      mGraphics.DrawString(s, SystemFonts.DialogFont, Brushes.Brown, Width - sz.Width - 5, Height - sz.Height - 5)

      s = "{Height=" & mScaledSize.Height & "}"
      sz = mGraphics.MeasureString(s.ToString, SystemFonts.DialogFont)
      mGraphics.DrawString(s, SystemFonts.DialogFont, Brushes.Brown, 5, 5)

      s = "{X=" & mScaledSize.Width + mOrigin.X & ", Y=" & mScaledSize.Height + mOrigin.Y & "}"
      sz = mGraphics.MeasureString(s.ToString, SystemFonts.DialogFont)
      mGraphics.DrawString(s, SystemFonts.DialogFont, Brushes.Brown, Width - sz.Width - 5, 5)
   End If
End Sub

Die Berechnung der Transformationsmatrix ist nicht besonders schwierig. Zu beachten ist allerdings, dass sich die Nettogröße des Zeichenbereichs (ClientRectangle) zur Skalierung genutzt werden muss.

 - Code verbergen | + Code anzeigen 

''' <summary>
''' Berechnet die Tranformationsmatrix
''' </summary>
Private Sub ComputeTransformMatrix()
   With ClientRectangle

      Dim mx = .Width / mScaledSize.Width ' Skalierung X
      Dim my = -.Height / mScaledSize.Height ' Skalierung Y

      mTransformMatrix = New Drawing2D.Matrix
      mTransformMatrix.Translate(-mOrigin.X, -mOrigin.Y) ' Skalierte linke untere Ecke auf skaliert (0 | 0)
      mTransformMatrix.Scale(mx, my, Drawing2D.MatrixOrder.Append) ' Skalierung auf Control-Größe
      mTransformMatrix.Translate(0, .Height - 1, Drawing2D.MatrixOrder.Append) ' Bezugspunkt ist links unten 
                                                                               '(anstatt links oben)
   End With
End Sub

Die Zeichen-Methoden erfolgen alle nach dem gleichen Muster:

Zu beachten ist, dass Arrays als Referenz übergeben werden. Hier muss die Transformation an einer Kopie des Arrays ausgeführt werden (Clone-Methode).

 - Code verbergen | + Code anzeigen 

''' <summary>
''' Zeichnet eine Reihe von Liniensegmenten, die ein Array von PointF-Strukturen verbinden.
''' </summary>
''' <param name="Pen">Pen, der die Farbe, Breite und den Stil der Liniensegmente bestimmt.</param>
''' <param name="points">Array von PointF-Strukturen, die die zu verbindenden Punkte darstellen.</param>
''' <seealso cref="Graphics.DrawLines(Pen, PointF())"/>
Public Sub DrawLines(Pen As Pen, points As PointF())
   Dim tmpMatrix = mGraphics.Transform
   mGraphics.ResetTransform()
   Dim pts = points.Clone
   mTransformMatrix.TransformPoints(pts)
   mGraphics.DrawLines(Pen, pts)
   mGraphics.Transform = tmpMatrix
End Sub