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.
15.1 Vorbemerkungen ![down](common/down.gif) ![top](common/15x15leer.gif)
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
|
eine anpassbare graphische Oberfläche (pluggable Look-and-Feels) |
Lightweight
Components
|
so genannte leichtgewichtige Komponenten (lightweight Components) |
Model-View-
Controller
|
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.
|
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
|
Nur in unternehmensweiten Applikationen (Intranet) ist eine enge Integration bzw. ein GUI wie Swing auf Seiten der Clients sinnvoll. |
|
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. WFC1 .
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.
|
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.
|
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.
15.2 Begriffe ![down](common/down.gif) ![top](common/top.gif)
Um in Java graphische Anwendungen zu schreiben, sind folgende Begriffe hilfreich.
Component
|
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
|
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
|
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
|
Container sind spezielle Komponenten, die andere Komponenten, d.h. auch andere Container, aufnehmen können. |
Layout-Manager
|
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
|
Top-Level-Container bezeichnet diejenigen Container, die als äußeres Fenster gewählt werden können.2 |
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.
15.3 Top-Level-Container ![down](common/down.gif) ![top](common/top.gif)
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 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.3
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äufig4 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 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.
15.3.1 JRootPane ![down](common/down.gif) ![top](common/top.gif)
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 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 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 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);
}
}
15.3.2 JLayeredPane ![down](common/down.gif) ![top](common/top.gif)
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.
|
Layer werden anhand Integer-Objekten geordnet, wobei Layer mit einer größeren Integer-Zahl über denen mit einer kleineren liegen. |
|
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 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.
15.3.3 JWindow ![down](common/down.gif) ![top](common/top.gif)
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 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.
|
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.
15.3.4 JFrame vs. Frame ![down](common/down.gif) ![top](common/top.gif)
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
|
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.5
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
|
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); }
15.3.5 JDialog und JOptionPane ![down](common/down.gif) ![top](common/top.gif)
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 15.8 JDialog, zentriert im zugehörigen Hauptfenster
show():
deprecated
|
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 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
|
Bestätigen/Confirm: Frage, Fehler, Warnung etc. mit diversen Schaltflächen. |
Textangaben
|
Eingabe/Input: Zeile, Liste etc. mit zwei Schaltflächen OK und Cancel. |
Mitteilungen
|
Mitteilung/Message: Mitteilung mit der Schaltfläche OK. |
Optionen
|
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 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 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 15.12 JOptionPane.showOptionDialog() im Windows-L&F
Die beiden null-Werte in showOptionDialog() bedeuten »kein eigenes Icon« und »keine Default-Selektion«.
15.4 Layout-Manager ![down](common/down.gif) ![top](common/top.gif)
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.
|
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
|
können für die meisten Container mit setLayout() frei gewählt werden. |
|
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().6 |
|
können selbst entwickelt werden, d.h., sie müssen das Interface LayoutManager oder das Subinterface LayoutManager2 implementieren. |
|
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 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.
15.4.1 Überblick ![down](common/down.gif) ![top](common/top.gif)
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](common/R.gif)
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
|
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
|
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.
15.4.2 BorderLayout und FlowLayout ![down](common/down.gif) ![top](common/top.gif)
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 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).
15.4.3 CardLayout ![down](common/down.gif) ![top](common/top.gif)
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 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
}
15.4.4 GridLayout ![down](common/down.gif) ![top](common/top.gif)
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 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.
15.4.5 GridBagLayout ![down](common/down.gif) ![top](common/top.gif)
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
|
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](common/T.gif)
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 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](common/T.gif)
GridBagLayout: Felder von Eingabeformularen optimal ausrichten
Aufgabe: Erstellen eines Formulars mit untereinander angeordneten Eingabefeldern mit vorangestellten Bezeichnungen (siehe Abb. 15.18).
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 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.
15.4.6 BoxLayout ![down](common/down.gif) ![top](common/top.gif)
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 Box7 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 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.
15.5 Ereignisbehandlung ![down](common/down.gif) ![top](common/top.gif)
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.
15.5.1 Observer-Pattern ![down](common/down.gif) ![top](common/top.gif)
Betrachtet man die Behandlung von Ereignissen, wird recht schnell klar, dass man ohne ein generelles, erweiterbares Modell nicht klar kommt.
![Icon](common/P.gif)
Nach dem ersten Missgriff in Java 1.0 wurde in 1.1 ein brauchbares Verhaltensmuster, das Event-Delegation-Modell8 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
|
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
|
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 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
|
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 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.
15.5.2 Ereignis-Delegations-Modell ![down](common/down.gif) ![top](common/top.gif)
![Icon](common/D.gif)
Ereignis- Delegation:
Notation und Aufbau
Das Ereignis-Delegations-Modell im AWT bzw. in Swing ist eine weit verzweigte Spezialisierung des o.a. Observer-Patterns.
|
Die Rolle des IObserver wird Event-Listener genannt. |
Event-Source/Listener
|
Die Observable-Klassen werden Event-Sources genannt, worunter alle Komponenten des AWTs und Swings fallen.9 |
|
Die Event-Listener registrieren sich bei den Event-Sources, an deren Ereignissen sie interessiert sind. |
|
Das Auftreten eines Ereignisses in einer Event-Source stellt dann die Zustandsänderung dar, die die Benachrichtigung auslöst. |
|
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 |
|
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.
|
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
|
Ist der Name eines Ereignisses <X>Event, ist der des zugehörigen Interfaces <X>Listener. |
add/remove<X>
Listener
|
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.
|
Die Adapter, zugehörig zum <X>Listener, heißt <X>Adapter. |
Template zur Struktur des Event-Delegations-Modells
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 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](common/jupiters.gif)
15.5.3 Ereignisverarbeitung ![down](common/down.gif) ![top](common/top.gif)
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
|
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
|
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 15.25 Formular zur Klasse InventarFrame
Formular-Aktionen
Mit den vier Schaltflächen des Formulars »Inventar-Eingabe« (Abb.15.24) sind folgende Aktionen verbunden:
|
<Neu>: Vergibt eine zufällige ganze Zahl als Inventar-Nr. und leert das Feld «Bezeichnung”. |
|
<Speichern>: Speichert den Inventar-Artikel ab, sofern eine Bezeichnung eingetragen wurde. |
|
<Pfeil zurück>: Zeigt – wenn vorhanden – den vorherigen Inventar-Artikel an, sonst den letzten. |
|
<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();
15.5.4 Fokus-Management ![down](common/down.gif) ![top](common/top.gif)
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
|
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
|
Unter einem Fokus-Zyklus versteht man alle Komponenten, die mittels <TAB> bzw. <SHIFT><TAB> angesprungen werden können. |
|
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
|
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 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);
}}
15.5.5 Ereignisverteilung (Event-Dispatching) ![down](common/down.gif) ![top](common/top.gif)
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.
|
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.
|
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.
|
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](common/R.gif)
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
|
Ein Ereignis wird einer Komponente nur zur Behandlung bzw. zur Verteilung übergeben, sofern |
|
|
hierfür mindestens ein Listener registriert ist. |
enableEvents()
|
|
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
|
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). |
15.5.6 Ereignisbearbeitung in abgeleiteten Komponenten ![down](common/down.gif) ![top](common/top.gif)
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](common/R.gif)
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:
|
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](common/T.gif)
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](common/Kaktus.gif)
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:
|
Erst bei FOCUS_GAINDED schaltet die Komponente JTextField den Textcursor ein, d.h., erst dann sieht man die Eingabeposition im Feld. |
15.6 MFC-Architektur ![down](common/down.gif) ![top](common/top.gif)
![Icon](common/D.gif)
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.
|
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. |
|
View: Die Sicht übernimmt die Darstellung der Komponente in Abhängigkeit von den Daten. Die Trennung ermöglicht verschiedene Sichten auf Basis derselben Datenstruktur. |
|
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.
15.6.1 Model-Delegate-Architektur in Swing ![down](common/down.gif) ![top](common/top.gif)
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
|
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).
|
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 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
|
<X>Model-Interface, das als Event-Source Listener für Modelländerungen registriert. |
Default<X>Model als Default-Klasse
|
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.
15.6.2 BoundedRangeModel für Bars und Sliders ![down](common/down.gif) ![top](common/top.gif)
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):
|
min, max: Grenzen des erlaubten Intervalls |
|
value: aktueller Wert im Intervall |
|
extent: der aktuelle Wert ist selbst ein Intervall [value,value+extent] |
|
isAdjusting: zeigt mit true an, dass der aktuelle Wert permanent angepasst wird |
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 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 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.
15.7 Multi-Threading in Swing ![down](common/down.gif) ![top](common/top.gif)
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
|
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.
15.7.1 Erlaubte Zugriffe von Threads auf Swing ![down](common/down.gif) ![top](common/top.gif)
![Icon](common/R.gif)
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. |
|
|
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. |
15.7.2 Notwendigkeit swing-externer Threads ![down](common/down.gif) ![top](common/top.gif)
Swing-GUI kann einfrieren!
Da nur der Dispatch-Thread die Methoden der Listener ausführt, können ernsthafte Probleme entstehen. Denn die Callback-Methode
|
kann blockieren (z.B. aufgrund von I/O-Operationen). |
|
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.
15.7.3 Kommunikation mit Swing von externen Threads ![down](common/down.gif) ![top](common/top.gif)
![Icon](common/R.gif)
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()
|
|
public static void invokeAndWait(Runnable r) |
|
|
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.
|
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 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 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();
}
}
15.8 Applet ![down](common/down.gif) ![top](common/top.gif)
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.
15.8.1 Eigenschaften ![down](common/down.gif) ![top](common/top.gif)
Applet-Restriktionen
Kurz zu den Eigenschaften, die Applets von normalen Applikationen unterscheiden:
|
Ein Applet startet seine Ausführung nicht mit main(), sondern nach dem Laden wird als Erstes die Methode init() ausgeführt. |
|
Ein Applet kann somit nicht von der Kommandozeile als eigenes Programm, sondern nur mit einem Appletviewer appletviewer oder in einem Browser gestartet werden. |
|
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
|
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.
15.8.2 Erstellen eines (J)Applets ![down](common/down.gif) ![top](common/top.gif)
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
15.8.3 Wichtige Getter-Methoden ![down](common/down.gif) ![top](common/top.gif)
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.
15.9 Zusammenfassung ![down](common/down.gif) ![top](common/top.gif)
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:
|
Tabellen |
|
Visualisierung von Baum-Modellen |
|
MDI-Fenster |
|
anpassbare, scrollbare Teilbereiche |
|
Registerkarten |
|
Textverarbeitung |
|
Drag&Drop-Operationen |
|
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:
|
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.
|
Trägheit: Swing ist träge, da leichtgewichtige Komponenten beim Zeichnen keine Hardware-Ressourcen nutzen, sondern jedes Pixel und jeden Event selbst verwalten. |
|
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«.
15.10 Chamäleon-Architektur ![top](common/15x15leer.gif) ![top](common/top.gif)
Nicht der Ersatz eines Betriebssystems, sondern die optimale Anpassung von Java macht es so wertvoll.
|
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.
|