Oberflächenprogrammierung mit Swing

Grafische Benutzeroberflächen in Java

  • GUI: englische Abkürzung für "Graphical User Interface" oft auch mit UI (User Interface) abgekürzt
  • Ziel graphischer Benutzeroberflächen:
    • Intuitiv bedienbare Benutzerschnittstellen für ungeübte Benutzer
    • Optionen zu effizienten Benutzung von erfahrenen Benutzern (Kommandos auf Befehlstasten, Tabulatoren zum Bewegen zwischen Eingabefeldern etc.)
  • Vorteil von Java-GUIs
    • Plattformunabhängigkeit (write once, run anywhere)

Entwicklungsgeschichte der grafischen Benutzerschnittstellen in Java (JRE)

Swing ist das Ergebnis einer Evolution der grafischen Benutzerschnittstellen die seit 1996 immer weiterentwickelt wurden. Der geschichtliche Überblick ist hilfreich beim Verstehen der recht komplexen Paketstrukturen von Swing und AWT.

Java 1.0: Another Windowing Toolkit (AWT)

Die ursprüngliche Grafikbibliothek AWT sollte die folgenden Anforderungen erfüllen

  • einfach zu verstehen und zu Programmieren ("Volks GUI")
    • weniger komplex als das damals populäre X11, Motif von Unix-Workstations
    • unabhängig von Windows (Rechte, Lizensen!)
    • Kleine Teilmenge der wichtigsten GUI Elemente
  • geplanter Einsatz Browser-Applets und TV-Settop Boxen (Auf Neudeutsch "Kabeltuner")
    • Internet Browsers zu mehr Interaktion und mehr Intelligenz zu verhelfen
  • leicht auf unterschiedlichen Betriebssytemen zu implementieren

AWT hat daher die folgenden Eigenschaften

  • nur wenige GUI Komponenten
  • HeavyWeight Implementierung (direkte Abbildung auf Betriebssystemkomponenten)
  • Threadsicher
  • keine Model-View-Controller Architektur

Java 1.1: AWT + Java Foundation Classes (JFC)

Das AWT musste um eine objektorientierte Ereignissteuerung erweitert werden da das Implementieren von GUIs mit vielen Komponenten sehr unübersichtich war

Gleichzeitig wurde ein vollkommenes Neudesign mit einem optionalen Package (javax Pakete) mit dem Projektnamen Swing (engl. Schaukel) vorgestellt. Der offizielle Name war "Java Foundation Classes".

Da JFC zum Zugriff auf die Betriebssytemkomponenten AWT benutzen muss, ist es von AWT abhängig. JFC ist eine "light weight" Implementierung und daher stärker vom gegebenen Betriebssytemen entkoppelt.

Duke auf Schaukel

Java 1.2: JFC/Swing Standard GUI

  • Jahr 1998

JFC (Swing) muss nicht mehr separat geladen werden und ist Bestandteil des Standard JREs. Es verdrängt AWT mehr und mehr. Seine wichtigsten Eigenschaften sind

Swing wurde seit JRE 1.2 kontinuierlich weiterentwickelt und erhielt in jeder neuen JRE Version zusätzliche Eigenschaften.

Die Demoanwendung SwingSet2 ist eine gute Referenzanwendung in der sehr viele Komponenten verwendet werden.

"Lightweight" versus "Heavyweight" Implementierungen

Man spricht von Heavyweight Komponenten wenn zu ihrer Implementierung die vom Betriebssytem zu Verfügung gestellten Komponenten verwendet werden.

In einer Heavyweight Implementierung wird z.Bsp. zur Anzeige einer Menüauswahliste direkt die Menüauswahlliste des Betriebssytem verwendet.

Man spricht von einer Lightweight Implementierung wenn eine Grafikbibliothek vom Betriebsystem nur einen Pixelbereich auf dem Bildschirm nutzt und dann die Komponenten selbst zeichnet (rendered). Bei einer Lightweigtimplemenierung muss die Anwendung zum Beispiel eine Menüauswahlliste selbst auf dem Bildschirm zeichnen und selbst darauf achten, welche Bereiche des Fenster überschrieben werden.

Abwägung
  Vorteile Nachteile
Heavyweight
  • Schnell, automatische Grafikbeschleunigung
  • Perfekte Abstimmung mit anderen grafischen Elementen des OS
  • Look & Feel des Betriebssystems
  • Plattformabhängig
    • Ereignisse
    • Komponenten
    • Look & Feel
Lightweight
  •  Plattformunabhängig
    • einheitliches Modell für Ereignisse
    • Look & Feel frei wählbar
    • Es können Ereignisse und Komponenten modelliert werden die eine Plattform nicht bietet
  • Plattform kennt Ereignisse und Komponenten nicht
  • Grafikbeschleunigung kann fehlen (nicht mehr relevant in den aktuellen Java Versionen)
  • Komplexere Interaktion mit grafischen Komponenten anderer Programme (Zwischenablagen, Verdecken von Bereichen etc.)

 

Anmerkung: Die historischen (vor 2005) Probleme von Swing sind in den aktuellen Javaversion nicht mehr vorhanden. Moderne Garbage-Kollektoren blockieren die Benutzerschnittstellen nicht mehr sichtbar. Die aktuelle 2D Bibliothek von Java zeichnet in der Regel ausreichend schnell und benutzt auf den gängigen Plattformen (Windows, Mac, Linux) die Betriebssystemoptimierungen für Fonts, 2D- und 3D-Operationen. JavaFX stellt auch die nötigen hardwareunterstützten Operationen für Bildtransformationen und Filme zur Verfügung.

Implementieren von einfachen Swingkomponenten

Swing-Komponenten

Eine sehr anschauliche Übersicht über die existierenden Swing-Komponenten kann man in de Oracle Tutorials finden.

Die SwingSet2 Demo zeigt viele Komponenten in einer einzigen Anwendung. Man startet Sie nach dem Herunterladen von Google Code so:

java -jar SwingSet2.jar
Definition
Komponente (In Java Swing)
Komponenten sind grafische Bereiche die mit dem Benutzer interagieren können.

 

Toplevel Container

Swing verfügt über drei "Top-level-Container" in denen Benutzeroberflächen implementiert werden können:

  • JFrame: reguläre freistehende Programmfenster mit (optionaler) Menüleiste
  • JDialog: Eine Dialog-Box für modale Dialoge wie z.Bsp. Fehlermeldungen
  • JWindow: Ein freistehendes Fenster ohne Menüleisten, ohne Buttons zur Verwaltung des Fensters
Definition
Container (In Java Swing)
Ein Container ist eine Komponente die andere AWT Kompomenten enthalten kann.

Fenster: Klasse JFrame

Fenster können mit der Klasse JFrame erzeugt werden. Das folgende Beispielprogramm erzeugt ein einfaches Fenster der Größe 300 Pixel x 100 Pixel mit einem Titel in der Kopfleiste:

package s2.swing;

import javax.swing.JFrame;

public class JFrameTest {
    public static void main(String[] args) {
        JFrame myFrame = new JFrame("Einfaches Fenster");
        myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Beende Anwendung bei Schließen des Fensters
        myFrame.setSize(300,100); // Fenstergroesse 300x100 Pixel
        myFrame.setVisible(true); // Mache Fenster sichtbar
    }
}

Erzeugt das folgende Fenster (MacOS):

Die Klasse JFrame erbt die Eigenschaften von Component, Container, Window und Frame aus dem AWT Paket:

 

Die vollständge Beschreibung findet man in der Java API Dokumentation.

 Ein JFrame besteht aus mehreren Ebenen in denen unterschiedliche grafische Objekt positioniert sind:

Die Verwendung der unterschiedlichen Ebenen ist ein fortgeschrittenes Thema und wird im Rahmen des Kurses nicht behandelt. Es ist jedoch wichtig zu wissen, dass diese verschiedenen Ebenen existieren. Das Glass ane erlaubt beispielsweise eine unsichtbare Struktur über das GUI zu legen und damit zum Beispiel Mausereignisse abzufangen. Mehr Informationen sind z. Bsp. in der Oracle Dokumentation zu finden.

Buttons: Die Klasse JButton

Buttons können leicht mit der Klasse JButton erzeugt werden. Sie können mit der add() Methode dem Fenster hinzugefügt werden da das JFrame als Container in der Lage ist Komponenten aufzunehmen.

Beispiel: JButton in einem JFrame

package s2.swing;

import javax.swing.JButton;
import javax.swing.JFrame;

public class JFrameButtonTest {
public static void main(String[] args) {
JFrame myFrame = new JFrame("Einfaches Fenster");
myFrame.setVisible(true);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myFrame.add(new JButton("Click mich ich bin ein JButton!"));
myFrame.setSize(300,100);
myFrame.setVisible(true);
}
}

Ergibt das folgende Fenster:

 Swing bietet eine Reihe von Varianten von Buttons an auch in Menüleisten verwendet werden können. Die entsprechenden Klassen werden von der Klasse AbstractButton abgeleitet:

Textfelder: Klasse JTextComponent und JTextField

Swing erlaubt die Verwaltung von Eingabetextfeldern durch die Klasse JTextField. Die Klasse JTextField ist die einfachste Möglichkeit Text auszugeben und einzugeben wie im folgenden Beispiel zu sehen ist.

Beispiel: JTextfield in JFrame

package s2.swing;
import javax.swing.JFrame;
import javax.swing.JTextField;

public class TextfeldTest {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Ein Fenster mit Textfeld");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JTextField("Editier mich. Ich bin ein JTexfield", 60));
        frame.setSize(300, 100);
        frame.setVisible(true);
    }
}

Das Programm erzeugt bei der Ausführung das folgende Fenster:

Beispiel: JTextField in einem Applet/Hauptprogramm

 

Swing bietet eine ganze Reihe von Möglichkeiten mit Texten umzugehen wie sich aus der Klassenhierarchie von JTtextComponent ergibt:

 

Beschriftungen: Klasse JLabel

Einfache, feste Texte für Beschriftungen werden in Swing mit der Klasse JLabel implementiert:

package s2.swing;

import javax.swing.JFrame;
import javax.swing.JLabel;

/**
*
* @author s@scalingbits.com
*/
public class JLabelTest {

public static void main(String[] args) {
JFrame f = new JFrame("Das Fenster zur Welt!");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JLabel("Hinweistext!"));
f.setSize(100, 80);
f.setVisible(true);
}
}

Das Programm erzeugt bei der Ausführung das folgende Fenster:

 

Container, Behälter für andere Komponenten: Klasse JPanel

Zum Anordnen und Positionieren von Komponenten wird die Klasse JPanel verwendet. Sie ist ein Behälter der andere Komponenten aus der JComponent-Hierarchie verwalten kann. Dies sind z.Bsp.:

  • andere JPanels
  • JButton
  • JTextField
  • JLabel etc.

JPanels haben zwei weitere wichtige Eigenschaften

  • sie können nur ihren eigenen Hintergrund zeichnen
  • sie benutzen Layoutmanager um die Positionierung der JComponenten durchzuführen

Jede Instanz eines JPanels hat einen eigenen Layoutmanager. Da typischerweise ein Layoutmanager nicht ausreicht, werden JPanels und deren Layoutmanager geschachtelt.

Beispiel: JTextfield in JFrame

import javax.swing.JFrame;
import javax.swing.JTextField;
/**
*
* @author s@scalingbits.com
*/ public class TextfeldTest {    public static void main(String[] args) {       JFrame frame = new JFrame("Ein Fenster mit Textfeld");       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);       frame.add(new JTextField("Editier mich. Ich bin ein JTextfield", 60));       frame.setSize(300, 100);       frame.setVisible(true); } }

Das Programm erzeugt bei der Ausführung das folgende Fenster:

Beispiel: JTextField in einem Applet

Quellcode
package s2.swing;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

/**
*
* @author s@scalingbits.com
*/
public class JPanelJApplet extends JApplet implements ActionListener {
final private JTextField myTextField;

public JPanelJApplet() {
myTextField =
new JTextField("Editier mich. Ich bin ein JTextfield", 25);
myTextField.addActionListener(this);

JButton myButton = new JButton("Click mich");
myButton.addActionListener(this);

JLabel myLabel = new JLabel("Ich bin ein JLabel");

JPanel myPanel = new JPanel();
myPanel.add(myButton, BorderLayout.NORTH);
myPanel.add(myTextField, BorderLayout.CENTER);
myPanel.add(myLabel, BorderLayout.SOUTH);

Container myPane = getContentPane();
myPane.add(myPanel);
}

@Override
public void actionPerformed(ActionEvent e) {
String eingabe = myTextField.getText();
StringBuffer st = new StringBuffer(eingabe.length());
for (int i = (eingabe.length() - 1); i >= 0; i--)
st.append(eingabe.charAt(i));
myTextField.setText(st.toString());
}
}

 

Layoutmanager

Layoutmanager erlauben die Anordnung von Komponenten in einem Container. Abhängig vom gewählten Manager werden die Komponenten in ihrer gewünschten oder in einer gestreckten, bzw. gestauchten Form angezeigt. Die Feinheiten der Layout-Manager werden hier nicht behandelt. Es werden auch nur die wichtigsten Layout-Manager vorgestellt. Die Swingdokumentation ist für die Entwicklung von GUIs unerlässlich.

Definition: Layout-Manager
Ein Layout-Manager ist ein Objekt, welches Methoden bereitstellt, um die grafische Repräsentation verschiedener Komponenten innerhalb eines Container-Objektes anzuordnen

 

Wichtig: Eine absolute Positionierung von Komponenten ist ungünstig, da Fenster eine unterschiedliche Größe haben können.

Java biete eine Reihe von Layout-Managern. Im Folgenden wird eine Auswahl vorgestellt:

Flowlayout

Das FlowLayout ist das einfachste und Standardlayout. Es wird benutzt wenn kein anderer Layoutmanager angeben wird. In ihm werden die Komponenten in der Reihenfolge des Einfügens von links nach rechts eingefügt.

Strategie: Alle Komponenten werden in einer Reihe wie in einem Fließtext angeordnet. Reicht die gegebene Breite nicht erfolgt ein Umbruch mit einer neuen Zeile.

Dem JPanel jp wird im Folgenden kein expliziter LayoutManager mitgegeben um die 6 Knöpfe zu verwalten es wird der FlowLayoutmanager als Standardeinstellung verwendet:

package s2.swing;

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

/**
* Zeigt einen sehr einfachen FlowlayoutManager mit 5 Buttons
* @author s@scalingbits.com
*/
public class FlowlayoutTest {     /**
* Hauptmethode
* @param args Es werden keine Parameter ausgewertet
*/
public static void main(String[] args) {         JFrame f = new JFrame("FlowLayout");         JPanel jp = new JPanel();         for (char c = 0; c <= 5; ++c) { // Stecke 6 Buttons in das Panel             jp.add(new JButton("Button " + (char)('A'+c)));         }         f.add(jp); // Füge Panel zu Frame         //Beende Anwendung beim Schliesen des Fensters f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);         f.pack(); // Berechne Layout         f.setVisible(true);// Zeige alles an     } }

Ergibt ein Fenster mit dem folgenden Layout:

Bei mangelndem horizontalem Platz wird ein automatischer Umbruch in eine neue Zeile durchgeführt (siehe rechts). Flowlayout mit wenig Platz

Eigenschaften Flowlayout

  • Komponenten behalten ihre Wunschgröße
  • Zeilenumbruch bei mangelndem horizontalen Platz
  • aus den beiden ersten Eigenschaften ergibt sich, dass Komponenten eventuell nur teilweise angezeigt werden oder völlig verdeckt sein können!

Borderlayout

Der Borderlayoutmanager erlaubt das Gruppieren von Komponenten abhängig von der Richtung im Panel.

Strategie: Die zur Verfügung stehende Fläche wird in fünf Bereiche nach den Himmelsrichtungen aufgeteilt

  • NORTH (Oben)
  • SOUTH (Unten)
  • EAST (Rechts)
  • WEST (Links)
  • CENTER (Mitte)

Der Centerbereich ist der priorisierte Bereich.

Das BorderLayout ist die Standardeinstellung für die Klassen Window und JFrame.

Im nächsten Beispiel wir je ein Knopf (Button) in einem der Bereiche angelegt:

package s2.swing;
 
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
 
public class BorderLayoutTest {
 
    public static void main(String[] args) {
        JFrame f = new JFrame("BorderLayout");
        JPanel jp = new JPanel();
 
        jp.setLayout(new BorderLayout());
        jp.add(new JButton("Norden"),BorderLayout.NORTH);
        jp.add(new JButton("Westen"),BorderLayout.WEST);
        jp.add(new JButton("Osten"),BorderLayout.EAST);
        jp.add(new JButton("Süden"),BorderLayout.SOUTH);
        jp.add(new JButton("Center"),BorderLayout.CENTER);
        f.add(jp);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.pack();
        f.setVisible(true);
    }
}

Zur Bestimmung der Position werden Konstanten mit den englischen Namen der Himmelsrichtung verwendet (Bsp. BorderLayout.NORTH). Beim Borderlayout ist zu beachten, dass Komponenten die oben, bzw. unten angeordnet werden über die ganze Breite des Containers gezeichnet werden. Komponenten die links und rechts angeordnet werden sind jedoch immer unter, bzw. über den Komponenten die oben und unten angelegt urden.

Eigenschaften Borderlayout

Siehe Fenster rechts mit größerem Fensterbereich:

  • Alle Komponenten füllen die jeweils gesamte Fläche aus
  • Die Bereiche im Norden und Süden haben die Wunschhöhe
  • Die Bereiche im Osten und Westen haben die Wunschbreite
  • Der Centerbereich erhält alle freien Flächen
BorderLayoutResize

 

BoxLayout

Das BoxLayout erlaubt das Anordnen von Komponenten in Zeilen oder Spalten. Durch das Verschachteln von BoxLayoutmanagern kann man ähnliche Effekte wie beim GridLayout erzielen. Man hat jedoch die Möglichkeit einzelne Zeilen oder Spalten individuell zu konfigurieren.

Das BoxLayout versucht alle Komponenten mit ihrer bevorzugten Breite bei der horizontalen Darstellung zu positionieren. Die Höhe aller Komponenten wird hier versucht auf Die Wunschhöhe der höchsten Komponente wird benutzt um die Gesamthöhe zubestimmen.

Bei der vertikalen Darstellung wird entsprechend versucht die bevorzugte Höhe zu verwenden. Bei der vertikalen Darstellung versucht der Layoutmanager alle Komponenten horizontal so weit wie die breiteste Komponente zu strecken.

Komponenten können aneinander

  • Linksbündig
  • Rechtsbündig
  • Zentriert

ausgerichtet werden

Das folgende Beispiel zeigt ein horizontales Boxlayout welches nach 2 Sekunden automatisch die Orientierung zu vertikal und dann wieder zurück triggert:

package s2.swing;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;

public class BoxLayoutTest {

public static void main(String[] args) {
int wartezeit = 2000; // in Millisekunden
JFrame f = new JFrame("BoxLayout");
JPanel jp = new JPanel();

// Erzeuge ein horizontales und ein vertikales BoxLayout
BoxLayout horizontal = new BoxLayout(jp, BoxLayout.X_AXIS);
BoxLayout vertikal = new BoxLayout(jp, BoxLayout.Y_AXIS);
jp.setLayout(horizontal);
for (char c = 0; c < 4; ++c) {

            jp.add(new JButton("Button " + (char) ('A' + c)));
        }
        JTextArea jta =new JTextArea(2,10);
        jta.append("JTextArea \nsecond row");
        jp.add(jta);
        f.add(jp);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true); // Warte 2 Sekunden try { Thread.sleep(wartezeit); // Wechsle fünfmal die Orientierung alle zwei Sekunden for (int i = 0; i < 5; i++) { jp.setLayout(vertikal); f.setTitle("BoxLayout - Vertikal"); f.pack(); Thread.sleep(wartezeit); jp.setLayout(horizontal); f.setTitle("BoxLayout - Horizontal"); f.pack(); Thread.sleep(wartezeit); } } catch (InterruptedException e) { // Mache nichts im Fall einer Ausnahme } } }

Ein horizontales Layout wird mit der Konstanten  BoxLayout.X_AXIS beim Konfigurieren des Layoutmanagers erzeugt:

Ein vertikales Layout wird beim Erzeugen des Layoutmanagers mit Hilfe der Konstanten BoxLayout.Y_AXIS konfiguriert:

Eigenschaften Boxlayout

  • Jede Komponente wird in ihrer Wunschgröße gargestellt
  • Die Größe des Containers ergibt sich aus der größten Wunschhöhe und der größten Wunschbreite

Hinweis: Die zweizeilige JTextArea im Beispiel oben hat eine andere Wunschgröße als die Knöpfe

GridLayout

Der GridLayoutmanager erlaubt die Anordnung von Komponenten in einem rechteckigen Raster (Grid: engl. Raster, Gitter). Der Gridlayoutmanager versucht die Komponenten von links nach rechts und von oben nach unten in das Raster einzufügen.

Strategie: Alle Zellen haben eine einheitliche Größe

Wird für die Größe der Zeilen oder Spalten eine 0 angegeben erlaubt dies das Anlegen beliebig vieler Element in den Spalten oder Zeilen.

Im folgenden Beispiel werden 11 Buttons und ein Textfeld in drei Reihen und vier Spalten angeordnet:

package s2.swing;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class GridLayoutTest {
  public static void main(String[] args) {
    JFrame f = new JFrame("GridLayout");
    JPanel jp = new JPanel();
    jp.setLayout(new GridLayout(3,4));
    for (char c = 0; c < 11; ++c) {
      jp.add(new JButton("Button " + (char)('A'+c)));
    }
    JTextArea jta =new JTextArea(3,10);
    jta.append("JTextArea \nsecond row\nthird row");
    jp.add(jta);
    f.add(jp);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.pack();
    f.setVisible(true);
    }
}

Dies ergibt das folgende Fenster bei der Ausführung:

Eigenschaften des Gridlayouts

  • Alle Komponenten werden in die gleiche Größe gezwungen
  • Das initiale Layout berechnet die Zellengröße nach der breitesten und höchsten Komponente
  • Komponenten, die die Zellengröße nicht einhalten können, werden eventuell nicht vollständig angezeigt (Beispiel rechts)

 

gestauchtes Fenster:

 

 

Ereignisse und deren Behandlung

 GUI Programme und Ereignisverarbeitung

GUI Programme laufen nicht linear ab. Bei Benutzerinteraktionen muss das Programm in der Lage sein sofort einen bestimmten Code zur Behandlung auszuführen.

Programmaktionen werden durch Benutzeraktionen getriggert. Man spricht hier von einem ereignisgesteuerten Programmablauf.

Definition: Ereignis

Ein Ereignis ist ein Vorgang in der Umwelt des Softwaresystems von vernachlässigbarer Dauer, der für das System von Bedeutung ist.

Im Rahmen dieses Abschnitts sprechen wir von einer wichtigen Gruppe von Ereignissen den Benutzerinteraktionen:

Beispiele sind

  • Mausclick
  • Tasteneingabe
  • Menülistenauswahl
  • Zeigen auf einen Bereich des GUI
  • Texteingabe oder Veränderung

Der Programmablauf wird aber auch von anderen weniger offensichtlichen Benutzerintreaktionen gesteuert

  • Verdecken des Programmfensters durch ein anderes Fenster
  • Fenstermodifikationen
    • Vergößern, verkleinern
    • Bewegen,
    • Schließen eines Fenster
  • Bewegen der Maus über das Programmfenster ohne Klicken (nicht auf allen Plattformen)
  • Erlangen des Fokus auf einem Fenster

Ereignisklassen

Java benutzt Klassen zur Behandlung von Ereignissen (engl. Events) der Java-Benutzeroberflächen. Typische Klassen sind

Die Klassen enthalten die Beschreibung von GUI Ereignissen (z.Bsp. Maus wurde auf Position x=17, y=24 geklickt).

Sie korrelieren mit den Klassen die die grafischen Komponenten implementieren: Z.Bsp.

Erzeugen von Ereignissen und deren Auswertung

Ereignisse werden automatisch von den Swingkomponenten erzeugt. Dies ist die Klasse JComponent mit ihren abgeleiteten Klassen. Sie werden zum Beispiel erzeugt, wenn ein Benutzer die Schaltfläche eines JButton betätigt. Die Aufgabe die dem Entwickler verbleibt ist die Registrierung seiner Anwendung für bestimmte GUI-Ereignisse. Die Anwendung kann nur auf Ereignisse reagieren gegen die sich sich vorher registriert hat.

Der Entwickler kann dann nach der Registrierung eines bestimmten Ereignisses ist die Auswertung des Ereignisses vornehmen und auf das Ereignis mit der gewünschten Aktion reagieren.

Java verwendet hierzu die Ereignis-Delegation um die Komponente die das Ereignis auslöst von der Ereignisbehandlung zu entkoppeln.

  • Der Benutzer betätigt z.Bsp. eine Schaltfläche
  • Das Laufzeitsystem erkennt das Ereignis
  • Objekte die das Ereignis beobachten (engl. "Listener" in Java) werden aufgerufen und das Ereignis wird behandelt.
    • Ein Listener erhält vom Laufzeitsystem ein ActionEvent-Objekt mit allen Informationen die zum Ereignis gehören.

Die Listenerobjekte müssen sich mit Hilfe einer Registrierung bei den GUI Objekten anmelden:

  • Komponenten die Ereignisse erzeugen (Klasse JComponent) können, erlauben die Registrierung von "Listener" Objekten (engl. zuhören; nicht nur hören!)
    • die Registrierung erfolgt mit Methoden der Syntax addXXXListener(...) der GUI Komponenten
  • "Listener" Objekte implementieren das Java Interface ActionListener und damit die Methoden die beim Eintritt eines Ereignisses ausgeführt werden sollen.

Wichtig: Beziehung zwischen Listenerobjekt und GUI Objekt

Ein Listenerobjekt kann sich gegen ein oder mehrere GUI Objekte registrieren

  • Registriert man ein Listenerobjekt gegen genau ein GUI Objekt (Bsp. 1 Button <-> 1 Listenerobjekt)
    • muß man den Urheber des Ereignisses und den Ereignistyp nicht analysieren. Der Typ des Ereignis und das GUI Objekt sind bekannt
  • Registriert man ein Listenerobjekt gegen mehrere GUI Objekte (Bsp. 3 Buttons <-> 1 Listenerobjekt)
    • muß man im Listenerobjekt das übergebene Ereignisobjekt auf den Verursacher, das GUI Objekt, untersuchen
    • muß man ein Listenerinterface implementieren, dass auch die Ereignisse aller Objekte verarbeiten kann.
      • Beispiel: 1 Button und ein Textfeld werden von einem Listenerobjekt verwaltet. Man benötigt hier eine gemeinsame Listener-Oberklasse die beide graphische Objekte verwalten kann

Beispiel

Das folgenden Beispiel ist eine sehr einfache Implementierung eines JFrame mit einem Button und einem ActionListener:

package s2.swing;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;

/**
*
* @author s@scalingbits.com
*/
public class ActionListenerBeispiel implements ActionListener {

@Override
public void actionPerformed(ActionEvent ae) {
//Ausgabe des zum ActionEvent gehörenden Kontexts
System.out.println("Aktion: " + ae.getActionCommand());
}

public static void main(String[] args) {
JFrame myJFrame = new JFrame("Einfacher ActionListener");
myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton jb = new JButton("Hier drücken");

ActionListenerBeispiel behandeln = new ActionListenerBeispiel();
jb.addActionListener(behandeln); // Füge Listener zu Button
myJFrame.add(jb); // Füge Button zu Frame
myJFrame.pack();
myJFrame.setVisible(true);
}
}

Die Klasse ActionListenerBeispiel implementiert die Methode actionPerformed() nach der Spezifikation eines ActionListener.

Das Programm startet in der main() Methode und erzeugt das folgende GUI:

 

  • Nach klicken des Button "feuert" das Buttonobjekt jb ein Ereignis (Event)
  • die Methode actionPerformed() des registrierten Listener behandeln wird mit einem Eventobjekt als Übergabeobjekt aufgerufen
  • Das Eventobjekt ae wird analysiert und das
  • Kommando wird als Text auf der Konsole ausgegeben:
Aktion: Hier drücken

Der Text "Hier drücken" wird ausgeben, da das Eventobjekt bei Buttons immer den Text des Buttons ausgibt.

Ereignisse (Events)

Es gibt eine reichhaltige Hierarchie von spezialisierten Event- und Listenerklassen. Hierzu sei auf die Java-Tutorials von Oracle verwiesen.

Weitere Events sind zum Beispiel:

  • ItemEvent: Z. Bsp. Analysieren von JCheckBox Komponenten
  • MouseEvent: Analysieren von Mausposition, Bewegung etc. 
  • ChangeEvent: Z. Bsp. Analysieren von Änderungen an einem JSlider

Der Swing "Event Dispatch Thread"

Damit Benutzeraktionen unabhängig vom normalen Programmablauf behandelt werden können, benutzt Swing eine eigene Ausführungseinheit, einen Thread, zum Bearbeiten der Benutzeraktionen. Dieser "Thread" ist ein Ausführungspfad der parallel zum normalen Programmablauf im main-Thread abläuft. Threads laufen parallel im gleichen Adressraum des Prozesses und haben daher Zugriff auf die gleichen Daten.

Blockieren des GUIs

Alle Aktionen der ActionListener werden vom "Swing-Event-Dispatch-Thread" ausgeführt. Diese Thread  arbeitet GUI Interaktionen ab während das Javaprogramm mit anderen Threads im Hintergrund weiterlaufen kann.

Wichtig

Alle Codestrecken von zur Behandlung von Ereignissen (Methode actionPerformed()) werden in nur einem Thread aufgerufen und blockieren alle anderen Behandlungen während sie ausgeführt werden.

Im Klartext: Das GUI wird während der Ausführungszeit einer Ereignisbehandlung nicht bedient und ist blockiert.

Vermeiden Sie aufwändige (=langlaufende)  Implementierungen in den actionPerformed() Methoden!

Beispiel

Das folgende Programm blockiert das GUI für 2 Sekunden. Die Blockade ist nach dem Klicken an der geänderten Farbe des Buttons zu erkennen. Er bleibt gedrückt:

package s2.swing;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;

/**
*
* @author s@scalingbits.com
*/
public class ActionListenerBlockiert implements ActionListener {
@Override
public void actionPerformed(ActionEvent ae) {
//Ausgabe des zum ActionEvent gehörenden Kontexts
System.out.println("Aktion: " + ae.getActionCommand());
try {// Thread für 2s blockieren (schlafen)
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
JFrame myJFrame = new JFrame("Einfacher ActionListener");
myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton jb = new JButton("Hier drücken");
ActionListenerBlockiert behandeln = new ActionListenerBlockiert();
jb.addActionListener(behandeln); // Füge Listener zu Button
myJFrame.add(jb); // Füge Button zu Frame
myJFrame.pack(); // Berechne Layout
myJFrame.setVisible(true);
}
}

Synchronisation mit Swing GUIs

Wichtig
Die meisten Swing Komponenten sind nicht synchronisiert. Veränderungen an den Datenstrukturen durch nicht synchronisiertern Zugriff von anderen Threads können zu Inkonsistenzen führen.

 

 

Event-Interface und Event-Adapter

Die Implementierung eines einfachen ActionListener-Schnittstelle ist recht einfach, da nur eine Methode implementiert werden muss.

Viele nicht triviale ActionListener erfordern jedoch die Implementierung von mehreren Methoden aufgrund der Komplexität der entsprechenden Komponente. Ein Beispiel hierfür ist die Schnittstelle MouseListener. Sie erfordert die Implementierung der folgenden Methoden:

  • mouseClicked(MouseEvent e)
  • mouseEntered()
  • mouseExited()
  • mousePressed()
  • mouseReleased()

Dies ist aufwändig wenn man sich nur für ein bestimmtes Ereignis wie z. Bsp. mouseReleased() interessiert. Man muss alle Interfacemethoden implementieren. Vier der Methoden bleiben leer und sind überflüssig.

Listener-Adapter Klassen

Um diese unnötige Schreibarbeit zu vermeiden, stellt Swing nach dem Entwurfsmuster Adapter Klassen als abstrakte Klassen mit leeren, aber schon implementierten Methoden zur Verfügung.

Vorteil: Der Entwickler kann seine Klasse aus der abstrakten Klasse ableiten und muss nur die Methoden für die Ereignisse implementieren die ihn interessieren.

Für die Implementierung von Maus-Events steht zum Beispiel die Klasse MouseAdapter zur Verfügung, die die entsprechenden Methoden implementiert.

Einige der Adapterklassen die Swing zur vereinfachten Implementierung von Ereignisschnittstellen (Event Interface) sind:

Beispiele von Adapterklassen
Spezialisierungen der Schnittsteller EventListener Adapterklasse
ComponentListener ComponentAdapter
ContainerListener ContainerAdapter
FocusListener FocusAdapter
KeyListener KeyAdapter
MouseListener MouseAdapter
MouseMotionListener MouseMotionAdapter
WindowListener WindowAdapter

Im folgenden Beispiel wird die Klasse MouseAdapterTest aus der abstrakten Klasse MouseAdapter abgeleitet um nur die Methode mouseClicked() implementieren zu müssen:

package s2.swing;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JFrame;

public class MouseAdapterTest extends MouseAdapter {

public MouseAdapterTest() {
erzeugeGUI();
}

public static void main(String[] args) {
MouseAdapterTest mat = new MouseAdapterTest();
}

private void erzeugeGUI() {
JFrame myJFrame = new JFrame("Mouse Click Adapter Test");
myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton jb = new JButton("Hier drücken");
jb.addMouseListener(this);
myJFrame.getContentPane().add(jb);
myJFrame.pack();
myJFrame.setVisible(true);
}

@Override
public void mouseClicked(MouseEvent mEvent) {
System.out.println("MouseClick wurde auf Position ["
+ mEvent.getX() + ","
+ mEvent.getY() + "] "
+ mEvent.getClickCount() + " mal geklickt");
}
}

Das Programm registriert sich selbst gegen den Button und ist jetzt in der Lage, die Information des des Eventobjekts mEvent auszulesen (x,y Position, Anzahl der Mehrfachclicks).

Beispiel

Im Diagramm (unten) sind die beiden Möglichkeiten aufgeführt die ein Entwickler zum Auslesen eines Mauscklicks benutzen kann. Die von Java zur Verfügung gestellten Infrastruktur besteht aus dem zu implementierenden Interface MouseListener und einer abstrakten Klasse Mouseadapter:

Ein Entwickler der sich nur gegen das MouseClick Ereignis registrieren möchte, kann wie im Diagramm oben:

  • das Interface MouseListener in z.Bsp. der Klasse ListenerAufwaendig implementieren. Hier müssen alle 5 Methoden des Interface implementiert werden. Die vier nicht benötigten Methoden können als leere Methoden implementiert werden.
  • aus der abstrakten Klasse MouseAdapter eine Klasse wie z.Bsp. ListenerEinfach ableiten. In ihr muss man nur eine einzige Methode mouseClicked() durch Überschreiben implementieren. Die anderen vier Methoden sind schon in der Klasse MouseAdapter als leere Proformamethoden implementiert. Beim Ableiten aus der abstrakten Klasse MouseAdapter müssen nur die gewünschten Methoden überschrieben werden. Ein Konstruktor ist nicht nötig, da die Klasse MouseAdapter einen Default-Konstruktor besitzt.

Anbei die vollständige Implementierung der semantisch gleichwertigen Methoden:

Vergleich
Klasse ListenerAufwaendig Klasse ListenerEinfach
package s2.swing;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
 
public class ListenerAufwaendig implements MouseListener{
    @Override public void mouseClicked(MouseEvent mEvent) {
        System.out.println("MouseClick wurde auf Position ["
                + mEvent.getX() + ","
                + mEvent.getY() + "] "
                + mEvent.getClickCount() + " mal geklickt");
    }
    @Override public void mouseEntered(MouseEvent mEvent) 
        { /* leere Implementierung, erzwungen */ }
    @Override public void mouseExited(MouseEvent mEvent) 
        { /* leere Implementierung, erzwungen */ }
    @Override public void mousePressed(MouseEvent mEvent)
        { /* leere Implementierung, erzwungen */ }
    @Override public void mouseReleased(MouseEvent mEvent)
     { /* leere Implementierung, erzwungen */ }
}
package s2.swing;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
 
public class ListenerEinfach extends MouseAdapter{
    @Override public void mouseClicked(MouseEvent mEvent) {
        System.out.println("MouseClick wurde auf Position ["
                + mEvent.getX() + ","
                + mEvent.getY() + "] "
                + mEvent.getClickCount() + " mal geklickt");
    }
}
 
 
 
 
 
 
 
 

 

Anonyme und innere Klassen

Nach den bisher vorgestellten Prinzipien erfordert die Implementierung eines GUI die Implementierung von vielen Listenern und führt zur Erzeugung sehr vieler Klassen. Java erlaubt hier die Implementierung von anoymen, inneren Klassen die nur im Kontext einer bestimmten Klasse existieren und den Namensraum der Klassen nicht unnötig belasten.

Innere Klassen

Innere Klassen helfen es zu vermeiden, dass man Klassen veröffentlicht  die nur von genau einer anderen Klasse benutzt werden. Innere Klassen werden syntaktisch wie normale Klassen implementiert. Der einzige Unterschied ist, dass sie im Block einer äusseren Klasse implementiert werden. Sie werden in der äusseren Klasse mit dem gleichen Block der äusseren Klasse wie die Klassenvariablen und Methoden der äusseren Klasse implementiert.

Die in der Vorlesung vorgestellten inneren Klassen sind Elementklassen.

Elementklasse
Definition Elementklasse

Elementklassen sind wie Instanzmethoden und Instanzvariablen Elemente einer (anderen) Klasse. 

Sie werden auf der gleichen Blockebene wie Instanzmethoden und Instanzvariablen implementiert. Sie haben einen Zugriffschutz wie Instanzmethoden und Instanzvariablen.

Innere Klassen können auch als lokale Klasse innerhalb eines beliebigen Blocks implementiert werden. Diese Variante ist nicht Gegenstand der Vorlesung.

Die inneren Klassen gehören zum Paket der äusseren Klasse. Importkommandos müssen in der äusseren Klasse implementiert werden.

Besondere Eigenschaften von Elementklassen
  • Elementklassen und deren Instanzen können nur existieren, wenn ein Objekt der Sie umschließenden Klasse existiert.
  • Elementklassen haben den vollen Zugriff auf die Instanzvariablen und -methoden des umgebenden Objekts!

Instanzen von Elementklassen sind Komponenten des umgebenden Objekts!

Das vorhergehende Beispiel kann jetzt wie folgt implementiert werden:

package s2.swing;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
/**
*
* @author s@scalingbits.com
*/ public class MouseAdapterInnereKlasseTest {
/**
* Die innere Klasses MyMouseListener
*/
class MyMouseListener extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent mEvent) {
System.out.println("MouseClick wurde auf Position ["
+ mEvent.getX() + ","
+ mEvent.getY() + "] "
+ mEvent.getClickCount() + " mal geklickt");
}
}

/**
* Erzeuge GUI im Konstruktor
*/
public MouseAdapterInnereKlasseTest() {
JFrame myJFrame = new JFrame("Mouse Click Innere Klasse Test");
myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton jb = new JButton("Hier drücken");
jb.addMouseListener(new MyMouseListener());
myJFrame.getContentPane().add(jb);
myJFrame.pack();
myJFrame.setVisible(true);
}
public static void main(String[] args) {
MouseAdapterInnereKlasseTest mat =
new MouseAdapterInnereKlasseTest();
}
}

Die Unterschiede sind die Folgenden:

  • Die Klasse MouseAdapterInnereKlasseTest muss nicht mehr von der Klasse MouseAdapter abgeleitet werden oder eine Schnitstelle MouseEvent implementieren
  • Es wird eine eigene innere Klasse MyMouseListener implementiert die nicht ausserhalb der umgebenden Klasse bekannt ist.
  • Die Klasse MyMouseListener erbt (extends Schlüsselwort) von der Klasse MouseAdapter.
  • Beim Hinzufügen eines Listeners zum Button wird eine Instanz der inneren Klasse erzeugt.

Diese innere Klasse hat für den Entwickler den Vorteil, dass er keine neuen Klassen nach aussen hin bekanntmachen muss. Die Implementierung des Listeners erfolgt in der gleichen Datei. Die Implementierung des Listener ist also visuell näher als wenn sie in einer eigenen Klasse und einer eigenen Datei geschehen würde.

Objektabhängigkeit von Instanzen innerer Klassen

Im obigen Beispiel wird eine Instanz der Klasse MyMouseListener erzeugt. Dies ist nur erlaubt wenn das Objekt aus dem Kontext (Methode) eines Objekts der Klasse MouseAdapterInnereKlasseTest erzeugt wird. Dieses äussere Objekt existiert, da die innere Klasse im Konstruktor von MouseAdapterInnereKlasseTest erzeugt wird.

Der Code zum Erzeugen des GUI im Konstruktor kann nicht einfach in die statische main() Methode kopiert werden. Hier gibt es noch keinen Kontext zu einem Objekt der Klasse MouseAdapterInnereKlasseTest. Der javac Übersetzer wird einen Fehler melden.

Anonyme, innere Klasse

Für Swing wurde das Konzept der anonymen, inneren Klasse entwickelt.

Definition anonyme, innere Klasse

Eine anonyme, innere Klasse ist eine lokale Klasse ohne Namen die innerhalb eines Ausdrucks (Block) definiert und instanziiert wird.

Anonyme, innere Klassen haben keinen Namen und daher auch keine Konstruktoren. Sie werden typischerweise als Implementierungen für Adapterklassen oder Schnittstellen verwendet.

Beispiel

Anonyme, innere Klassen erlauben die benötigte Listenerimplementierung noch eleganter durchzuführen:

An der Stelle an der eine Instanz eines Listeners benötigt wird kann man auch direkt eine vollständige Klasse implementieren und instanziieren.

Das folgende Beispiel hat die gleiche Funktion wie das vorgehende Beispiel mit der Implementierung einer inneren Klasse. Es kommt aber ohne einen eigenen Klassennamen aus:

package s2.swing;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
public class MouseAdapterAnonymeInnereKlasseTest {
/**
* Erzeuge GUI im Konstruktor
*/
public MouseAdapterAnonymeInnereKlasseTest() {
JFrame myJFrame = new JFrame("Mouse Click Adapter Test");
myJFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton jb = new JButton("Hier drücken");
jb.addMouseListener(new MouseAdapter() { @Override
public void mouseClicked(MouseEvent mEvent) {
System.out.println("MouseClick wurde auf Position ["
+ mEvent.getX() + ","
+ mEvent.getY() + "] "
+ mEvent.getClickCount() + " mal geklickt");
}
}
);
myJFrame.getContentPane().add(jb);
myJFrame.pack();
myJFrame.setVisible(true);
}
public static void main(String[] args) {
MouseAdapterAnonymeInnereKlasseTest mat =
new MouseAdapterAnonymeInnereKlasseTest();
}
}

Die Unterschiede sind die Folgenden:

  • Die Klasse MouseAdapterInnereAnonymeKlasseTest muss nicht mehr von MouseAdapter abgeleitet werden oder einen MouseEvent implementieren
  • Beim Hinzufügen eines Listeners zum Button wird
    • mit dem new Operator eine Instanz einer anonymen Klasse angelegt, die die abstrakte Klasse MouseAdapter implementiert. Sie ist anonym da sie selbst keinen Namen besitzt.
    • die anonyme Klasse wird innerhalb der Klasse MouseAdapterInnereAnonymKlasseTest soweit wie nötig implementiert um aus der abstrakten Oberklasse eine normale Klasse zu erzeugen

Diese "Hilfskonstruktion" hat für den Entwickler eine Reihe von Vorteilen:

  • Das Ereignis kann textuell sehr nahe an der Erzeugung der Komponente implementiert werden. Der Code wird übersichtlicher
  • Es müssen keine neuen Klassen mit eigenen Namen erzeugt werden. Hierdurch wird die gesamte Klassenhierarchie übersichtlicher und deutlich kleiner.

Statische innere Klassen, nicht anonyme lokale Klassen

... sind nicht Gegenstand dieser Vorlesung.

 

Übungen (Swing)

Ausnahmefenster

Implementieren Sie ein Fenster mit Hilfe der Klasse JFrame zur Behandlung von Ausnahmen.

  • Das JFrame soll beim Auftreten einer Ausnahmen aufgerufen werden und den Namen der Ausnahme zeigen.
  • Das Programm soll dann auf Wunsch beendet werden oder es soll ein Stacktrace auf der Konsole angezeigt werden
  • Das Programm soll ein GIF aus dem Internet als Label verwenden

 Das Fenster soll in etwa wie das folgende GUI aussehen:

Verwenden Sie das Rahmenprogramm AusnahmeFenster.java welches eine Infrastruktur zum Testen zur Verfügung stellt:

package s2.swing;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;

public class AusnahmeFenster {
final private JFrame hf;
final private JButton okButton;
final private JButton exitButton;
final private Exception myException;
/**
* Aufbau des Fensters zur Ausnahmebehandlung
*
* @param fehlermeldung ein beliebiger Fehlertext der angezeigt wird
* @param e Die Ausnahme die angezeigt werden soll
*/
public AusnahmeFenster(String fehlermeldung, Exception e) {
JLabel logo;
JPanel buttonPanel;
myException = e;
// 1. Erzeugen einer neuen Instanz eines Swingfensters
System.out.println("Hier beginnt die Arbeit: Löschen Sie dieses Kommando");
// ...
hf = null;
// 3. Gewünschte Größe setzen
// 1. Parameter: horizontale Größe in Pixel: 220
// 2. Parameter: vertikale Größe: 230
// ...
// 8. Labelerzeugung
logo = meinLogo();
// 4. Nicht Beenden bei Schliessen des Fenster
// 5. Anlegen der Buttons
okButton = null;
exitButton= null;
// 10. Hinzügen der Eventbehandlung
// Tipp: Die Klasse muss noch das Interface ActionListener implementieren!
// ...
// 6. Aufbau des Panels
// ...
// 7. Aubau des ContentPanes
// ...
// 2.1 Das Layout des JFrame berechnen.
// ...
// 3. Gewünschte Größe setzen
// 1. Parameter: horizontale Größe in Pixel
// 2. Parameter: vertikale Größe
// ...
// 2.2 Sichtbar machen des JFrames. Immer im Vordergrund
// ...
// ...
}
/**
* Implementieren des Logos
* 9.ter Schritt
* @return Zeiger auf das Logoobjekt
*/
private JLabel meinLogo() {
URL logoURL;
JLabel logoLabel;
String myURL = "http://www.dhbw-mannheim.de/fileadmin/templates/default/img/DHBW_Header_Logo.gif";
try {
logoURL = new URL(myURL);
ImageIcon myImage = new ImageIcon(logoURL);
logoLabel = new JLabel(myImage);
} catch (java.net.MalformedURLException e) {
System.out.println(e);
System.out.println("Logo URL kann nicht aufgelöst werden");
logoLabel = new JLabel("Logo fehlt");
}
return logoLabel;
}
/**
* Behandlung der JButton-Ereignisse
* 11. ter Schritt
* @param e
*/
public void actionPerformed(ActionEvent e) {
//System.exit(0);
//System.out.println("OK Button clicked");
//myException.printStackTrace();
}
/**
* Hauptprogramm zum Testen des Ausnahmefensters
* @throws Exception
*/
public static void main(String[] args) {
AusnahmeFenster dasFenster;
try {
myTestMethod();
} catch (Exception e) {
dasFenster = new AusnahmeFenster("Hier läuft etwas schief", e);
}
}
/**
* Eine Testmethode die mit einer Division durch Null eine
* Ausnahme provoziert
* @throws Exception
*/
public static void myTestMethod() throws Exception {
int a = 5;
int b = 5;
int c = 10;
c = c / (a - b);
System.out.println("Programm regulär beendet");
}
}

Empfehlung: Bauen Sie das GUI schrittweise auf und testen Sie es Schritt für Schritt

  1. Erzeugen eines einfachen JFrame
  2. Sichtbarmachen des JFrame
  3. Größe des JFrames setzen
  4. Programm nach Schließen des JFrames weiterlaufen lassen
  5. Anlegen der Buttons
  6. Aufbau des Panels
  7. Verbinden von Buttons, Panel und JFrame
  8. Erzeugen des Labels mit dem GIF und Hinzufügen zum Pane
  9. Implementieren des Labels mit Logo
  10. Hinzufügen der ActionListener zu den Buttons
  11. Implementieren der Aktionen

Taschenrechner

Implementieren Sie einen Taschenrechner mit den vier Grundrechenarten.

Benutzen Sie hierfür die Aufgabenstellung der Universität Bielefeld von Herrn Jan Krüger:

Innere Klasse und anonyme Klasse

Das Programm der vorhergehenden Übung (Ausnahmefenster)  benutzt in der Musterlösung eine einzige Methode actionPerformed() in der Klasse AusnahmeFenster um die Aktionen der beiden Buttons auszuführen.

public class AusnahmeFensterFertig implements ActionListener {

...

public AusnahmeFenster(String fehlermeldung, Exception e) {
...
okButton = new JButton();
exitButton = new JButton();
// 10. Hinzügen der Eventbehandlung
okButton.addActionListener(this);
exitButton.addActionListener(this);
...
}
public void actionPerformed(ActionEvent e) {
JButton source = (JButton) (e.getSource());
if (source == exitButton) {
System.exit(0);
}
if (source == okButton) {
System.out.println("OK Button clicked");
myException.printStackTrace();
}
}

}

Aufgabe 1:

  • Implementieren Sie eine externe Klasse SystemExitListener die als Listener für den "Beenden" Button genutzt werden kann.
  • Ändern Sie die Klasse AusnahmeFenster derart,dass Sie für den "Beenden" Button nicht mehr das Ausnahmefenster als Objekt registrieren (this). Erzeugen Sie als registrierten Listener eine Instanz der Klasse SystemExitListener

Sie haben nun eine eigene Klasse mit einem eigenen Objekt zur Behandlung des "Beenden" Ereignisses

Weiterführend (optional)

Implementieren Sie einen Menüeintrag "Beenden" in einer Menüleiste mit dem EIntrag "Ablage".

Herangehen:

  • Nutzen Sie Ihre Klasse SystemExitListener
  • Nutzen Sie das Java API und erzeugen Sie
    • eine Menülisteneintrag durch eine Instanz der Klasse JMenuItem
    • registrieren Sie das Listenerobjekt in dieser Klasse
  • Erzeugen Sie eine Menüliste mit Hilfe der Klasse JMenu
    • Fügen Sie den Menülisteneintrag zum Menü hinzu
  • Erzeugen Sie eine Menüleiste mit Hilfe der Klasse JMenuBar
    • Fügen Sie das zu erzeugte Menü in die Menüleiste ein
    • Fügen Sie die Menüleiste zum JFrame hinzu.
Menüleiste

 

Aufgabe 2:

  • Die Klasse AusnahmeFenster soll NICHT mehr die Schnittstelle ActionListener implementieren.
  • Implementieren Sie eine innere Klasse SystemExitAction. Sie soll einen ActionListener implementieren, der das Programm beendet.
  • Ändern sie den Aufruf addActionListener() des exitButton so, dass er die Klasse SystemExitAction benutzt.
  • Ändern Sie den Aufruf addActionListener() des okButton so, dass eine anonyme innere Klasse aufgerufen wird die einen Stacktrace ausdruckt

Hierdurch entfällt die Analyse der auslösenden Aktion in der ursprünglichen Implementierung. Für jede Aktion wurde jetzt eine eigene Methode implementiert

Lösungen (Swing)

Ausnahmefenster

package s2.swing;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;


public class AusnahmeFensterLoesung implements ActionListener {
final private JFrame hf;
final private JButton okButton;
final private JButton exitButton;
final private Exception myException;
/**
* Aufbau des Fensters zur Ausnahmebehandlung
*
* @param fehlermeldung ein beliebiger Fehlertext der angezeigt wird
* @param e Die Ausnahme die angezeigt werden soll
*/
public AusnahmeFensterLoesung(String fehlermeldung, Exception e) {
JLabel logo;
JPanel buttonPanel;
myException = e;
hf=null;
okButton=null;
exitButton=null;
// 1. Erzeugen einer neuen Instanz eines Swingfensters
hf = new JFrame("Anwendungsfehler");
// 8. Labelerzeugung
logo = meinLogo();
// 4. Beenden bei Schliesen des Fenster
hf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 5. Anlegen der Buttons
okButton = new JButton();
okButton.setText("Stack Trace");
exitButton = new JButton();
exitButton.setText("Beenden");
// 10. Hinzügen der Eventbehandlung
okButton.addActionListener(this);
exitButton.addActionListener(this);
// 6. Aufbau des Panels
buttonPanel = new JPanel(new GridLayout(1, 0));
buttonPanel.add(exitButton);
buttonPanel.add(okButton);

JTextArea fehlertextArea = new JTextArea(2, 20);
fehlertextArea.append(fehlermeldung + "\n");
fehlertextArea.append("Exception: "+ myException);
// 7. Aubau des ContentPanes
Container myPane = hf.getContentPane();
myPane.setLayout(new BorderLayout());
myPane.add(logo, BorderLayout.NORTH);
myPane.add(fehlertextArea, BorderLayout.CENTER);
myPane.add(buttonPanel, BorderLayout.SOUTH);
// 2.1 Das Layout des JFrame berechnen.
hf.pack();
// 3. Gewünschte Größe setzen
// 1. Parameter: horizontale Größe in Pixel
// 2. Parameter: vertikale Größe
hf.setSize(350, 300);
// 2.2 Sichtbar machen des JFrames. Immer im Vordergrund
hf.setVisible(true);
hf.setAlwaysOnTop(true);
}
/**
* Implementieren des Logos
* 9.ter Schritt
* @return Zeiger auf das Logoobjekt
*/
private JLabel meinLogo() {
URL logoURL;
JLabel logoLabel;
String myURL = "http://www.dhbw-mannheim.de/fileadmin/templates/default/img/signet.gif";
try {
logoURL = new URL(myURL);
ImageIcon myImage = new ImageIcon(logoURL);
logoLabel = new JLabel(myImage);
} catch (java.net.MalformedURLException e) {
System.out.println(e);
System.out.println("Logo URL kann nicht aufgelöst werden");
logoLabel = new JLabel("Logo fehlt");
}
return logoLabel;
}
/**
* Behandlung der JButton Ereignisse
* 11. ter Schritt
* @param e
*/
public void actionPerformed(ActionEvent e) {
JButton source = (JButton) (e.getSource());
if (source == exitButton) {
System.exit(0);
}
if (source == okButton) {
System.out.println("OK Button clicked");
myException.printStackTrace();
}
}
/**
* Hauptprogramm zum Testen des Ausnahmefensters
* @throws Exception
*/
public static void main(String[] args) {
AusnahmeFensterLoesung dasFenster;
try {myTestMethod();}
catch (Exception e) {
dasFenster = new AusnahmeFensterLoesung("Hier läuft etwas schief",e);
}
}
/**
* Eine Testmethode die eine durch eine Division durch Null eine
* Ausnahme provoziert
* @throws Exception
*/
public static void myTestMethod() throws Exception {
int a = 5;
int b = 5;
int c = 10;
c = c / (a-b);
System.out.println("Programm regulär beendet");
}
}

Taschenrechner

Die Universität Bielefeld (Herr Jan Krüger) bieten die Lösung der Programmieraufgabe an unter:

Innere und anonyme Klasse

Aufgabe 1

Klasse SystemExitListener

package s2.swing;
 
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
   /**
   *
   * @author s@scalingbits.com
   * Implementierung eines ActionListener der als Aktion die Anwendung
   * beendet
   */
   public class SystemExitListener implements ActionListener{
      @Override
      public void actionPerformed(ActionEvent e) {
         System.exit(0);
      }
   }

Einfügen eines Menüs (Optional)

import javax.swing.*;
...
JMenuItem jmi = new JMenuItem("Beenden");
jmi.addActionListener(new SystemExitListener());
JMenu jm = new JMenu("Ablage");
jm.add(jmi);
JMenuBar jmb = new JMenuBar();
jmb.add(jm);
hf.setJMenuBar(jmb);

Aufgabe 2

Hinweis: Die Klasse wurde in AusnahmeFensterInnerer umbenannt 

package s2.swing;
 
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
 
/**
*
* @author s@scalingbits.com
*/  
public class AusnahmeFensterInnere {
    private JFrame hf;
    private JButton okButton;
    private JButton exitButton;
    private Exception myException;
 
    public class SystemExitAction implements ActionListener{
    @Override
    public void actionPerformed(ActionEvent e) {
        System.exit(0);
        System.out.println(hf);
    }
}
    /**
     * Aufbau des Fensters zur Ausnahmebehandlung
     *
     * @param fehlermeldung ein beliebiger Fehlertext der angezeigt wird
     * @param e Die Ausnahme die angezeigt werden soll
     */
    public AusnahmeFensterInnere(String fehlermeldung, Exception e) {
                JLabel logo;
        JPanel buttonPanel;
        myException = e;
        // 1. Erzeugen einer neuen Instanz eines Swingfensters
        hf = new JFrame("Anwendungsfehler");
        // 8. Labelerzeugung
        logo = meinLogo();
        // 4. Beenden bei Schliesen des Fenster
hf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        // 5. Anlegen der Buttons
        okButton = new JButton();
        okButton.setText("Stack Trace");
        exitButton = new JButton();
        exitButton.setText("Beenden");
        // 10. Hinzügen der Eventbehandlung
        okButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                myException.printStackTrace();
            }
            });
        exitButton.addActionListener(new SystemExitAction());
        // 6. Aufbau des Panels
        buttonPanel = new JPanel(new GridLayout(1, 0));
        buttonPanel.add(exitButton);
        buttonPanel.add(okButton);
 
        JTextArea fehlertextArea = new JTextArea(2, 20);
        fehlertextArea.append(fehlermeldung + "\n");
        fehlertextArea.append("Exception: "+ myException);
        // 7. Aubau des ContentPanes
        Container myPane = hf.getContentPane();
        myPane.add(logo, BorderLayout.NORTH);
        myPane.add(fehlertextArea, BorderLayout.CENTER);
        myPane.add(buttonPanel, BorderLayout.SOUTH);
        JMenuItem jmi = new JMenuItem("Beenden");
        jmi.addActionListener(new SystemExitListener());
        JMenu jm = new JMenu("Ablage");
        jm.add(jmi);
        JMenuBar jmb = new JMenuBar();
        jmb.add(jm);
        hf.setJMenuBar(jmb);
        // 2.1 Das Layout des JFrame berechnen.
        hf.pack();
        // 3. Gewünschte Größe setzen
        //     1. Parameter: horizontale Größe in Pixel
        //     2. Parameter: vertikale Größe
        hf.setSize(350, 300);
        // 2.2 Sichtbar machen des JFrames. Immer im Vordergrund
        hf.setVisible(true);
        hf.setAlwaysOnTop(true);
    }
 
    /**
     * Implementieren des Logos
     * 9.ter Schritt
     * @return Zeiger auf das Logoobjekt
     */
    private JLabel meinLogo() {
        URL logoURL;
        JLabel logoLabel;
        String myURL =
                "http://www.dhbw-mannheim.de/fileadmin/templates/default/img/signet.gif";
 
        try {
            logoURL = new URL(myURL);
            ImageIcon myImage = new ImageIcon(logoURL);
            logoLabel = new JLabel(myImage);
        } catch (java.net.MalformedURLException e) {
            System.out.println(e);
            System.out.println("Logo URL kann nicht aufgelöst werden");
            logoLabel = new JLabel("Logo fehlt");
        }
        return logoLabel;
    }
 
    /**
     * Behandlung der JButton Ereignisse
     * 11. ter Schritt
     * @param e
     */
    public void actionPerformed(ActionEvent e) {
        JButton source = (JButton) (e.getSource());
        if (source == exitButton) {
            System.exit(0);
        }
        if (source == okButton) {
            System.out.println("OK Button clicked");
            myException.printStackTrace();
        }
 
    }
 
    /**
     * Hauptporgramm zum Testen des Ausnahmefensters
     * @throws Exception
     */
    public static void main(String[] args) {
        AusnahmeFensterInnere dasFenster;
 
        try {myTestMethod();}
        catch (Exception e) {
            dasFenster = new AusnahmeFensterInnere("Hier läuft etwas schief",e);
            }
 
    }
 
    /**
     * Eine Testmethode die eine durch eine Division durch Null eine
     * Ausnahme provoziert
     * @throws Exception
     */
    public static void myTestMethod() throws Exception {
 
        int a = 5;
        int b = 5;
        int c = 10;
        c = c / (a-5);
        System.out.println("Programm regulär beendet");
    }
}
 

 

Lernziele (Swing, innere und anonyme Klassen)

Am Ende dieses Blocks können Sie:

  • ... einfache Swing Oberflächen mit
    • Komponenten,
    • Layoutmanagern
    • Eventlistenern
    • Fenster, Menüleisten, Menüeinträge implementieren
  • ... innere and anonyme Klassen benutzen um Eventlistener einfacher und übersichtlicher zu implementieren
  • ... die wichtigsten Layoutmanager von Swing benennen und kennen die Strategien nach denen sie das Layout für Komponenten berechnen

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:Fragen zur graphischen Programmierung (Swing)

Zur Umfrage

QR Code für Umfrage