Graphics¶
Wir werden unsere GUI nun um eine Komponente erweitern, in der wir zeichnen können. Im Prinzip wird unsere GUI immer gezeichnet. Die Steuerelemente sind nur vorformatiert und bestehen aus lauter Linien, Polygonen und Punkten, die zusammen dann so, wie z.B. ein Button aussehen. Wenn wir eine GUI haben und dieses Fenster z.B. die Größe ändert, wird es dabei jedes Mal neu gezeichnet. Diese (Neu-)Zeichnen wird dadurch angestoßen, dass für unser JFrame
die Methode paint()
aufgerufen wird. Diese Methode wird z.B. auch in der Methode setVisible(true)
aufgerufen.
Jede Komponente (also jedes Steuerelement und jeder Container) hat ihre eigene Objektmethode paint()
, in der beschrieben ist, wie diese Komponente zu zeichnen ist. Genauer gesagt, werden in paint()
folgende drei Methoden aufgerufen:
paintBorder()
- zeichnet den Rahmen der KomponentepaintChildren()
- ruftpaint()
für alle Kindkomponenten auf (also z.B. alle Steurelemente in einemJPanel
)paintComponent()
- zeichnet die Komponente (und ihre Kinder)
Damit überhaupt Linien und Punkte dargestellt werden können, gibt es eine Klasse Graphics
, die, etwas vereinfacht gesagt, die Schnittstelle zwischen Hardware (dem Monitor) und dem zu zeichnenden Fenster darstellt. Alle Komponenten eines Fensters teilen sich genau ein Objekt dieser Klasse Graphics
. Bei diesem Objekt wird auch vom Graphics-Context (Grafikkontext) gesprochen. Diese Klasse stellt eine Vielzahl von Methoden zur Verfügung, um einfache geometrische Objekte zu zeichnen, z.B.
drawLine()
, um eine Linie zu zeichnen,drawOval()
, um eine (leere) Ellipse zu zeichnen,drawRect()
, um ein (leeres) Rechteck zu zeichnen,drawPolygon()
, um ein (leeres) Polygon (also ein Vieleck) zu zeichnen,drawString()
, um ein Text zu zeichnen,fillOval()
, um eine (ausgefüllte) Ellipse zu zeichnen,fillRect()
, um ein (ausgefülltes) Rechteck zu zeichnen,fillPolygon()
, um ein (ausgefülltes) Polygon (also ein Vieleck) zu zeichnen.
Damit nun alle paintX()
-Methoden (also paint()
, paintBorder()
, paintComponent
und paintChildren()
) Zugriff auf diesen Grafikkontext (das Objekt von Graphics
) bekommen, wird es diesen Methoden übergeben. Das heißt, die Methoden sind so deklariert (alle void
):
paint(Graphics g)
paintBorder(Graphics g)
paintChildren(Graphics g)
paintComponent(Graphics g)
Die Graphics
-Klasse gibt es schon seit Java 1.0. Allerdings hat man bereits sehr früh festgestellt, dass die Methoden in dieser Klasse nicht genügen, um "schöne" Grafiken zu erstellen. Deshalb hat man bereits in Java 1.1 eine neue Klasse Graphics2D
eingeführt (hat von Graphics
geerbt), in der hauptsächlich die Darstellung der geometrischen Objekte verbessert wurde, aber in der auch einige Methoden dazukamen, um z.B. andere Linienformen (gestrichelt, gepunktet, ...) zu definieren, andere Fonts für den Text usw. Tatsächlich handelt es sich seit JDK 1.2 bei dem Grafikkontext, also dem Graphics
-Objekt g
um ein Objekt der Klasse Graphics2D
.
Eigene geometrische Objekte zeichnen¶
Bevor wir eigene geometrische Objekte zeichnen können, schauen wir zunächst nochmal auf unser "Grundgerüst" für eine GUI (siehe Kapitel GUI Einführung):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Darin erzeugt die Methode initContent()
ein JPanel
und gibt es zurück. Dieses JPanel
wird der ContentPane
unseres Fensters hinzugefügt (Zeile 13
). Somit hat das JFrame
ein Kind, nämlich das der ContentPane
hinzugefügte JPanel
. Wenn das JFrame
nun gezeichnet wird, wird für dieses JPanel
die Methode paintComponent(Graphics g)
aufgerufen. Diese ist für ein JPanel
so implementiert, dass ein hellgraues Rechteck ohne Rand (Border
) gezeichnet wird. Würden wir dem mainPanel
in der initContent()
-Methode nun weitere Komponenten (Container oder Steuerlemente) hinzufügen, so würden diese alle so gezeichnet werden, wie für diese Komponenten die paintComponent(Graphics g)
-Methode implementiert ist.
Wir wollen nun aber selbst die paintComponent(Graphics g)
-Methode für eine Komponente implementieren. Dazu entscheiden wir uns dafür, die paintComponent()
-Methode von einem JPanel
zu implementieren. Aber wie kommen wir an diese Implementierung ran? Indem wir von JPanel
erben. Wenn wir eine Klasse erstellen, die von JPanel
erbt, dann erben wir auch die Implementierung der paintComponent()
-Methode von JPanel
und können diese überschreiben.
Wir ändern dafür unser "Grundgerüst":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
An der Darstellung des Fensters ändert das zunächst gar nichts. Wenn wir das Programm ausführen, dann erscheint folgendes Fenster.
Das der ContentPane
hinzugefügte JPanel
wird also "ganz normal" als hellgraues Rechteck dargestellt. Der große Unterschied ist, dass es sich nun um ein Objekt unserer inneren Klasse Canvas
handelt. Diese hat von JPanel
geerbt. Ein Objekt von Canvas
ist somit auch ein Objekt vom Typ JPanel
. Aber wir können in Canvas
die Methode paintComponent(Graphics g)
überschreiben und somit eine eigene Implementierung für das Zeichnen unserer Komponente (einem Canvas
-Objekt) erstellen.
Innerhalb unserer eigenen Implementierung der paintComponent()
-Methode rufen wir zunächst die paintComponent()
-Methode von JPanel
auf. Damit wird ein hellgraues Rechteck gezeichnet. Das sollten wir immer tun, da ansonsten manchmal unschöne Nebeneffekte entstehen.
Außerdem sollten wir auch immer das Graphics
-Objekt nach Graphics2D
konvertieren, da uns in Graphics2D
deutlich mehr Methoden zur Verfügung stehen (siehe z.B. hier und hier).
Übung
Die Methode draw3DRect()
ist ein Beispiel für eine Methode, die in der Graphics2D
-Klasse implementiert ist, aber in der Klasse Graphics
nicht vorkommt.
1. Warum funktioniert Graphics2D g2 = (Graphics2D)g;
?
2. Warum funktioniert g.draw3DRect()
nicht, aber g2.draw3DRect()
doch (wenn jeweils Parameterliste stimmt)?
Erste geometrische Objekte¶
Unser Fenster ist nun soweit vorbereitet und wir überschreiben die paintComponent()
-Methode in unserer Klasse Canvas
. Nun können wir darin beliebige Grafiken einfügen. Dazu rufen wir Methoden von Graphics2D
auf.
24 25 26 27 28 29 30 31 32 |
|
Die Methode drawRect(int x, int y, int width, int height)
zeichnet ein Rechteck mit der Breite width
und der Höhe height
. Bei uns hat beides den Wert 200
und somit wird ein Quadrat gezeichnet. Die Werte x
und y
stehen für die Koordinaten des linken oberen Punktes des Rechtecks innerhalb des JPanel
s, in dem wir zeichnen (also innerhalb der ContentPane
). Der linke obere Punkt des JPanel
hat die Koordinaten (0,0)
, also gehen wir in dem Beispiel um 40
Pixel nach rechts und um 30
Pixel nach unten, um mit dem Zeichnen des Quadrates zu beginnen.
Die Methode drawOval(int x, int y, int width, int height)
zeichnet eine Ellipse mit der Breite width
und der Höhe height
. Bei uns hat beides den Wert 200
und somit wird ein Kreis gezeichnet. Die Werte x
und y
stehen für die Koordinaten des linken oberen Punktes des gedachten Tangenetenvierecks um die Ellipse.
Der linke obere "Startpunkt" ist hier sowohl für den Kreis, als auch für das Quadrat der gleiche. Da auch Höhe und Breite jeweils gleich sind, passen die geometrischen Objekte genau ineinander, d.h. das Quadrat beschreibt das Tangenetenviereck um den Kreis.
Sie können nun gerne beliebig die Methoden aus Graphics2D ausprobieren. Es macht Spaß! Gerne auch beliebige Muster, z.B.
24 25 26 27 28 29 30 31 32 33 34 35 |
|
Anstelle der "vorgefertigten" Methoden für das Zeichnen von Rechtecken (drawRect()
) und Ellipsen (drawOval()
), können Sie auch immer erst entsprechende Objekte erzeugen und diese mithilfe der draw()
-Methode zeichnen lassen, z.B.
24 25 26 27 28 29 30 31 32 33 34 35 |
|
Das Interface Shape
ist aus dem java.awt
-Paket und die geometrischen Objekte sind aus dem java.awt.geom
-Paket. Der Vorteil ist, dass für die geometrischen Objekte selbst viele Methoden existieren, um z.B. die Eigenschaften dieser Objekte abzufragen (z.B. Höhe oder Breite). Es gibt viele solcher Klassen - siehe dazu Shape und darin All Known Implementing Classes
.
Ein Kreisbogen kann z.B. mithilfe der Klassen Arc2D.Double
oder Arc2D.Float
gezeichnet werden. Ein Kreisbogen ist ein Teil einer Ellipse. Die Konstruktoren erwarten jeweils 7 Parameter (gibt auch jeweils noch andere Konstruktoren):
x
, x-Wert der linken oberen Ecke des Tangentenvierecks um die (gesamte) Ellipse,y
, y-Wert der linken oberen Ecke des Tangentenvierecks um die (gesamte) Ellipse,width
, Breite der (gesamten) Ellipse,height
, Höhe der (gesamten) Ellipse,start
, Startpunkt des Kreisbogens in Grad (0 ist "3 Uhr", 90 ist "12 Uhr", 180 ist "9 Uhr", 270 ist "6 Uhr"),extent
, Länge des Kreisbogens in Grad (90 ist Viertelkreis, 180 ist Halbkreis, positiver Wert "gegen die Uhr", negativer Wert "mit der Uhr"),type
, Auswahl zwischenArc2D.OPEN
,Arc2D.PIE
undArc2D.CHORD
, siehe Abbildung.
Beispiele
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
Übung
Erstellen Sie folgende Zeichnung:
Um die geometrischen Objekte gefüllt darzustellen, verwenden Sie anstelle von draw(Shape s)
die fill(Shape s)
-Methode.
Linien¶
Die Linienart setzen Sie mithilfe der Methode g2.setStroke(Stroke s)
. Das Interface Stroke
wird von der Klasse BasicStroke
implementiert (Beides aus dem java.awt
-Paket). Das bedeutet, der Methode setStroke()
wird ein BasicStroke
-Objekt übergeben.
Die Klasse BasicStroke
besitzt folgende Konstruktoren:
BasicStroke()
BasicStroke(float width)
BasicStroke(float width, int cap, int join)
BasicStroke(float width, int cap, int join, float miterlimit)
BasicStroke(float width, int cap, int join, float miterlimit, float[] dash, float dash_phase)
Dabei gibt
- width
die Linienstärke als float
an, z.B. new BasicStroke(4.0f)
,
- cap
beschreibt die Enden einer Linie. Es stehen drei vordefinierte Konstanten zur Verfügung: CAP_BUTT
, CAP_ROUND
und CAP_SQUARE
Beispiel:
24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
ergibt:
- Die Eigenschaft
join
vonBasicStroke
gibt an, wie sich zwei Linien an den Endpunkten verbinden. Es gibt die drei vordefinierten KonstantenJOIN_BEVEL
,JOIN_MITER
undJOIN_ROUND
. Die Unterscheidung zeigt am besten die folgende Grafik:
- Mit dem
join
von Linien hat auchmiterlimit
zu tun. Es muss größer gleich1.0f
sein und ist nur fürJOIN_MITER
von Bedeutung. - Mit
dash
kann einfloat[]
für ein Muster des Strichelns der Linie angegeben werden. - Mit
dash_phase
kann angegeben werden, nach welcher Verzögerung die Anwendung desdash
-Musters erfolgt.
Beispiel:
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
ergibt:
Farben¶
Das Java-Farbmodell basiert auf dem RGB-Farbmodell, wobei jede Farbe durch 24Bit
repräsentiert wird (24Bit
Farbtiefe). Das bedeutet, dass für jeden Farbanteil von Rot, Grün, Blau jeweils 8Bit
zur Verfügung stehen und die einzelnen Farbanteile somit einen Wert zwischen 0
und 255
annehmen können. Einige Farben und deren Farbanteile von Rot, Grün und Blau sind in der folgenden Tabelle dargestellt:
Farbe | Rot-Anteil | Grün-Anteil | Blau-Anteil |
---|---|---|---|
Weiß | 255 | 255 | 255 |
Schwarz | 0 | 0 | 0 |
Grau | 127 | 127 | 127 |
Rot | 255 | 0 | 0 |
Grün | 0 | 255 | 0 |
Blau | 0 | 0 | 255 |
Gelb | 255 | 255 | 0 |
Magenta | 255 | 0 | 255 |
Cyan | 0 | 255 | 255 |
Die Klasse Color
aus dem java.awt
-Paket besitzt sieben Konstruktoren. Die wichtigsten sind
public Color(int r, int g, int b)
, mit denint
-Werten jeweils von0
bis255
für die Farbanteile,public Color(float r, float g, float b)
, mit denfloat
-Werten jeweils von0.0f
bis1.0f
für die Farbanteile,
und die Pendants mit einem zusätzlichen Alpha-Anteil für die Transparenz:
public Color(int r, int g, int b, int alpha)
,0
bis255
(0
vollständig transparent,255
deckend),public Color(float r, float g, float b, float alpha)
,0.0f
bis1.0f
(0
vollständig transparent,1.0
deckend).
In Color
sind darüber hinaus folgende Farben als statische Konstanten definiert:
WHITE
,BLACK
,BLUE
,CYAN
,DARKGRAY
,GRAY
,GREEN
,LIGHTGRAY
,MAGENTA
,ORANGE
,PINK
,RED
,YELLOW
Beispiel:
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
|
ergibt:
Mit den Klassen LinearGradientPaint und GradientPaint können Farbverläufe implementiert werden.
Beispiel:
24 25 26 27 28 29 30 31 32 33 |
|
ergibt:
Höhe und Breite abhängig von der Canvas-Größe¶
Angenommen, Sie wollen eine geometrischen Figur so zeichnen, dass sich ihre Größe der Größe des Fensters anpasst. Dazu stehen Ihnen die Methoden getHeight()
und getWidth()
von JPanel
(und somit von Ihrem Canvas
-Objekt) zur Verfügung. Die linke obere Ecke des JPanels
hat die Koordinaten [x=0, y=0]
und die rechte untere Ecke hat die Koordinaten [x=this.getWidth(), y= this.getHeight()]
, d.h
- für den linken Rand gilt
x = 0
, - für den rechten Rand gilt
x = this.width()
, - für den oberen Rand gilt
y = 0
und - für den unteren Rand gilt
y = this.height()
.
Angenommen, wir wollen ein Rechteck einpassen, das jeweils 30
Pixel von allen vier Rändern Abstand hat, dann definieren wir:
Beispiel:
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
ergibt:
noch ein Beispiel - wir passen ein Dreieck in das Fenster ein:
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
ergibt:
Success
Wir können eigene Grafiken erstellen. Wir erstellen dazu eine innere Klasse, die von JPanel
erbt. Der Grund dafür ist, dass wir die Methode paintComponent(Graphics g)
überschreiben wollen und darin mithilfe der Methoden aus der Klasse Grahpcs2D
geometrische Objekte erstellen. Außerdem haben wir das Farbmodell von Java kennengelernt und können die Grafiken an die Fenstergröße anpassen. In der kommenden Lektion lernen wir, wie wir mithilfe der Maus zeichnen können.