18.4 Mit dem Eclipse WindowBuilder zur ersten Swing-Oberfläche
Ohne uns intensiv mit den Swing-Klassen auseinanderzusetzen, wollen wir ein erstes Beispiel programmieren und das Swing-Wissen sozusagen im Vorbeigehen mitnehmen. Wir wollen im Folgenden ein Programm Bing entwickeln. Mit einem Schieberegler können wir die Zeit einstellen, und nach dem Aufruf erscheint eine Meldung auf dem Bildschirm. Wer vor lauter Java immer vergisst, den Teebeutel aus der Tasse zu nehmen, für den ist diese Applikation genau richtig.
18.4.1 WindowBuilder installieren
Einige Entwicklungsumgebungen bringen zur Gestaltung grafischer Oberflächen einen GUI-Builder mit. Eclipse hat kein Werkzeug mit an Bord, doch es gibt ein freies Plugin, das wir installieren können.
Nach dem Start von Eclipse wählen wir im Menü Help • Eclipse Marketplace (siehe Abbildung 18.1).
Anschließend suchen wir nach »WindowBuilder« (siehe Abbildung 18.2).
Ein Klick auf Install startet die Installation. Nach einem Neustart von Eclipse ist das Plugin aktiviert, und es kann mit dem GUI-Builder losgehen.
18.4.2 Mit WindowBuilder eine GUI-Klasse hinzufügen
Der WindowBuilder legt neue Klassen an; ein Java-Projekt muss schon vorhanden sein. Wir gehen zunächst auf File • New • Other (siehe Abbildung 18.3).
In dem Dialog wählen wir WindowBuilder • Swing Designer • Application Window (siehe Abbildung 18.4).
Nachdem wir auf Next gedrückt haben, erscheint ein Dialog, den wir vom Anlegen neuer Klassen kennen. Wir geben das Paket und einen Klassennamen an (siehe Abbildung 18.5).
WindowBuilder erzeugt eine neue Klasse und öffnet standardmäßig den grafischen Editor. Wir können das Programm starten, aber ohne Funktion bleibt das langweilig. Zwischen dem GUI-Builder und dem Quellcode lässt sich jederzeit umschalten, und zwar mit den kleinen Reitern Source und Design (siehe Abbildung 18.6 unten links).
Sind wir in der Ansicht Design, können wir per Drag & Drop Komponenten auf die Zeichenfläche setzen und hin und her schieben.
Mittig sind unter Palette die Komponenten aufgelistet, die wir per Drag & Drop auf den Formulardesigner ziehen können. Links bei Properties finden wir die Eigenschaften der Komponenten, etwa den Titel des Fensters.
Der WindowBuilder gießt das Layout direkt in Quellcode, den wir unter Source einsehen können (siehe Abbildung 18.7). In einem gewissen Rahmen können wir auch Code ändern, und das äußert sich wieder in der Änderung der GUI.
18.4.3 Das Layoutprogramm starten
Im Quellcode lässt sich ablesen, dass die Klasse eine main(String[])-Methode hat, sodass wir MainFrame starten können. Mit (F11) springt dann ein unspektakuläres leeres Fenster auf (siehe Abbildung 18.8).
Eine Vorschau gibt es auch. Über der Palette (siehe Abbildung 18.6) gibt es eine Schaltfläche mit grünem Play-Symbol, der nach einem Klick einen ersten Eindruck vom Design vermittelt.
18.4.4 Grafische Oberfläche aufbauen
Nun ist es an der Zeit, der Oberfläche ein paar Komponenten zu geben. Wenn wir in der Ansicht Components den Zweig getContentPane() auswählen (siehe Abbildung 18.9), können wir bei den Properties einen Layout-Manager wählen. Die Aufgabe eines Layout-Managers ist die Anordnung der Kinder. Dazu gibt es diverse Techniken. Wir wählen GroupLayout, da es für GUI-Builder am leistungsfähigsten und am besten geeignet ist.
In der Palette suchen wir im Bereich Components nach JLabel und ziehen es per Drag &Drop auf die graue Designerfläche (siehe Abbildung 18.10). Bemerkenswert ist, dass WindowBuilder vorgibt, was eine gute Position für die Beschriftung ist. Positionieren wir sie links oben, so rastet sie quasi ein.
Das hat zwei Konsequenzen: Zum einen ergibt sich automatisch eine gut aussehende Oberfläche mit sinnvollen Abständen, und zum anderen »kleben« die Komponenten so aneinander, dass sie bei einer Größenanpassung nicht auseinandergerissen werden.
Nachdem das Label positioniert ist, geben wir ihm einen Namen. Dazu kann links bei den Properties der text verändert werden (siehe Abbildung 18.11) oder auch im Designer über einen Doppelklick auf den Text.
Jetzt, da die erste Beschriftung steht, komplettieren wir das GUI. Unter der Beschriftung setzen wir einen Slider (Schieberegler), allerdings nicht auf die ganze Breite, sondern etwa bis zur Hälfte. Unter den Properties auf der linken Seite gibt es Eigenschaften für minimum (0) und maximum (100). Das minimum 0 erhöhen wir wie in Abbildung 18.12 auf 1 und das maximum auf 1440 (die Minuten von 24 Stunden sollten reichen).
Rechts vom Slider setzen wir ein Text Field und wiederum rechts davon ein neues Label. Das Label hängt am rechten Fensterrand, und wir beschriften es mit Minuten. Anschließend wird die Textbox rechts an das Minuten-Label und der Slider rechts an die Textbox gesetzt, sodass alle drei gut ausgerichtet sind. Bei einem Klick auf die Vorschau sollte es nun so aussehen wie in Abbildung 18.13.
Das Schöne an den automatischen Ausrichtungen ist, dass wir die Breite verändern können und die Komponenten alle mitlaufen, also nicht absolut positioniert sind – so sollte eine grafische Oberfläche sein!
Die Oberfläche ist jetzt schon fast fertig. Geben wir noch zwei Labels und eine Schaltfläche hinzu wie in Abbildung 18.14.
Unter dem Slider stehen zwei Labels nebeneinander (das zweite enthält nur ein Leerzeichen). Startet später die Anwendung, soll in der jetzt unsichtbaren Beschriftung die Restzeit stehen. Die Schaltflächen sind bei den Swing Controls als JButton aufgeführt. Der Button dient zum Starten der Applikation. Über den Property-Editor geben wir gleichzeitig dem Fenster noch einen Titel (BING), und fertig ist die Oberfläche.
18.4.5 Swing-Komponenten-Klassen
Die Oberfläche ist jetzt fertig, und in der Ansicht Source lässt sich ablesen, dass viel Quellcode für die Ausrichtung erstellt wurde. Der Quellcode gliedert sich in folgende Teile:
Einen Standard-Konstruktor: Er ruft initialize() auf. Eigene Funktionalität können wir hier hinzuschreiben.
Die statische main(String[])-Methode könnte das Fenster gleich starten, denn sie baut ein Exemplar der eigenen Klasse auf, die ja Unterklasse von JFrame ist, und zeigt es mit setVisible(true) an.
In der Methode selbst finden sich Variablen. Es sind keine Objektvariablen, sondern lokale Variablen. Das lässt sich jedoch individuell ändern.
Es lässt sich ablesen, dass für Schaltflächen die Klasse JButton, für Beschriftungen die Klasse JLabel, für den Schieberegler ein JSlider und für einfache Textfelder die Klasse JTextField zum Einsatz kommt.
Variablen umbenennen
Die von WindowBuilder automatisch generierten Namen machen semantisch nicht klar, worum es geht. Daher wollen wir die Variablennamen ändern, sodass klarer wird, was welche Komponente bewirkt. Hier kommt wieder die Sicht components ins Spiel. Sie zeigt die hierarchische Struktur der Komponenten an. Mit einem Klick neben den Punkt Variable in unseren Properties (siehe Abbildung 18.15) lässt sich jeder Variablenname ändern. Das wollen wir machen:
Startzustände programmatisch setzen
In unserer initialize()-Methode wollen wir die Schiebereglerposition auf 1 setzen und das Textfeld ebenfalls mit 1 vorbelegen:
private void initialize() {
JSlider minutesSlider = new JSlider();
minutesSlider.setMaximum(1440);
minutesSlider.setMinimum(1);
minutesSlider.setValue( 1 );
minutesTextField = new JTextField();
minutesTextField.setColumns(10);
minutesTextField.setText( "1" );
}
Die Methode setValue(int) erwartet einen numerischen Wert für den Schieberegler, und setText(String) erwartet einen String für das Textfeld.
18.4.6 Funktionalität geben
Nachdem die Variablen gut benannt sind, soll es an die Implementierung der Funktionalität gehen. Folgendes gilt es zu realisieren:
Das Bewegen des Sliders aktualisiert das Textfeld.
Das Verändern des Textfeldes aktualisiert den Slider.
Nach dem Start läuft das Programm, und die verbleibende Zeit wird aktualisiert. Ist die Zeit um, erscheint eine Dialogbox.
Den Wert des Schiebereglers und des Textfelds synchronisieren
Als Erstes halten wir den Wert vom Schieberegler und den Wert im Textfeld synchron. Zurück in der Design-Ansicht selektieren wir den Slider und finden im Kontextmenü den Menüpunkt Add event handler (siehe Abbildung 18.16). Das sind alle Ereignisse, die der Schieberegler auslösen kann. Wir interessieren uns für change:
Nach dem Aktivieren des Menüpunktes bekommen wir vom WindowBuilder Quellcode generiert. Die Listener-Methode wird immer dann aufgerufen, wenn der Anwender den Schieberegler bewegt. Lesen wir einfach den aktuellen Wert aus, und schreiben wir ihn in das Textfeld:
minutesSlider.addChangeListener(new ChangeListener(){
public void stateChanged(ChangeEvent e) {
minutesTextField.setText( "" + minutesSlider.getValue() );
}
});
Die JSlider-Methode getValue() liefert den aktuell eingestellten Wert als Ganzzahl, und setText(String) vom JTextField setzt einen String in die Textzeile.
Nach dem Start des Programms können wir den Schieberegler bewegen, und im Textfeld steht die ausgewählte Zahl. Der erste Schritt ist gemacht.
Kommen wir zum umgekehrten Fall: Wenn das Textfeld mit (¢) bestätigt wird, soll der Wert ausgelesen und damit die JSlider-Position gesetzt werden. Mit einem Rechtsklick gehen wir im Designer auf das Textfeld, wählen unter Add event handler dann action und klicken auf actionPerformed. Das fügt Programmcode für den Listener ein. Füllen wir ihn wie folgt:
public void actionPerformed(ActionEvent evt) {
try {
minutesSlider.setValue( Integer.parseInt( minutesTextField.getText() ) );
}
catch ( NumberFormatException e ) { }
}
Da im Textfeld ja fälschlicherweise Nicht-Zahlen stehen können, fangen wir den Fehler ab, ignorieren ihn aber. Falsche Wertebereiche ignorieren wir.
[»] Hinweis
Eine Rückkopplung zwischen Textfeld und Schieberegler kann in diesem Fall nicht auftreten. Bewegen wir den Schieberegler und setzen wir mit setText(…) den Text im Textfeld, so löst das kein ActionEvent aus.
Nachdem jetzt Änderungen im Textfeld und Schieberegler synchron gehalten werden, ist es an der Zeit, die Implementierung mit dem Start eines Timers abzuschließen.
Timer starten
In der Ansicht Designer doppelklicken wir auf die Schaltfläche Start. Jetzt muss die aktuelle Wartezeit ausgelesen werden, nach deren Ende eine Dialogbox erscheint (siehe Abbildung 18.17). Die konstante Abarbeitung übernimmt ein Swing-Timer (javax.swing.Timer), der alle 100 Millisekunden die Oberfläche aktualisiert und dann beendet wird, wenn die Wartezeit abgelaufen ist:
public void actionPerformed(ActionEvent e) {
startButton.setEnabled( false );
final long start = System.currentTimeMillis();
final long end = start + minutesSlider.getValue() * 60 * 1000;
final javax.swing.Timer timer = new javax.swing.Timer( 100, null );
timer.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
long now = System.currentTimeMillis();
if ( now >= end )
{
remainingMinLabel.setText( "" );
startButton.setEnabled( true );
JOptionPane.showMessageDialog( null, "BING!" );
timer.stop();
}
else
remainingMinLabel.setText( (end - now) / 1000 + " Sekunden" );
}
} );
timer.start();
}
Unser Programm ist fertig!