Samstag, 4. Juli 2009

GUIs in Java: Layout

Grafische Oberflächen stellen klarerweise einen großen Teil eines Programmes dar. Nur wenige Programme, die für den Endnutzer gedacht sind, kommen ohne eine GUI aus.

Es gibt 3 grundsätzliche Wege, in Java eine GUI zu erstellen
  • Zum Beispiel könntest du einen grafischen Editor für Benutzeroberflächen haben. Das trifft zum Beispiel bei der NetBeans IDE zu.
  • Außerdem gibt es das AWT Framework...
  • ...und das Swing/JFC framework
Ein grafischer Editor ist möglicherweise schneller und vergleichsweise einfacher. Andererseits fehlt mit so einem Editor die volle Kontrolle über das Resultat - Das Ergebnis könnte anders aussehen oder sich anders verhalten als erwünscht. Außer aus der Kompatibilität mit alten Java-Versionen (und zwar SEEHR alten Versionen) gibt es eigentlich keinen Grund für AWT.
Die klare Wahl ist deshalb Swing. Das Swing-Framework baut auf AWT auf, hat aber viele Vorteile. Die meisten spürt man allerdings erst, wenn man komplexere Aufgaben bewältigen will, deswegen verschiebe ich die Erklärung auf später.

Das war die Theorie zu GUI in Java - Zeit für ein bisschen Praxis. Für den Anfang genügt ein einfaches Layout-Beispiel:

Hier siehst du ein Textfeld, einen Button und eine Dropdown-Liste (Combo Box). Das Fenster ist übrigens von Ubuntu.
Hier sieht man zwar die Anordnung, aber das ist nicht genug, um das Layout festzustellen. Was wir benötigen ist das Verhalten bei einer Größenänderung:

Die Höhe unserer Komponenten ist gleichgeblieben, allerdings hat sich die Breite geändert. Nur der Button ist gleich geblieben. Das Textfeld hat den übrigen Platz konsumiert, und unten ist die Combo Box breiter geworden.

Was wird hier also für ein Layout verwendet? Es ist nicht einfach ein BorderLayout... es sind zwei! Ein BorderLayout hat 5 Bereiche:

Die North- und South-Bereiche haben ihre preferred height, werden aber von links bis rechts gestreckt. Die West- und East-Bereiche haben im Gegensatz dazu ihre preferred width und sind von oben bis unten gestreckt. Der Center-Bereich bekommt den restlichen Platz, ohne Rücksicht auf die preferred size.

Jeder der Bereiche kann nur einen Component enthalten. Wie du dir aber sicher schon gedacht hast, kann dieser eine Component wieder mehrere Components enthalten:

Die Frage, die sich jetzt noch stellt, ist: Welcher unserer Komponenten muss in welchen Bereich? Die Combo Box bietet sich eindeutig für South an. Im North-Bereich gibt es sowohl Textfeld als auch Button. Der Button Behält immer seine Breite und ist rechts, ist also im North-East-Bereich. Das Textfeld wird breiter, ist also im North-Center-Bereich. Obwohl das Textfeld im Center so viel Platz bekommt, wie da ist, bleibt die Höhe konstant. Das liegt daran, wie die Layouts zusammenarbeiten. Das zweite BorderLayout ist im North-Bereich untergebracht und bekommt deshalb nur so viel Höhe, wie unbedingt nötig.

Jetzt wo das Layout klar ist, musst du das ganze nur noch in Code umsetzen. Zuerst brauchen wir eine Klasse für unseren Code. Um es für spätere Erweiterungen einfacher zu machen, wird diese Klasse eine Subklasse von JPanel. JPanel ist ein Component, der eigentlich nur dafür gut ist, andere Komponenten zu enthalten.

package post01;


import java.awt.BorderLayout;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;


public class Example extends JPanel {
//Jetzt brauchen wir sie zwar noch nicht permanent, aber für später speichere ich die
//Komponenten in Attributen.
private JTextField tf;
private JButton b;
private JComboBox cb;

public Example() {
//super() ruft explizit den Konstruktor der Superklasse (JPanel in this case) auf.
//Ich benutze es, um das Objekt mit einem BorderLayout zu bestücken. Ich könnte aber
//auch später setLayout(new BorderLayout()); benutzen.
super(new BorderLayout());

//Der Parameter von JTextField gibt die Breite, in Textzeichen, an.
//Die Java API doc ist eine perfekte Quelle für solche Informationen.
tf = new JTextField(5);
cb = new JComboBox();
b = new JButton("Add");

//Das ist das Panel, das gleich Textfeld und Button enthalten wird.
JPanel north = new JPanel(new BorderLayout());

//Hier wird das Panel im North-Bereich hinzugefügt. Der zweite Parameter
//Gibt den Ort des panels an. BorderLayout hat für alle 5 Bereiche Konstanten.
add(north, BorderLayout.NORTH);

//Ohne zweiten Parameter, benutzt BorderLayout den Center-Bereich
north.add(tf);
north.add(b, BorderLayout.EAST);
add(cb, BorderLayout.SOUTH);
}
}
Hier kannst du sehen dass die Reihenfolge, in der die Komponenten hinzugefügt werden, unwichtig ist. obwohl das north-Panel schon hinzugefügt wird, bevor es Textfeld und Button enthält, sind sie trotzdem sichtbar.

...also eigentlich wird es sichtbar sein. Denn zum Anzeigen fehlen noch 2 Dinge: Eine main-Methode und ein frame

Die main-Methode ist der Startpunkt jeder richtigen Java-Applikation. In BlueJ werden (normalerweise) keine main-Methoden benutzt: Es werden direkt Konstruktoren und Methoden aufgerufen. Was entsteht ist aber auch keine echte Java-Applikation, weil ja der Programmierer selbst die Objekte erzeugt.
Die main-Methode kann man sich vorstellen als ein "Skript", das die Aufgabe, die notwendigen Objekte zu erzeugen, übernimmt.
Ein frame ist ein spezieller Component mit der Besonderheit, dass er auf dem Bildschirm sichtbar gemacht werden kann. Es gibt allerdings ein paar Dinge zu beachten. Du siehst sie in der main-Methode:
//Die "Signatur" einer main-Methode muss immer genau so aussehen
//public static void main(String[] args)

public static void main(String[] args) {
//Der frame ist ein object vom Typ JFrame
JFrame jf = new JFrame();
//Das ist nötig, damit das Programm sauber beendet wird, wenn man das Fenster schließt.
//Der Grund ist aber für diesen Post etwas zu viel
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//Wie bei jedem Component hat ein JFrame ein Jayout, und es können Components hinzugefügt
//werden. Das Standard-Layout für Frames ist BorderLayout. diese Zeile fügt unser panel also
//im Center Bereich ein, wo es das Gesamte Fenster ausfüllt.
jf.add(new Example());

//Standardmäßig hat ein frame die minimale Größe, die vom Betriebssystem vorausgesetzt ist.
//pack() berechnet die benötigte Größe, um den Inhalt darzustellen, und vergrößert das Fenster.
jf.pack();
jf.setVisible(true);
}
Das ist also die vollständigen Main-Methode. Hier ist nochmal die vollständige Klasse:

package post01;


import java.awt.BorderLayout;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;


public class Example extends JPanel {
//Die "Signatur" einer main-Methode muss immer genau so aussehen
//public static void main(String[] args)

public static void main(String[] args) {
//Der frame ist ein object vom Typ JFrame
JFrame jf = new JFrame();
//Das ist nötig, damit das Programm sauber beendet wird, wenn man das Fenster schließt.
//Der Grund ist aber für diesen Post etwas zu viel
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//Wie bei jedem Component hat ein JFrame ein Jayout, und es können Components hinzugefügt
//werden. Das Standard-Layout für Frames ist BorderLayout. diese Zeile fügt unser panel also
//im Center Bereich ein, wo es das Gesamte Fenster ausfüllt.
jf.add(new Example());

//Standardmäßig hat ein frame die minimale Größe, die vom Betriebssystem vorausgesetzt ist.
//pack() berechnet die benötigte Größe, um den Inhalt darzustellen, und vergrößert das Fenster.
jf.pack();
jf.setVisible(true);
}

//Jetzt brauchen wir sie zwar noch nicht permanent, aber für später speichere ich die
//Komponenten in Attributen.
private JTextField tf;
private JButton b;
private JComboBox cb;

public Example() {
//super() ruft explizit den Konstruktor der Superklasse (JPanel in this case) auf.
//Ich benutze es, um das Objekt mit einem BorderLayout zu bestücken. Ich könnte aber
//auch später setLayout(new BorderLayout()); benutzen.
super(new BorderLayout());

//Der Parameter von JTextField gibt die Breite, in Textzeichen, an.
//Die Java API doc ist eine perfekte Quelle für solche Informationen.
tf = new JTextField(5);
cb = new JComboBox();
b = new JButton("Add");

//Das ist das Panel, das gleich Textfeld und Button enthalten wird.
JPanel north = new JPanel(new BorderLayout());

//Hier wird das Panel im North-Bereich hinzugefügt. Der zweite Parameter
//Gibt den Ort des panels an. BorderLayout hat für alle 5 Bereiche Konstanten.
add(north, BorderLayout.NORTH);

//Ohne zweiten Parameter, benutzt BorderLayout den Center-Bereich
north.add(tf);
north.add(b, BorderLayout.EAST);
add(cb, BorderLayout.SOUTH);
}
}
Das war's für heute! Fragen und Kommentare sind stark erwünscht ;)

3 Kommentare:

  1. bzgl. der 3 wege eine (java) gui zu erstellen:
    grafische editoren machen auch nichts anderes, als code zu generieren, der wiederum auf eine gui-library wie awt oder swing zugreift. zudem gibt es noch andere gui-libraries wie swt (+jface?). und auch für die gibt es meist grafische editoren :)

    nur aus intresse: in welche klasse gehst du?

    lg

    ps: syntax highlighting für deinen code hier im blog wäre schön ;)

    AntwortenLöschen
  2. Da hast du natürlich recht ;) Je nachdem, wie "abstrakt" der GUI-Editor arbeitet, ist aber die Library, die im Hintergrund verwendet wird, immer weniger einsehbar. Das geht dann in Richtung Deklarative Programmierung, wo aus dem GUI-Editor-spezifischen Modell der Library-spezifische Sourcecode generiert werden kann.
    Bei so einer abstrakten sicht kann dem Entwickler dann aber die Library schon "egal" sein. (so richtig egal natürlich nicht, aber du weißt wos hingeht)

    Die zusätzlichen Libraries waren mir jetzt gar nicht bewusst. aber zu meiner verteidigung muss ich sagen, dass der typische Anwender mit seiner Standard-JRE eben genau zugriff auf die beiden Libs hat

    ich bin bis vorgestern in die 3BHITS gegangen ;)

    Das mit Syntax highlighting ist natürlich ein super tip. ich werds versuchen, aber wenn das nicht leicht automatisierbar ist, ist es natürlich ein erheblicher aufwand ;)

    AntwortenLöschen
  3. eclipse und azureus basieren was die gui angeht komplett auf SWT/JFace ;)

    AntwortenLöschen