Posts mit dem Label GUI werden angezeigt. Alle Posts anzeigen
Posts mit dem Label GUI werden angezeigt. Alle Posts anzeigen

Mittwoch, 8. Juli 2009

Multithreading: Threads in grafischen Oberflächen

Sehen wir uns einmal kurz ein Programm an, das du ganz am Anfang in Java geschrieben haben könntest: Mit der Konsole eine Grundrechenoperation auf zwei positive ganze Zahlen anwenden.

package post06;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;


public class Grundrechnung {
public static void main(String[] args) throws IOException {
System.out.print("Gib eine Rechnung ein: ");
//Liest eine einzeilige Eingabe von der Konsole
String s = new BufferedReader(new InputStreamReader(System.in)).readLine();
//Der reguläre Ausdruck [*+/-] entspricht dem einmaligen Auftauchen eines der
//in eckigen Klammern aufgelisteten Zeichen. Das Bedeutet, dass split die beiden
//Zahlen, aber nicht den Operator zurückgibt. Wegen "-" sind negative Zahlen
//nicht möglich.
String[] parts = s.split("[*+/-]");
if(parts.length != 2) {
System.err.println("Rechnung falsch");
return;
}
//Den Operator erhält man, in dem man das Zeichen direkt nach der ersten Zahl nimmt.
char operator = s.charAt(parts[0].length());

int zahl1 = Integer.parseInt(parts[0]);
int zahl2 = Integer.parseInt(parts[1]);

switch(operator) {
case '+':
System.out.println("Das Ergebnis: " + (zahl1 + zahl2));
break;
case '-':
System.out.println("Das Ergebnis: " + (zahl1 - zahl2));
break;
case '*':
System.out.println("Das Ergebnis: " + (zahl1 * zahl2));
break;
case '/':
if(zahl2 == 0) System.err.println("Division durch 0!");
else System.out.println("Das Ergebnis: " + (zahl1 / zahl2));
break;
default:
System.err.println("Unzulässiger Operator");
break;
}
}
}
Der Ablauf ist hier ganz klar, in der Reihenfolge, in der die Anweisungen im Sourcecode auftauchen. Jetzt nehmen wir das GUI-Beispiel vom letzten mal.
Übersichtlich ist es auch: Wenn man auf den Button drückt, wird das Element hinzugefügt. Allerdings ist aus dem von uns geschriebenen Code nicht ersichtlich, wie dieses wenn zustande kommt.

Sicherlich hast du auch schon eine Fehlermeldung in so einem Programm gesehen:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at post06.EditListener.actionPerformed(EditListener.java:25)
at javax.swing.JTextField.fireActionPerformed(JTextField.java:492)
at javax.swing.JTextField.postActionEvent(JTextField.java:705)
at javax.swing.JTextField$NotifyAction.actionPerformed(JTextField.java:820)
at javax.swing.SwingUtilities.notifyAction(SwingUtilities.java:1636)
at javax.swing.JComponent.processKeyBinding(JComponent.java:2849)
at javax.swing.JComponent.processKeyBindings(JComponent.java:2884)
at javax.swing.JComponent.processKeyEvent(JComponent.java:2812)
at java.awt.Component.processEvent(Component.java:5993)
at java.awt.Container.processEvent(Container.java:2041)
at java.awt.Component.dispatchEventImpl(Component.java:4583)
at java.awt.Container.dispatchEventImpl(Container.java:2099)
at java.awt.Component.dispatchEvent(Component.java:4413)
at java.awt.KeyboardFocusManager.redispatchEvent(KeyboardFocusManager.java:1848)
at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(DefaultKeyboardFocusManager.java:704)
at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(DefaultKeyboardFocusManager.java:969)
at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(DefaultKeyboardFocusManager.java:841)
at java.awt.DefaultKeyboardFocusManager.dispatchEvent(DefaultKeyboardFocusManager.java:668)
at java.awt.Component.dispatchEventImpl(Component.java:4455)
at java.awt.Container.dispatchEventImpl(Container.java:2099)
at java.awt.Window.dispatchEventImpl(Window.java:2475)
at java.awt.Component.dispatchEvent(Component.java:4413)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Das ist die sogenannte "Stack Trace" der Exception: Die genaue Abfolge von Methodenaufrufen, die dazu geführt hat, dass die Exception aufgetreten ist.
Ganz oben steht in diesem Fall unsere Methode:
at post06.EditListener.actionPerformed(EditListener.java:25)
Das ist die Methode die die Exception geworfen hat. Direkt darunter steht die Methode, die unsere aufgerufen hat usw.

Im Gegensatz dazu einfach eine falsche Eingabe in dem neuen Beispiel ("a+1"):
Exception in thread "main" java.lang.NumberFormatException: For input string: "a"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
at java.lang.Integer.parseInt(Integer.java:447)
at java.lang.Integer.parseInt(Integer.java:497)
at post06.Grundrechnung.main(Grundrechnung.java:27)
Ich nehme mal an, dass ihr gesehen habt, worum es geht: Im oberen Fall ist der thread "AWT-EventQueue-0", im unteren "main". main bezieht sich einleuchtenderweise auf unsere main-Methode, die das Programm startet. Das sieht man auch in der Stack Trace: Die unterste Methode, die Methode, die alle anderen aufgerufen hat, ist die main-Methode.

alle anderen Methoden werden in main aufgerufen. Erst, wenn die Methode fertig ist, wird die nächste aufgerufen. Die Reihenfolge ist fix.

Aber was ist mit der anderen Stack Trace?
   at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Die unterste Methode ist nicht die main-Methode! Das bedeutet, dass unsere GUI einen eigenen, unabhängigen Programmablauf hat:

Auch in unserer GUI-Anwendung gibt es eine main-Methode, aber alles, was in der GUI passiert (z.B. die actionPerformed-Methode unseres Listeners), passiert in einem separaten Thread: Diese Dinge können gleichzeitig und unabhängig zum main-Thread passieren.

Das war eine ziemlich lange Einführung für das Thema. Was ich zeigen wollte ist:
  • Mit mehreren Threads können mehrere Aufgaben gleichzeitig ablaufen
  • Auch, wenn du es nicht gewusst hast, hast du sicher schon lange mit Threads gearbeitet
  • Swing (mit AWT zusammen) verwendet einen eigenen Thread dafür, dass die Benutzereingaben zu deinem Programm gelangen
  • Das ist notwendig, weil Benutzereingaben großteils warten bedeutet: Warten, bis eine Taste oder ein Button gedrückt wurde. Und warten braucht Zeit.
  • Ohne eigenen Thread könntest du in deinem Programm keine Sachen im Hintergrund machen, während die GUI angezeigt wird.
Das gilt aber auch in die andere Richtung: wenn deine actionPerformed-Methode eine Aufgabe ausführt, die sehr lange benötigt (Suche im Dateisystem, Netzwerkarbeit, Grafikoperationen, ...), dann blockiert die Methode den Swing-Thread.
Innerhalb des Threads gibt es, genau so wie im main-Thread, eine fixe Reihenfolge der Methodenaufrufe. Solange deine actionPerformed-Methode nicht abläuft, kann Swing keine Events bearbeiten. Das sieht dann so aus, dass deine Anwendung nicht reagiert.

Für heute war das lang genug, ein paar Praxisbeispiele machen wir später ;)

Montag, 6. Juli 2009

GUIs in Java: Event Handling

Die Oberflächen, die wir bis jetzt gemacht haben, haben vielleicht nett ausgesehen, aber überhaupt nichts gemacht. Deswegen schauen wir jetzt weiter. Am Anfang haben wir diese Oberfläche entworfen.

Jetzt geben wir ihr Funktionalität: Mit einem Klick auf Add soll der ComboBox der eingegebene Text als Eintrag hinzugefügt werden.
Zum Glück funktioniert auch Swing nach dem MVC-Prinzip. Denn ansonsten müsste man eine Subklasse von JButton machen, um dort die Aktion einzuprogrammieren. Stattdessen ist JButton dafür nicht verantwortlich. Du musst JButton nur sagen, wo er bescheid sagen muss, sobald er gedrückt wurde.

Was heißt bescheid sagen? Eine bestimmte Methode aufzurufen. zu diesem Zweck gibt es das interface ActionListener. Die einzige Methode, die ActionListener definiert, ist actionPerformed, und diese wird vom Button (oder anderem Component, mit dem der Listener registriert ist) aufgerufen, wenn die Action passiert ist.
Das Herzstück unserer Änderung ist also die Aktion, die durch den ActionListener durchgeführt werden soll. Um die Aktion durchzuführen, benötigt der Listener zwei Dinge: das Textfeld und die ComboBox. Das einzige, das der Listener vom Button benötigt, ist der Methodenaufruf zur richtigen Zeit. Informationen über den Button benötigt er nicht.

package post05;


import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JComboBox;
import javax.swing.JTextField;


public class EditListener implements ActionListener {
private JTextField tf;
private JComboBox cb;

public EditListener(JTextField tf, JComboBox cb) {
this.tf = tf;
this.cb = cb;
}

//@Override zeigt dem Compiler, dass die Methode in der Superklasse oder einem Interface
//definiert ist. dadurck können Tippfehler o.Ä. vermieden werden.
@Override
public void actionPerformed(ActionEvent e) {
cb.addItem(tf.getText());
}
}
Der nächste Schritt ist, dem Button zu sagen, dass bei einem Klick die Aktion ausgeführt werden soll. dazu ist nur eine Änderung in unserer GUI-Klasse vorzunehmen.

package post05;


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 {
//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");
ActionListener l = new EditListener(tf, cb);
b.addActionListener(l);

//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);
}
}
Aber ein kleines Makel gibt es immer noch. Wenn man das Programm laufen lässt, fallen drei Dinge auf:
  • Das Textfeld wird nicht geleert, wenn der Text hinzugefügt wird
  • Man muss das Textfeld erst markieren, bevor man wieder etwas eingeben kann
  • Die Enter-Taste bewirkt nach der Eingabe nichts
Der dritte Punkt ist eine Art, die Aktion auszulösen, es kommt deshalb in die GUI. Die ersten beiden sind Teil der Aktion, sind also Teil des Listeners.
Der Listener:

package post05;


import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JComboBox;
import javax.swing.JTextField;


public class EditListener implements ActionListener {
private JTextField tf;
private JComboBox cb;

public EditListener(JTextField tf, JComboBox cb) {
this.tf = tf;
this.cb = cb;
}

//@Override zeigt dem Compiler, dass die Methode in der Superklasse oder einem Interface
//definiert ist. dadurck können Tippfehler o.Ä. vermieden werden.
@Override
public void actionPerformed(ActionEvent e) {
cb.addItem(tf.getText());
tf.setText("");
tf.requestFocusInWindow();
}
}
...und die GUI:

package post05;


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;

import post05.EditListener;


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");
ActionListener l = new EditListener(tf, cb);
b.addActionListener(l);
tf.addActionListener(l);

//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);
}
}
Auch ein Textfeld kann einen ActionListener haben. Hier sieht man wieder, wie die Teile unabhängig sind und trotzdem zusammenarbeiten: Bei einem Textfeld wird ein ActionEvent durch die Enter-Taste ausgelöst, bei einem Button durch einen Klick. Trotzdem können beide den gleichen Code benutzen, um die Aktion umzusetzen.

Sonntag, 5. Juli 2009

GUIs in Java: Andere Layouts

Letztes mal haben wir ein bisschen mit Layouts gespielt, und dabei zwei BorderLayouts verwendet.

BorderLayout ist aber bei weitem nicht das letzte und mächtigste, das Java zu bieten hat:

FlowLayout:
FlowLayout kann man wirklich nicht als mächtig bezeichnen. Wichtig ist es allerdings, weil es das Standardlayout für Panels und Applets ist. Wenn in deinem Applet der Inhalt nur sehr klein erscheint, könnte es sein, dass du vergessen hast ein BorderLayout einzustellen. Denn das FlowLayout streckt den Inhalt nicht über den gesamten Component.
Alle Komponenten, die mit einem FlowLayout verwaltet werden (vergiss dabei nicht, dass das Layout immer auf einen Component angewendet wird, und diesem dann die Child-Components hinzugefügt werden), werden hintereinander in einer Zeile dargestellt. Ist die erste Zeile voll, wird in der nächsten weitergemacht.

Hier sieht man auch, wie die Zeilenhöhe zustandekommt: der höchste Component jeder Zeile bestimmt die Höhe der Zeile.

GridLayout:
GridLayout geht schon eher in die Richtung mächtig. GridLayout ermöglicht es, beliebig viele Komponenten in eine rechteckigen Gitter anzuordnen. Jeder Component erhält dabei genau gleich viel Platz.

Für dieses GridLayout wurden als Parameter als Zeilen- und Spaltenzahl 3 angegeben. Es hätte aber eine Angabe genügt (dafür nimmt man als den zweiten Parameter 0). Gibt man nur die Spaltenzahl an, wird die Zeilenzahl durch die Anzahl der Kind-Components bestimmt und andersherum.

BoxLayout:
BoxLayout hat Gemeinsamkeiten mit FlowLayout, ist aber um einiges nützlicher: In einem BoxLayout kann man die Komponenten in einer Zeile oder einer Spalte ausrichten.
In einem Spaltenorientierten BoxLayout hat jeder Component seine preferred height, und die breite wird (soweit möglich) an den breitesten component angepasst. In ähnlicher Art gilt das eben für zeilenorientierte BoxLayouts.

hier habe ich ein bisschen nachgeholfen. Das BoxLayout verwendet 3 Einstellungsmöglichkeiten eines Component: minimum size, maximum size und preferred size. Die maximum size eines Buttons ist standardmäßig genau so groß wie die preferred size. Damit diese Buttons auf gleiche Länge gestreckt werden, musste ich die maximum size manuell vergrößern.

CardLayout:
CardLayout ist ein spezielles Layout, das von allen hinzugefügten Components immer nur einen darstellt. Es könnte einmal eine Situation geben, wo du es brauchst, aber wahrscheinlich ist dann eine JTabbedPane sowieso die bessere Wahl. JTabbedPane stelln nämlich gleich die Registerkarten bereit, um zwischen den Tabs zu wechseln.


Darüber hinaus gibt es noch mehr, mächtigere Layouts, zum Beispiel:
GridBagLayout
GroupLayout
SpringLayout
Diese sind aber recht kompliziert zu verwenden und hauptsächlich für grafische Editoren gedacht.


Ein einzelnes Layout führt allerdings selten zum Ziel. Meistens wird man Panels mit verschiedenen Layouts Schachteln, um zum erwünschten Ergebnis zu kommen. Als Beispiel hier ein Formular, in das beliebig viele Eingabefelder eingefügt werden können:

Hier werden 3 Ebenen verwendet: Ganz oben ein BorderLayout. Der Button ist im South-Bereich, in der Mitte ist ein Panel für den Rest. Dieses Panel benutzt ein BoxLayout, in dem die Komponenten untereinander ausgerichtet ist.
Jede Zeile im BoxLayout stellt eine Eingabe dar. Weil ein BoxLayout beliebig viele Components enthalten kann, ist diese Struktur sehr flexibel.
Jede Eingabe ist wieder ein Panel mit BorderLayout. Das Label braucht nicht größer zu werden, deshalb ist es im West-Bereich. Das Eingabefeld soll den restlichen Platz verbrauchen und ist im Center-Bereich.

Solche komliziertere Layouts könnte man auch mit einem der oberen Layouts (GridBag, Group, Spring) realisieren, aber ohne einen Code-Editor sind verschachtelte Layouts ziemlich sicher schneller, kürzer und übersichtlicher, als eines dieser Layouts zu benutzen.

Für das letzte Beispiel gibts hier nochmal den Code:

package post02;


import java.awt.BorderLayout;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;


public class Form extends JPanel {
public static void main(String[] args) {
JFrame jf = new JFrame();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

//Ein Array kann man bei der Deklaration der Variable so initialisieren:
//String[] s = {...};
//Wenn man nicht gerade eine Variable deklariert kann man aber auch
//new String[] {...}
//benutzen.
jf.add(new Form(new String[] {"Vorname:", "Nachname:", "Adresse:"}));

jf.pack();
jf.setVisible(true);
}

private String[] labels;
private JTextField[] textFields;
private JButton ok;

public Form(String[] labels) {
super(new BorderLayout());

this.labels = labels;
textFields = new JTextField[labels.length];

JPanel form = new JPanel();
//Der Konstruktor von BoxLayout nimmt (im Gegensatz zu den anderen üblichen Layouts) auch den Component,
//mit dem es verwendet werden soll. Deshalb kann man das BoxLayout auch noch nicht im Konstruktor von
//JPanel angeben
form.setLayout(new BoxLayout(form, BoxLayout.Y_AXIS));
for(int i = 0; i < labels.length; i++) {
JLabel l = new JLabel(labels[i]);
textFields[i] = new JTextField(5);

JPanel p = new JPanel(new BorderLayout());
p.add(l, BorderLayout.WEST);
p.add(textFields[i]);
form.add(p);
}

add(form);
//hier wird gleichzeitig eine Zuweisung gemacht und die Variable abgefragt. Das ist also das gleiche wie
//ok = new JButton("OK");
//odd(ok, ...);
add(ok = new JButton("OK"), BorderLayout.SOUTH);
}
}

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 ;)