So, hiermit veröffentliche ich mein "großes GDI+ Tutorial für VB.NET" und hoffe, dass der ein oder andere so einen leichten Einstieg in dieses Gebiet bekommt. Im Forum tauchen ja immer häufiger Fragen und Kritik zu gemachten Schritten in diesem Themenbereich auf. Ich bemühe mich so allgemein zu bleiben, dass das Verständnis erhalten bleibt aber trotzdem mehr als nur die Oberfläche angekratzt wird^^ Viel Spaß beim Lesen und Lernen, wer Fehler findet oder weiß wie etwas besser geht bitte melden - auch ich kann nicht alles wissen.
1: Was ist GDI+?
GDI+ ist die Abkürzung für "Graphics Device Interface" und stellt System von Windows zum Arbeiten und Zeichnen von Grafiken dar. Es übernimmt Aufgaben wie das Zeichnen von Linien, Darstellen von Texten und Grafiken, etc. In VB.NET greift man auf das "Graphics-Objekt" zurück um zeichnen zu können.
2: Woher bekommt man das Graphics-Object?
Um das Graphics-Object zu bekommen gibt es mehrere Möglichkeiten die je nach Situation zum Einsatz kommen:
Das Graphics-Object im Paint-Event
Wenn man auf Controls Zeichnen möchte (z.b. einen Panel oder Form1) sollte man das im Paint-Event tun. Dinge die mit GDI gezeichnet wurden "verschwinden" wenn z.b. ein anderes Fenster über die Zeichnung geschoben wird. Würde man das zeichnen irgendwo (siehe createGraphics) durchführen wären Grafikfehler die Folge. Im Paint-Event hingegen wird ein zerstörter Bereich (ein Bereich der für ungültig erklärt wurde) automatisch neu gezeichnet, richtig gesagt: das Paint-Event wird immer aufgerufen wenn die grafische Oberfläche bzw ein Teil von ihr für ungültig erklärt wurde. Um zu verdeutlichen wie Leistungsstark GDI+ ist: Die komplette Benutzeroberfläche von VB.NET Programmen wird mittels GDI+ gezeichnet - so lässt sich nicht nur auf die Controls zeichnen sonder man kann die Controls selbst zeichnen. Dies geschieht (meist) im Paint-Event und ist eine andere Baustelle^^
So wird auf das Graphics-Object zugegriffen:
Das Grapics-Object einer Bitmap
Eine weitere häufig genutze Möglichkeit ist das erstellen eines Graphics-Objektes einer Bitmap. Dies tut man wenn man z.B. einen Text (Name des Fotografen) auf eine Bilddatei schreiben will. Vorteil ist, dass man das Ergebnis abspeichern (JPG, BMP, PNG, etc) kann. Im Paint-Event ist das Bild hingegen nur zur Programmlaufzeit da. Bevor einige schlaue Leute auf die Idee kommen einfach alles auf eine Bitmap zu zeichnen und diese dann als Hintergrundbild einer Form zu setzten um das Paint-Event zu umgehen, sei gesagt, dass dies nicht Sinn der Sache ist^^ Das Zeichnen auf eine Grafik kann überall geschehen - es wird also kein Paint-Event etc gebraucht!
Weitere Möglichkeiten
Es gibt auch noch andere Möglichkeiten an ein Graphics-Objekt zu kommen. Hier nenne ich mal das mehr oder weniger verhasste createGraphics (z.B. form1.createGraphics). Dieses ist eher unbeliebt, da viele Anfänger den Fehler machen dieses zu nutzen anstatt das Paint-Event. Der Nachteil ist, dass mit createGraphics nur genau einmal gezeichnet wird. Wird der Bereich ungültig verschwindet das Gezeichnete teilweise oder komplett. Ebenfalls gibt es Graphics.FromHwnd oder Graphics.FromHdc. Wie diese genau funktionieren weiß ich nicht genau und ich bin der Ansicht, dass dies auch zu Tief in die Materie geht.
3: Einfache Zeichenfunktionen
Ich denke jeder fängt mit einer einfachen Linie an und arbeitet sich dann nach und nach hoch. Da die Überladungen der ZeichnenFunktionen eigentlich selbsterklärend sind will ich nur ein paar Beispiele aufzeigen und erst im nächsten Unterpunkt erklären womit (sprich mit Pinsel oder Stift) gezeichnet wird.
Linien zeichnen ist ganz einfach: Man braucht zwei Punkte und einen Stift (bzw man benutzt die von VB vorgegebenen). Einfach den folgenden Code ins Paint-Event und schon ist die Linie da:
In diesem Beispiel wird eine Ellipse ausgefüllt (das geht nicht mit einem Stift sonder mit einem Pinsel => Brush), darüber werden die Umrisse der Ellipse gezeichnet (Linien werden mit einem Stift => Pen gezogen).
Ebenfalls lässt sich Text recht einfach Zeichnen:
Man kann schnell weitere Funktionen zum Zeichnen einfacher Formen (Rectangle für Rechtecke, Elipse für Kreise oder Elipsen, etc) finden. IntelliSense hilft dabei ungemein und zeigt auch gleich welche Daten benötigt werden (BSP Rechteck: Position X und Y sowie Breite und Höhe). Man wird feststellen, dass die Zeichenfunktionen welche mit "Draw" beginnen (DrawLine, DrawEllipse) immer eine Figur "umranden" und einen Pen benutzen. Hingegen benutzen die "Fill"-Funktionen einen Brush und füllen eine Figur aus.
4: Unsere Zeichenwerkzeuge: Pen und Brush
Brush:
Der Brush (Pinsel) wird genutz um Grafikbereiche oder Formen auszufüllen. Im einfachsten Falle mit einer Farbe. Dazu deklarieren wir unseren Pinsel so:
SolidBrush
mit diesem könnten wir nun irgendetwas Rot ausmalen (der Code gehört ins Paint-Event!):
Das Ganze ist recht unspektakulär, wir bauen allerdings darauf auf. Es gibt nämlich mehr als nur den langweiligen einfarben-Pinsel:
TextureBrush
Schon etwas cooler ist es eine Grafik (jpg-datei, etc) als Textur zu benutzen. An dieser Stelle sei gesagt, dass das laden der Bitmap aus einer Datei AUßERHALB der Paint-Routine geschehen sollte, da dies unperformant ist und nur einmal geschehen sollte - und dann eben nicht in einer recht Zeitkritischen Routine^^ Mit dem TextureBrush kann genau wie mit dem SolidBrush gearbeitet werden, so deklariert man ihn:
HatchBrush
Dieser Brush ist ähnlich dem TextureBrush, nur lässt er ausschließlich vordefinierte Muster zu - lediglich die Farben können frei gewählt werden. Dieser eignet sich besonders um z.B. Schrafuren darzustellen. Wie immer ist auch dieser zu benutzen wie der ganz normale SolidBrush:
LinearGradientBrush
Um einen Farbverlauf darzustellen greift man am besten auf diesen Pinsel zu. Er fordert zwei Punkte und zwei Farben. Füllt man mit ihm eine Fläche aus herrscht bei Punkt1 die Farbe1. Diese verändert sich je näher man Punkt2 kommt immer mehr bis sie schließlich der Farbe2 entspricht. Dieses Muster wiederholt sich! Am besten ausprobieren denn dabei lernt man am besten! Zur deklaration:
PathGradientBrush
Mit diesem Brush lassen sich auch komplexere Farbübergänge realisieren. Ich denke mal, dass dies ebenso ein Thema für sich werden kann und den Rahmen dieses Tutorials sprengt
Pen:
Kommen wir nun zum Pen. Dieser ist wie ein richtiger Stift - er zeichnet also Linien mit einer bestimmten Dicke. Die Dicke des Pens kann immer verändert werden - ebenso die Farbe mit der er zeichnet. Auch der Pen kann mehr als nur langweilige einfarbige Linien fabrizieren: Er verfügt über eine Eigenschaft "Brush" und damit stehen ihm zum zeichnen der Linien die Möglichkeiten der Pinsel offen. Diese sind dann allerdings auf die Dicke der Linie begrenzt - die Figur wird nicht ausgefüllt sondern nur die Umrandung.
So deklariert man einen Schwarzen Stift mit einer Linienstärke von 4. Ohne Transformation bedeutet das, dass der Stift 4 Pixel dicke Linien zeichnet.
Sieht man sich die Überladungen des Subs New an sieht man, dass er auch einen Brush- und einen Widh (LinienStärke)-Wert akzeptiert. Es ist also möglich z.B. Linien mit einer Textur zu zeichnen:
Der Pen kann noch mehr, so lassen sich beispielsweise die Linienenden oder Anfänge manipulieren - Pfeile darzustellen ist so ein Kinderspiel! Weitere Informationen über das Zeichnen mit Pens erhält man in einem anderen Tutorial von mir: Tutorial Pen & Linien in GDI+
5: Transformation und Manipulation
Oft sieht man, dass ganze Codeblöcke von Berechnungen geschrieben werden um eine Figur rotiert oder verschoben darzustellen. Jeder der sich das zumutet weiß offensichtlich nicht, dass das Graphics-Objekt ihm diese Mühen abnehmen kann und da es die Berechnungen sowieso durchführt vermutlich auch noch schneller ist! Ebenso verunstaltet man den eigenen Code beim nutzen den Transform-Eigenschaft nicht und hält ihn übersichtlich.
Häufig will man auch nur in einem bestimmten bereich Zeichnen, die anderen sollen, egal was man tut, unberührt bleiben. Mit SetClip kann man einen Bereich/Region festlegen in dem gezeichnet werden darf. Alles was über diesen hinausgeht wird ignoriert.
6: Einstellungen zur Qualitäts- oder Geschwindigkeitsverbesserung
Das Graphics-Object beinhaltet viele Eigenschaften um die Renderqualität zu beeinflussen. Ich liste hier mal die besagten Eigenschaften auf. Eine Ausreichende Erklärung liefert IntelliSense. Ich sage an dieser stelle nur, dass man mit diesen Werten spielen muss! Oft sieht "hightQuality" nicht am besten aus - vor allem bei der Textdarstellung.
7: Erweiterte Möglichkeiten/Nützliches
Auch das bisher gezeigte ist nur die Spitze des Eisberges! Jeder Brush beinhaltet eine Vielzahl von weiteren Eigenschaften und Funktionen. Das stöbern mittels Objektbrowser oder IntelliSense lohnt sich immer. Trotzdem werde ich nun ein paar weitere nützliche Funtionen von GDI+ zeigen.
GraphicsPath
Viele Effekte lassen sich mit dem GraphicsPath realisieren. Der GraphicsPath fasst dabei mehre Figuren oder auch Buchstaben zusammen. Das Einheitliche ausfüllen z.B. mit einem Farbverlauf oder Textur sind dann möglich. Ebenso lassen sich die Umrisse von Text nachzeichnen, was sonst nur sehr schwer möglich ist.
Was der GraphicsPath noch so kann
MeasureString
Das GraphicsObjekt bietet eine Funktion um zu "messen" welche Maße ein gerenderter Text später einnehmen würde. Nützlich ist dies z.B. wenn man eine Funktion schreiben möchte die die Schriftgröße automatisch an einen Rahmen etc anpasst.
Bitmap-Funktionen nicht vergessen!
Eine Funktion die ich häufiger benutze ist die .makeTransparent Funktion der Bitmap.
Colors können mehr
So verfügen die Colors neben dem RGB-Kanälen auch über den Alpha-Kanal. Dieser regelt die Transparenz der Farbe. Mittels Color.FromARGB(A,R,G,B) kann eine beliebige Farbe erstellt werden. Statt A = 255 einfach mal A = 100 setzten und schon hat man eine Transparente Farbe. Ideal um Bereiche zu Heighleighten!
Links:
Für Controlbauer: Beispielprojekt Listbox erweitern
GDI Tutorial: Tutorial Pen & Linien in GDI+
Für Fortgeschrittene: Control mit beweglicher Figur und Gezieltes OwnerDrawing
1: Was ist GDI+?
GDI+ ist die Abkürzung für "Graphics Device Interface" und stellt System von Windows zum Arbeiten und Zeichnen von Grafiken dar. Es übernimmt Aufgaben wie das Zeichnen von Linien, Darstellen von Texten und Grafiken, etc. In VB.NET greift man auf das "Graphics-Objekt" zurück um zeichnen zu können.
2: Woher bekommt man das Graphics-Object?
Um das Graphics-Object zu bekommen gibt es mehrere Möglichkeiten die je nach Situation zum Einsatz kommen:
Das Graphics-Object im Paint-Event
Wenn man auf Controls Zeichnen möchte (z.b. einen Panel oder Form1) sollte man das im Paint-Event tun. Dinge die mit GDI gezeichnet wurden "verschwinden" wenn z.b. ein anderes Fenster über die Zeichnung geschoben wird. Würde man das zeichnen irgendwo (siehe createGraphics) durchführen wären Grafikfehler die Folge. Im Paint-Event hingegen wird ein zerstörter Bereich (ein Bereich der für ungültig erklärt wurde) automatisch neu gezeichnet, richtig gesagt: das Paint-Event wird immer aufgerufen wenn die grafische Oberfläche bzw ein Teil von ihr für ungültig erklärt wurde. Um zu verdeutlichen wie Leistungsstark GDI+ ist: Die komplette Benutzeroberfläche von VB.NET Programmen wird mittels GDI+ gezeichnet - so lässt sich nicht nur auf die Controls zeichnen sonder man kann die Controls selbst zeichnen. Dies geschieht (meist) im Paint-Event und ist eine andere Baustelle^^
So wird auf das Graphics-Object zugegriffen:
Das Grapics-Object einer Bitmap
Eine weitere häufig genutze Möglichkeit ist das erstellen eines Graphics-Objektes einer Bitmap. Dies tut man wenn man z.B. einen Text (Name des Fotografen) auf eine Bilddatei schreiben will. Vorteil ist, dass man das Ergebnis abspeichern (JPG, BMP, PNG, etc) kann. Im Paint-Event ist das Bild hingegen nur zur Programmlaufzeit da. Bevor einige schlaue Leute auf die Idee kommen einfach alles auf eine Bitmap zu zeichnen und diese dann als Hintergrundbild einer Form zu setzten um das Paint-Event zu umgehen, sei gesagt, dass dies nicht Sinn der Sache ist^^ Das Zeichnen auf eine Grafik kann überall geschehen - es wird also kein Paint-Event etc gebraucht!
Weitere Möglichkeiten
Es gibt auch noch andere Möglichkeiten an ein Graphics-Objekt zu kommen. Hier nenne ich mal das mehr oder weniger verhasste createGraphics (z.B. form1.createGraphics). Dieses ist eher unbeliebt, da viele Anfänger den Fehler machen dieses zu nutzen anstatt das Paint-Event. Der Nachteil ist, dass mit createGraphics nur genau einmal gezeichnet wird. Wird der Bereich ungültig verschwindet das Gezeichnete teilweise oder komplett. Ebenfalls gibt es Graphics.FromHwnd oder Graphics.FromHdc. Wie diese genau funktionieren weiß ich nicht genau und ich bin der Ansicht, dass dies auch zu Tief in die Materie geht.
3: Einfache Zeichenfunktionen
Ich denke jeder fängt mit einer einfachen Linie an und arbeitet sich dann nach und nach hoch. Da die Überladungen der ZeichnenFunktionen eigentlich selbsterklärend sind will ich nur ein paar Beispiele aufzeigen und erst im nächsten Unterpunkt erklären womit (sprich mit Pinsel oder Stift) gezeichnet wird.
Linien zeichnen ist ganz einfach: Man braucht zwei Punkte und einen Stift (bzw man benutzt die von VB vorgegebenen). Einfach den folgenden Code ins Paint-Event und schon ist die Linie da:
In diesem Beispiel wird eine Ellipse ausgefüllt (das geht nicht mit einem Stift sonder mit einem Pinsel => Brush), darüber werden die Umrisse der Ellipse gezeichnet (Linien werden mit einem Stift => Pen gezogen).
Ebenfalls lässt sich Text recht einfach Zeichnen:
Man kann schnell weitere Funktionen zum Zeichnen einfacher Formen (Rectangle für Rechtecke, Elipse für Kreise oder Elipsen, etc) finden. IntelliSense hilft dabei ungemein und zeigt auch gleich welche Daten benötigt werden (BSP Rechteck: Position X und Y sowie Breite und Höhe). Man wird feststellen, dass die Zeichenfunktionen welche mit "Draw" beginnen (DrawLine, DrawEllipse) immer eine Figur "umranden" und einen Pen benutzen. Hingegen benutzen die "Fill"-Funktionen einen Brush und füllen eine Figur aus.
4: Unsere Zeichenwerkzeuge: Pen und Brush
Brush:
Der Brush (Pinsel) wird genutz um Grafikbereiche oder Formen auszufüllen. Im einfachsten Falle mit einer Farbe. Dazu deklarieren wir unseren Pinsel so:
SolidBrush
mit diesem könnten wir nun irgendetwas Rot ausmalen (der Code gehört ins Paint-Event!):
Das Ganze ist recht unspektakulär, wir bauen allerdings darauf auf. Es gibt nämlich mehr als nur den langweiligen einfarben-Pinsel:
TextureBrush
Schon etwas cooler ist es eine Grafik (jpg-datei, etc) als Textur zu benutzen. An dieser Stelle sei gesagt, dass das laden der Bitmap aus einer Datei AUßERHALB der Paint-Routine geschehen sollte, da dies unperformant ist und nur einmal geschehen sollte - und dann eben nicht in einer recht Zeitkritischen Routine^^ Mit dem TextureBrush kann genau wie mit dem SolidBrush gearbeitet werden, so deklariert man ihn:
HatchBrush
Dieser Brush ist ähnlich dem TextureBrush, nur lässt er ausschließlich vordefinierte Muster zu - lediglich die Farben können frei gewählt werden. Dieser eignet sich besonders um z.B. Schrafuren darzustellen. Wie immer ist auch dieser zu benutzen wie der ganz normale SolidBrush:
LinearGradientBrush
Um einen Farbverlauf darzustellen greift man am besten auf diesen Pinsel zu. Er fordert zwei Punkte und zwei Farben. Füllt man mit ihm eine Fläche aus herrscht bei Punkt1 die Farbe1. Diese verändert sich je näher man Punkt2 kommt immer mehr bis sie schließlich der Farbe2 entspricht. Dieses Muster wiederholt sich! Am besten ausprobieren denn dabei lernt man am besten! Zur deklaration:
PathGradientBrush
Mit diesem Brush lassen sich auch komplexere Farbübergänge realisieren. Ich denke mal, dass dies ebenso ein Thema für sich werden kann und den Rahmen dieses Tutorials sprengt
Pen:
Kommen wir nun zum Pen. Dieser ist wie ein richtiger Stift - er zeichnet also Linien mit einer bestimmten Dicke. Die Dicke des Pens kann immer verändert werden - ebenso die Farbe mit der er zeichnet. Auch der Pen kann mehr als nur langweilige einfarbige Linien fabrizieren: Er verfügt über eine Eigenschaft "Brush" und damit stehen ihm zum zeichnen der Linien die Möglichkeiten der Pinsel offen. Diese sind dann allerdings auf die Dicke der Linie begrenzt - die Figur wird nicht ausgefüllt sondern nur die Umrandung.
So deklariert man einen Schwarzen Stift mit einer Linienstärke von 4. Ohne Transformation bedeutet das, dass der Stift 4 Pixel dicke Linien zeichnet.
Sieht man sich die Überladungen des Subs New an sieht man, dass er auch einen Brush- und einen Widh (LinienStärke)-Wert akzeptiert. Es ist also möglich z.B. Linien mit einer Textur zu zeichnen:
Der Pen kann noch mehr, so lassen sich beispielsweise die Linienenden oder Anfänge manipulieren - Pfeile darzustellen ist so ein Kinderspiel! Weitere Informationen über das Zeichnen mit Pens erhält man in einem anderen Tutorial von mir: Tutorial Pen & Linien in GDI+
5: Transformation und Manipulation
Oft sieht man, dass ganze Codeblöcke von Berechnungen geschrieben werden um eine Figur rotiert oder verschoben darzustellen. Jeder der sich das zumutet weiß offensichtlich nicht, dass das Graphics-Objekt ihm diese Mühen abnehmen kann und da es die Berechnungen sowieso durchführt vermutlich auch noch schneller ist! Ebenso verunstaltet man den eigenen Code beim nutzen den Transform-Eigenschaft nicht und hält ihn übersichtlich.
Häufig will man auch nur in einem bestimmten bereich Zeichnen, die anderen sollen, egal was man tut, unberührt bleiben. Mit SetClip kann man einen Bereich/Region festlegen in dem gezeichnet werden darf. Alles was über diesen hinausgeht wird ignoriert.
6: Einstellungen zur Qualitäts- oder Geschwindigkeitsverbesserung
Das Graphics-Object beinhaltet viele Eigenschaften um die Renderqualität zu beeinflussen. Ich liste hier mal die besagten Eigenschaften auf. Eine Ausreichende Erklärung liefert IntelliSense. Ich sage an dieser stelle nur, dass man mit diesen Werten spielen muss! Oft sieht "hightQuality" nicht am besten aus - vor allem bei der Textdarstellung.
VB.NET-Quellcode
- With e.Graphics
- .SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
- .TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAlias
- .PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality
- .InterpolationMode = Drawing2D.InterpolationMode.HighQualityBilinear
- .CompositingQuality = Drawing2D.CompositingQuality.HighQuality
- End With
7: Erweiterte Möglichkeiten/Nützliches
Auch das bisher gezeigte ist nur die Spitze des Eisberges! Jeder Brush beinhaltet eine Vielzahl von weiteren Eigenschaften und Funktionen. Das stöbern mittels Objektbrowser oder IntelliSense lohnt sich immer. Trotzdem werde ich nun ein paar weitere nützliche Funtionen von GDI+ zeigen.
GraphicsPath
Viele Effekte lassen sich mit dem GraphicsPath realisieren. Der GraphicsPath fasst dabei mehre Figuren oder auch Buchstaben zusammen. Das Einheitliche ausfüllen z.B. mit einem Farbverlauf oder Textur sind dann möglich. Ebenso lassen sich die Umrisse von Text nachzeichnen, was sonst nur sehr schwer möglich ist.
VB.NET-Quellcode
- Dim GraPath As New Drawing2D.GraphicsPath
- GraPath.AddString("TEXT", Font.FontFamily, Font.Style, 150, New Point(20, 20), New StringFormat)
- Dim TexBrush As New TextureBrush(New Bitmap("C:\a.jpg"))
- Dim tmpPen As New Pen(Color.Black, 3)
- With e.Graphics
- .FillPath(TexBrush, GraPath)
- .DrawPath(tmpPen, GraPath)
- End With
Was der GraphicsPath noch so kann
MeasureString
Das GraphicsObjekt bietet eine Funktion um zu "messen" welche Maße ein gerenderter Text später einnehmen würde. Nützlich ist dies z.B. wenn man eine Funktion schreiben möchte die die Schriftgröße automatisch an einen Rahmen etc anpasst.
Bitmap-Funktionen nicht vergessen!
Eine Funktion die ich häufiger benutze ist die .makeTransparent Funktion der Bitmap.
Colors können mehr
So verfügen die Colors neben dem RGB-Kanälen auch über den Alpha-Kanal. Dieser regelt die Transparenz der Farbe. Mittels Color.FromARGB(A,R,G,B) kann eine beliebige Farbe erstellt werden. Statt A = 255 einfach mal A = 100 setzten und schon hat man eine Transparente Farbe. Ideal um Bereiche zu Heighleighten!
Links:
Für Controlbauer: Beispielprojekt Listbox erweitern
GDI Tutorial: Tutorial Pen & Linien in GDI+
Für Fortgeschrittene: Control mit beweglicher Figur und Gezieltes OwnerDrawing
Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „FreakJNS“ ()