Im letzten Artikel haben wir unserer GUI Anwendung mithilfe einer Komponente (main component) etwas Inhalt geschenkt. Dieser Inhalt bestand aber nur aus einfachem Text. Das geht natürlich noch besser. Wir machen zunächst ungefähr da weiter, wo wir das letzte mal aufgehört haben. Dazu öffnen wir das letzte Projekt MainComponentTutorial nochmal und ändern die paint() Methode wieder so ab, dass uns das beliebte „Hello World!“ wieder angezeigt wird. Außerdem können wir dann auch die Member-Variable currentSizeAsString wieder entfernen (auch deren Nutzung in der resize() Methode).

Damit sieht unsere paint() Methode wieder so aus:

void MainComponent::paint (Graphics& g)
{
    g.fillAll(Colours::darkgrey);
    g.setColour (Colours::orange);
    g.setFont (28.0f);
    g.drawText ("Hello World!", getLocalBounds(),
                Justification::centred, true);  
}

Wenn wir das Projekt nun kompilieren und ausführen, haben wir wieder unser dunkelgraues Fenster mit dem orangen „Hello World!“ Text in der Mitte.

Mithilfe der Graphics Klasse können wir nun noch andere Dinge in unsere MainComponent „zeichnen“.

Die Graphics Klasse

Wenn wir uns nochmal die paint() Methode ansehen, sieht man dass eine Referenz auf eine Instanz der Graphics Klasse als Argument an die Methode übergeben wird (Graphics& g). Das Graphics Objekt wird vom zugrunde liegenden Framework bereitgestellt und hiermit kann ich nun Linien, geometrische Formen, Farben, Farbverläufe und vieles mehr rendern.

Den Text rendern

Text haben wir in unserem Tutorial Projekt schon dargestellt. Die Zeile…

g.setFont (28.0f);

… stellt die Schriftgröße auf 28 Pixel ein und die nachfolgende Zeile „zeichnet“ den Text dann in die Komponente. Natürlich können wir aber nicht nur die Schriftgröße ändern, sondern auch die Schriftart. Es gibt noch eine andere Variante der setFont() Funktion, die zusätzlich ein Font Objekt nutzt.

Wir könnten nun ein eigenes Font Objekt erzeugen und dem Konstruktor die gewünschte Schriftart, -größe und weitere Parameter mitgeben:

Font mainComponentFont ("Times New Roman", 28.0f, Font::italic);

Einfacher wäre allerdings dieses Objekt gleich in den Aufruf der Funktion setFont() mit einzubinden:

g.setFont (Font ("Times New Roman", 28.0f, Font::italic));

Die Position ändern

Es gibt eine Reihe von Justification::Flags, die man anwenden könnte. In unserem Beispiel haben wir Justification::centred gewählt, um den Text innerhalb der Komponente zu zentrieren. Mit Justification::topLeft könnte ich das „Hello World!“ auch oben links platzieren.

Eine genauere Methode wäre allerdings die explizite Angabe von Größe und Position. Einer anderen Version der drawText() Funktion kann man diese Angaben in Pixeln übergeben:

g.drawText("Hello, World!", 20, 40, 200, 40, Justification::centred, true);

Was passiert, wenn man nun die Breite (200px) zu klein wählt und der Text dort nicht mehr hinein passen würde? Ich gebe einfach mal 100px an:

Das letzte Argument der drawText() Funktion ist ein bool und wenn es true ist, werden drei Punkte (…) dargestellt, wenn der Text zu lang ist für die angegebene Breite.

Geometrische Formen zeichnen

Ich füge mal folgende Zeilen ans Ende der paint() Methode:

g.setColour (Colours::white);
g.drawLine (10, 100, 390, 100, 5);

Nach dem Ausführen sehen wir, dass eine weiße horizontale Linie mit einer Dicke von 5px im Fenster gezeichnet wurde. Sie startet bei (10, 100) und endet bei (390, 100). Übrigens: jedes Mal wenn ich in einer anderen Farbe zeichnen will, muss ich diese davor angeben, ansonsten würde ich in der letzten angegeben Farbe (Colour) zeichnen. Hätte ich hier jetzt nicht die weiße Farbe angegeben, wäre unsere Linie orange.

Es gibt natürlich noch andere Linien als langweilige Horizontale. Wenn man einen Blick in die Graphics Klasse wirft, sieht man alle Möglichkeiten…

void MainComponent::paint (Graphics& g)
{
    g.fillAll(Colours::darkgrey);
    g.setColour (Colours::orange);
    g.setFont(Font("Times New Roman", 28.0f, Font::italic));
    g.drawText("Hello, World!", 20, 40, 200, 40, Justification::centred, true);
    g.setColour(Colours::white);
    Line<float> line(10, 100, 390, 100);
    g.drawArrow(line, 3, 20, 25);
}

Wenn man Rechtecke zeichnen möchte, ist das kein Problem mit dem Graphics Objekt.

g.setColour(Colours::sandybrown);
g.drawRect(100, 120, 200, 170);

Hiermit wird ein hellbraunes Rechteck gezeichnet, 200px breit, 170px hoch und bei (100, 120) positioniert. Mit einem weiteren (fünften) Argument könnte man noch die Liniendicke angeben:

g.setColour(Colours::sandybrown);
g.drawRect(100, 120, 200, 170, 3);

Falls das Rechteck ausgefüllt sein soll, nutzt man einfach die Funktion Graphics::fillRect()

g.setColour(Colours::sandybrown);
g.fillRect(100, 120, 200, 170);

Man muss das Rechteck aber nicht mit einer einfachen Farbe füllen. Es gibt verschiedene Methoden eine geometrische Figur zu füllen.

Rectangle<float> house(100, 120, 200, 170);
g.fillCheckerBoard(house, 30, 20, Colours::saddlebrown, Colours::black);

Kreise zeichnen

Um Kreise oder Ellipsen zu zeichnen, sollte man sich Graphics::drawEllipse() und Graphics::fillEllipse() ansehen, sie arbeiten genauso wie Graphics::drawRect() und Graphics::fillRect().

Wir können unser Rechteck nun mal etwas kleiner machen und verschieben und dann eine Sonne hinzufügen:

Rectangle<float> house(100, 180, 120, 100);
g.fillCheckerBoard(house, 30, 20, Colours::saddlebrown, Colours::black);

g.setColour(Colours::yellow);
g.fillEllipse(300, 10, 60, 60);

Hier wäre noch anzumerken, dass die Position (300, 10) nicht die Mitte des Kreises ist, sondern ebenfalls die linke obere Ecke des umschließenden Rechtecks. Man kann so auch anhand der umgebenden Komponente die Position angeben:

g.setColour(Colours::yellow);
g.fillEllipse(getWidth() - 100, 10, 60, 60);

Andere Polygone zeichnen

Was fehlt noch? Genau, ein Dach. Ein Dach ist einfach ein rotes Dreieck. Es gibt keine Funktion drawTriangle() oder drawPolygon(). Hier müssen wir etwas generischer vorgehen. Stichwort: Path Klasse.

Die Path Klasse kann jede geometrische Form zeichnen, einfach indem sie bestimmte Punkte verbindet. Da wir ein Dreieck für unser Dach benötigen, brauchen wir 3 Punkte. Z.B. könnten wir die Punkte (100, 175), (220, 175) und (160, 120) nehmen, damit das Dach auch genau auf unserem Haus passt.

g.setColour(Colours::red);
Path roof;
roof.addTriangle(100, 175, 220, 175, 160, 120);
g.fillPath(roof);

Hier nochmal unsere komplette paint() Methode:

void MainComponent::paint(Graphics& g)
{
    g.fillAll(Colours::darkgrey);

    g.setColour(Colours::orange);
    g.setFont(Font("Times New Roman", 28.0f, Font::italic));
    g.drawText("Hello, World!", 20, 40, 200, 40, Justification::centred, true);

    g.setColour(Colours::white);
    Line<float> line(10, 100, 390, 100);
    g.drawArrow(line, 3, 20, 25);

    Rectangle<float> house(100, 180, 120, 100);
    g.fillCheckerBoard(house, 30, 20, Colours::saddlebrown, Colours::black);

    g.setColour(Colours::yellow);
    g.fillEllipse(getWidth() - 100, 10, 60, 60);

    g.setColour(Colours::red);
    Path roof;
    roof.addTriangle(100, 175, 220, 175, 160, 120);
    g.fillPath(roof);
}

Die Graphics Klasse kann natürlich noch viel mehr, als nur Text und geometrische Formen zeichnen. Man kann sie z.B. nutzen um Bilder (aus einer Datei) darzustellen. Auch die Path Klasse ist noch viel nützlicher, als hier gezeigt. Wie bereits schon erwähnt ist es immer eine gute Idee sich die Klassen genauer anzusehen und mit den Funktionen etwas zu experimentieren.

Im nächsten Artikel will ich nochmal etwas näher auf Punkte, Linien und Rechtecke eingehen…