Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java 2 von Friedrich Esser
Designmuster und Zertifizierungswissen
Zum Katalog
gp Kapitel 15 Java Foundation Classes
  gp 15.1 Vorbemerkungen
  gp 15.2 Begriffe
  gp 15.3 Top-Level-Container
  gp 15.4 Layout-Manager
  gp 15.5 Ereignisbehandlung
  gp 15.6 MFC-Architektur
  gp 15.7 Multi-Threading in Swing
  gp 15.8 Applet
  gp 15.9 Zusammenfassung
  gp 15.10 Chamäleon-Architektur

Kapitel 15 Java Foundation Classes

Unter Java Foundation Classes, kurz JFC, sind mehrere Packages zusammengefasst, die für verschiedene Aufgaben APIs bereitstellen.
Die Ereignisbehandlung und grundlegende Grafik-Komponenten sind im AWT (Abstract Windowing Toolkit) enthalten.
Auf dem AWT bauen 2D-Graphik, Swing als plattformunabhängiges GUI (Graphical User Interfaces) sowie diverse APIs zum Drucken, Drag&Drop und Accessibility für die behindertengerechte Ein- und Ausgabe auf.
Der Gesamtkonzeption folgend werden die grundlegenden JVC-Mechanismen und -Muster behandelt.


Galileo Computing

15.1 Vorbemerkungen  downtop

Das AWT gibt es bereits seit Java 1.0. Mit Java 1.1 wurde ein anderes Ereignis-Modell und mit Java 1.2 dann eine verbesserte 2D-Graphik mit Namen Java 2D und die Benutzerschnittstelle Swing eingeführt.

Vor allem wurde Swing als Meilenstein einer neuen GUI-Generation gefeiert, da es unabhängig von dem GUI-API des Betriebssystems eine komplette graphische Benutzerschnittstelle mit vielen Komponenten bereitstellt. Als herausragende Features von Swing werden

Pluggable Look & Feels

gp  eine anpassbare graphische Oberfläche (pluggable Look-and-Feels)

Lightweight
Components

gp  so genannte leichtgewichtige Komponenten (lightweight Components)

Model-View-
Controller

gp  eine MVC-Architektur (Model-View-Controller)

angesehen.

Paradigma der heterogenen IT:
lose Kopplung auf Basis universeller Standards

Nachdem sich nun die Euphorie um Swing gelegt hat, wird ihr Stellenwert sowie Stärken und Schwächen klarer.

In einer verteilten, heterogenen IT-Welt, in der Applikationen auf verschiedenen Ebenen und Systemen laufen, ist eine möglichst lose Kopplung zwischen Servern und Clients essenziell.

gp  Kommunikation zwischen Servern und Clients funktioniert dann am besten, wenn beide möglichst wenig Annahmen über die Fähigkeiten des anderen zugrunde legen.

Swing ist
ressourcen-intensiv

Damit Swing als Client-GUI ein für Benutzer akzeptables Verhalten zeigt, benötigt es entsprechende Ressourcen, schnelle Prozessoren und einen swing-konformen Browser.

Internet-Anwendungen können diese o.a. Annahmen kaum über Clients machen, was impliziert:

Swing vs. Internet und Markup-Sprachen

gp  Nur in unternehmensweiten Applikationen (Intranet) ist eine enge Integration bzw. ein GUI wie Swing auf Seiten der Clients sinnvoll.
gp  Für Internet bzw. ressourcenschonende Applikationen, die gleichermaßen von PDAs, PCs und Workstations genutzt werden, sind Techniken, die auf Markup-Sprachen beruhen, besser geeignet.

Denn im ersten Fall liegt die Aufgabe der grafischen Darstellung und Interaktion mit dem Benutzer eindeutig beim (Java-)Programmierer. Er wird bei Einsatz von Swing feste Annahmen über die Client-Maschinen machen müssen.

Da Maschinen und Betriebssystem somit bekannt sind, konkurriert Swing dann auch mit proprietären Lösungen wie z.B. WFC .

Frontend: Domäne der Nicht-/BindestrichInformatiker

Im zweiten Fall liegt die Benutzer-Interaktion in der Regel in den Händen von Grafik-Designern, bestenfalls Bindestrich-Informatikern.

gp  Frontend-Ersteller – nicht unbedingt Informatiker – bevorzugen eindeutig auch Frontend-Werkzeuge, die automatisch Markup-Code erzeugen, in den dann die Funktionalität bzw. Interaktion mit dem Server möglichst transparent eingebettet werden soll.

Swing konkurriert daher mit verschiedenen anderen Lösungen, die natürlich auch in Java zur Verfügung stehen. Hierzu zählen u.a. Java Server Pages und Packages zur XML-Unterstützung.

Swing: eine interessante Design-Studie

Abgesehen davon sind aber Ereignisverarbeitung, Komponenten-, Layout- oder MVC-Techniken nicht uninteressant, da sie auf Pattern und Basiswissen beruhen, deren Kenntnis für andere Bereiche durchaus nützlich sein kann.

gp  Die Intention der nachfolgenden JFC-Betrachtung ist neben der Vermittlung absolut notwendiger Grundlagen das Kennenlernen von Konzepten, die übergreifend auch für andere Bereiche interessant sind.

Grundlagen, Beispiele und Konzepte werden fast ausschließlich anhand der neuen APIs vorgestellt.


Galileo Computing

15.2 Begriffe  downtop

Um in Java graphische Anwendungen zu schreiben, sind folgende Begriffe hilfreich.

Component

gp  Component umfasst alle möglichen graphischen Komponenten, angefangen von dem obersten Applikations- bzw. Applet-Fenster bis hin zu spezialisierten Komponenten für Passwörter, Bilder oder Zeichnungen.

Heavyweight Component

gp  Heavyweight Components sind Komponenten, die direkt an Fenster des Betriebssystems gebunden sind. Damit ist das Look-and-Feel (Form und Verhalten) dieser Komponenten ebenfalls an das Betriebssystem gebunden.

Lightweight
Component

gp  Lightweight Components sind Komponenten, die mit Hilfe von 2D-Graphikroutinen innerhalb von heavyweight Komponenten gezeichnet werden. Aussehen und Verhalten sind damit unabhängig vom Betriebssystem.

Container

gp  Container sind spezielle Komponenten, die andere Komponenten, d.h. auch andere Container, aufnehmen können.

Layout-Manager

gp  Layout-Manager übernehmen die Anordnung der Komponenten in einem Container. Es gibt verschiedene vordefinierte Layout-Manager, die entweder dem Container fest zugeordnet oder frei wählbar bzw. auch implementierbar sind.

Top-Level-
Container

gp  Top-Level-Container bezeichnet diejenigen Container, die als äußeres Fenster gewählt werden können.

AWT 1.0:
heavyweight

Das AWT von Java 1.0 besteht nur aus heavyweight Components. Damit wurde das Aussehen z.B. einer Schaltfläche oder Liste fest an Windows, Motif oder Macintosh gebunden.

Bei Swing wird es als Vorteil angesehen, jederzeit auf einer Plattform alle anderen Look-and-Feels emulieren zu können.

Swing: awaiting the GigaHertz-World

Die Kehrseite ist klar. Swing ist äußerst ressourcenintensiv und selbst auf schnellen CPUs nicht sehr reaktiv. Dies ist der Preis der flexiblen Konzeption, die sich in Hunderten von Klassen mit einem komplexen Beziehungsgeflecht und Mustern kristallisiert.


Galileo Computing

15.3 Top-Level-Container  downtop

Top-Level-Container
mit native Peer

Ausgangspunkt jeder graphischen Anwendung ist ein Top-Level-Container, der als äußere Komponente heavyweight ist und damit einen native Peer – ein gleichartiges Fenster der Plattform – hat.

In diesen Container werden dann Subcontainer und graphische Komponenten eingefügt (siehe Abb. 15.1).

Klassen-Struktur der Komponenten


Abbildung
Abbildung 15.1   Logischer Komponenten-Aufbau

(J)Applet/Dialog/Frame/Window

Die Top-Level-Container im »alten« AWT sind Applet, Dialog, Frame und Window, die in Swing JApplet, JDialog, JFrame und JWindow.

Im AWT waren allerdings gewisse Ungereimtheiten enthalten, die einer einfachen Erweiterung entgegenstanden. Unter anderem wurden Menü-Komponenten nicht von Component abgeleitet.

JComponent:
Basisklasse in Swing

Eine weitere Schwierigkeit besteht darin, dass in Swing alle Komponenten von JComponent abgeleitet werden, um die erweiterten Eigenschaften zu nutzen.

Ausnahme:
Root-Container

Natürlich bilden die Top-Level-Container von Swing eine Ausnahme, da sie bereits von ihren AWT-Pendants abgeleitet wurden. Also muss zwangsläufig ein einzelnes inneres Objekt JRootPane in jedem Root-Container von Swing erzeugt werden, das sich dann – der neuen Swing-Hierarchie folgend – von JComponent ableitet.

Damit sieht die Klassen-Hierarchie nicht mehr ganz so einfach aus (siehe Abb.15.2).

Klassen-Hierarchie der Root-Container


Abbildung
Abbildung 15.2   Packages und Hierarchie der Root-Container

Interessant an der Hierarchie ist, dass alle Swing-Komponenten auch Container sind. Aber nur wenige werden tatsächlich als Container benutzt.

JInternalFrame:
virtueller Top-Level-Container

Des Weiteren gibt es noch einen lightweight Container JInternalFrame, der sich wie ein Top-Level-Container verhält, allerdings nicht als äußeres Fenster verwendet werden kann. Da er von keinem AWT-Container abgeleitet ist, ist JInternalFrame Subklasse von JComponent.

MDI-
Unterstützung

Zusammen mit JDesktopPane, einem Container für JInternalFrame, unterstützt somit Swing auch MDI (Multiple Document Interface), d.h., es können komplette Fenster in Fenster eingebettet werden.


Galileo Computing

15.3.1 JRootPane  downtop

Die fünf Root-Container, d.h. die vier Top-Level-Container und JInternalFrame, enthalten genau eine Instanz von JRootPane, die automatisch erzeugt wird.

JRootPane =
glassPane + layeredPane

JRootPane ist ein Container mit einem festen Aufbau. Er setzt sich aus einer Komponente, glassPane, und layeredPane, Instanz der Klasse JLayeredPane, zusammen.

glassPane ist per Default eine Instanz von JPanel, liegt über den anderen Komponenten und fängt die Ereignisse der Maus ab. Allerdings könnte es als beliebige Komponente auch zum Zeichnen eingesetzt werden.

Wie bereits der Name sagt, wird JLayeredPane dazu benutzt, mehrere Komponenten, die sich möglicherweise überlappen, in einer Reihenfolge anzuordnen (siehe 15.3.2).

layeredPane =
menuBar + contentPane

Die layeredPane enthält in JRootPane allerdings nur die beiden Komponenten menuBar, Instanz von JMenuBar, und einen Container contentPane. Der Container contentPane ist per Default ein JPanel und nimmt die eigentlichen Komponenten des Fensters auf (siehe Abb. 15.3).

Anordnung in JRootPane


Abbildung
Abbildung 15.3   Aufbau von JRootPane

Von allen aufgeführten Komponenten ist nur die Menüleiste optional.

Interface
RootPaneContainer

Das Interface RootPaneContainer wird von allen Root-Containern implementiert und enthält Zugriffs-Methoden auf JRootPane und seine internen Komponenten (mit Ausnahme der optionalen Menüleiste).

interface RootPaneContainer {
  JRootPane    getRootPane();
  Component    getGlassPane();
  JLayeredPane getLayeredPane();
  Container    getContentPane();
  void setGlassPane  (Component glassPane); 
  void setLayeredPane(JLayeredPane layeredPane);
  void setContentPane(Container contentPane); 
}

Beispiele

Da die Ereignisbehandlung erst nachfolgend behandelt wird, reagiert der Top-Level-Container JFrame einzig auf das Ereignis »Schließen des Fensters« mit Beenden der Applikation (siehe Abb. 15.4).

JFrame-Beispiel:
JMenuBar + JLabel

package kap15;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Test {
  public static void main(String[] args) {
    // neues Top-Level-Fenster anlegen
    JFrame jf= new JFrame();
    // Ereignis "Schließen der Frame" beendet 
Applikation
    jf.addWindowListener( new WindowAdapter() {
        public void windowClosing(WindowEvent e)
         { System.exit(0); }
    });

getRootPane()

    // eine Menüleiste hinzufügen
    JMenuBar jbar= new JMenuBar();
    jf.getRootPane().setJMenuBar(jbar);
    // einen Menüeintrag hinzufügen
    JMenu jmenu= new JMenu("Menü1");
    jbar.add(jmenu);
    // zwei Einträge zu Menü1 hinzufügen
    jmenu.add("1. Eintrag"); jmenu.add("2. Eintrag");

getContentPane()

    //auch möglich: JPanel jp= (JPanel) jf.getContentPane(); 

    Container jp= jf.getContentPane();
    // Eine Text in den contentPane einfügen
    jp.add(new JLabel("--- Komponente im contentPane ---"));
    // Anzeigen des Fensters
    jf.pack();
    jf.setVisible(true);           // show() ist deprecated!
  }
}

Alle Container verfügen über eine Methode add(), um andere Komponenten aufzunehmen.


Abbildung
Abbildung 15.4   Windows-Fenster zum Test-Programm

GlasPane-Beispiel

GlassPane-Variation: Im folgenden Test wird eine Komponente in glassPane eingefügt. Da glassPane über menuBar und contentPane liegt, werden die Komponenten in glassPane zum Schluss gezeichnet und überdecken eventuell die darunter liegenden (siehe Abb. 15.5).


Abbildung
Abbildung 15.5   glassPane überdeckt die anderen Container

Subklasse von JFrame:
reagiert auf Ereignis »Schließen«

Damit nicht immer wieder der Code für »Schließen des Fensters« hinzugefügt werden muss, wird eine Subklasse EJFrame von JFrame abgeleitet.

class EJFrame 
extends JFrame {
  public EJFrame() {
    addWindowListener(new WindowAdapter() {

windowClosing()

        public void windowClosing(WindowEvent 
e)
         { System.exit(0); }
    });
  }
}

Komponenten im glassPane überdecken alles

public class Test {
  public static void main(String[] args) {
    JFrame jf= new EJFrame();  // Kommentar siehe unten

getGlassPane()

    JPanel jg= (JPanel) jf.getGlassPane();
    jg.add(new JLabel("--- Komponente im 
glassPane ---"));
    jg.setVisible(true); // sonst unsichtbar
    Container jp= jf.getContentPane();
    jp.add(new JLabel("--- Komponente im contentPane ---"));
    jf.setSize(250,70);  // Fenstergröße manuell setzen
    jf.setVisible(true);
  }
}

Galileo Computing

15.3.2 JLayeredPane  downtop

Anordnung mittels Layers und Positionen

Der Container layeredPane von JRootPane ordnet alle Komponenten, die man mittels seiner speziellen add()-Methoden einfügt, in einem Stack an, sofern sie sich überlappen. Hierzu benutzt layeredPane Ebenen bzw. Layer und innerhalb der Ebenen wieder Positionen.

gp  Layer werden anhand Integer-Objekten geordnet, wobei Layer mit einer größeren Integer-Zahl über denen mit einer kleineren liegen.
gp  Komponenten innerhalb derselben Layer werden anhand einer int-Position geordnet, wobei die Komponente mit der Position 0 zuoberst liegt. Komponenten mit einer größeren Position liegen dann unter denen mit einer kleineren.

Beispiel

In der nächsten Test-Variation werden Schaltflächen zur besseren Sichtbarkeit in verschiedenen Ebenen und Positionen angeordnet. Da JLayeredPane keinen Layout-Manager benutzt, müssen die Komponenten ihre Größe und Pixel-Position selbst setzen.

class MyButton extends JButton {
  private static int num= 0;

JLayeredPane

  public MyButton (JLayeredPane lp, int layer, int 
pos) {
    setBounds(num*30,num*30,150,50);     // x,y, Breite, Höhe
    setForeground(Color.black);          // Schrift schwarz
    setText("Nr. "+ num++ +" Layer "+ layer+", Pos. "+ pos);
    lp.add(this,new Integer(layer),pos); 
// anordnen
  }
}

Anordnung von Schaltflächen mittels Layer- und Positionsangabe

public class Test {
  public static void main(String[] args) {
    JFrame jf= new EJFrame();            // EJFrame wie oben

getLayeredPane()

    JLayeredPane jlp= jf.getLayeredPane();
    new MyButton(jlp,3,1);  // obere Layer
    new MyButton(jlp,1,1);  // untere Layer, untere Position
    new MyButton(jlp,1,0);  // untere Layer, obere  Position
    new MyButton(jlp,2,1);  // mittlere Layer
    jf.setSize(300,180);
    jf.setVisible(true);
  }
}

Die Anordnung entspricht dann der in Abb. 15.6 dargestellten.

Layer und Positionen am Beispiel


Abbildung
Abbildung 15.6   Anordnung in Ebenen und Positionen bei JLayeredPane

DRAG_LAYER:
ganz oben

Die beiden Komponenten contendPane und menuBar werden in einer vordefinierten Ebene JLayeredPane.FRAME_CONTENT_LAYER (der zugehörige Wert ist -30000) eingeordnet, d.h. im Normalfall ganz unten.

CONTENT_LAYER:
ganz unten

Die oberste vordefinierte Ebene JLayeredPane.DRAG_LAYER (Wert: 400) ist dann Drag-Komponenten vorbehalten, die beim Ziehen über die Oberfläche immer über den anderen erscheinen müssen.


Galileo Computing

15.3.3 JWindow  downtop

JWindow: Fenster ohne Dekoration

JWindow, direkt abgeleitet von Window, ist ein rudimentäres Fenster ohne Dekoration (Fensterleiste, Größenanpassung, Schließen).

Damit sind die Einsatzmöglichkeiten recht gering und in der Regel auf Einsätze wie z.B. Startfenster begrenzt.

Beispiel

Zentrieren des Fensters auf dem Bildschirm

Diese Variation der Test-Klasse verwendet eine ScreenUtil-Klasse zur Zentrierung eines Windows auf dem Bildschirm. Des Weiteren werden der Font und die Größe des Textes manipuliert.

getDefaultToolkit(),
getScreenSize()

final class ScreenUtil {
  private ScreenUtil() {}
  public static void center(Window w, int 
width, int height) {
    Dimension scr= Toolkit.getDefaultToolkit().getScreenSize();
    w.setBounds((scr.width-width)/2,(scr.height-height)/2,
                 width,height);
  }
}

JWindow:
ein ideales Startfenster

public class Test {
  public static void main(String[] args) {
    JWindow jw= new JWindow();
    JLabel jl= new JLabel();

JLabel, Font:
setText(), setFont()

    // Font setzen: Font-Name, Stil, Größe
    jl.setFont(new Font("Bookman",Font.BOLD+Font.ITALIC,18));
    jl.setText("Startfenster");

setLayout(null)
setBounds()

    // ohne Layout-Manager: Größe und Position 
selbst setzen
    jw.getContentPane().setLayout(null);
    jl.setBounds(20,10,180,50);
    jw.getContentPane().add(jl);
    ScreenUtil.center(jw,150,70);
    jw.setVisible(true);
    // ... weitere Aktionen, Window schließen etc.
  }
}

Abbildung
Abbildung 15.7   JWindow, zentriert im Bildschirm

Toolkit-Klasse
mit nützlichen Operationen

Die abstrakte Klasse java.awt.Toolkit liefert mit Hilfe der statischen Methode getDefaultToolkit() eine Instanz von sich, die Informationen und Anpassungen an die Plattform/Hardware bereitstellt.

Die Toolkit-Instanz liefert u.a. Bildschirmauflösung und -größe, bietet Cursor-Anpassungen und Font-Informationen.

gp  Die Klasse Toolkit sollte selten direkt genutzt werden, da sie die Plattformunabhängigkeit nicht unbedingt fördert.

Font-
Anpassungen

Die Klasse java.awt.Font erlaubt alle Font-Familiennamen im Konstruktor, die das System bietet. Im zweiten Argument wird der Stil (als Konstanten Font.PLAIN, BOLD, ITALIC oder BOLD+ITALIC) und im dritten die Größe in Punkten übergeben.


Galileo Computing

15.3.4 JFrame vs. Frame  downtop

JFrame mit plattformspezifischer
Dekoration

Eine Instanz von JFrame oder Frame ist normalerweise das Hauptfenster einer Applikation. Beide Top-Level-Container bieten per Default plattformspezifisch einen Rahmen, Titelleiste mit Minimierungs-, Maximierungs- und Schließ-Schaltflächen.

Frame vs. JFrame

gp  Die Superklasse Frame aus dem AWT enthält kein JRootPane, d.h., in Frame werden die Komponenten direkt mit add() eingefügt.

JFrame enthält dagegen wie alle Top-Level-Container von Swing ein JRootPane und lässt das Einfügen von Komponenten nur in contentPane zu. Der Versuch, Komponenten in JFrame direkt mit add() einzufügen, endet mit einer Ausnahme.

Wie bereits bekannt, reagiert JFrame nicht automatisch auf das Ereignis »Schließen« mit dem Beenden der Applikation, sondern macht nur das Fenster unsichtbar.

Frame, JFrame:
subtile Unterschiede

Das folgende Code-Fragment öffnet ein AWT- und ein Swing-Fenster. Der Code für beide ist sehr ähnlich, jedoch gibt es subtile Unterschiede:

Frame:
setBackground(), add()

Frame f= new Frame();     // oder direkt Frame("Titel")
f.setSize(100,100);
f.setTitle("Titel");
f.setBackground(Color.green);  // direkt 
für die Frame
f.add(new Label("hallo"));     // direkt 
in die Frame
f.setVisible(true);
// Frame lässt sich per Default nicht schließen!
JFrame jf= new JFrame();  // oder direkt JFrame("Titel")
jf.setSize(100,100);
jf.setTitle("Titel");

JFrame:
setBackground(), add()

// jf.setBackground(Color.green);  wäre möglich, 

// aber nutzlos, da contentPane alles überdeckt.
jf.getContentPane().setBackground(Color.green);
jf.getContentPane().add(new JLabel("hallo"));
jf.setVisible(true);
// JFrame wird per Default beim Schließen nur unsichtbar!

Frame reicht
für einfache Java-Apps

gp  Für einfache Oberflächen und zur Schonung der Ressourcen reicht in der Regel ein Frame. Haben Flexibilität oder die Nutzung neuer Fähigkeiten Priorität, ist JFrame vorzuziehen.

WindowConstants

WindowConstants:
Anpassen des Schließverhaltens

JFrame zusammen mit JDialog und JInternalFrame implementieren das Interface WindowConstants, welches nur drei Konstanten definiert, um das Verhalten beim »Schließen« anzupassen.

public interface WindowConstants 
{
  int DO_NOTHING_ON_CLOSE= 0; // wie Frame
  int HIDE_ON_CLOSE= 1;       // default
  int DISPOSE_ON_CLOSE= 2;    // Ressourcen freigeben
}

setDefaultCloseOperation()

Ein bestimmtes Schließverhalten wird dann wie folgt gesetzt:

jf.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

L&F: Look-and-Feels mit UIManager

UIManager:
L&F innerhalb eines Top-Level-Containers

Für die grafische Darstellung der Komponenten in einem Top-Level-Container wie JFrame stehen zumindest zwei bzw. drei verschiedene Oberflächen zur Verfügung.

Im folgenden Code-Fragment werden die verschiedenen L&F angezeigt und die erste gesetzt:

UIManager.LookAndFeelInfo[] lafArr= 
                       UIManager.getInstalledLookAndFeels();
for (int i= 0; i<lafArr.length; i++)
  System.out.print(lafArr[i].getName()+" ");
try {
  UIManager.setLookAndFeel(lafArr[0].getClassName());
} catch (Exception e) { System.out.println(e); }

Galileo Computing

15.3.5 JDialog und JOptionPane  downtop

JDialog:
spezialisiert auf Dialoge

Die Klasse JDialog ist ein Top-Level-Container, der auf Dialoge spezialisiert ist. Er hat deshalb auch keine Minimierungs- bzw. Maximierungs- Schaltflächen, sondern nur eine Schließ-Schaltfläche.

Modaler Modus

Des Weiteren gibt es einen Konstruktor, der das Fenster im modalen Modus öffnet, d.h., kein anderes Fenster derselben Applikation kann aktiv sein, bevor dieser Dialog nicht geschlossen wird.

Beispiel

Nachfolgend wird das L&F auf Motif gesetzt, der Dialog im Hauptfenster zentriert und das Schließverhalten auf DISPOSE_ON_CLOSE gesetzt.

setLookAndFeel()

try {
  UIManager.setLookAndFeel(
           "com.sun.java.swing.plaf.motif.MotifLookAndFeel");
} catch (Exception e) { System.out.println(e); }
JFrame jf= new JFrame("JFrame");
ScreenUtil.center(jf,200,150);    // siehe Beispiel in JWindow
jf.setVisible(true);

setLocationRelativeTo()

JDialog jd= new JDialog(jf,"JDialog",true);   // true= modal
jd.setSize(100,50);
jd.setLocationRelativeTo(jf);     // zentiert in jf   

setDefaultCloseOperation()

jd.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
jd.show();                        // wie setVisible(true)

Abbildung
Abbildung 15.8   JDialog, zentriert im zugehörigen Hauptfenster

show():
deprecated

gp  Die Superklasse Component im AWT enthält eine Methode show(), die als deprecated markiert ist. In der Regel sollte setVisible(true) verwendet werden.

Allerdings wird in einigen Subklassen wie z.B. Dialog die Methode show() wieder gültig überschrieben und wie in JInternalFrame mit zusätzlicher Funktionalität versehen.

JOptionPane

JOptionPane:
nützliche Dialog-Muster

Kurze Mitteilungen oder Abfragen sollten eine einheitliche Struktur aufweisen. Immer wieder den gleichen JDialog-Code zu schreiben, ist lästig und redundant.

Hier kommt die Klasse JOptionPane zur Hilfe. Sie ist direkt von JComponent abgeleitet und definiert bzw. implementiert eine Palette brauchbarer Dialog-Muster mit Hilfe von JDialog oder JInternalFrame (siehe Abb. 15.9).

Visuelles Äußeres von JOptionPane


Abbildung
Abbildung 15.9   Prinzipieller visueller Aufbau von JOptionPane

JOptionPane bietet Dialog-Schablonen

JOptionPane stellt für verschieden Aufgaben statische Methoden und Konstanten zur Verfügung, die für die meisten Aufgaben ausreichen. Sollte eine Instanz von JOptionPane wiederverwendet werden, können sie auch mit Hilfe passender Konstruktoren angelegt werden.

Die statischen Methoden decken vier Typen von Dialogen ab:

Bestätigungen

gp  Bestätigen/Confirm: Frage, Fehler, Warnung etc. mit diversen Schaltflächen.

Textangaben

gp  Eingabe/Input: Zeile, Liste etc. mit zwei Schaltflächen OK und Cancel.

Mitteilungen

gp  Mitteilung/Message: Mitteilung mit der Schaltfläche OK.

Optionen

gp  Option: Beliebige Daten mit Schaltflächen.

Drei statische Methoden sollen an kleinen Code-Fragmenten demonstriert werden.

Beispiele

Der folgende Dialog hat kein übergeordnetes Fenster (parentComponent ist null), hat einen String als Mitteilungs-Objekt, einen Titel, die Schaltfläche OK und ein Icon, das einen Fehler symbolisieren soll (siehe Abb. 15.10). Ist das übergeordnete Fenster null, wird der Dialog im Bildschirm zentriert.

showConfirmDialog()

System.out.println(
   JOptionPane.showConfirmDialog (null,
      "Mitteilung mit CLOSED_OPTION","ConfirmDialog",
      JOptionPane.CLOSED_OPTION,
      JOptionPane.ERROR_MESSAGE)
   );                                               // :: 0

Bestätigung mit JOptionPane im
Motif-L&F


Abbildung
Abbildung 15.10   JOptionPane.showConfirmDialog() im Motif-L&F

Die Methode showInputDialog() akzeptiert ein Array von Objekten als Auswahl, wobei als letztes Argument das per Default selektierte Objekt übergeben werden kann.

Die Darstellung der Objekte erfolgt in diesem Fall in einer JComboBox (siehe Abb. 15.11).

showInputDialog()

System.out.println(
  JOptionPane.showInputDialog (null,
      "Auswahliste","InputDialog",
      JOptionPane.QUESTION_MESSAGE, null,
      new String[] { "1.","2.","3.","4.","5.","6."},
      "3.")
  );                                  // Bei Cancel :: null 
                                      // Bei OK     :: 3.

Auswahlliste mit JOptionPane im
Motif-L&F


Abbildung
Abbildung 15.11   JOptionPane.showInputDialog() im Motif-L&F

Modaler
Options-Dialog

Im folgenden Beispiel wird ein Hauptfenster erzeugt, in dem das Option-Dialog-Fenster zentriert und modal geöffnet wird.

Die Darstellung der Objekte erfolgt in diesem Fall mit Schaltflächen. Bei einer Auswahl wird als Rückgabewert der Array-Index zurückgegeben, ansonsten der Wert -1 (siehe Abb. 15.12).

try {
  UIManager.setLookAndFeel(
    "com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (Exception e) { System.out.println(e); }
JFrame jf= new JFrame("JFrame"); jf.show();

showOptionDialog()

String[] sarr= new String[] { "1.","2.","3.","4.","5.","6."};
System.out.println(
  JOptionPane.showOptionDialog(jf,
      "Optionsauswahl","OptionDialog",
      JOptionPane.DEFAULT_OPTION, 
      OptionPane.QUESTION_MESSAGE,null,sarr,null)
  );

Optionen mit JOptionPane in einem Hauptfenster im Windows-L&F


Abbildung
Abbildung 15.12   JOptionPane.showOptionDialog() im Windows-L&F

Die beiden null-Werte in showOptionDialog() bedeuten »kein eigenes Icon« und »keine Default-Selektion«.


Galileo Computing

15.4 Layout-Manager  downtop

Layout-Manager:
Design- Kompromisse

Container verfügen per Default über einen Layout-Manager, der die Aufgabe hat, die Komponenten, die hinzugefügt werden, im Inneren nach einem Schema anzuordnen.

gp  Layout-Manager sind immer ein Kompromiss zwischen optimalem Design und Flexibilität.

Alternative zum
XY-Design

Kennt man alle Umgebungs-Variablen wie Fenster-, Schrift- oder Randgrößen, führt eine exakte Positionierung und Größe der Komponenten zu einem optimalen Design der Oberfläche.

Layout-Manager: Anpassungen zur Laufzeit

Da die Oberfläche allerdings meistens auf unterschiedlichen Plattformen bzw. unterschiedlicher Hardware laufen soll, muss man die Arbeit einem Layout-Manager überlassen, der dann zur Laufzeit versucht, die Lage der Komponenten an die Umgebung (sub)optimal anzupassen.

Eigenschaften von Layout-Managern

Layout-Manager

gp  können für die meisten Container mit setLayout() frei gewählt werden.
gp  können auf null gesetzt werden, was bedeutet, dass man Position und Größe für jede Komponente selbst setzen muss, z.B. mit setBounds().
gp  können selbst entwickelt werden, d.h., sie müssen das Interface LayoutManager oder das Subinterface LayoutManager2 implementieren.
gp  werden vom Container bei allen Änderungen, die das Layout berühren, wie z.B. beim Einfügen und Entfernen von Komponenten aufgerufen.

Das AWT und Swing definieren bereits einige häufiger benötigte Layout-Schemata (siehe Abb. 15.13).

AWT- und Swing- Layout-Manager: interface-basierende Hierarchie


Abbildung
Abbildung 15.13   Layout-Manager im AWT und in Swing

Der LayoutManager2 erweitert also das ursprüngliche Interface um weitere Methoden, um unter anderem Constrains – zusätzliche Beschränkungen – spezifizieren zu können.


Galileo Computing

15.4.1 Überblick  downtop

Größeneigenschaften von JComponent

Jede Swing-Komponente JComponent hat eine minimale, maximale und bevorzugte Größe. Sie kann – sofern erforderlich – mit setMinimumSize(), setMaximumSize() bzw. setPreferredSize() gesetzt werden.

Bevorzugte Größe

Die letztgenannte bevorzugte Größe (Typ Dimension) gibt an, wie groß die Komponente sein sollte, um alle internen Elemente optimal darzustellen und ist deshalb für Layout-Manager von besonderer Bedeutung.

Icon

Bei einer Schaltfläche JButton berechnet sich die bevorzugte (und minimale) Größe z.B. aus der Textgröße und -länge, dem Textabstand zum Rand und der Randgröße selbst. Sie wird automatisch berechnet, wobei das Maximum praktisch unbegrenzt ist.

Wahl der Größe

gp  Lassen es die Umgebung und das Layout-Schema zu, wird der Layout-Manager die bevorzugte Größe der Komponente einhalten.

setSize(),
setBounds()

Versuche, z.B. mittels setSize() oder setBounds(), die Größe der Komponenten selbst zu bestimmen, scheitern immer daran kläglich, dass der Layout-Manager das letzte Wort hat.

Schemata der AWT-Layout-Manager

Tabelle 15.1   Layout-Manager im AWT
Layout-Manager Layout-Schema
FlowLayout Komponenten werden mit ihrer bevorzugten Größe wie Wörter in Zeilen angeordnet, wobei diese umbrechen können. Wie bei Text kann die Zentrierung mit LEFT, RIGHT oder CENTER angegeben werden.
GridLayout Komponenten werden ohne Rücksicht auf ihre bevorzugte Größe in die Zellen einer Matrix eingeordnet.
BorderLayout Komponenten können in die Kompass-Bereiche NORTH, SOUTH, EAST, WEST oder in das Zentrum CENTER eingefügt werden. Die bevorzugte Größe wird abhängig vom Bereich berücksichtigt.
CardLayout Jede Komponente ist so groß wie der Container und zu einem Zeitpunkt ist immer nur eine Komponente sichtbar.
GridBagLayout Ein GridLayout, in dem Reihen und Spalten unterschiedliche Höhen oder Breiten haben können. Komponenten können eine oder mehrere Zelle(n) einnehmen, wobei die Informationen beim add() an eigene Instanzen der Klasse GridBagConstraints übergeben werden.

Alle in Swing definierten Layout-Manager sind für spezifische Komponenten definiert, wobei das BoxLayout allerdings auch generell nützlich sein kann.

Schemata der Swing-Layout-Manager

Tabelle 15.2   Spezialisierte Layout-Manager in Swing
Layout-Manager Layout-Schema
ScrollPaneLayout Spezialisiert für den JScrollPane-Container, definiert dieses Layout neun Bereiche, in die ein »Lauf«-Bereich eingeteilt werden kann. Neben dem eigentlichen Laufbereich JViewPort sind dies die vier Ecken, die Zeilen- und Spaltenköpfe und die vertikalen und horizontalen Rollbalken.
ViewportLayout Spezialisiertes Layout für JViewPort (siehe oben).
BoxLayout Dieses Layout, benutzt vom Box-Container, fügt die Komponenten entweder horizontal von links nach rechts oder vertikal von oben nach unten in einer Reihe/Box ein. Dabei beachtet es im Gegensatz zum GridLayout die bevorzugte Größe der Komponenten.
OverlayLayout Überlagert Komponenten anhand eines Ausrichtungspunkts und wird u.a. vom JMenuItem für Text und Icons benutzt.

Ausgangspunkt für einen optimalen Entwurf ist ein idealer Bildschirm mit exakt positionierten Komponenten idealer Größe, mit anderen Worten ein typisches XY-Design.

Es gibt – bis auf triviale Anordnungen – keinen einzelnen Layout-Manager, den man der contentPane eines Top-Level-Fensters zuweisen könnte, um dieses XY-Design nachzubilden. Deshalb gilt:

Kunst der Komposition von Layout-Managern

gp  Die Kunst besteht darin, eine geeignete Komposition von passenden Layouts zu finden, die dem idealen Design am nächsten kommt.

Hierzu muss man sicherlich die Wirkung der o.a. Layouts im Detail kennen, um Container mit passenden Layout-Managern entsprechend zusammenzusetzen.


Galileo Computing

15.4.2 BorderLayout und FlowLayout  downtop

Im folgenden Code-Fragment wird die Wirkung eines Border-Layouts mit einem eingebetteten Container mit Flow-Layout demonstriert.

Border-Layout mit internem Flow-Layout

JFrame jf= new EJFrame("Border/Flow-Layout"); jf.setSize(200,200); 
                      // siehe Erklärung
Container cp= jf.getContentPane();

setLayout()

//cp.setLayout(new BorderLayout()); 
überflüssig, da default! cp.add(new JButton("Nord"),BorderLayout.NORTH);
cp.add(new JButton("Süd"),BorderLayout.SOUTH);
cp.add(new JButton("West"),BorderLayout.WEST);
cp.add(new JButton("Ost"),BorderLayout.EAST);
Container jp= new JPanel();
//jp.setLayout(new FlowLayout()); 
  überflüssig, da default!
for (int i= 0; i<9; i++) jp.add(new JButton("Nr. "+(i+1)));
jp.setSize(100,50);                         // ohne 
Wirkung!
cp.add(jp,BorderLayout.CENTER);

pack()

//jf.pack();        
                   siehe Kommentar unten!
jf.setVisible(true);

Reaktionen von Border- und Flow-Layout zur Laufzeit


Abbildung
Abbildung 15.14   Border-Layout mit Flow-Layout im Center

Erklärung: Die Größe des Top-Level-Fensters lässt im Center-Bereich nur eine Schaltfläche pro Zeile des Flow-Layouts zu (Abb. 15.14 oben links).

Sollte jf.pack() in der vorletzten Zeile ausgeführt werden, wird die Größe des Top-Level-Fensters anhand der inneren Komponenten bestimmt, d.h., jp.setSize() in der zweiten Zeile wird wirkungslos (Abb. 15.14 unten).

Eine Größenänderung des Top-Level-Fensters durch den Benutzer führt dann zu diversen anderen Anordnungen (Abb. 15.14 oben rechts).


Galileo Computing

15.4.3 CardLayout  downtop

CardLayout:
Karten nebeneinander angeordnet

Bei Containern mit CardLayout kann mittels add(Component c, Object constraints) jeweils eine Komponente pro Karte eingefügt werden. Das Objekt constraints muss dabei ein String sein, der die Aufgabe hat, die Karte zu identifizieren (kann aber auch "" gesetzt werden).

Kartenwechsel

CardLayout enthält dann fünf Methoden, die Karten zu wechseln:

   public void first   (Container parent);
   public void last    (Container parent); 
   public void next    (Container parent);
   public void previous(Container parent);
   public void show    (Container parent, String name);

Die contendPane wird nachfolgend auf CardLayout gesetzt und es werden drei Bilder eingefügt. Mittels next() werden dann die Karten gewechselt.


Abbildung
Abbildung 15.15   CardLayout mit drei »Karten«

setIcon()
ImageIcon next()

JFrame jf= new EJFrame("3 Cards");
Container cp= jf.getContentPane();
CardLayout cl; 
cp.setLayout(cl= new CardLayout());
JLabel im;
for (int i= 0; i<3; i++) {
  im= new JLabel();     // langweilige Version, siehe GridLayout
  im.setIcon(new ImageIcon("C:/Scan/bild"+i+".gif"));
  cp.add(im,"image"+i);
}
jf.pack();
jf.setVisible(true);
while (true) {
  try { Thread.sleep(2000); } catch (Exception e) {}
  cl.next(cp);  // wechselt am Ende zur 
ersten Karte
}

Galileo Computing

15.4.4 GridLayout  downtop

GridLayout:
grafische Matrix mit einheitlichen Zellen

Wie bei einer Matrix, wird bei der Anlage eines GridLayouts in der Regel die Anzahl der Zeilen und Spalten angegeben.

Da es sich um eine »grafische« Matrix handelt, können die Abstände in Pixel zwischen den Zellen angegeben werden. Die beiden ersten Konstruktoren sind Spezialfälle des letzten unten angeführten, d.h. rufen diesen über this(..) auf.

GridLayout-Konstruktoren

public GridLayout();                   // this(1, 
0, 0, 0);
public GridLayout(int rows, int cols); // this(rows,cols,0,0)
public GridLayout(int rows, int cols, int hgap, int vgap);

Das folgende Code-Fragment ist nur eine einfache Variation des Beispiels in CardLayout.


Abbildung
Abbildung 15.16   Gridlayout mit vier Zellen
JFrame jf= new EJFrame("GridLayout");
Container cp= jf.getContentPane();

Bildübergabe an
JLabel-Konstruktor

cp.setLayout(new GridLayout(2,2,10,10));
for (int i= 0; i< 3; i++) 
  cp.add(new JLabel(new ImageIcon("C:/Scan/bild"+i+".gif")));
cp.add(new JButton("Schaltfläche"));
jf.pack();
jf.setVisible(true);

JLabel kann im Konstruktor sofort ein Bild übergeben werden. Alle Komponenten bekommen die gleiche Größe, und zwar die der größten Komponente.


Galileo Computing

15.4.5 GridBagLayout  downtop

GridBagLayout:
große Flexibilität vs. Codier-Aufwand

Das GridBagLayout ist aufgrund der GridBagConstraints-Instanz, die bei add() mit jeder Komponente übergeben wird, sehr flexibel.

GridBagConstraints:
Steuerung mittels elf Parametern

gp  Der Aufwand für das Einfügen einer Komponente mit Hilfe eines GridBagLayout ist in der Regel größer als beim null-Layout.

Mit Hilfe von elf Parametern(!), gekapselt in einer GridBagConstraints-Instanz, wird das Verhalten jeder einzelnen Komponente definiert.

public GridBagConstraints();           // setzt Default-Werte
public GridBagConstraints(int gridx,int gridy,
 int gridwidth,int gridheight, double weightx,double weighty,
 int anchor, int fill, Insets insets, int ipadx,int ipady)

Übersicht über GridBagConstraints- Parameter

Tabelle 15.3   Positionierung von Komponenten mit GridBagConstraints
Parameter Bedeutung
gridx
gridy
Zeile/Spalte in der Matrix. Die Konstante RELATIVE (der Default-Wert) bedeutet bei x bzw. y rechts bzw. unter der letzten eingefügten Komponente.
gridwidth
gridheight
Anzahl der benötigten Zellen in der Zeile/Spalte (Default-Wert: 1,1). RELATIVE zeigt die vorletzte Komponente der Zeile bzw. Spalte an. REMAINDER bedeutet die letzte Komponente, die alle restlichen Zellen der Zeile/Spalte besetzt.
weightx
weighty
Steht zusätzlicher Platz in der Zeile oder Spalte zur Verfügung, wird dieser nach der relativen Gewichtung in der entsprechenden Zeile bzw. Spalte aufgeteilt. Je höher der Wert, umso größer die Zuteilung (Default-Wert: 0.0). Bei 0.0 erhält die Komponente nichts.
anchor Richtet die Komponente in der Zelle aus und bestimmt, welche Seite der Komponente zuerst abgeschnitten werden soll. Es können die Kompass-Bereiche NORTH, NORTHEAST, NORTHWEST, SOUTH, SOUTHEAST, SOUTHWEST, EAST, WEST und das Zentrum CENTER (der Default-Wert) angegeben werden.
fill Art, wie der zusätzliche Platz von der Komponente selbst ausgefüllt wird: NONE (der Default-Wert), HORIZONTAL, VERTICAL oder BOTH.
insets Instanz der Klasse Insets, welche die Abstände um die Komponente herum spezifiziert (siehe zweites Beispiel).
ipadx
ipady
Anzahl der Pixel, um die die minimale Größe der Komponente selbst vergrößert werden soll.

Beispiele

GridBagLayout
nur am Beispiel

Myriaden von Constrains-Kombinationen lassen sich ohnehin nicht darstellen. Zwei Beispiele zeigen deshalb nur die prinzipiellen Möglichkeiten, wobei das zweite ein Layout für einfache Bildschirmformulare nachbildet (und deshalb recht nützlich ist).

Icon
Fixieren einer Komponente

Aufgabe: Ein Bild mit einer Schaltfläche gleicher Breite unter dem Bild soll links am Fenster fixiert werden (siehe Abb. 15.17).


Abbildung
Abbildung 15.17   Laufzeitverhalten des GridBagLayouts bei Fixierung
JFrame jf= new EJFrame("GridBagLayout");
Container cp= jf.getContentPane();
cp.setLayout(new GridBagLayout());
GridBagConstraints c= 
new GridBagConstraints();  // Default
cp.add(new JLabel(new 
ImageIcon("C:/Bilder/bild0.gif")),c);

weightx: Unsichtbare Komponente bekommt gesamten zusätzlichen Raum

// c kann wiederverwendet werden, da es im add() 
kopiert wird
c.weightx=1.;
cp.add(new JLabel(""),c);
c.weightx=0;                  // wieder auf Default-Wert
c.gridy= 1;                   // unterhalb anordnen
c.ipadx= 75;                  // Komponentenbreite vergrößern
cp.add(new JButton("Nr. 1"),c);  // Übergabe 
des Constraints
jf.pack();
jf.setVisible(true);

Erklärung: Jedem add() wird ein Constrains c übergeben, welches wiederverwendet werden kann.

Mit Hilfe eines positiven weightx für die rechte unsichtbare JLabel-Komponente wird der zweiten Spalte die gesamte zusätzliche Breite zugeordnet.

Icon
GridBagLayout: Felder von Eingabeformularen optimal ausrichten

Aufgabe: Erstellen eines Formulars mit untereinander angeordneten Eingabefeldern mit vorangestellten Bezeichnungen (siehe Abb. 15.18).


Abbildung
Abbildung 15.18   Eingabeformular mit westlich ausgerichteten Feldern

Formular
InputForm

class InputField {
  private String name;  private int len;
  public InputField(String s, int l) { name= s; len= l; }
  public String getName()            { return name; }
  public int    getLength()          { return len;  }
}

JTextField

class InputForm extends JPanel {
  private JTextField[] txtfld;
  public InputForm (InputField[] fld) {
    setLayout(new GridBagLayout());

anchor, insets

    GridBagConstraints c= new GridBagConstraints();
    c.anchor= GridBagConstraints.WEST;
    c.insets= new Insets(3,3,3,3);            
                  ¨
    txtfld= new JTextField[fld.length]; // Texteingabefelder
    for (int i= 0; i<fld.length; i++) {

gridx, gridy

      c.gridy= i; c.gridx=0; 
         // neue Zeile, 1.Spalte
      add(new JLabel(fld[i].getName()),c); c.gridx= 
1;
      add(txtfld[i]= new JTextField(fld[i].getLength()),c);
      c.weightx= 1.; add(new JLabel(),c); 
c.weightx= 0.;        ¦
    }

weightx, weighty

    c.gridy=fld.length; 
c.weighty=1.; add(new JLabel(),c);     
 Æ
  }
  String getInput(int fpos) { return txtfld[fpos].getText(); }
  void   setInput(int fpos,String s) { txtfld[fpos].setText(s);}
}

Zu ¨: Damit die Felder nicht zu dicht aneinander »kleben«, wird mit Hilfe einer Insets-Instanz ein gewisser Abstand gehalten.

Zu ¦ und Æ: In Abb. 15.18 nicht zu erkennen ist die zweite Forderung, dass die Felder in der linken oberen Ecke fixiert werden sollen, d.h. bei Fenstervergrößerungen stabil bleiben (siehe Abb. 15.19).

Dies wird (wie im ersten Beispiel) durch unsichtbare Endkomponenten erreicht, deren weightx oder am Ende weighty größer Null ist.

Reaktion des
Formulars auf Vergrößerung


Abbildung
Abbildung 15.19   Fixierung der Komponenten in der oberen linken Ecke

Abschließend die obligatorische Test-Klasse:

Anlage des
Formulars

public class Test {
  public static void main(String[] args) {
    JFrame jf= new EJFrame("Eingabeformular");
    Container cp= jf.getContentPane();
    cp.setLayout(new BorderLayout()); 
    cp.add(new InputForm(               // default: 
im CENTER
      new InputField[] { new InputField("Erstes Feld",10),
                         new InputField("2. Feld",2),
                         new InputField("Letztes Feld",20)})
    );
    JPanel jp= new JPanel();           // default: 
FlowLayout
    jp.add(new JButton("Ok"));
    jp.add(new JButton("Abbruch"));
    cp.add(jp,BorderLayout.SOUTH);
    jf.pack();
    jf.setVisible(true);
  }
}

Das BorderLayout wird für die abschließende Schaltleiste benötigt.


Galileo Computing

15.4.6 BoxLayout  downtop

BoxLayout:
optimal für eine Zeile bzw. Spalte

In Swing wurde BoxLayout speziell für Toolbars entworfen, d.h. Symbolleisten, die entweder horizontal oder vertikal ausgerichtet sind.

Hierzu wird dann die zugehörige Klasse Box verwendet, die über zusätzliche Methoden für Toolbars verfügt.

Unterschiedlich große Komponenten möglich

Aber BoxLayout kann durchaus auch für andere Container verwendet werden und ist dann eine Alternative zu GridLayout, wenn nur eine Zeile oder Spalte mit unterschiedlich großen Komponenten angezeigt werden soll.

Ein Dialog-Fenster mit BoxLayout enthält nachfolgend horizontal, dann vertikal zwei Bilder und eine Schaltfläche (siehe Abb. 15.20).


Abbildung
Abbildung 15.20   JDialog mit BoxLayout
JDialog jf= new JDialog((Frame)null," ");
Container cp= jf.getContentPane();
cp.setLayout(new BoxLayout(cp,BoxLayout.X_AXIS));
cp.add(new JLabel(new ImageIcon("C:/Temp/cross.gif")));
cp.add(new JLabel(new ImageIcon("C:/Temp/Kaktus.gif")));
cp.add(new JButton("Ok"));
jf.pack();
jf.setVisible(true);

Umschalten der Darstellung von Zeile auf Spalte

try { Thread.sleep(5000); } catch (Exception e) {}
jf.setVisible(false);
cp.setLayout(new BoxLayout(cp,BoxLayout.Y_AXIS));
jf.pack();
jf.setVisible(true);

Wie zu erkennen ist, bleiben die bevorzugten Größen der Komponenten erhalten.


Galileo Computing

15.5 Ereignisbehandlung  downtop

Ereignisse contra Autismus

Ohne eine Art der Behandlung von äußeren Ereignissen (events) wie Tastatureingabe, Mausbewegung oder -klicken ist die schönste Grafikoberfläche ziemlich autistisch (wie z.B. auch das Eingabeformular in GridBagLayout).

Ein Ereignis-Modell war also von Anfang an Bestandteil von Java, wobei es sich allerdings von Java 1.0 zu 1.1 drastisch geändert hat.

Notgedrungen wurden eine Zeit lang zwei Modelle erklärt. Mit dem Aussterben der alten JVM kann man sich nun auf das neue beschränken.


Galileo Computing

15.5.1 Observer-Pattern  downtop

Betrachtet man die Behandlung von Ereignissen, wird recht schnell klar, dass man ohne ein generelles, erweiterbares Modell nicht klar kommt.

Icon

Nach dem ersten Missgriff in Java 1.0 wurde in 1.1 ein brauchbares Verhaltensmuster, das Event-Delegation-Modell eingeführt. Dieses Modell entspricht einer speziellen Form des Observer-Patterns, was daher zuerst vorgestellt werden soll.

Observer-Pattern:
Muster für Beobachter und zu Beobachtende

gp  Das Observer-Pattern lässt die dynamische Registrierung von Beobachtern (Observers) bei zu beobachtenden Objekten (Observable) zu, von deren Zustandsänderung sie per Callback informiert werden wollen.

Beobachter und zu Beobachtende sind nur Interfaces IObserver und IObservable, da keine weiteren Kenntnisse über die Klassen vorliegen.

Einsatz von Callback-Methoden

Die konkreten Beobachter-Klassen implementieren als IObserver nur Callback-Methoden, allgemein notify() genannt. notify() wird bei Zustandsänderung vom Observable-Objekt aufgerufen und mit entsprechenden Argumenten als Information versehen.

Multicaster für Benachrichtigungen

gp  Ein Multicaster dient zur Entkopplung der Registrierungs- und Benachrichtigungs-Mechanismen der Observable-Klassen.

Damit kann ein Multicaster von verschiedenen Observable-Klassen genutzt werden, ohne dass diese die Implementierung der Registrierung bzw. Benachrichtigung wiederholen müssen (siehe Abb. 15.21).

Der Multicaster ist natürlich eine Indirektionsstufe mehr, die sich nur lohnt, wenn es mehrere Observable-Klassen und mehrere Observer-Instanzen für eine Observable-Instanz gibt.

Observer-Pattern mit Interfaces und Multicaster


Abbildung
Abbildung 15.21   Observer-Pattern

Die Methoden notify(), register() und unregister() im Modell können natürlich abhängig von der Applikation unterschiedlich benannt werden, wie z.B. prozess<X>(), add<X>() und delete<X>().

Multicaster-Queue

Bei sehr vielen Zustandsänderungen im Observable-Objekt besteht die Notwendigkeit, die Benachrichtigungen erst in eine Warteschlange (z.B. des Multicasters) einzureihen, um dann ein Batch-notify() – eine Zusammenfassung von mehreren Benachrichtigungen – durchzuführen.

Vorteile des Batch-Notify

gp  Der Vorteil der Batch-Verarbeitung liegt einerseits in weniger notify()-Aufrufen und andererseits darin, dass mehrere gleichartige Zustandswechsel zu einem einzigen zusammengefasst werden können.

Im Package java.util gibt es bereits seit Java 1.0 eine Klasse Observable und ein Interface Observer, die zusammen eine simple Version des Patterns darstellen und auch nicht weiter benutzt werden.

Vereinfachtes Observer-Pattern in java.util


Abbildung
Abbildung 15.22   Simple Variation des Observer-Patterns in java.util

Observable vereint Interface, Klasse und Multicaster und kann also nur als Superklasse oder per Delegation genutzt werden.


Galileo Computing

15.5.2 Ereignis-Delegations-Modell  downtop

Icon
Ereignis- Delegation:
Notation und Aufbau

Das Ereignis-Delegations-Modell im AWT bzw. in Swing ist eine weit verzweigte Spezialisierung des o.a. Observer-Patterns.

gp  Die Rolle des IObserver wird Event-Listener genannt.

Event-Source/Listener

gp  Die Observable-Klassen werden Event-Sources genannt, worunter alle Komponenten des AWTs und Swings fallen.
gp  Die Event-Listener registrieren sich bei den Event-Sources, an deren Ereignissen sie interessiert sind.
gp  Das Auftreten eines Ereignisses in einer Event-Source stellt dann die Zustandsänderung dar, die die Benachrichtigung auslöst.
gp  Mit Hilfe der Callback-Methode(n) der Event-Listener-Interfaces wird das Ereignis dem Event-Listener übergeben. Die Ereignisse enthalten eine Referenz auf die Event-Source und ein Ident ihrer Art.10 
gp  Die Benachrichtigungs-Methoden in den Event-Listener-Interfaces haben statt notify() Namen, die zu den Ereignissen passen.

Die meisten Ereignisse haben natürlich ihren Ursprung im Betriebssystem und werden durch einen internen, für die Event-Listener transparenten Verteiler-Thread den Event-Sources zugeordnet.

Aber aus der Sicht der Event-Listener generieren die Event-Sources die Ereignisse.

Event-Dispatch-Thread als Multicaster

Natürlich gibt es auch einen Multicaster in einem Event-Dispatch-Thread, der die Callback-Methoden für die Event-Sources aufruft. Aber auch dieser ist aus der Sicht der Event-Listener transparent.

Nur bei Multi-Threading oder der Entwicklung eigener AWT-Komponenten muss man sich dieser Tatsache bewusst sein.11 

Packages und Konventionen

Das Ereignis-Modell ist wirklich sehr verzweigt und verteilt sich auf verschiedene Packages, Klassen- und Interface-Hierarchien.

Um den Überblick zu behalten, sind die meisten Klassen und Interfaces in den Packages java.awt.event und javax.swing.event enthalten.

Namenskonvention zur Ereignis-Delegation

Es gibt auch eine Namenskonvention, die Klassen, Interfaces und Zusammengehörigkeiten identifiziert.

gp  Alle Ereignisse in den Packages leiten sich aus der Basisklasse java.util.EventObject ab und enden auf Event.

Die Basisklasse enthält u.a. getSource() zur Identifizierung der Event-Source.

<X>Event,
<X>Listener

gp  Ist der Name eines Ereignisses <X>Event, ist der des zugehörigen Interfaces <X>Listener.

add/remove<X>
Listener

gp  Die Methoden zur (De-)Registrierung in den Event-Sources heißen dann add<X>Listener und remove<X>Listener.

Die Listener-Interfaces haben nicht nur eine notify()-Methode, sondern abhängig vom Ereignis durchaus mehrere. Jede dieser Methoden hat jedoch nur genau einen <X>Event-Parameter (siehe Abb. 15.23).

Ist ein Observer nur an einer Methode interessiert, kann er auf Adapter-Klassen zurückgreifen, die bereits die Methoden des Listener-Interfaces mit leeren Implementierungen überschrieben haben. Er braucht dann nur die zu überschreiben, an der er interessiert ist.

gp  Die Adapter, zugehörig zum <X>Listener, heißt <X>Adapter.

Template zur Struktur des Event-Delegations-Modells


Abbildung
Abbildung 15.23   Prinzipielles Klassen-Modell zur Event-Delegation

Das o.a. Diagramm ist ein Template, da es keine wirklichen Klassen und Interfaces enthält. <X>Listener steht z.B. allein für 15 Listener im AWT. <EventSource> steht z.B. für jede beliebige Komponente, wobei diese durchaus mehr als einen Listener registrieren kann.

Wie fast zu erwarten, steht <X>Event für unterschiedliche Ereignis-Typen in package-abhängigen Unterhierarchien.

Hier unterscheidet man vor allem die Basis-AWT-Ereignisse, abgeleitet von java.awt.AWTEvent, und die zusätzlichen swing-spezifischen Ereignisse, abgeleitet vom AWTEvent oder direkt von EventObject.

Klassen-Hierarchie zu Events und Listeners


Abbildung
Abbildung 15.24   Event-Klassen und Listener-Interfaces des AWTs

EventQueue mit Dispatch-Thread

Ein Objekt der Klasse EventQueue enthält in seiner Warteschlange alle Ereignisse, die an die Listener übergeben (dispatched) werden müssen.

Das AWT legt genau ein Objekt von EventQueue an. Es erzeugt automatisch einen zugehörigen Dispatch-Thread, der Ereignisse am Kopf der Warteschlange versendet und am Ende eingefügt.

Überblick über
Events, Listeners und Methoden

Tabelle 15.4   AWT Events, Listeners bzw. Listener-Methoden
Event /Listener Ereignis-Generierung Listener-Methoden
ActionEvent
ActionListener
Komponenten-Ereignis (z.B. Anklicken) actionPerformed()
AWTEvent
AWTEventListener
Bei allen AWT-Ereignissen, nützlich für Event-Recoder eventDispatched()
AdjustmentEvent
AdjustmentListener
Bei Anpassaktionen, z.B. bei Roll/Scrolling-Operationen adjustmentValue-
Changed()
ComponentEvent
ComponentListener
Bei Bewegung, Änderung der Größe, Sichtbarkeit der Komponente componentHidden()
componentMoved()
componentResized()
componentShown()
ContainerEvent
ContainerListener
Bei Änderung der Komponenten in einem Container componentAdded()
componentRemoved()
FocusEvent
FocusListener
Komponente erhält/verliert den Fokus (siehe ) focusGained()
focusLost()
HierarchyEvent
HierarchyListener
Bei Änderung der Hierarchie der Komponenten hierarchyChanged()
HierarchyEvent HierarchyBoundsListener Bei Änderung eines übergeordneten Containers ancestorMoved()
ancestorResized()
InputMethodEvent
InputMethodListener
Texteditier-Methode ändert Text oder Textposition inputMethodTextChanged()
caretPositionChanged()
ItemEvent
ItemListener
(De-)Selektieren von Elementen in Komponenten itemStateChanged()
KeyEvent
KeyListener
Drücken/Loslassen einer Taste. Bei Eingabe einer Taste keyPressed()
keyReleased()
keyTyped()
MouseEvent
MouseListener
Bei Drücken/Loslassen oder beidem (Klicken der Maus). Bei Eintritt/Verlassen des Mauszeigers in/aus Komponente mouseClicked()
mouseEntered()
mouseExited()
mousePressed()
mouseReleased()
MouseEvent
MouseMotionListener
Bei Mausbewegung mouseDragged()
mouseMoved()
TextEvent
TextListener
Bei Änderung eines Texts textValueChanged()
WindowEvent
WindowListener
Bei Fensteränderungen (z.B. Öffnen, Schließen, Verkleinern, Vergrößern zum Icon) windowActivated()
windowClosed()
windowClosing()
windowDeactivated()
windowDeiconified()
WindowIconified()
WindowOpened()


Galileo Computing

15.5.3 Ereignisverarbeitung  downtop

Um über interessante Ereignisse informiert zu werden, muss man nach dem Ereignis-Delegations-Modell den passenden Listener implementieren und sich bei der Source mittels add<X>() anmelden.

Innere Klassen zur Ereignisverarbeitung

gp  Da die Implementierung in der Regel nur aus einer Methode besteht, kann man die eigentliche Ereignisverarbeitung an eine innere Klasse delegieren.

Listener-Adapter: vereinfachte Implementation

gp  Sollte es ein Listener mit mehreren Methoden sein und man ist nur an einer oder zwei interessiert, wird die innere Klasse vom Listener-Adapter abgeleitet.

Beispiel

Im Folgenden wird die Klasse InputForm zum Eingabeformular in Abb. 15.1 (siehe GridBagLayout) benutzt, um vier verschiedene Möglichkeiten zu zeigen, Listener zu implementieren.

Ereignisverarbeitung in einem Eingabeformular


Abbildung
Abbildung 15.25   Formular zur Klasse InventarFrame

Formular-Aktionen

Mit den vier Schaltflächen des Formulars »Inventar-Eingabe« (Abb.15.24) sind folgende Aktionen verbunden:

gp  <Neu>: Vergibt eine zufällige ganze Zahl als Inventar-Nr. und leert das Feld «Bezeichnung”.
gp  <Speichern>: Speichert den Inventar-Artikel ab, sofern eine Bezeichnung eingetragen wurde.
gp  <Pfeil zurück>: Zeigt – wenn vorhanden – den vorherigen Inventar-Artikel an, sonst den letzten.
gp  <Pfeil vor>: Zeigt – wenn vorhanden – den nächsten Inventar-Artikel an, sonst den ersten.

Wird das Icon »Fenster schließen« angeklickt, wird das Programm (wie bisher) ohne Vorwarnung beendet.

Listener-
Alternativen:

// 1. Alternative: die Frame selbst ist Listener
class InventarFrame extends JFrame implements ActionListener 
{

[A1]
Frame ist selbst Listener (Implementierung am Ende)

  InputForm frm;         // Implementierung in GridBagLayout
  Item inv;              // zeigt auf aktuellen Eintrag
  java.util.List iLst;   // Liste enthält Inventar-Einträge
  int pos;               // Position in Liste
  // Inventar-Eintrag, nur das absolut notwendige
  private static class Item {
    int nr;
    String bez;
    public Item(int nr, String s) { this.nr= nr; bez= s; }
  }
  // Darstellung des Eintrags im Formular
  public void showCurrentItem() {
    frm.setInput(0,Integer.toString(inv.nr));
    frm.setInput(1,inv.bez);
  }
  // Neuer Eintrag im Formular: zufällige Nr., Bez. leer
  private void newItem() {
    frm.setInput(0,
        Integer.toString((int)(Math.random()*1000000.)));
    frm.setInput(1,"");
    pos= -1;
  }

[A2]
Member-Klasse ist Listener

  // 2. Alternative: Member-Klasse als Listener
  private class StoreItem implements ActionListener 
{
    public void actionPerformed (ActionEvent e) {
      if(frm.getInput(1).equals("")) return;
      if (pos==-1) {
        // Lazy Creation: Anlage nur, wenn notwendig
        if (iLst==null) iLst= new ArrayList();
        iLst.add( // RuntimeException wird nicht abgefangen
          inv= new Item(Integer.parseInt(frm.getInput(0)),
                                         frm.getInput(1)));
        pos= iLst.size()-1;
      }  
      else if (inv!=null) inv.bez= frm.getInput(1);
    }
  }
/* Im Konstruktor wird das Formular 
mit Aktionen angelegt  */
  public InventarFrame() {
    super("Inventar-Eingabe");  // Konstruktor: JFrame

[A3]
Anonyme Klasse als Listener: abgeleitet von WindowAdapter

    // 3. Alternative: 
Anonyme Subklasse eines Adapters
    //                 Event-Source ist InventarFrame selbst
    //                 Registrierung der anonymen Klasse
    addWindowListener(new WindowAdapter() {
        // es wird nur die benötigte Methode überschrieben
        public void windowClosing(WindowEvent e) { 
           System.exit(0); }
    });
    Container cp= getContentPane();
    cp.setLayout(new BorderLayout());
    cp.add(frm= new InputForm(        // default: im CENTER
      new InputField[] { new InputField("Inventar-Nr.",6),
                         new InputField("Bezeichnung",20)})
    );
    JPanel jp= new JPanel();
    JButton newB= new JButton("Neu"),
      storeB= new JButton("Speichern"),
      prevB=  new JButton(new ImageIcon("C:/Scan/prev.gif")),
      nextB=  new JButton(new ImageIcon("C:/Scan/next.gif"));
    jp.add(newB);jp.add(storeB);jp.add(prevB); jp.add(nextB);
    cp.add(jp,BorderLayout.SOUTH);

[A4]
Anonyme Klasse als Listener: abgeleitet vom Interface ActionListener

    // verbindet mit der Schaltfläche einen 
String-Schlüssel
    revB.setActionCommand("P");
    // 4. Alternative: 
Anonyme Klasse vom Listener-Interface    
    newB.addActionListener( new ActionListener() 
{
      public void actionPerformed(ActionEvent e) {newItem();}
    });
    // zur 2. Alternative: Registrierung bei Schaltfläche
    storeB.addActionListener( new StoreItem() 
);
    // zur 1. Alternative: Registrierung von InventarFrame
    prevB.addActionListener(this);
    nextB.addActionListener(this);
    pack();
    setVisible(true);
  }
  /* Zum Schluss die Implementierung der 1. Alternative 
*/
  public void actionPerformed(ActionEvent 
e) {
    if (iLst==null) return;
    int opos=pos;

getActionComand()

    // getActionCommand() liefert den String-Schlüssel
    // zur Unterscheidung der beiden Schaltflächen 
    if(e.getActionCommand().equals("P") || pos==-1)
      pos= (pos>0 ? pos-1:iLst.size()-1); // round robin
    else
      pos= (pos+1<iLst.size() ? pos+1:0); // andere Richtung
    if (pos!= opos) {
      inv= (Item) iLst.get(pos); showCurrentItem();
    }
  }
}

Der Test besteht dann z.B. im Erzeugen einer InventarFrame-Instanz:

  new InventarFrame();

Galileo Computing

15.5.4 Fokus-Management  downtop

Eine GUI-Oberfläche hat in der Regel diverse Fenster mit Containern und Komponenten. Für eine geregelte Interaktion mit dem Benutzer existiert folgende Regelung:

Fokus und Fokus-Management

gp  Es gibt immer ein aktives Fenster und innerhalb des Fensters eine aktive Komponente. Sie besitzen dann den Fokus.   12 

Das Fokus-Management innerhalb von Swing übernimmt der DefaultFocusManager, Subklasse der abstrakten Klasse FocusManager.

Fokus-Zyklus

gp  Unter einem Fokus-Zyklus versteht man alle Komponenten, die mittels <TAB> bzw. <SHIFT><TAB> angesprungen werden können.
gp  Das Default-Sprungverhalten beim Fokus-Zyklus ist von links nach rechts und von oben nach unten. Es umfasst alle Komponenten des Top-Level-Containers.

Container mit eigenem Fokus-Zyklus

Ein Container kann einen eigenen Fokus-Zyklus festlegen, d.h., mit <TAB> bzw. <SHIFT><TAB> können dann nur seine Komponenten angesprungen werden.

Der Container muss hierzu die Methode isFocusCycleRoot() mit return true überschreiben, da sie per Default false liefert.

Anpassungen des Fokus-Managers

Bestimmte Komponenten wie z.B. JLabel erhalten vom Fokus-Manager per Default nie den Fokus, d.h., isFocusTraversable() liefert false. Sie können dann nicht per <TAB> bzw. <SHIFT><TAB> angesprungen werden. Auch hier kann das Verhalten isFocusTraversable() entsprechend überschrieben werden.

Eine Komponente, die vom Fokus-Manager nie den Fokus erhält, kann dies aber durchaus programmintern mit Hilfe der Methode requestFocus(), sofern die Methode setRequestFocusEnabled() den Wert true liefert. Auch hier kann das Verhalten überschrieben werden.

Komponenten ohne Fokus

gp  Liefern beide Methoden, isFocusTraversable() und setRequestFocusEnabled(), einer Komponente den Wert false, kann sie keinen Fokus mehr erhalten.

Ändern der Sprungfolge

Mit Hilfe der Methode setNextFocusableComponent() kann man entgegen dem Default die Sprungfolge des Fokus-Managers selbst bestimmen.

Die Methode isManagingFocus() muss mit return true; überschrieben werden, sofern eine Komponente die Sprungfolge mit <TAB> bzw. <SHIFT><TAB> selbst steuern will. Hierzu muss sie dann processComponentKeyEvent(KeyEvent e) implementieren.

In Tabelle 15.4 sind noch einmal die o.a. Methoden zusammengefasst.

Methoden zur Manipulation des Fokus

Tabelle 15.5   Wichtige Fokus-Methoden in JComponent
Fokus-Methoden
zu setzen bzw. zu überschreiben
Beschreibung
boolean isFocusCycleRoot() Zu überschreiben für Container mit eigenem Fokus-Zyklus
boolean isFocusTraversable() Zu überschreiben, wenn nicht im Fokus-Zyklus
boolean isManagingFocus() Zu überschreiben, wenn das Fokus-Management von einer Komponente übernommen werden soll
void setNextFocusableComponent
(Component aComponent)
Setzen der Sprungfolge (über Fokus-Zyklen hinweg)
void setRequestFocusEnabled
(boolean aFlag)
Erlaubt (nicht) das programmtechnische Setzen des Fokus

Beispiel

Zwei Instanzen von JPanel mit einem Grid-Layout enthalten vier Textfelder bzw. vier Schaltflächen. Jedes Panel hat einen eigenen Fokus-Zyklus.

Fokus-Management: Beispiel mit zwei Zyklen

Im ersten Panel werden die Textfelder der zweiten Spalte aus dem Zyklus herausgenommen, im zweiten wird die Sprungfolge auf »Spalten zuerst« abgeändert (siehe Abb. 15.26).


Abbildung
Abbildung 15.26   Zwei Fokus-Zyklen mit Variationen

isFocusCycleRoot()

public class Test {
  public static void main(String[] args) {
    JFrame jf= new EJFrame("Fokus-Management");
    Container cp= jf.getContentPane();
    cp.setLayout(new BoxLayout(cp,BoxLayout.X_AXIS));
    JPanel[] jp= new JPanel[2];
    for (int i= 0; i<2; i++) {
      cp.add(new JLabel(i+1+".Zyklus "));
      jp[i]= new JPanel() {               // anonyme Klasse
        public boolean isFocusCycleRoot() { return true; }
      };
      jp[i].setLayout(new GridLayout(2,2)); cp.add(jp[i]);
    }
    JButton[] jb= new JButton[4];
    int[] jmp= new int[] {0,2,1,3}, nxt= new int[] {2,3,1,0};
    for (int i= 0; i< 4; i++) {
      jp[0].add(i%2==0? new JTextField(10):new JTextField() {
        { super.setColumns(10); }         // anonyme Klasse

isFocusTraversable()

        public boolean isFocusTraversable() { return false; }
      });
      jp[1].add(jb[i]= new JButton("Nr. "+jmp[i]));
    }
    for (int i= 0; i< 4; i++)

setNextFocusable-Component()

      jb[i].setNextFocusableComponent(jb[nxt[i]]);
    jf.pack(); jf.setVisible(true);
  }}

Galileo Computing

15.5.5 Ereignisverteilung (Event-Dispatchingdowntop

Event-Dispatching

Betrachtet man die Ereignisse, aufgelistet in Tabelle 15.3, kann man sie anhand ihrer Entstehung bzw. Semantik klassifizieren.

Low- vs. -Events

Low- vs. High-Level-Ereignisse

Ereignisse werden aufgrund ihres Typus in Ebenen eingeordnet.

gp  Die meisten Ereignisse haben ihren Ursprung im Betriebssystem und liegen semantisch in der Regel auf einem unteren Niveau.

Hierzu zählen unter anderem alle MouseEvent-Ereignisse. Die Methoden keyPressed() und keyReleased() gehören ebenfalls zu zwei Low-Level-Ereignissen von KeyEvent, keyTyped() dagegen zu einem höherstufigen.

gp  Ereignisse, die nicht vom Betriebsystem, sondern von Komponenten generiert werden, liegen auf einer semantisch höheren Ebene.

Hierzu zählt z.B. das ActionEvent. Die Komponente muss in diesem Fall Betriebssystem-Ereignisse abfangen und gegebenenfalls in ein eigenes Ereignis vom Typ ActionEvent umwandeln.

gp  Event-Listener sollten – sofern sie die Wahl haben – ein Ereignis möglichst hoher Ebene wählen.

Low-Level-Event-Dispatching

Eine Komponente vom Typ Component wird über ein Ereignis auf Betriebssystem-Ebene durch Aufruf ihrer Methode processEvent() informiert.

process<X>Event():
spezialisierte Low-Level Events

In processEvent() erfolgt die erste Verteilung (Dispatching) anhand des Event-Typs auf spezialisierte process<X>Event(). Diese Methoden übergeben dann die Ereignisse an die registrierten Listener.

Icon

Alle Methoden sind protected. Deshalb können Subklassen von Component wie z.B. JComponent diese – sofern notwendig – überschreiben. Es gilt folgende Regel für die Ereignisbearbeitung:

Ereignisbearbeitung von Low-Level-Events

gp  Ein Ereignis wird einer Komponente nur zur Behandlung bzw. zur Verteilung übergeben, sofern
    gp  hierfür mindestens ein Listener registriert ist.

enableEvents()

    gp  sie mittels Aufruf von void enableEvents(long eventsToEnable) die Ereignisbehandlung für sich selbst ermöglicht hat.

Dabei ist eventsToEnable eine Bitmaske, die mit Hilfe von Konstanten der Form <X>_EVENT_MASK der Klasse AWTEvent zur Identifizierung der Ereignisse gesetzt wird.

In Tabelle 15.5 sind die zugehörigen Ereignisse, Methoden und Masken für Low-Level-Events zusammengestellt, und zwar in der Reihenfolge, in der sie in Component bearbeitet werden.13 

Bearbeitungsreihenfolge von Low-Level-Events

Tabelle 15.6   Bearbeitung der Low-Level-Ereignisse in Component
Ereignis process<X>Events ...EVENT_MASK
FokusEvent processFocusEvent() FOCUS_
MouseEvent processMouseEvent() MOUSE_
MouseEvent processMouseMotionEvent() MOUSE_MOTION_
KeyEvent processKeyEvent() KEY_
ComponentEvent processComponentEvent() COMPONENT_
InputMethodEvent processInputMethodEvent() INPUT_METHOD_
HierarchyEvent processHierarchyEvent() HIERARCHY_
HierarchyEvent processHierarchyBoundsEvent() HIERARCHY_BOUNDS_

AWT vs. Swing: geänderte Low-Level Event-Bearbeitung

AWT vs. Swing: Die spezialisierten Komponenten im AWT erzeugen aufgrund der Low-Level-Ereignisse eigene Ereignisse. Diese können im AWT nach demselben Muster wie in Tabelle 15.5 in den Komponenten verarbeitet werden, wie z.B.:

ActionEvent processActionEvent() ACTION_EVENT_MASK

gp  Dies ist in Swing nicht mehr in der Form möglich, da Swing eine verteilte Komponenten-Architektur hat. Als Ersatz bietet Swing andere Mechanismen (wie z.B. Tastatur-Aktionen).

Galileo Computing

15.5.6 Ereignisbearbeitung in abgeleiteten Komponenten  downtop

Die Ereignisbehandlung einer Komponente wird normalerweise automatisch durch die Registrierung von Listenern ermöglicht (Regel in Ereignisverteilung bzw. Event-Dispatching).

Soll eine Subklasse einer Komponente die Bearbeitung und/oder Verteilung selbst übernehmen, so gibt es zwei Alternativen:

Die neue Subklasse der Komponente implementiert

1. zu den Ereignissen, die sie bearbeiten will, selbst den entsprechenden Listener.

Icon

2. Ruft sie enableEvents() auf und überschreibt process<X>Event().

Reihenfolge der Listener-Aufrufe

Für die Beurteilung der beiden Alternativen ist folgende Regel wichtig:

gp  Hat eine Komponente mehrere Listener, gibt es keine feste Ordnung, in der die Listener aufgerufen werden.

Dies bedeutet, dass bei der ersten Alternative die neue Komponente nicht unbedingt zuerst, sondern durchaus auch nach den externen Listenern benachrichtigt werden kann, d.h. natürlich in der Regel zu spät.

Icon

Bei der zweiten Alternative ist dagegen gewährleistet, dass zuerst process<X>Event() aufgerufen wird, bevor andere Listener benachrichtigt werden.

Beispiel

Konsumieren vs. Weitergeben von Events

Bei einem Textfeld soll der erste Buchstabe immer groß geschrieben werden. Dies soll nach Verlassen des Felds geprüft werden.

enableEvents()

class FUTextField extends JTextField {
  public FUTextField(int size) {
    super(size);
    enableEvents(AWTEvent.FOCUS_EVENT_MASK);
  }

processFocusEvent()

  protected void processFocusEvent(FocusEvent e) {
    if (FocusEvent.FOCUS_LOST==e.getID()) {
      String s= getText(); 
      if (s.length()>0) setText(
            s.substring(0,1).toUpperCase()+s.substring(1));
    }
    //super.processFocusEvent(e);                              ¨
  }
}

Icon
Probleme beim Konsumieren des Events

Zu ¨: Die Frage bei der Ereignisbearbeitung ist immer »konsumieren vs. nicht konsumieren«. Im oberen Beispiel wird das Fokus-Ereignis konsumiert und nicht mit super weitergereicht. Somit bekommt die Superklasse das FokusEvent nicht mit. Unangenehm, denn:

gp  Erst bei FOCUS_GAINDED schaltet die Komponente JTextField den Textcursor ein, d.h., erst dann sieht man die Eingabeposition im Feld.

Galileo Computing

15.6 MFC-Architektur  downtop

Icon

Das Ereignis-Delegations-Modell in Ereignis-Delegations-Modell (Abb. 15.23) ist die Umsetzung des Observer-Patterns. Dabei wurden die Event-Sources bisher als monolithische Komponenten betrachtet, was für die AWT-Komponenten auch richtig ist.

MFC-Architektur

Swing fügt der Ereignisverarbeitung jedoch noch ein weiteres Muster hinzu, das auf der Model-View-Controller-Architektur, kurz MVC, basiert.

Es folgt einer bereits in Smalltalk eingesetzten Architektur, die eine GUI-Komponente prinzipiell aus drei Elementen mit verteilten Aufgaben zusammensetzt.

gp  Model: Das Modell ist zuständig für den Zustand der Komponente, d.h., es enthält abhängig davon, was die Komponente darstellt, die dazu passenden Daten.
gp  View: Die Sicht übernimmt die Darstellung der Komponente in Abhängigkeit von den Daten. Die Trennung ermöglicht verschiedene Sichten auf Basis derselben Datenstruktur.
gp  Controller: Die Kontrollinstanz ist letztendlich für die Interaktion zuständig. Sie bestimmt, ob und wie die Komponente auf Ereignisse reagiert.

MFC:
weniger ein Pattern, eher Design-Strategie

Offen in der Architektur ist außer der genauen Realisierung auch die Kommunikation zwischen den drei Elementen.

Denn ohne Daten weiß z.B. die View-Instanz sich nicht korrekt darzustellen, und ohne passende Controller-Mitteilung können wiederum keine Änderungen im Modell vorgenommen werden. Des Weiteren muss der Controller mit Ereignissen versorgt werden.

Eine konkrete MVC-Implementierung basiert in der Regel auf einer Variante des Observer-Patterns.


Galileo Computing

15.6.1 Model-Delegate-Architektur in Swing  downtop

Wie zu erwarten basiert die Kommunikation zwischen den MVC-Komponenten in Swing wieder auf dem Ereignis-Delegations-Modell.

Die abstrakte Basisklasse JComponent enthält genau eine Instanz der abstrakten Klasse ComponentUI, die das View-Element darstellt.

View: UI-Delegate

gp  Das View-Element, die konkrete Implementierung mit allen darin enthaltenen Elementen, wird in Swing UI-Delegate genannt (siehe Abb. 15.27).

installUI()

JComponent meldet sich bei dem UI-Delegate mit installUI(this) an. Somit ist UI-Delegate in der Lage, eigene spezialisierte Listener bei JComponent anzumelden, die dann Controller-Aufgaben übernehmen.

JComponent enthält selbst keine Instanz einer Modell-Klasse. Aus der Sicht der Swing-Designer sind die Anforderungen an ein Modell derart abhängig vom Typ der Komponente, dass eine Definition auf der Ebene nicht sinnvoll scheint.

Konkrete Komponenten J<X> enthalten Referenz auf Modell

Erst die konkreten Komponenten-Klassen enthalten eine Referenz auf ein Modell-Interface. Somit kann der Swing-Anwender eine beliebige Klasse als Modell benutzen, sofern es nur das Interface implementiert (siehe Abb. 15.27).

gp  Jede konkrete Swing-Komponente J<X> enthält zumindest eine Modell-Instanz <X>Model und genau eine UI-Delegate-Instanz.14 

Template zur Model-Delegate-Variante des MFC


Abbildung
Abbildung 15.27   Model-Delegate-Aufbau, eine MVC-Variante

In der Anwendung hat man mit dem UI-Delegate eigentlich nichts zu tun. Die Komponenten erzeugen – je nach L&F – automatisch eine passende Instanz.15 

Dies gilt aber nicht unbedingt für die Modell-Klassen. Einfachen Komponenten wie JLabel und JButton überlässt man gerne die automatische Anlage einer Instanz einer Default-Modell-Klasse.

Bei komplexen Komponenten wie JTable oder JTree ist man aber häufig aufgrund der Anwendung gezwungen, eine eigene Modell-Klasse einzusetzen.

Nach Swing-Konvention existiert zu jeder Komponente ein(e)

<X>Model als Interface

gp  <X>Model-Interface, das als Event-Source Listener für Modelländerungen registriert.

Default<X>Model als Default-Klasse

gp  Default<X>Model-Klasse, die das Interface implementiert und automatisch als Daten-Modell benutzt wird, sofern kein anderes angemeldet wird.

Damit steht man vor der Wahl, eine eigene Modell-Klasse auf Basis des Interfaces zu entwickeln oder die bereits vorhandene Default-Modell-Klasse als Superklasse zu nutzen.


Galileo Computing

15.6.2 BoundedRangeModel für Bars und Sliders  downtop

Scroll-Bar, Slider, Progress-Bar

Ein sehr einfaches Daten-Modell verbindet die grafischen Komponenten Scroll-Bar (Rollbalken), Slider (Schieberegler) und Progress-Bar (Fortschrittsleisten).

Eigenschaften/Zustände: BoundedRangeModel

Die gemeinsamen Zustandswerte im BoundedRange-Modell sind (siehe Abb. 15.28):

gp  min, max: Grenzen des erlaubten Intervalls
gp  value: aktueller Wert im Intervall
gp  extent: der aktuelle Wert ist selbst ein Intervall [value,value+extent]
gp  isAdjusting: zeigt mit true an, dass der aktuelle Wert permanent angepasst wird

Abbildung
Abbildung 15.28   : Werte, zugehörig zum Interface BoundedRangeModel

Swing fügt einige High-Level-Events dem AWT hinzu. Eines davon ist das ChangeEvent, das – wie der Name bereits sagt – Änderungen von Werten anzeigt.

ChangeListener zur Kommunikation der Zustandswerte

Ein Daten-Modell muss Änderungen seiner Werte kommunizieren. Objekte, die an diesen Wertänderungen interessiert sind, müssen das ChangeListener-Interface implementieren und sich mit addChangeListener() bzw. removeChangeListener() bei dem Daten-Modell an- und abmelden.

Beispiel

Ein Scroll-Bar und ein Slider sollen über ein gemeinsames Daten-Modell synchronisiert werden. Der aktuelle Wert soll in einem Textfeld angezeigt und verändert werden können (Abb. 15.29).

Scroll-Bar und Slider mit gemeinsamen Zuständen


Abbildung
Abbildung 15.29   Gemeinsames Daten-Modell für Scroll-Bar und Slider

Zur Implementierung wird hier das DefaultBoundedRangeModel als Superklasse verwendet (Abb. 15.30).

Klassen-Diagramm zum BoundedRangeModel-Beispiel


Abbildung
Abbildung 15.30   Gemeinsame Nutzung des BoundedRangeModels

Um Veränderungen der Werte zu kommunizieren, müssen MyRangeModel und ScrollValueField gegenseitige Listener sein.

Im Klassen-Diagramm erkennt man, dass eine Referenz des BoundedRangeModel gleichermaßen von drei visuellen Darstellungen genutzt werden kann.

Synchrone Anzeige des Zustands: gemeinsames Daten-Modell

In unserem Beispiel dient eine Instanz von MyRangeModel gleichzeitig als Daten-Modell für eine Scroll-Bar und einen Slider, womit diese dann die Werte von value synchron anzeigen.

In der Test-Klasse werden die Instanzen angelegt und miteinander verbunden:

public class Test {
  public static void main(String[] args) {
    JFrame jf= new EJFrame("BoundedRangeModel");
    Container cp= jf.getContentPane();
    int val= 50;  // Default-Wert zum Starten
    // Modell mit Intervall [0,100] und extent 0
    MyRangeModel rm= new MyRangeModel(val,0,0,100);
    // Anlage einer vertikalem Scroll-Bar 
    JScrollBar jsb= new JScrollBar();
    jsb.setOrientation(JScrollBar.VERTICAL);
    jsb.setPreferredSize(new Dimension(15,100));

Ein RangeModel –Object, zwei visuelle Instanzen

    // Anlage eines Sliders mit Markierungsstrichen/Werten
    JSlider jsl= new JSlider();
    jsl.setMajorTickSpacing(20); // Striche alle 20 Einheiten
    jsl.setPaintTicks(true);     // Striche zeigen
    jsl.setPaintLabels(true);    // Zahlenwerte zeigen

setModel()

    // Slider und Scroll-Bar auf gleiches Modell 
setzen
    jsl.setModel(rm); jsb.setModel(rm);
    JTextField jtf= new JTextField(5);

addFocusListener()
addChangeListener()

    // gegenseitig Textfeld und Modell als Listener 
anmelden
    jtf.addFocusListener(rm);
    rm.addChangeListener(new ScrollValueField(jtf,val));
    cp.add(jtf,BorderLayout.CENTER);
    cp.add(jsb,BorderLayout.EAST);
    cp.add(jsl,BorderLayout.SOUTH);
    jf.pack(); jf.setVisible(true);
  }
}

Nachfolgend werden noch die beiden Klassen ScrollValueField und MyRangeModel implementiert.

Beide Klassen gehen der Einfachheit halber davon aus, dass sie und keine anderen Klassen gegenseitige Listener sind.

Gegenseitige Listener
am Beispiel ScrollValueField und MyRangeModel

class ScrollValueField implements ChangeListener {
  JTextField jtf;
  public ScrollValueField(JTextField tf, int val) 
{
    jtf= tf; 
    jtf.setText(Integer.toString(val));
  }

stateChanged()

  public void stateChanged(ChangeEvent e) {
    // unsauber, aber kurz: geht davon aus, dass der
    // Event von einem BoundedRangeModel ausgelöst wird
    jtf.setText(Integer.toString(
      ((BoundedRangeModel)e.getSource()).getValue()));
  }
}
class MyRangeModel extends DefaultBoundedRangeModel
                   implements FocusListener {
  public MyRangeModel(int val, int ext, int min, int max) {
    super(val,ext,min,max);
  }

focusLost()

  public void focusLost (FocusEvent e) {
    // unsauber, aber kurz: geht davon aus, dass der 
    // Event von einem Textfeld ausgelöst wird
    setValue(Integer.parseInt(
                     ((JTextField)e.getSource()).getText()));
    }
  public void focusGained (FocusEvent e) {}
}

Die Interaktion des Benutzers mit dem Programm zeugt von der Robustheit der Implementierung des RangeModels.

Denn bei Eingaben im Textfeld, die außerhalb des erlaubten Intervalls [0,100] liegen, werden diese vom Modell automatisch auf die obere bzw. untere Grenze korrigiert und an das Textfeld zurückgegeben, das prompt 0 oder 100 anzeigt. Natürlich synchronisieren auch die Regler.


Galileo Computing

15.7 Multi-Threading in Swing  downtop

Observer- und Event-Delegation: keine dynamischen Modelle

Das Observer- bzw. Event-Delegations-Modell sagt nichts über den Ablauf aus. Es beschreibt nur eine Klassen-Struktur, nicht die Dynamik, d.h. die Verteilung der Arbeit auf Threads. Gerade eine Ereignisverarbeitung muss aber schnell und reaktiv sein.

Swing läuft nicht in dem Hauptthread main(). In Swing wird die Ereignisbearbeitung und die MVC-Kommunikation unter mehreren Threads aufgeteilt, um wichtige Aufgaben asynchron ausführen zu können.

Die AWT/Swing-Ereignisse werden in einer Warteschlange, einer Instanz der Klasse EventQueue, verwaltet.

Event-
Verarbeitung: PostEventThread Dispatch-Thread

Der »PostEventQueue«-Thread ist dafür zuständig, Ereignisse am Ende dieser Warteschlage einzufügen. Mit Erschaffen der EventQueue wird weiterhin ein Dispatch-Thread gestartet, der die Ereignisse vom Kopf der Warteschlange entfernt und verteilt.

Dispatch-Thread ruft Callbacks

gp  Der Dispatch-Thread ist aus der Sicht des Swing-Anwenders von entscheidender Bedeutung, da aus dem Dispatch-Thread alle Callback-Methoden der Listener aufgerufen werden.

Damit laufen alle Zugriffe der Listener auf Swing-Komponenten, bzw. das zugehörige Daten-Modell innerhalb des Dispatch-Threads, und sind deshalb auch thread-sicher.

Im Folgenden soll kurz die Frage geklärt werden, inwieweit Zugriffe anderer Threads sicher und wann sie überhaupt notwendig sind.


Galileo Computing

15.7.1 Erlaubte Zugriffe von Threads auf Swing  downtop

Icon

Unter erlaubten Zugriffen versteht man thread-sichere Zugriffe auf Swing, wobei diese nur sehr restriktiv möglich sind.

Restriktionen für
externe Threads

Für externe Threads gelten folgende Regeln:

1. Nur wenige Methoden, die in Swing als thread-sicher markiert sind, dürfen aufgerufen werden.
    gp  Hierzu zählen als wichtigste asynchrone Methoden repaint() und revalidate() aus JComponent.
2. Aus externen Threads können Komponenten erschaffen, gesetzt und eingefügt werden, sofern für die beteiligten Komponenten (einschließlich der Container) nicht bereits pack(), setVisible() oder show() aufgerufen wurde.
3. Aus externen Threads können Listener an- und abgemeldet werden.

Galileo Computing

15.7.2 Notwendigkeit swing-externer Threads  downtop

Swing-GUI kann einfrieren!

Da nur der Dispatch-Thread die Methoden der Listener ausführt, können ernsthafte Probleme entstehen. Denn die Callback-Methode

gp  kann blockieren (z.B. aufgrund von I/O-Operationen).
gp  kann für ihre Ausführung einen längeren Zeitraum benötigen.

Fazit

Die Arbeit des Dispatchers setzt aus, d.h. es werden keine Ereignisse mehr verteilt und verarbeitet, die GUI-Oberfläche »friert ein«. Man ist also gezwungen, einen neuen Thread zu starten.


Galileo Computing

15.7.3 Kommunikation mit Swing von externen Threads  downtop

Icon

Startet man aus den oben genannten Gründen einen externen Thread, hat man nach Erlaubte Zugriffe von Threads auf Swing folgende Regel zu beachten:

Ausführung externer Threads

1. Vor dem Start der Thread müssen alle Informationen den Komponenten entnommen werden.
2. Sind Methoden während der Thread-Ausführung aufzurufen, die die Swing-Komponenten betreffen, müssen diese im Dispatch-Thread ausgeführt werden.
Hierzu stehen zwei statische Methoden in der Klasse EventQueue zur Verfügung16  , die mit Hilfe eines Runnable-Wrappers beliebige Methoden im Dispatch-Thread ausführen kann:

invokeAndWait()
invokeLater()

    gp  public static void invokeAndWait(Runnable r)
    gp  public static void invokeLater(Runnable r)

Der Unterschied zwischen beiden Methoden ist der, dass die erste synchron abläuft, also den externen Thread mit wait() bis zum Ende suspendiert, wogegen die zweite asynchron ausgeführt wird.

Runnable-
Interface als Kommando-Klasse

Das Runnable-Interface wurde nun nicht etwa genommen, um die Ausführung von run() in einem neuen Thread auszuführen17  , sondern nur um genau eine fest definierte Methode ohne Parameter innerhalb des Dispatch-Threads ausführen zu müssen.

gp  Die Methode run() ist somit ein Wrapper für die eigentliche Methode.

Beispiele

Timer-Klasse in javax.swing

Die Klasse Timer im Package javax.swing erlaubt eine thread-sichere einmalige oder periodische Ausführung der actionPerformed()-Methode von Listenern. Es existiert eine weitere Klasse Timer in java.util, die ähnliche Aufgaben wahrnimmt, allerdings außerhalb von Swing.

Dazu verwendet Timer intern ebenfalls die Methode invokeLater(), um die Listener-Methoden im Dispatch-Thread auszuführen.

Im Test wird der Timer dazu benutzt, die abgelaufenen Sekunden seit Anzeige des Fensters in einem Label anzuzeigen.

Simulation:
Eine GUI friert ein!

In der actionPerformed()-Methode der Schaltfläche wird dann mittels sleep() eine lang andauernde Ausführung simuliert, die die GUI auf nichts mehr reagieren lässt (Abb. 15.31).


Abbildung
Abbildung 15.31   Timer zeigt blockierten Event-Dispatch-Thread

Solange man nicht die Schaltfläche auslöst, werden rechts daneben die abgelaufenen Sekunden angezeigt (Abb. 15.31, Bild links).

Wird aber die Schaltfläche gedrückt, so blockiert sie jegliche weitere Ereignisverarbeitung. Der Dispatch-Thread wartet auf das Ende der Ausführung (in diesem Fall zehn Sekunden).

Ergebnis: Die Zeit bleibt stehen und selbst die grafische Anzeige »gedrückt« wird nicht mehr zurückgesetzt (Abb. 15.31, Bild rechts).

class LongRunner implements ActionListener {

ActionListener
mit sleep(): No Swinging!

  // simuliert eine Ausführungszeit von 10 Sekunden
  public void actionPerformed(ActionEvent e) {
    try {
      Thread.sleep(10000);
    } catch (Exception ex) {}
  }
}

Nachfolgend lässt LongRunner in Test die Zeit stillstehen.

Test-Klasse zur Simulation »Eingefrorene GUI«

public class Test {
  public static void main(String[] args) {
    JFrame jf= new EJFrame("Timer");
    Container cp= jf.getContentPane();
    JPanel jp= new JPanel(); cp.add(jp);
    JButton jb= new JButton("GUI friert ein");
    jb.addActionListener(new LongRunner());
    jp.add(jb);
    final JLabel jl= 
new JLabel("0 sec"); jp.add(jl);

javax.swing.Timer

    // anonyme Klasse setzt Label jede Sekunde 
    javax.swing.Timer t= new javax.swing.Timer(1000,
      new ActionListener() {
         int i= 1;
         public void actionPerformed(ActionEvent e) {
           jl.setText(i++ +" sec");     // jl muss final sein
         }
      });
    jf.setSize(200,70); jf.setVisible(true);
    t.start();                               // startet Timer
  }
}

Simulation:
Mehrere Threads bearbeiten eine Komponente

Im folgenden Beispiel werden aus mehreren Threads in eine Liste Strings eingefügt. Hierzu wird invokeLater() verwendet.


Abbildung
Abbildung 15.32   JList, gefüllt mit invokeLater() aus mehreren Threads

Die Klasse JList verwendet im Test das DefaultListModel für Strings.

JList

class WorkerThread implements Runnable {
  private JList lst;
  private String[] words;
  public WorkerThread(JList lst, String[] words) 
{
    this.lst= lst; this.words= words;
  }

run():
Thread- Ausführung

  public void run() {
    for(int i=0; i<words.length; i++) {
      try { Thread.sleep((int)(2000* Math.random()));
      } catch (Exception ex) {}

invokeLater() mit anonymer Klasse

      final int j= i;  // notwendig wegen anonymer 
Klasse
      EventQueue.invokeLater(
        // Wrapper hüllt die eigentliche Methode ein 
        new Runnable() {
          public void run() {

run():
Callback-Kommando in der Event-Queue

            ((DefaultListModel)lst.getModel())
                                  .addElement(words[j]);
          }
        });
    }
  }
}
public class Test {
  public static void main(String[] args) {
    JFrame jf= new EJFrame("InvokeLater");
    Container cp= jf.getContentPane();

DefaultLlistModel
ScrollPane

    String[] pat= {"Adapter","Decorator","Delegation",
                   "Facade", "Factory","Immutable",
                   "Interface", "Iterator","Marker",
                   "Observer","Singleton", "Proxy"};
    JList jl= new JList(new DefaultListModel());
    JScrollPane jsp= new JScrollPane(jl);
    cp.add(jsp);
    jf.setSize(160,200); jf.setVisible(true);
    for (int i= 0; i<5; i++)
      new Thread(new WorkerThread(jl,pat)).start();
  }
}

Galileo Computing

15.8 Applet  downtop

Applets:
back to the roots

Ein Zitat18  vorab: »Als Java aus dem Ei schlüpfte und umherschaute, sah es zuerst einen Browser und dachte von da an, es sei ein Applet«.

Dies bedeutet, dass der Fokus von Java einmal – in grauer Vorzeit – auf kleinen Programmen für Browser lag. Java ist nun erwachsen geworden.

Die Zertifizierung enthält so gut wie keine relevanten Fragen zu Applets, man sollte nur wissen, wie man sie schreibt und startet. Auch dieser Abschnitt beschreibt wirklich nur die Grundlagen und Methoden, da die Entwicklung von Applets nicht zum Inhalt dieses Buchs gehört.


Galileo Computing

15.8.1 Eigenschaften  downtop

Applet-Restriktionen

Kurz zu den Eigenschaften, die Applets von normalen Applikationen unterscheiden:

gp  Ein Applet startet seine Ausführung nicht mit main(), sondern nach dem Laden wird als Erstes die Methode init() ausgeführt.
gp  Ein Applet kann somit nicht von der Kommandozeile als eigenes Programm, sondern nur mit einem Appletviewer appletviewer oder in einem Browser gestartet werden.
gp  Ein Applet wird in HTML-Code mit Hilfe von Applet-Tags eingebettet, wobei alle Tags ab OBJECT optional sind:

HTML-Tags zum Applet

    <HTML>
    ...
    <APPLET CODE="AnyApplet.class" WIDTH=300 HEIGHT=400>
            OBJECT=   serializedfilename
            ARCHIVE=  jarfile1, jarfiel2, ...
            CODEBASE= absoluteOrRelativeURLOfApplet
            ALT=      alternativeTextForNonDisplayingApplet
            NAME=     NameOfApplet
            ALIGN=    left|rigth|top|bottom|...
            VSPACE=   NumberOfPixelAboveApplet
            HSPACE=   NumberOfPixelOnBothSidesOfApplet
       <PARAM NAME= "parmName1" VALUE="value1">
       ...
    </APPLET>

Sicherheitsrestriktionen für (fremde) Applets

gp  Alle Applets unterliegen per Default Sicherheitsrestriktionen, sodass ohne explizite Erlaubnis praktisch kein Zugriff auf den Client-Rechner möglich ist.

Die Sicherheitsrestriktionen können aber browserabhängig für Applets, die lokal vom Client selbst gestartet werden, geringer sein als für die aus dem Netz.


Galileo Computing

15.8.2 Erstellen eines (J)Applets  downtop

Applet vs. JApplet oder das Browser-Dilemma

Applets werden als Subklassen von Applet bzw. JApplet erstellt. Entscheidet man sich direkt für ein JApplet, stehen einem zwar alle Möglichkeiten von Swing offen, aber leider verstehen Browser mit dem höchsten Marktanteil JApplets nicht ohne Anpassung der HTML-Datei und Plug-In.

Deshalb werden hier – entgegen den vorherigen Abschnitten – nur Applets besprochen, die auf dem normalen AWT basieren. Da JApplet eine Subklasse von Applet ist, gelten ohnehin alle Aussagen auch für Swing-Applets.

Im Folgenden werden die Methoden und ihre Bedeutungen beschrieben, die eine Subklasse der Klasse Applet überschreiben bzw. aufrufen kann.

Statt immer beide Begriffe »Browser« und »Appletviewer« aufzuführen, wird für beide nachstehend »Browser« verwendet.

No-Arg-Konstruktor

No-Arg-Konstruktor

Ein Browser führt nur den No-Arg-Konstruktor eines Applets beim Laden aus. Normalerweise wird ein Applet mit Hilfe der folgenden Methode init() initialisiert.

init()

Methode init()

Beim Laden des Applets wird einmalig die Methode init() vom Browser aufgerufen. Somit können hier alle Initialisierungen erfolgen, die normalerweise im Konstruktor einer Applikation ausgeführt werden.

start(), stop()

Methoden start() und stop()

Die Methode start() wird immer dann aufgerufen, wenn ein Applet (wieder) sichtbar wird.

Diese Methode wird zusammen mit stop() eingesetzt, um visuelle Ausgaben zu stoppen, wenn das Applet nicht mehr sichtbar ist (obwohl noch aktiv), und diese wieder zu starten, wenn es erneut sichtbar wird.

destroy()

Methode destroy()

Bevor die Applet-Ausführung beendet wird, wird vom Browser einmalig destroy() aufgerufen, um eventuell Ressourcen freizugeben.

showStatus()

Methode showStatus()

Sie zeigt eine Mitteilung in der Statuszeile des Browsers an.

getAppletInfo()

Methode getAppletInfo()

Sie holt Informationen zum Applet und kann passend überschrieben werden.

Wichtige Getters


Galileo Computing

15.8.3 Wichtige Getter-Methoden  downtop

getParameter()

Methode getParameterInfo() und getParameter()

In der HTML-Datei können Parameter mittels Parameter-Tags (siehe Eigenschaften) übergeben werden. Diese werden dann als String[][] zurückgeliefert.

Die Methode getParameter(String paramName) gibt dagegen nur den Wert des benannten Parameters als String zurück.

getImage(), getAudioClip()

Methode getImage() und getAudioClip()

Wie die Namen besagen, laden diese Methoden entweder ein Bild oder ein Audio-Clip (z.B. eine wav-Datei) von einer URL-Adresse und liefern diese als Objekt zurück.

Die Aufrufe werden allerdings an die zum Applet gehörige AppletContext-Instanz delegiert, die direkt angesprochen werden kann (siehe unten).

getCodeBase()

Methode getCodeBase()

Sie liefert die URL-Adresse, von der das Applet geladen wurde.

getAppletContext()
Klasse AppletContext

Methode getAppletContext()

Diese Methode holt die zum Applet gehörige AppletContext-Instanz, die das Dokument des Applets repräsentiert.

Die Klasse AppletContext definiert zwar nur sieben, dafür aber wichtige Methoden. Neben dem Laden von Bildern und Audio-Clips kann z.B. mit showDocument(URL url) eine neue Web-Seite angezeigt werden.

Abschließend sei angemerkt, dass die Ereignisverarbeitung bei Applets genauso wie für alle AWT- bzw. Swing-Komponenten erfolgt, da das Ereignis-Modell auf dem AWT 1.1 aufbaut.


Galileo Computing

15.9 Zusammenfassung  downtop

Das rudimentäre AWT 1.0 hat nur eine beschränkte Anzahl von einfachen visuellen Komponenten, die nicht sehr attraktiv für GUI-Oberflächen sind.

Swing ist angetreten, das alte und das neue 2D-AWT um folgende komfortable Komponenten zu ergänzen:

gp  Tabellen
gp  Visualisierung von Baum-Modellen
gp  MDI-Fenster
gp  anpassbare, scrollbare Teilbereiche
gp  Registerkarten
gp  Textverarbeitung
gp  Drag&Drop-Operationen
gp  Menüs, Symbolleisten, Aktionen und Quickinfos

Die daraus resultierende Vielfalt an Komponenten zusammen mit ihren Daten-Modellen macht es notwendig, dedizierte Bücher zum Thema »Swing« zu schreiben.

Die Aufgabe dieses Kapitels war dagegen, die für alle gemeinsamen Konzepte und Basismechanismen so darzustellen, dass eine Evaluierung und der Einsatz von Swing leicht fällt.

Plattform vs. Betriebssystem

Swing hat seinen Ursprung in der Idee, dass Java als Plattform in jeglicher Hinsicht unabhängig vom Betriebssystem sein muss, auch oder gerade für 2D-GUIs.

Dieses Swing-Konzept – so lobenswert es sein mag – ist aus mehreren Gründen problematisch. Hier nur drei:

gp  Zerstörte Oberfläche: Heavyweight-Komponenten zerstören beim Neuzeichnen jede logisch über ihr liegende Lightweight-Komponente.

Zu den Heavyweight-Komponenten zählen aber alle performanten Anwendungen wie 3D-Grafik, Video etc., die die Hardware und das Betriebssystem optimal nutzen müssen.

Multimedia zusammen mit Swing ist ein Adventure-Game.

gp  Trägheit: Swing ist träge, da leichtgewichtige Komponenten beim Zeichnen keine Hardware-Ressourcen nutzen, sondern jedes Pixel und jeden Event selbst verwalten.
gp  Nicht thread-sicher: Swing ist zwar intern multi-threaded, jedoch keineswegs thread-sicher. Das ist gerade für Grafikapplikationen ein gravierender Nachteil.

Look and Feels

Und was ist mit einer Java-Anwendung, deren L&F sich von allen anderen nativen Applikationen derselben GUI unterscheidet?

Betriebssysteme mit ihren GUIs werden geliebt (Mac), beschworen (Linux) und beschimpft (Windows), aber jeder Anwender ist aufgrund der täglichen Nutzung an seine eigene Oberfläche gewöhnt. So what?

Hase und Igel

Betriebssysteme und zugehörige GUIs entwickeln sich parallel zu Java sehr schnell fort, haben neue Erscheinungsformen (MAC OS X), verbesserte Funktionalität und – nicht zu vergessen – sie sind nativ, d.h. schnell.

Swing und Betriebssysteme spielen also Hase und Igel.

Swing im Rennen zu halten kostet mehr Ressourcen, als mittels native Peers die Fähigkeiten der OS-Plattformen optimal zu nutzen und nur den unbedingt notwendigen Rest zu ergänzen. Der Anwender fühlt sich Zuhause und der Programmierer hat »lightweighted problems«.


Galileo Computing

15.10 Chamäleon-Architektur  toptop

Nicht der Ersatz eines Betriebssystems, sondern die optimale Anpassung von Java macht es so wertvoll.

gp  Denn der eigentliche Vorteil von Java ist nicht eine überdimensionierte Plattform, sondern – wie Sun selbst sagt – »one size doesn't fit it all« und »one language is all you need«.

Egal, ob Handys, PDAs, PCs, Workstations, Mainframes, ob Realtime- oder normales OS, ob GH- oder MH-CPU, die Sprache Java ist schon da und sollte sich optimal in ihre Umgebung einpassen, auch wenn es ein »intelligenter« Kühlschrank sein sollte.






1    WFC: Window Foundation Classes von Microsoft.

2    Alternativ könnte ein Top-Level-Container als oberster Root-Container definiert werden, was jedoch den Container JInternalFrame einschließt, der nicht als äußeres Fenster verwendet werden kann.

3    Eine zu AWT äquivalente Komponente in Swing hat denselben Namen,
nur mit Präfix »J«.

4    Die Alternative wäre Mehrfachvererbung oder ein Bruch mit dem alten
AWT gewesen.

5    Was eventuell auch katastrophale Auswirkungen auf die Applikation haben könnte.

6    Die meisten Entwicklungsumgebungen bieten eine komfortablere Alternative zu null an, den so genannten XYLayout-Manager, der eine exakte XY-Positionierung relativ zum Container erlaubt.

7    Merkwürdigerweise heißt es nach Swing-Konvention nicht JBox.

8    Auch hier ist wieder das generelle Konzept der Delegation flexibler als das
der Vererbung.

9    Es gibt kein allgemeines Interface IObservable.

10    Damit wird das Argument IObservable im notify() überflüssig.

11    Für die Entwicklung stellt java.awt eine Klasse AWTEventMulticaster bereit.

12    Unter Windows werden Fenster über die Tastatur mit <ALT><TAB> bzw. <SHIFT> <ALT><TAB> gewechselt.

13    Die Reihenfolge ist deshalb wichtig, weil Events entweder weitergereicht oder konsumiert werden können.

14    Eine Komponente kann auch mehr als ein Modell enthalten. Zum Beispiel enthält JList ein ListModel und ein SelectionModel.

15    Sofern man nicht eine Komponente inklusive L&F komplett neu entwickeln will.

16    Auch in der Klasse SwingUtilities.

17    Damit würde man ja den Teufel (externen Thread) mit dem Belzebub (neuen externen Thread) austreiben.

18    Aufgefangen auf einer Konferenz von einem Sun-Evangelisten, dessen Namen mir leider nicht mehr präsent ist. Es ist so übersetzt, wie ich es in Erinnerung habe.

  

Perl – Der Einstieg




Copyright © Galileo Press GmbH 2001 - 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken und speichern. Ansonsten unterliegt das <openbook> denselben Bestimmungen wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.
Die Veröffentlichung der Inhalte oder Teilen davon bedarf der ausdrücklichen schriftlichen Genehmigung von Galileo Press. Falls Sie Interesse daran haben sollten, die Inhalte auf Ihrer Website oder einer CD anzubieten, melden Sie sich bitte bei: stefan.krumbiegel@galileo-press.de


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de