Groovy: Swing-GUI

Aus Wikibooks


Swing-GUI[Bearbeiten]

Mit Swing implementierte grafische Benutzeroberflächen (GUIs) gehören häufig zu den komplexesten und unübersichtlichsten Komponenten von Java-Anwendungen. Sie bestehen aus einer Vielfalt von miteinander verknüpften Objekten mit diversen Parametern, deren Struktur kaum einen erkennbaren Zusammenhang mit der sichtbaren Oberfläche hat. Um die Arbeit zu erleichtern, enthalten die integrierten Java-Entwicklungsumgebungen typischerweise GUI-Design-Tools, die eine grafische Gestaltung der Oberflächen ermöglichen und den zugehörigen Java-Code generieren. Dieser ist aber häufig noch unübersichtlicher als der manuell programmierte; da er von den Werkzeugen anderer Tool-Hersteller oft nicht richtig interpretiert wird, kann dieses Vorgehen leicht zu einer Abhängigkeit von einem bestimmten Werkzeug führen.

Groovy bietet mit dem SwingBuilder ein Hilfsmittel, mit dem sich Swing-Oberflächen in einer Weise darstellen lassen, dass die Struktur der Elemente auch im Programmcode erkennbar ist. Es basiert auf dem Builder-Prinzip, das wir schon in verschiedenen anderen Zusammenhängen kennen gelernt haben, und das ein quasi-deklaratorisches Herangehen erlaubt. Da man bei diesem Werkzeug ‒ zumindest wenn man sich mit der Funktionsweise von Swing gut auskennt ‒ das Ergebnis gut unter Kontrolle hat und verschiedene Programmiertechniken miteinander verknüpfen kann, kann das Arbeiten mit dem SwingBuilder durchaus effizienter sein als die Anwendung eines grafischen Gestaltungswerkzeugs.

So einfach die Arbeit mit dem SwingBuilder auf den ersten Blick erscheint, so unübersehbar ist doch die Menge der Möglichkeiten, die er zusammen mit der höchst komplexen Swing-API zur Gestaltung von Benutzungsoberflächen bietet. Aus Platzgründen werden wir uns hier darauf beschränken müssen, anhand einiger Beispiele das grundsätzliche Herangehen zu zeigen. Wenn Sie das Prinzip erst einmal verstanden haben, wird es Ihnen nicht schwer fallen, die weiteren Möglichkeiten zu erkunden.

Das Vorgehen[Bearbeiten]

Typischerweise bauen Sie mit dem SwingBuilder ein komplettes Bildschirmfenster oder einen kompletten Dialog (also ein Objekt der Klasse JFrame oder JDialog) auf, das Sie dann mit den üblichen Mitteln sichtbar machen. Das folgende Beispiel erstellt ein javax.swing.JFrame-Objekt mit zwei Buttons:

import groovy.swing.SwingBuilder
import javax.swing.*
import java.awt.*

sb = new groovy.swing.SwingBuilder()
frame = sb.frame(title:"Klicken", size:[200,100],
        defaultCloseOperation:WindowConstants.EXIT_ON_CLOSE) {
  panel() {
    button ("Rot",
    foreground:Color.RED,
    actionPerformed: {println "Rot gedrückt"})
    button ("Blau",foreground:Color.BLUE,
        actionPerformed: {println "Blau gedrückt"})
  }
}

frame.visible = true

Die Methodenaufrufe frame(), panel() und button() generieren jeweils ein Objekt einer Klasse mit dem korrespondierenden Namen JFrame, JPanel oder JButton. Dieses Prinzip wird weitgehend durchgängig für alle GUI-Komponentenklassen so gehandhabt. Allen Elementen werden die zu setzenden Properties in der Form von benannten Parametern mitgegeben. Bei Container-Elementen wird eine Closure angegeben, die die Definitionen der enthaltenen Objekte umfasst. Zwei Besonderheiten fallen auf:

  • Der benannte Parameter size:[200,100] in der frame()-Methode wird von Groovy automatisch in den Aufruf setSize(200,100) des JFrame umgesetzt, dies macht bereits die Sprache von sich aus.
  • Die Parameter actionPerformed ordnet die übergebene Closure der jeweiligen Komponente als Handler für das ActionEvent zu; diese Closure wird also in diesen beiden Fällen beim Drücken der Buttons ausgeführt.

Wenn Sie dieses Skript laufen lassen, sollte ein Fenster auf dem Bildschirm erscheinen, das folgendermaßen aussieht:

Beispiel-Fenster mit zwei Buttons

Wenn Sie auf die Buttons klicken, erscheint jeweils der Text »Rot gedrückt« oder »Blau Gedrückt« auf der Konsole. Da wir die Property defaultCloseOperation entsprechend gesetzt haben, wird die Anwendung beim Schließen des Fensters beendet.

Aktionen[Bearbeiten]

Einfache Aktionen lassen sich gut als Closure per actionPerformed-Property den Elementen zuordnen. Wenn Aktionen länger sind oder wiederverwendet werden sollen, bietet sich dieses Vorgehen weniger an. Natürlich können Sie aus der Closure eine Methode aufrufen, die die eigentliche Arbeit macht. Bessere Möglichkeiten bieten aber spezielle Aktionen, die Sie sich vom SwingBuilder mit Hilfe seiner Methode action() erzeugen lassen und die bei der auslösenden Komponente nur noch referenziert werden. In unserem Beispiel bietet es sich etwa an, nur eine Ausgabe-Aktion zu definieren.

import groovy.swing.SwingBuilder
import javax.swing.*
import java.awt.*
sb = new groovy.swing.SwingBuilder()

anzeigeAktion = sb.action(closure: { println "$it.source.text gedrückt"})

frame = sb.frame(title:"Klicken", size:[200,100],
        defaultCloseOperation:WindowConstants.EXIT_ON_CLOSE) {
  panel() {
    button ("Rot",foreground:Color.RED, action:anzeigeAktion)
    button ("Blau",foreground:Color.BLUE, action:anzeigeAktion)
  }
}
frame.visible = true

Das sichtbare Ergebnis unterscheidet sich nicht vom obigen Beispiel.

Layout[Bearbeiten]

Eine Stärke von Swing besteht in der Möglichkeit, mit Hilfe von als Layout-Manager bezeichneten Objekten die Darstellung der Oberfläche sehr vielfältig steuern zu können. Der Layout-Manager kann dem Container-Element als gewöhnliche Property zugeordnet werden (z.B. layout=GridBagLayout). Einfacher aber ist es, einen Methodenaufruf, der mit dem Namen des Layout-Managers korrespondiert, einzutragen ‒ so als wäre es ein enthaltenes Element. Um den Layout-Manager GridBagLayout zu verwenden, benutzen wir also den Methodenaufruf gridBagLayout(). Die erforderlichen Layout-Constraints können dann bei den enthaltenen Elementen als Properties gesetzt werden.

Im folgenden Beispiel wollen wir nun die Textausgabe nicht mehr auf der Konsole ausführen, sondern in ein zusätzliches Textfeld leiten. Mit Hilfe eines GridBagConstraint legen wir fest, dass das Textfeld unterhalb beider Buttons liegen soll. Für die beiden Buttons brauchen wir hier keine Constraints, da deren Anordnung dem Standard entspricht.

Um den Ausgabetext in das Textfeld eintragen zu können, müssen wir es irgendwie referenzieren. Dazu gibt es mehrere Möglichkeiten: Wir könnten durch die Hierarchie der Elemente navigieren oder die Elemente Variablen zuweisen. Am einfachsten aber ist es, den Elementen mit Hilfe der Property id eine eindeutige Kennung zuzuweisen; unter dieser Kennung erscheinen sie dann wieder als Property der SwingBuilder-Instanz. In diesem Beispiel ordnen wir dem Textfeld die Kennung »ausgabe« zu, und können es also mit sb.ausgabe referenzieren.

import groovy.swing.SwingBuilder
import javax.swing.*
import java.awt.*

sb = new groovy.swing.SwingBuilder()
anzeigeAktion = sb.action(closure: { sb.ausgabe.text="$it.source.text gedrückt"})
frame = sb.frame(title:"Klicken", size:[200,100],
        defaultCloseOperation:WindowConstants.EXIT_ON_CLOSE) {
  panel() {
    gridBagLayout()
    button ("Rot",foreground:Color.RED, action:anzeigeAktion)
    button ("Blau",foreground:Color.BLUE, action:anzeigeAktion)
            textField(id:'ausgabe',
            constraints:new GridBagConstraints(gridx:0,gridy:1,gridwidth:2,fill:1))
  }
}
frame.visible = true

Das Ergebnis sieht so aus:

Beispiel-Fenster mit zusätzlichem Textfeld

Sie haben jetzt hoffentlich einen ersten Eindruck davon bekommen, wie in Groovy Swing-Oberflächen gestaltet werden können. Eine Übersicht der verschiedenen Factory-Methoden, mit denen Sie GUI-Komponenten, Layout-Manager und weitere Objekte erzeugen können, finden Sie im Anhang.