GUI programmieren
Grundlage für die Programmierung einer grafischen
Benutzeroberfläche ist die Kenntnis des Abstract Windowing Toolkit
(kurz: AWT), Ereignissen, AWT-Komponenten und Layout-Managern.
AWT - Abstract Windowing Toolkit
Das AWT ist neben SWING und SWT (nur in der Entwicklungsumgebung
"Eclipse" enthalten) eine Klassenbibliothek, um grafische
Anwendungen zu programmieren. AWT benutzt entgegen den anderen
beiden Klassenbibliotheken das darunter liegende Betriebssystem um
seine grafischen Elemente anzuzeigen.
AWT stellt folgende Funktionalität bereit:
- Anzeige von Bitmaps
- Abspielen von Sound
- Zeichnen geometrischer Objekte
- Schriften
- Komponenten (Schaltflächen, Menüs,...)
- Ereignisbehandlung
Die Klassenhierarchie sieht folgendermaßen aus: |
|
Abb. 2.1: Klassenhierarchie
AWT
|
|
Unterhalb der Klasse
java.util.EventObject befindet sich die Klasse java.awt.EventObject,
die für die Ereignisbehandlung zuständig ist. Der Zweig
java.awt.Component enthält alle Komponenten, die zum Bau einer
grafischen Benutzeroberfläche benötigt werden. |
Klassenname |
Aufgabe |
java.awt.Component |
- Größe und Position einer Komponente
- Grafische Eigenschaften
- Ereignisse empfangen
|
java.awt.Container |
- Aufnahme von Komponenten
- Entfernen von Komponenten
- Anordnen von Komponenten
|
java.awt.Window |
- Erzeugt Top-Level-Fenster ohne Rahmen, Menü oder
Titelleiste
|
java.awt.Frame |
- Erzeugt Fenster, wie es uns von Windows oder KDE/Gnome
bekannt ist
|
|
Aufbau eines Fensters
Quelle: [Moser, 2004]
|
|
Abb. 2.2: Elemente eines
Fensters der Klasse java.awt.Frame
|
|
Über die Klassendokumentation von
java.awt.Frame erhält man Zugriff auf die Konstruktoren und
Methoden, die diese Klasse zur Verfügung stellt. Mit diesem Link
kann auf die Onlinedokumentation von Sun zugegriffen werden. Unter
Package "java.awt" --> Classes "Frame" findet
man diese Angaben.
Folgender Beispielcode, der nur ein Label mit Text anzeigt.
Außerdem das Schließfeld implementiert. |
import java.awt.*;
import java.awt.event.*;
public class LabelFenster extends Frame {
Label lbl; // Fensterkomponenten deklarieren
// Konstruktor
public LabelFenster () {
// Fenster
super("Label Fenster Beispiel"); // Titel des Fensters
this.setLocation(50,50); // Position des Fensters
this.setSize(200, 100); // Groesse des Fensters
this.setBackground(Color.lightGray); // Hintergr.Farbe
this.setLayout(null); // Layoutmanager ausschalten
// Label
this.lbl = new Label("Hallo liebe Benutzer!"); //Nachricht
this.lbl.setLocation(30,40); // Position Label
this.lbl.setSize(150, 30); // Groesse Label
this.add(this.lbl); // Label Fenster hinzufuegen
// Fenster "vollenden"
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
System.exit(0);
}
});
// Fenster anzeigen
this.setVisible(true);
}
public static void main(String [] args) {
LabelFenster fenster = new LabelFenster();
}
} |
Tab. 2.1: Beispieldatei
FensterLabel.java |
|
In diesem Programm werden ganz
grundlegende Schritte angewandt, um eine Fenster zu programmieren.
- Es müssen die richtigen Klassen importiert werden (java.awt.*
und java.awt.event.*)
- Die zu erstellende Klasse "LabelFenster" muss von
Klasse java.awt.Frame abgeleitet werden.
- Die Fensterkomponenten müssen deklariert werden.
- Anschließend wird im Konstruktor der Klasse "LabelFenster"
das Fenster aufgebaut.
- In der main-Methode wird ein Objekt vom Typ LabelFenster
erstellt.
- Fertig!
zu 4.: Fenster aufbauen:
- Fenster programmieren
- Komponenten (hier: lbl) programmieren
- WindowAdapter, um die Fensterfelder Minimieren, Maximieren und
Schließen zu programmieren
- Fenster anzeigen.
Die gebräuchlichsten Methoden um ein Fenster zu programmieren
sind in diesem Beispiel aufgeführt. Die Aufgaben sind in der Zeile
als Kommentare enthalten. Nähere Informationen finden Sie in der
Java-Dokumentation in der entsprechenden Klasse.
Ereignisse
Ereignisse ermöglichen erst die Kommunikation des Benutzers mit
der GUI.
Ereignisse bestehen aus einer Ereignisquelle und einem
Ereignisziel. Die Ereignisquelle stellen Komponenten dar, zum
Beispiel Schaltflächen und Textfelder. Ereignisziele sind so
genannte Listener, die auf ein Ereignisauslösung warten.
Das Zusammenspiel zwischen Ereignisquelle und Ereignisziel
geschieht über das Delegation Event Modell.
Delegation Event Modell
Um ein Ereignis behandeln zu können muss ein Ereignisziel sich
bei einer Ereignisquelle registrieren. Danach kann eine
Ereignisquelle das Ereignis an das Ereignisziel zur Verarbeitung
weitergeben. |
|
Abb. 2.3: Delegation Event
Modell
|
|
Ereignisklassen
Es gibt Low-Level-Ereignisse und sematische Ereignisse.
Low-Level-Ereignisse können den grafischen Komponenten des Fensters
zugeordnet werden. Semantische Ereignisse beziehen sich auf
"höherwertigere" Ereignisse wie Zustandsänderungen oder
Aktionen. |
|
Abb. 2.4: Klassenhierarchie
Ereignisklassen
|
|
Listener-Interfaces
Diese Listener-Interfaces sind dafür zuständig, dass Ereignisse entgegengenommen werden können. |
|
Abb. 2.5: Listener
Klassenhierarchie
|
|
Bringt man alles zusammen, so erhält man
zum Beispiel folgenden Quelltext: |
import java.awt.*;
import java.awt.event.*;
public class EingabeFenster extends Frame implements MouseListener {
Label lbl, lblAusgabe;
TextField txtEingabe;
Button btn;
public EingabeFenster() {
// Fenster
super("Eingabe Fenster Beispiel");
this.setLocation(50,50);
this.setSize(300, 200);
this.setBackground(Color.lightGray);
this.setLayout(null);
// Textfield txtEingabe
txtEingabe = new TextField();
txtEingabe.setLocation(20,30);
txtEingabe.setSize(250, 20);
this.add(txtEingabe);
// Label lbl
lbl = new Label("Eingegebener Text:");
lbl.setLocation(20,60);
lbl.setSize(250,30);
this.add(lbl);
// Label lblAusgabe
lblAusgabe = new Label("");
lblAusgabe.setLocation(20,90);
lblAusgabe.setSize(250,30);
this.add(lblAusgabe);
// Button btn
btn = new Button("Text anzeigen!");
btn.setLocation(20,150);
btn.setSize(100,30);
btn.addMouseListener(this);
this.add(btn);
// Ereignisbehandlung ueber Adapterklasse
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
System.exit(0);
}
});
this.setVisible(true);
}
// Ereignisbehandlung ueber Interface
public void mouseClicked(MouseEvent me) {
lblAusgabe.setText(txtEingabe.getText());
txtEingabe.setText("");
txtEingabe.requestFocus();
}
public void mouseReleased(MouseEvent me) {}
public void mousePressed(MouseEvent me) {}
public void mouseEntered(MouseEvent me) {}
public void mouseExited(MouseEvent me) {}
public static void main(String[] args) {
EingabeFenster fenster = new EingabeFenster();
}
} |
Tab. 2.2: Beispieldatei
"EingabeFenster.java |
|
Wichtig ist an dieser Stelle, dass
verstanden wird, welche Komponente des Fensters zu einer
Ereignisquelle wird und welche nicht. In diesem Fall ist es
ausschließlich der Button btn, da die anderen Komponenten nur
passiv sind. Das Textfeld ist ebenfalls nur passiv, da die
Texteingabe nicht durch das Textfeld gesteuert wird, sondern vom
Button. Passive Fensterkomponenten können aber dennoch mit Daten
gefüttert werden. Sie lösen nur keine Ereignisse aus. |
|
Abb. 2.6: Delegation Event
Modell zu Beispiel EingabeFenster.java
|
|
Registrieren
Das Registrieren erfolgt beim definieren des Buttons. Hier wird
dem Ereignisziel mitgeteilt, dass der Button ein Ereignis liefert.
Dieses Ereignis ist ein Mausklick auf den Button, der vom
MouseListener übernommen wird.
Delegieren
Geschieht durch das ausprogrammieren der Methoden, die das
MouseListener-Interface bereitstellt. Falls dem MouseListener
mehrere Ereignisquellen zugeordnet sind, muss durch eine if-Abfrage
die Ereignisquelle bestimmt werden. Das ist hier aber nicht der
Fall. Darum wird die Methode mouseClicked(MouseEvent me) mit der
konkreten Aktion, die beim Klick auf den Button ausgelöst wird,
ausprogrammiert. Also wird der Text des Textfeldes direkt im Label
ausgegeben. Anschließend wird das Textfeld gelöscht und angewählt
Ereignisbehandlung implementieren
Im oberen Beispiel sind zwei Arten der Benutzung von
Ereignisbehandlung vorgegeben. |
Implementierungsart |
Anwendung |
Adapterklasse |
Interfaceklassen mit vielen Methoden |
Interface implementieren |
Interfaceklassen mit wenigen Methoden |
|
Adapterklasse
// Ereignisbehandlung ueber
Adapterklasse
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
System.exit(0);
}
});
Wie oben erwähnt müssen bei einer Adapterklasse nur die
Methoden implementiert werden, die tatsächlich benötigt werden. In
diesem Fall wird das Schließenfeld eines Fensters mit dem Beenden
des Programms belegt. Würde man diese Funktion über ein Interface
implementieren, dann müssten alle Methoden aufgeführt werden. Egal
ob mit oder ohne Funktion.
Interface implementieren
public class EingabeFenster extends Frame
implements MouseListener {
...
//
Ereignisbehandlung ueber Interface
public void mouseClicked(MouseEvent me) {
lblAusgabe.setText(txtEingabe.getText());
txtEingabe.setText("");
txtEingabe.requestFocus();
}
public void mouseReleased(MouseEvent me) {}
public void mousePressed(MouseEvent me) {}
public void mouseEntered(MouseEvent me) {}
public void mouseExited(MouseEvent me) {}
...
}
Hier wird die Ereignisbehandlung über das Implementieren eines
Interface erreicht. Das Gute daran ist, dass die potentiellen
Möglichkeiten dieses Listeners dargestellt sind. Der Nachteil ist,
dass leere Methoden übernommen werden müssen.
Darum wird je nach Umfang des Interface zwischen den beiden
Implementierungsarten (Adapterklasse oder Interface) gewechselt.
Layoutmanager
Ein Layoutmanager übernimmt die Anordnung der Fensterkomponenten
automatisch. In den bisherigen Beispielen wird kein Layoutmanager
verwendet.
this.setLayout(null);
Folgende Layoutmanager stehen zur Auswahl: |
|
Abb. 2.7: Layoutmanager
|
|
Unabhängig davon ob ein Layoutmanager
eingesetzt wird oder nicht werden die Elemente genau in der
Reihenfolge ins Fenster eingefügt in der sie durch den Befehl
this.add(Komponente);
tatsächlich dem Fenster zugeordnet werden. Das ist besonders zu
beachten, da Fenster auch mit der Tastatur steuerbar sind
(Tabulator-Taste). Falls die Reihenfolge eine Rolle spielen soll, müssen
die Komponenten in der selben Reihenfolge dem Fenster zugeordnet
werden, wie die logische Anordnung auf dem Bildschirm sein soll.
Trennen von Applikationslogik und grafischer Benutzeroberfläche
Wie dem Codeumfang im letzten Beispiel entnommen werden kann, so
wird ein ausgewachsenes GUI-Programm sehr umfangreich. Dadurch ist
es wichtig eine Dreiteilung des Codes vorzunehmen:
- Hauptprogramm
- GUI
- Anwendungslogik
Dazu das obere Beispiel darauf abgebildet:
|
import java.awt.*;
import java.awt.event.*;
public class EventDelegationFenster {
// Part 1: Hauptprogramm
public static void main(String[] args) {
EreignisBehandlung ereignis = new EreignisBehandlung();
EDFenster fenster = new EDFenster(ereignis);
}
}
// Part 2: GUI
class EDFenster extends Frame {
Label lbl, lblAusgabe;
TextField txtEingabe;
Button btn;
public EDFenster(EreignisBehandlung ereignis) {
// Fenster
super("Eingabe Fenster Beispiel");
this.setLocation(50,50);
this.setSize(300, 200);
this.setBackground(Color.lightGray);
this.setLayout(null);
// Textfield txtEingabe
txtEingabe = new TextField();
txtEingabe.setLocation(20,30);
txtEingabe.setSize(250, 20);
this.add(txtEingabe);
// Label lbl
lbl = new Label("Eingegebener Text:");
lbl.setLocation(20,60);
lbl.setSize(250,30);
this.add(lbl);
// Label lblAusgabe
lblAusgabe = new Label("");
lblAusgabe.setLocation(20,90);
lblAusgabe.setSize(250,30);
this.add(lblAusgabe);
// Button btn
btn = new Button("Text anzeigen!");
btn.setLocation(20,150);
btn.setSize(100,30);
this.add(btn);
this.setVisible(true);
ereignis.setFenster(this);
// Ereignisbehandlung
btn.addMouseListener(ereignis);
this.addWindowListener(ereignis);
}
}
// Part 3: Anwendungslogik
class EreignisBehandlung implements WindowListener, MouseListener {
// GUI
private EDFenster fenster;
public void setFenster(EDFenster fenster) {
this.fenster = fenster;
}
// Alle Methoden des Interface WindowListener
public void windowClosing(WindowEvent we) {
System.exit(0);
}
public void windowOpened(WindowEvent we) {}
public void windowActivated(WindowEvent we) {}
public void windowClosed(WindowEvent we) {}
public void windowDeactivated(WindowEvent we) {}
public void windowDeiconified(WindowEvent we) {}
public void windowIconified(WindowEvent we) {}
// Alle Methoden des Interface MouseListener
public void mouseClicked(MouseEvent me) {
fenster.lblAusgabe.setText(fenster.txtEingabe.getText());
fenster.txtEingabe.setText("");
fenster.txtEingabe.requestFocus();
}
public void mouseReleased(MouseEvent me) {}
public void mousePressed(MouseEvent me) {}
public void mouseEntered(MouseEvent me) {}
public void mouseExited(MouseEvent me) {}
} |
Tab. 2.3: Beispieldatei
EventDelegationFenster.java |
|
Hier hat eine Trennung stattgefunden. Im
Gegensatz zu dem ursprünglichen Beispiel nimmt der Umfang an
geschriebenem Code zu. Bei steigender Komplexität bleibt der Code
jedoch lesbar.
Die wichtigsten Elemente des oberen Codes werden kurz erläutert
:public class
EventDelegationFenster {
// Part 1: Hauptprogramm
public static void main(String[] args) {
EreignisBehandlung ereignis = new EreignisBehandlung();
EDFenster fenster = new EDFenster(ereignis);
}
}
Im Hauptprogramm werden die beiden Klassen instanziiert. Zuerst
das Objekt ereignis vom Typ EreignisBehandlung. Anschließend wird
dieses Objekt ereignis an das Objekt fenster vom Typ EDFenster
übergeben.
// Part 2: GUI
class EDFenster extends Frame {
Label lbl, lblAusgabe;
TextField txtEingabe;
Button btn;
public EDFenster(EreignisBehandlung ereignis) { //
1.
// Fenster
super("Eingabe Fenster Beispiel");
...
// Textfield txtEingabe
txtEingabe = new TextField();
txtEingabe.setLocation(20,30);
txtEingabe.setSize(250, 20);
this.add(txtEingabe);
// Label lbl
...
// Label lblAusgabe
lblAusgabe = new Label("");
lblAusgabe.setLocation(20,90);
lblAusgabe.setSize(250,30);
this.add(lblAusgabe);
// Button btn
...
this.setVisible(true);
ereignis.setFenster(this); // 2.
// Ereignisbehandlung
btn.addMouseListener(ereignis); // 3.
this.addWindowListener(ereignis); // 3.
}
}
Die Klasse EDFenster dient dem Aufbau der GUI. Somit
unterscheidet sie sich nur an manchen Stellen vom ursprünglichen
Beispiel aus Tab. 2.2. Diese Stellen sind fett hervorgehoben:
- Konstruktor übernimmt Objekt vom Typ Ereignisbehandlung.
- Bei den Abschnitt ereignis.setFenster(this) wird die GUI beim
Objekt ereignis registriert.
- Den Ereignis-Listenern wird am Ende das Ereignis übergeben.
// Part 3: Anwendungslogik
class EreignisBehandlung implements WindowListener, MouseListener {
// GUI
EDFenster
fenster;
public void setFenster(EDFenster fenster) {
this.fenster = fenster;
}
// Alle Methoden des Interface WindowListener
public void windowClosing(WindowEvent we) {
System.exit(0);
}
...
// Alle Methoden des Interface MouseListener
public void mouseClicked(MouseEvent me) {
fenster.lblAusgabe.setText(fenster.txtEingabe.getText());
fenster.txtEingabe.setText("");
fenster.txtEingabe.requestFocus();
}
...
}
Im letzten Abschnitt wird die Anwendungslogik programmiert. Damit
die grafische Benutzeroberfläche GUI angesprochen werden kann, muss
sie über die set-Methode dem Objekt ereignis bekannt gemacht
werden.
Somit können alle Fensterkomponenten problemlos in den Methoden angesprochen werden. |
|
Fazit
In diesem Kapitel haben wir die Programmierung von grafischen
Benutzeroberflächen erlernt.
Dazu gehört das Verständnis des Abstract Windowing Toolkit (AWT),
Ereignisse und die Ereignisbehandlung, Layoutmanger und schließlich
die Trennung von Anwendungslogik und GUI.
Mit diesen Grundlagen sind wir für die weiteren Kapitel über
Patterns - Entwurfmuster - bestens gerüstet. |