Das Abstract Window Toolkit

Werbung
3-1
Das Abstract Window Toolkit
Das Abstract Window Toolkit
Dieser Abschnitt erklärt die verschiedenen Elemente der grafischen
Benutzeroberfläche, also die meisten Klassen des Abstract Window Toolkit (AWT),
anhand einer Folge von sehr primitiven Applikationen. Die Klassen befinden sich alle im
Paket java.awt.
Alle Applikationen verwenden den gleichen Rahmen, mit dem man untersuchen kann,
welche Events von einem bestimmten Element erzeugt werden. Wie man mit den
Events dann zweckmäßig umgeht, erklärt der folgende Abschnitt.
Über diesen Knopf können die Applikationen in einem Java-fähigen Browser
auch als Applets aufgerufen werden; dabei können die Events gefiltert werden.
Eine Oberfläche besteht aus verschiedenen grafischen Component-Objekten, die mit
Container-Objekten in einer Hierarchie zusammengefaßt werden. Die geometrische
Anordnung im Container kontrolliert ein LayoutManager-Objekt. In diesem Abschnitt
werden nur die einfacheren Versionen eingeführt — BorderLayout und GridLayout; mit
dem allgemeinen Konzept und den komplizierteren Beispielen — CardLayout und
GridBagLayout — befaßt sich der übernächste Abschnitt.
Themen
Die Rolle der grafischen Oberfläche
Button, Taster
Event, Instrumentierung und Dekodierung
Canvas, zum Aufbau neuer Elemente
Checkbox, binäre und Auswahl-Schalter
Choice, Auswahl-Schalter
Dialog, zusätzliches Fenster
FileDialog, Laden und Speichern von Dateien
Label und Color, Textzeile
List, Auswahl aus Liste
Image und MediaTracker, Bitmap-Grafik
Menu und TextArea, kaskadierte Menüs, Text
Scrollbar, Schieberegler
Die Beispiele als Applets
Abstract-Window-Toolkit-Klassen
Folgende Klassen (ohne abstrakte Klassen) werden in diesem Abschnitt verwendet:
BorderLayout
Button
3-2
Canvas
Checkbox
CheckboxGroup
CheckboxMenuItem
Choice
Color
Dialog
Dimension
Event
FileDialog
FlowLayout
Font
Frame
Graphics
GridLayout
Image
Label
List
MediaTracker
Menu
MenuBar
MenuItem
Panel
Scrollbar
TextArea
TextField
Toolkit
Das LoaderApplet und das TraceApplet sind Beispiele für die Applet-Klasse und
verwenden den AppletContext. In der EventList wird ein Hashtable eingesetzt.
3-3
Die Rolle der grafischen Oberfläche
Die Rolle der grafischen Oberfläche
Button
Events
Choice
Nachrichten
Window-Server
Algorithmus
TextArea
...
Der Grafik-Arbeitsplatz — Bildschirm, Tastatur und Maus — wird vom Window-Server
verwaltet, der als Eingabe Events an die Applikationen (Klienten) schickt.
Die grafische Oberfläche muß diese Events in Nachrichten umwandeln, die dann den
Algorithmus des Klienten treiben. Entscheidend ist, daß die Controls, also die
grafischen Bedienungselemente, als Event-Filter anpaßbar an ähnliche Aufgaben und
wiederverwendbar sind.
Das AWT stellt zwar Controls zur Verfügung, aber man muß die Disposition der Events
als Nachrichten an einen Algorithmus selbst jedesmal neu programmieren.
Das AWT ist plattform-unabhängig, das heißt, die Controls werden aus Elementen der
lokalen Plattform aufgebaut. Eine mit dem A WT implementierte grafische Oberfläche
hat dadurch das lokale, bekannte look and feel, aber das AWT bietet nur den
Durchschnitt verschiedener Toolkits — Motif, Windows, MacIntosh ... — und die
Implementierung ist, vor allem im Bereich der tatsächlich sichtbaren Events, nicht
immer äquivalent.
Um das AWT zu beherrschen, muß man folgende Fragen klären:
Welche Controls gibt es?
Welche Events filtern und liefern sie?
Wie verwandelt man die Events wiederverwendbar in Nachrichten?
Wie kontrolliert man das Layout?
Wie zeichnet man zelbst — auch neue Controls?
3-4
Button
Button
Beispiel A dient zum Experimentieren mit Button-Objekten und der ComponentHierarchie und mit der Weiterleitung von Events.
Ein Button stellt einen String dar und liefert Event-Objekte mit ACTION_EVENT für MausClicks.
A enthält mehrere Button-Objekte sowie ein Panel, die in folgender Hierarchie
angeordnet sind:
A
MyPanel
NW
E
SW
A demonstriert die prinzipielle Architektur aller Beispiele in diesem Abschnitt: Das
Hauptprogramm liegt in einer Klasse awt, die trace() zur Event-Dekodierung
bereitstellt. Das Hauptprogramm lädt und instantiiert die Klasse, die als Argument
angegeben ist:
$ java awt A
$ java -Dtrace=- awt A
$ java -Dtrace=trace.out awt A
# ohne Dekodierung
# mit Dekodierung zur Standard-Ausgabe
# mit Dekodierung in Datei
Die Klasse ist entweder abgeleitet von MyFrame oder ein Panel, das dann in ein MyFrameObjekt eingebettet wird. Das Hauptprogramm hinterlegt in diesem Objekt, ob und
wohin die Dekodierung ausgegeben werden soll.
Wie später erklärt wird, können die Beispiele dann auch durch ein Applet geladen und
mit Dekodierung in einem zweiten Applet dargestellt werden.
Alle Beispiele auf der Basis von MyFrame enden, wenn entweder ihr Window zerstört
wird, oder wenn (außerhalb von Textfeldern) control -C eingegeben wird.
3-5
Aufbau der Hierarchie
Da sich awt um die Umgebung eines Beispiels kümmert, muß der Konstruktor der
eigentlichen Beispiel-Klasse A nur die Component-Hierarchie aufbauen. Hier genügt ein
Panel, das awt in einem MyFrame zeigt:
import java.awt.*;
/** A class to test buttons in a panel */
class A extends MyPanel {
A () {
setLayout(new BorderLayout());
MyPanel p;
// group west buttons
add("West", p = new MyPanel());
p.setLayout(new GridLayout(2,1, 10,10));
p.add(new MyButton("NW"));
p.add(new MyButton("SW"));
add("East", new MyButton("E"));
}
}
Klassen wie Frame und Panel sind Container, die zur Verzweigung in der Hierarchie
dienen. Zum Einfügen dient add(). Je nach Layout hat add() ein zusätzliches, erstes
Argument.
Ein Frame verwendet nach Voreinstellung BorderLayout. Dabei kann man vier Objekte
entlang der Himmelsrichtungen anordnen, ein fünftes kommt ins Center.
Ein Panel verwendet FlowLayout, also rein sequentielle Anordnung, wobei Zeilen nur
unter Druck entstehen. Mit setLayout() kann man aber ein anderes Verfahren explizit
verlangen.
definiert Zeilen und Spalten für eine Tabelle aus gleichgroßen Elementen, in
die mit add() dann zeilenweise aufgefüllt wird.
GridLayout
Komplexere Anordnungen entstehen durch Gliederung mit Panel-Objekten — die aber
Einfluß auf die Event-Weitergabe hat — oder durch Verwendung des GridBagLayout.
3-6
Event-Weitergabe
Beispiel A zeigt, daß Event-Objekte bei den Blättern der Component-Hierarchie
ankommen und zur Wurzel weitergereicht werden, falls nicht unterwegs ein Objekt
den Event konsumiert. Eine Auswahl der Events:
$ java -Dtrace=- awt A
MyFrame: target
MyFrame[0,8,65x84,layout=java.awt.BorderLayout,resizable,title=A]
WINDOW_MOVED, clickCount 0, x 0, y 8
Das Fenster wird angelegt und positioniert.
MyFrame: target
MyFrame[0,8,65x84,layout=java.awt.BorderLayout,resizable,title=A]
MOUSE_ENTER, clickCount 0, when Wed Jan 14 15:14:25 MET 1970, x 32, y 78
Die Maus betritt das Fenster.
MyPanel: target MyPanel[0,0,34x54,layout=java.awt.GridLayout]
MOUSE_ENTER, clickCount 0, when Wed Jan 14 15:14:25 MET 1970, x 27, y 53
A: target MyPanel[0,0,34x54,layout=java.awt.GridLayout]
MOUSE_ENTER, clickCount 0, when Wed Jan 14 15:14:25 MET 1970, x 27, y 53
MyFrame: target MyPanel[0,0,34x54,layout=java.awt.GridLayout]
MOUSE_ENTER, clickCount 0, when Wed Jan 14 15:14:25 MET 1970, x 32, y 78
Die Maus betritt das Panel; der Event wird auch an A und MyFrame weitergegeben.
MyPanel: target MyPanel[0,0,34x54,layout=java.awt.GridLayout]
MOUSE_MOVE, clickCount 0, modifiers, when Wed Jan 14 15:14:27 MET 1970, x 31,
y 27
A: target MyPanel[0,0,34x54,layout=java.awt.GridLayout]
MOUSE_MOVE, clickCount 0, modifiers, when Wed Jan 14 15:14:27 MET 1970, x 31,
y 27
MyFrame: target MyPanel[0,0,34x54,layout=java.awt.GridLayout]
MOUSE_MOVE, clickCount 0, modifiers, when Wed Jan 14 15:14:27 MET 1970, x 36,
y 52
Die Maus bewegt sich im Panel, die Events gehen immer auch in der ComponentHierarchie aufwärts.
MyPanel: target MyPanel[0,0,34x54,layout=java.awt.GridLayout]
MOUSE_EXIT, clickCount 0, when Wed Jan 14 15:14:29 MET 1970, x 23, y 36
A: target MyPanel[0,0,34x54,layout=java.awt.GridLayout]
MOUSE_EXIT, clickCount 0, when Wed Jan 14 15:14:29 MET 1970, x 23, y 36
MyFrame: target MyPanel[0,0,34x54,layout=java.awt.GridLayout]
MOUSE_EXIT, clickCount 0, when Wed Jan 14 15:14:29 MET 1970, x 28, y 61
Die Maus verläßt das Panel, weil sie im Panel in einen Button eintritt. Der Button liefert
dazu jedoch keinen Event.
MyButton: target MyButton[0,32,34x22,label=SW]
3-7
ACTION_EVENT, clickCount 0, arg java.lang.String SW
MyPanel: target MyButton[0,32,34x22,label=SW]
ACTION_EVENT, clickCount 0, arg java.lang.String SW
A: target MyButton[0,32,34x22,label=SW]
ACTION_EVENT, clickCount 0, arg java.lang.String SW
MyFrame: target MyButton[0,32,34x22,label=SW]
ACTION_EVENT, clickCount 0, arg java.lang.String SW
Ein Button ist ein Event-Filter: Bei Click liefert er einen ACTION_EVENT, der ebenfalls bis
zur Wurzel der Component-Hierarchie kommen kann.
MyFrame: target
MyFrame[6,31,65x84,layout=java.awt.BorderLayout,resizable,title=A]
WINDOW_DESTROY, clickCount 0
Wenn das Fenster zerstört wird, erhält MyFrame dann WINDOW_DESTROY und beendet
deshalb das Programm.
Das Programm kann auch durch Eingabe voncontrol -C beendet werden:
MyButton: target MyButton[34,0,21x54,label=E]
KEY_PRESS, clickCount 0, key 3, modifiers ctrl, when Wed Jan 14 15:22:23 MET
1970, x 25, y 22
A: target MyButton[34,0,21x54,label=E]
KEY_PRESS, clickCount 0, key 3, modifiers ctrl, when Wed Jan 14 15:22:23 MET
1970, x 59, y 22
MyFrame: target MyButton[34,0,21x54,label=E]
KEY_PRESS, clickCount 0, key 3, modifiers ctrl, when Wed Jan 14 15:22:23 MET
1970, x 64, y 47
3-8
Event
Event
Instrumentierung
Damit die Event-Objekte sichtbar werden, müssen die A WT-Klassen instrumentiert
werden. Ein Panel dient zur Zusammenfassung von Component-Objekten. Hier genügt
es, handleEvent() zu ersetzen:
import java.awt.*;
/** A class to track events in a panel */
public class MyPanel extends Panel {
protected Trace tracer;
public boolean handleEvent (Event e) {
if (tracer == null)
tracer = awt.tracer(this);
tracer = tracer.trace(this, e);
return super.handleEvent(e);
}
}
sucht zu einer Component in einer Hierarchie einen Vorgänger, der das
Interface Trace implementiert:
awt.tracer()
import java.awt.*;
public interface Trace {
Trace trace (Object o, Event e);
}
Die Suche beruht auf getParent():
/** must find Trace above caller */
public static Trace tracer (Component c) {
while (! (c instanceof Trace))
if ((c = c.getParent()) == null) {
System.err.println("no tracer for "+c);
System.exit(1);
}
return (Trace)c;
}
Die instrumentierten Klassen merken sich beim ersten Aufruf das zuständige TraceObjekt und schicken ihre Events dorthin zur Dekodierung. Dabei kann die Zuständigkeit
weitervermittelt werden.
liefert true, wenn der Event nicht zum Vorgänger in der ComponentHierarchie weitergegeben werden soll.
handleEvent()
Wenn die Methode in einer abgeleiteten Klasse ersetzt wird, um die EventVerarbeitung nur zu erweitern, sollte sie super.handleEvent() aufrufen. Erreicht man
dadurch Component.handleEvent() — immer noch für das aktuelle Objekt in der
Component-Hierarchie — wird je nach Event noch eine einzige Methode wie
mouseDown() aufgerufen, die ebenfalls ersetzt werden könnte.
3-9
Ein Button soll bei Click einen ACTION_EVENT liefern, Hier gibt es auch einen Konstruktor
mit Argument, der explizit ersetzt werden muß:
import java.awt.*;
/** A class to track event handling in a button */
public class MyButton extends Button {
public MyButton (String label) {
super(label);
}
protected Trace tracer;
public boolean handleEvent (Event e) {
if (tracer == null)
tracer = awt.tracer(this);
tracer = tracer.trace(this, e);
return super.handleEvent(e);
}
}
Ein Frame ist ein Fenster, das vom Window-Manager in Zusammenarbeit mit dem
Window-Server dekoriert und manipuliert werden kann. Damit ein Programm beendet
werden kann, erweitert MyFrame die Event-Verarbeitung für Frame. Außerdem leitet
MyFrame die Dekodierung durch trace() weiter, falls awt oder das LoaderApplet das
entsprechend vorbereitet haben:
import java.awt.*;
import java.io.PrintStream;
/** A class to trace event handling in a frame
* and terminate on control-C and WINDOW_DESTROY
*/
public class MyFrame extends Frame implements Trace {
public boolean handleEvent (Event e) {
trace(this, e);
if (e.id == Event.KEY_PRESS && e.key == (’C’ & 0x1f)
|| e.id == Event.WINDOW_DESTROY && e.target == this)
System.exit(0);
return super.handleEvent(e);
}
protected PrintStream out = null;
// set by awt.main()
protected Trace tracer = null;
// set by LoaderApplet.init()
public Trace trace (Object o, Event e) {
if (out != null)
awt.trace(out, o, e);
else if (tracer != null)
tracer.trace(o, e);
return tracer == null ? this : tracer;
}
}
Hier wird control -C für beliebige Component-Objekte und WINDOW_DESTROY nur für den
aktuellen MyFrame als Aufforderung verstanden, das Programm abzubrechen — das
funktioniert nicht im Zusammenhang mit einem Applet.
Framework
Die Klasse awt enthält das Hauptprogramm:
3-10
import java.awt.*;
import java.io.*;
import java.util.Date;
/** A class to manage event decoding and tracing */
class awt {
/** main program, creates and displays main window */
public static void main (String args []) {
if (args.length == 1)
try {
Component c = (Component)Class.forName(args[0]).newInstance();
MyFrame f = c instanceof MyFrame ? (MyFrame)c : new MyFrame();
if (f != c)
f.add("Center", c);
String path = System.getProperty("trace");
if ("-".equals(path))
// -Dtrace=- to stdout
f.out = System.out;
else if (path != null)
// -Dtrace=path to file
f.out = new PrintStream(new FileOutputStream(path));
f.setTitle(args[0]); f.pack(); f.show();
} catch(Exception e) {
System.err.println(args[0]+" "+e);
}
else {
System.err.println("usage: java [-Dtrace=-|path] awt class");
System.exit(1);
}
}
Zuerst wird ein Objekt einer Klasse instantiiert, deren Namen als String auf der
Kommandozeile angegeben ist. Class.forName() liefert ein Class-Objekt, das mit
newInstance() ein Objekt seiner Klasse — allerdings nur ohne Parameter für den
Konstruktor — erzeugen kann.
Wenn es sich nicht um ein MyFrame-Objekt handelt, wird es entsprechend eingebettet.
Anschließend wird die System-Property trace untersucht, die auf der Kommandozeile
von java zum Beispiel als -Dtrace=- gesetzt werden kann. Dem MyFrame wird dann ein
PrintStream für die Event-Dekodierung zugewiesen — innerhalb einer Package können
als protected markierte Variablen auch von außen geändert werden.
setzt den Titel, mit dem der Window-Manager das Fenster dekoriert.
pack() sorgt in einem Window dafür daß sich die Component-Hierarchie gemäß dem
Layout einrichtet, show() zeigt das Fenster samt Inhalt.
setTitle()
Die Exception bezieht sich darauf, daß die Klasse nicht existiert, ein Objekt nicht
erzeugt werden kann oder das Objekt keine geeignete Klasse besitzt.
Event-Dokodierung
Leider werden Event-Objekte nicht wie Exception in Unterklassen differenziert. Das idFeld definiert durch spezielle Werte, was für ein Event vorliegt und welche anderen
Felder gültig sind:
3-11
/** flags defining what can be shown */
private static final int W = 1,
// show when
XY = W << 1,
// show x and y
K = XY << 1,
// show key
M = K << 1;
// show mods
/** drives event decoding */
public static void trace (PrintStream out, Object o, Event e) {
out.print(o.getClass().getName()+": ");
switch (e.id) {
case Event.ACTION_EVENT:
t(out, e, "ACTION_EVENT", 0); break;
case Event.GOT_FOCUS:
t(out, e, "GOT_FOCUS", 0); break;
case Event.LOST_FOCUS:
t(out, e, "LOST_FOCUS", 0); break;
case Event.KEY_ACTION:
t(out, e, "KEY_ACTION", W|XY|K|M); break;
case Event.KEY_ACTION_RELEASE:
t(out, e, "KEY_ACTION_RELEASE", W|XY|K|M); break;
case Event.KEY_PRESS:
t(out, e, "KEY_PRESS", W|XY|K|M); break;
case Event.KEY_RELEASE:
t(out, e, "KEY_RELEASE", W|XY|K|M); break;
case Event.LIST_SELECT:
t(out, e, "LIST_SELECT", 0); break;
case Event.LIST_DESELECT:
t(out, e, "LIST_DESELECT", 0); break;
case Event.LOAD_FILE:
t(out, e, "LOAD_FILE", 0); break;
case Event.SAVE_FILE:
t(out, e, "SAVE_FILE", 0); break;
case Event.MOUSE_DOWN:
t(out, e, "MOUSE_DOWN", W|XY|M); break;
case Event.MOUSE_UP:
t(out, e, "MOUSE_UP", W|XY|M); break;
case Event.MOUSE_DRAG:
t(out, e, "MOUSE_DRAG", W|XY|M); break;
case Event.MOUSE_MOVE:
t(out, e, "MOUSE_MOVE", W|XY|M); break;
case Event.MOUSE_ENTER:
t(out, e, "MOUSE_ENTER", W|XY); break;
case Event.MOUSE_EXIT:
t(out, e, "MOUSE_EXIT", W|XY); break;
case Event.SCROLL_ABSOLUTE: t(out, e, "SCROLL_ABSOLUTE", 0); break;
case Event.SCROLL_LINE_UP: t(out, e, "SCROLL_LINE_UP", 0); break;
case Event.SCROLL_LINE_DOWN:t(out, e, "SCROLL_LINE_DOWN", 0); break;
case Event.SCROLL_PAGE_UP: t(out, e, "SCROLL_PAGE_UP", 0); break;
case Event.SCROLL_PAGE_DOWN:t(out, e, "SCROLL_PAGE_DOWN", 0); break;
case Event.WINDOW_EXPOSE:
t(out, e, "WINDOW_EXPOSE", 0); break;
case Event.WINDOW_ICONIFY: t(out, e, "WINDOW_ICONIFY", 0); break;
case Event.WINDOW_DEICONIFY:t(out, e, "WINDOW_DEICONIFY", 0); break;
case Event.WINDOW_DESTROY: t(out, e, "WINDOW_DESTROY", 0); break;
case Event.WINDOW_MOVED:
t(out, e, "WINDOW_MOVED", XY); break;
default:
out.println(e);
}
}
Ein Event kann einen Zeitstempel when, Koordinaten x und y, einen Tastenwert key und
modifiers enthalten, die sich auf Tasten wie control etc. beziehen oder Mausknöpfe
klassifizieren.
Das Koordinatensystem ist relativ zu jeder Component, beginnt links oben und ist positiv
nach rechts und unten. Die Koordinaten im Event sind relativ zum Empfänger von
handleEvent(). Sie können mit translate() geändert werden.
Jeder Event enthält ein target, eine id und einen clickCount. Der Rest hängt von id
ab:
3-12
/** event decoding */
private static void t (PrintStream out, Event e, String name, int mask) {
out.println("target "+e.target);
out.print(name);
out.print(", clickCount "+e.clickCount);
if ((mask & K) != 0) {
out.print(", key ");
if (e.key >= ’ ’ && e.key <= ’~’)
out.print("’"+(char)e.key+"’");
else switch (e.key) {
case Event.F1:
out.print("F1"); break;
case Event.F2:
out.print("F2"); break;
case Event.F3:
out.print("F3"); break;
case Event.F4:
out.print("F4"); break;
case Event.F5:
out.print("F5"); break;
case Event.F6:
out.print("F6"); break;
case Event.F7:
out.print("F7"); break;
case Event.F8:
out.print("F8"); break;
case Event.F9:
out.print("F9"); break;
case Event.F10:
out.print("F10"); break;
case Event.F11:
out.print("F11"); break;
case Event.F12:
out.print("F12"); break;
case Event.LEFT:
out.print("LEFT"); break;
case Event.RIGHT:
out.print("RIGHT"); break;
case Event.UP:
out.print("UP"); break;
case Event.DOWN:
out.print("DOWN"); break;
case Event.HOME:
out.print("HOME"); break;
case Event.END:
out.print("END"); break;
case Event.PGUP:
out.print("PGUP"); break;
case Event.PGDN:
out.print("PGDN"); break;
default:
out.print(e.key);
}
}
if ((mask & M) != 0) {
out.print(", modifiers");
if ((e.modifiers & Event.ALT_MASK) != 0)
out.print(" alt");
if ((e.modifiers & Event.CTRL_MASK) != 0)
out.print(" ctrl");
if ((e.modifiers & Event.META_MASK) != 0)
out.print(" meta");
if ((e.modifiers & Event.SHIFT_MASK) != 0) out.print(" shift");
}
if ((mask & W) != 0)
out.print(", when "+new Date(e.when));
if ((mask & XY) != 0)
out.print(", x "+e.x+", y "+e.y);
switch (e.id) {
case Event.LOAD_FILE:
case Event.SAVE_FILE:
if (e.arg == null) break;
case Event.ACTION_EVENT:
case Event.LIST_SELECT:
case Event.LIST_DESELECT:
case Event.SCROLL_ABSOLUTE:
case Event.SCROLL_LINE_UP:
case Event.SCROLL_LINE_DOWN:
case Event.SCROLL_PAGE_UP:
case Event.SCROLL_PAGE_DOWN:
out.print(", arg "+e.arg.getClass().getName()+" "+e.arg);
}
out.println(’\n’);
}
}
kann einen Latin-1-Wert, also Steuerzeichen, Groß- oder Kleinbuchstaben etc. oder
eine speziell definierte Konstante enthalten.
key
für key hängen von der Tastatur am Window-Server ab. Sie qualifizieren
außerdem die Mausknöpfe, denn Java geht von einer Ein-Knopf-Maus aus.
modifiers
3-13
Der Zeitstempel stammt von der Systemuhr (des Servers?).
Manche Events enthalten noch ein Objekt arg, dessen Klasse nicht nur von id sondern
auch von der Klasse von target abhängt:
Button, Choice, List, MenuItem
Checkbox
und TextField liefern bei ACTION_EVENT einen String.
liefert bei ACTION_EVENT ihren Zustand als Boolean-Objekt.
liefert bei LIST_SELECT und LIST_DESELECT den betroffenen Index als IntegerObjekt.
List
Scrollbar
liefert die Zielposition als Integer-Objekt.
3-14
Canvas
Canvas
Beispiel B dient zum Experimentieren mit einem Canvas-Objekt.
Ein Canvas ist leer und hat auch keine vordefinierte Größe, erzeugt aber eine Vielzahl
von Events. Er kann deshalb vor allem zum Aufbau neuer Controls dienen.
B enthält ein einziges Canvas-Objekt, das explizit dimensioniert wird und sich dann mit
dem Fenster ändert:
import java.awt.*;
/** A class to test a canvas */
class B extends Panel {
B () {
setLayout(new BorderLayout());
Canvas c;
add("Center", c = new Canvas());
c.resize(100,100);
}
}
Da nur MyFrame instrumentiert ist, sieht man alle Events auf der äußersten Ebene. Eine
Auswahl:
$ java -Dtrace=- awt B
MyFrame: target java.awt.Canvas[0,0,100x100]
GOT_FOCUS, clickCount 0
MyFrame: target java.awt.Canvas[0,0,100x100]
MOUSE_ENTER, clickCount 0, when Wed Jan 14 16:20:31 MET 1970, x 30, y 47
MyFrame: target java.awt.Canvas[0,0,100x100]
MOUSE_MOVE, clickCount 0, modifiers, when Wed Jan 14 16:20:43 MET 1970, x 53,
y 108
MyFrame: target java.awt.Canvas[0,0,100x100]
MOUSE_DOWN, clickCount 1, modifiers, when Wed Jan 14 16:20:46 MET 1970, x 53,
y 107
MyFrame: target java.awt.Canvas[0,0,100x100]
MOUSE_DRAG, clickCount 0, modifiers, when Wed Jan 14 16:20:47 MET 1970, x 52,
y 107
3-15
MyFrame: target java.awt.Canvas[0,0,100x100]
MOUSE_UP, clickCount 0, modifiers, when Wed Jan 14 16:20:48 MET 1970, x 53, y
96
MyFrame: target java.awt.Canvas[0,0,100x100]
KEY_PRESS, clickCount 0, key ’f’, modifiers, when Wed Jan 14 16:20:54 MET
1970, x 53, y 96
MyFrame: target java.awt.Canvas[0,0,100x100]
KEY_RELEASE, clickCount 0, key ’f’, modifiers, when Wed Jan 14 16:20:54 MET
1970, x 53, y 96
MyFrame: target java.awt.Canvas[0,0,100x100]
KEY_ACTION, clickCount 0, key F3, modifiers, when Wed Jan 14 16:20:57 MET
1970, x 53, y 96
MyFrame: target java.awt.Canvas[0,0,100x100]
KEY_ACTION_RELEASE, clickCount 0, key F3, modifiers, when Wed Jan 14 16:20:57
MET 1970, x 53, y 96
MyFrame: target java.awt.Canvas[0,0,100x100]
MOUSE_EXIT, clickCount 0, when Wed Jan 14 16:21:02 MET 1970, x 55, y 135
MyFrame: target
MyFrame[4,13,110x130,layout=java.awt.BorderLayout,resizable,title=B]
WINDOW_DESTROY, clickCount 0
Leider erhält man WINDOW_EXPOSE, WINDOW_ICONIFY und WINDOW_DEICONIFY offenbar nur
bei bestimmten Window-Managern (Hier war es co-Xist auf NeXT für Java auf Linux)
und es gibt keinen Event bei einer Größenänderung — dazu müßte man wohl
repaint() oder update() ersetzen.
3-16
Checkbox
Checkbox
Beispiel C dient zum Experimentieren mit Checkbox-Objekten, von denen die drei linken
mit einer CheckboxGroup logisch verbunden sind.
Eine Checkbox stellt einen String und einen binären Zustand dar und liefert bei Click
einen ACTION_EVENT mit dem neuen Zustand.
Eine CheckboxGroup verbindet Checkbox-Objekte so, daß exakt eines wahr ist.
Ein Panel dient nur zur Anordnung der Checkbox-Objekte, es hat mit der logischen
Verknüpfung nichts zu tun:
import java.awt.*;
/* A class to test checkboxes */
class C extends Panel {
C () {
setLayout(new BorderLayout());
CheckboxGroup g = new CheckboxGroup();
Panel p;
// group radio buttons
add("West", p = new Panel());
p.setLayout(new GridLayout(3,1, 10,10));
p.add(new Checkbox("NW", g, false));
p.add(new Checkbox("W", g, true));
p.add(new Checkbox("SW", g, false));
add("Center", new Checkbox("E"));
}
}
3-17
Choice
Choice
Beispiel D dient zum Experimentieren mit einem Choice-Objekt.
Ein Choice stellt einen String von vielen dar und erlaubt die Auswahl durch Ausklappen
einer Liste. Man kann Strings hinzufügen, aber anscheinend nicht mehr entfernen.
import java.awt.*;
/* A class to test a choice */
class D extends Panel {
D () {
setLayout(new BorderLayout());
Choice c;
add("Center", c = new Choice());
c.addItem("top");
c.addItem("above");
c.addItem("center");
c.addItem("below");
c.addItem("bottom");
}
}
3-18
Dialog
Dialog
Beispiel E dient zum Experimentieren mit zwei Dialog-Objekten, von denen eines
einen modalen Dialog vorsieht, der allerdings erst ab Release 1.0.2 korrekt realisiert ist.
Ein Dialog ist ein eigenes, dekoriertes Fenster, das optional modal erklärt werden kann,
das heißt, daß es alle Events an andere Fenster der Applikation ausschließen soll. Hier
unterscheiden sich die Fenster nur durch die Titel:
import java.awt.*;
/* A class to test dialogs and component parenting */
class E extends MyFrame {
Dialog a, b;
E () {
a = new MyDialog(this, "Dialog", false);
b = new MyDialog(this, "Modal", true);
fill(a); fill(b); fill(this);
}
static void fill (Window to) {
Panel p;
// group west buttons
to.add("West", p = new Panel());
p.setLayout(new GridLayout(3,1, 10,10));
p.add(new Button("NW"));
p.add(new Button("W"));
p.add(new Button("SW"));
to.add("East", new Button("E"));
}
public boolean handleEvent (Event e) {
boolean result = super.handleEvent(e); // show first
if (e.id == Event.ACTION_EVENT) {
a.move(50, 50); a.pack(); a.show();
b.move(100, 100); b.pack(); b.show();
}
return result;
}
}
Ein Dialog muß bei der Konstruktion mit einem übergeordneten Frame verknüpft
werden, was von einem Applet aus nur dadurch möglich ist, daß zuerst ein Frame
erzeugt wird.
Der Frame soll wohl bei Release 1.0.2 keine Events vom Dialog erhalten — das
funktioniert aber nur bedingt. Es dürfte sich empfehlen, Dialog so abzuleiten, daß
3-19
handleEvent()
zuverlässig kontrolliert.
Bei E enthalten alle Fenster die vom Beispiel A her bekannte Anordnung von ButtonObjekten und ein Panel.
ordnet eine Component im Koordinatensystem des Vorgängers an; das
funktioniert hier für die Dialog-Fenster je nach Kooperation des Window-Managers.
move()
wird in MyDialog so ersetzt, daß bei Knopfdruck im Frame die Dialoge
dargestellt werden und bei control -C oder WINDOW_DESTROY in einem Dialog der Dialog
mit dispose() entfernt wird.
handleEvent()
import java.awt.*;
/** A class to track event handling in a dialog
* and dispose the dialog on control-C and WINDOW_DESTROY
* (release 1.0.2 does not pass events to parent of Dialog)
*/
public class MyDialog extends Dialog {
public MyDialog (Frame parent, String title, boolean modal) {
super(parent, title, modal);
}
protected Trace tracer;
public boolean handleEvent (Event e) {
if (tracer == null)
tracer = awt.tracer(this);
tracer = tracer.trace(this, e);
if (e.id == Event.KEY_PRESS && e.key == (’C’ & 0x1f)
|| e.id == Event.WINDOW_DESTROY && e.target == this)
dispose();
return super.handleEvent(e);
}
}
3-20
FileDialog
FileDialog
Beispiel F dient zum Experimentieren mit FileDialog-Objekten. Bei Click auf den Knopf
erscheint abwechselnd ein Load- oder Save-Fenster — allerdings nicht im Applet, denn
ein Applet darf nicht auf lokale Dateien zugreifen.
Ein FileDialog ist modal und dient zur Auswahl eines Katalogs und einer Datei, die bei
Load existieren muß. Ein FilenameFilter kann eingeschaltet werden, aber je nach
Window-System sind nicht alle Möglichkeiten des Standard-Dialogs ansprechbar.
Nach erfolgreichem Abschluß kann man Katalog und Datei als Strings abfragen;
allerdings sorgt der FileDialog insbesondere bei Save nicht für sinnvolle W erte. Bei
Aufgabe des Dialogs durch den Benutzer sind die Strings offenbar leer.
F enthält einen Button zum Aufruf der Dialoge und zwei TextField-Objekte zur
Darstellung des Resultats. Die Event-Verarbeitung im äußersten Frame ist erweitert,
um die Dialoge abzuwickeln:
3-21
import java.awt.*;
/* A class to test filedialogs */
class F extends MyFrame {
TextField dir, file;
int n;
F () {
add("North", new Button("Dialog..."));
add("Center", dir = new TextField(50));
add("South", file = new TextField(50));
}
public boolean handleEvent (Event e) {
boolean result = super.handleEvent(e); // show first
if (e.id == Event.ACTION_EVENT) {
FileDialog f = n == 0
? new FileDialog(this, "Load", FileDialog.LOAD)
: new FileDialog(this, "Save", FileDialog.SAVE);
n = 1 - n;
f.pack(); f.show();
// modal
dir.setText(f.getDirectory());
file.setText(f.getFile());
try {
f.dispose();
// Linux has a problem...
} catch (Exception ex) {
System.err.println(ex);
}
}
return result;
}
}
Der ACTION_EVENT stammt vom Button. n sorgt für alternative W ahl des Dialogs, der mit
pack() und show() dargestellt wird.
Ein TextField kann eine Textzeile darstellen und editieren lassen. Katalog und Datei
werden mit getDirectory() und getFile() abgeholt und mit setText() dargestellt.
Ein Dialog soll als Window mit dispose() sofort nach Gebrauch zur Aufgabe seiner
Ressourcen im Window-System veranlaßt werden. Bei Linux führt dies für den
FileDialog zu einer NullPointerException.
3-22
Label und Color
Label und Color
Beispiel G dient zum Experimentieren mit Label- und Color-Objekten.
Ein Label-Objekt stellt einen String auf einer Zeile ausgerichtet dar und kann — wie
jede Component — je nach Kooperation des Window-Systems auch farbig sein. Bei
Windows 95 geht es offenbar nicht, aber bei Motif durchaus:
G demonstriert die fünf Positionen im BorderLayout:
import java.awt.*;
/* A class to test labels */
class G extends Panel {
G () {
setLayout(new BorderLayout());
Label l;
add("North", l = new Label("North", Label.CENTER));
l.setBackground(Color.white);
add("West", l = new Label("West", Label.RIGHT));
l.setBackground(Color.yellow);
add("East", l = new Label("East", Label.LEFT));
l.setBackground(Color.magenta);
add("South", l = new Label("South", Label.CENTER));
l.setBackground(Color.orange);
add("Center", l = new Label("Center", Label.CENTER));
l.setBackground(Color.green);
}
}
_FOCUS-, KEY_
und MOUSE_-Events sind theoretisch bei jeder Component zu beobachten.
Eine Label ist passiv, kann aber — wenigstens bei der NeXT/Linux-Kombination —
doch einige Events beobachten:
$ java -Dtrace=- awt G
MyFrame: target java.awt.Label[0,44,137x22,align=center,label=South]
KEY_PRESS, clickCount 0, key ’g’, modifiers, when Wed Jan 14 17:01:07 MET
1970, x 66, y 87
MyFrame: target G[5,25,137x66,layout=java.awt.BorderLayout]
3-23
KEY_RELEASE, clickCount 0, key ’g’, modifiers, when Wed Jan 14 17:01:08 MET
1970, x 66, y 87
MyFrame: target java.awt.Label[0,44,137x22,align=center,label=South]
KEY_ACTION, clickCount 0, key F4, modifiers, when Wed Jan 14 17:01:16 MET
1970, x 66, y 87
MyFrame: target G[5,25,137x66,layout=java.awt.BorderLayout]
KEY_ACTION_RELEASE, clickCount 0, key F4, modifiers, when Wed Jan 14 17:01:16
MET 1970, x 66, y 87
Die Verteilung der target-Objekte erscheint unlogisch.
3-24
List
List
Beispiel H dient zum Experimentieren mit einem List-Objekt.
Eine List verwaltet Strings, stellt einen Ausschnitt dar und läßt eine einfache oder
mehrfache Auswahl zu. Für An- und Abwahl gibt es LIST_-Events, bei Doppel-Click
zusätzlich einen ACTION_EVENT.
import java.awt.*;
/* A class to test a list */
class H extends Panel {
H () {
setLayout(new BorderLayout());
List l;
add("Center", l = new List(3, true));
l.addItem("header");
l.addItem("top");
l.addItem("upper");
l.addItem("center");
l.addItem("lower");
l.addItem("bottom");
l.addItem("footer");
}
}
Dia Ausgabe zeigt, daß die Events nicht völlig logisch gefiltert werden:
$ java -Dtrace=- awt H
MyFrame: target java.awt.List[0,0,136x59,selected=top]
LIST_SELECT, clickCount 0, arg java.lang.Integer 1
MyFrame: target java.awt.List[0,0,136x59,selected=null]
LIST_DESELECT, clickCount 0, arg java.lang.Integer 1
MyFrame: target java.awt.List[0,0,136x59,selected=top]
ACTION_EVENT, clickCount 0, arg java.lang.String top
MyFrame: target java.awt.List[0,0,136x59,selected=null]
LIST_DESELECT, clickCount 0, arg java.lang.Integer 1
MyFrame: target java.awt.List[0,0,136x59,selected=top]
LIST_SELECT, clickCount 0, arg java.lang.Integer 1
MyFrame: target java.awt.List[0,0,136x59,selected=null]
ACTION_EVENT, clickCount 0, arg java.lang.String top
Hier wurde einfach, dann doppelt, dann einfach, und dann nochmals doppelt geclickt.
3-25
Dem ACTION_EVENT geht offenbar immer ein LIST_-Event voraus, der allerdings dann
nur den vorletzten Zustand spiegelt. Offenbar führt bei Windows 95 ein Doppel-Click
immer zur Darstellung der Anwahl; hier bei Motif wird stur ein Ein/Aus-T akter
implementiert, was bei Doppel-Click auf ein ausgeschaltetes Element merkwürdig
anmutet, denn es ist dann ausgeschaltet trotz ACTION_EVENT.
Die Events liefern nur den Index, auf den geclickt wurde. Mit getSelectedItems() kann
man alle insgesamt selektierten Strings bekommen — damit nicht synchronisiert
werden muß, erhält man sie als Vektor.
3-26
Image und MediaTracker
Image und MediaTracker
Beispiel I dient zum Experimentieren mit einem Image-Objekt zur Darstellung der GIFund JPEG-Bildformate. Das Image wird aus einer Datei geladen, deren Name gefolgt
von return im TextField eingetippt wird, wobei Schrägstriche offenbar recht tolerant
interpretiert werden.
Ein TextField stellt eine optional editierbare Textzeile dar und liefert einen
ACTION_EVENT, wenn return eingegeben wird.
Prinzipiell könnte man die Tastatur auf ein TextField mit requestFocus() fixieren; die
Erfahrung zeigt aber, daß dann offenbar netscape unter Windows 95 Events verliert.
I verwendet ein von Canvas abgeleitetes Icon-Objekt, das in handleEvent() verwaltet
wird. Es kann mit Hilfe des Window-Managers skaliert werden.
3-27
import java.awt.*;
/** A class to create icons through a textfield */
class I extends Panel {
protected Canvas icon;
I () {
setLayout(new BorderLayout());
TextField t;
add("North", t = new TextField(30));
add("Center", icon = new Canvas());
icon.resize(200,200);
}
public boolean handleEvent (Event e) {
if (e.id == Event.ACTION_EVENT) {
try {
remove(icon);
String file = ((TextField)e.target).getText();
((TextField)e.target).selectAll(); // feedback
add("Center", icon = new Icon(this, file));
validate();
} catch(Exception ex) {
System.err.println(ex);
}
}
return super.handleEvent(e);
}
}
Mit remove() kann eine Component aus einem Container entfernt werden.
liefert den aktuellen Text eines TextField-Objekts. selectAll() selektiert
den gesamten Text — das dient hier als sofort sichtbare Reaktion und erleichtert
zumeist die Eingabe eines neuen Dateinamens. Für eine realistische Applikation —
aber nicht für ein Applet — hätte man einen FileDialog verwenden sollen.
getText()
Icon
ist eine eigene Unterklasse von Canvas, die einen Dateinamen erhält, ein Image
lädt und in einem Canvas darstellt, dessen Größe allerdings explizit gesetzt werden
muß.
Icon
Ein Image ist eine portable und skalierbare Repräsentierung eines Bilds. Es kann mit
Hilfe der getImage()-Methoden der Toolkit- und Applet-Klassen aus einer lokalen
Datei oder vom Netz geladen und mit Hilfe von drawImage() dargestellt werden. Java
treibt ziemlichen Aufwand, um die Lade- und Darstellungsvorgänge asynchron
abzuwickeln.
Ein MediaTracker kann das asynchrone Laden von vielen Image-Objekten überwachen.
3-28
import java.applet.*;
import java.awt.*;
import java.io.*;
/** A class to display an image on a canvas */
class Icon extends Canvas {
protected Image image;
Icon (Container c, String file) throws Exception {
do {
if (c instanceof Applet) {
Applet a = (Applet)c;
image = a.getImage(a.getCodeBase(), file);
} else if (c instanceof Frame)
image = getToolkit().getImage(file);
else
continue;
MediaTracker track = new MediaTracker(this);
track.addImage(image, 1);
track.waitForID(1);
if (track.isErrorID(1))
break;
return;
} while ((c = c.getParent()) != null);
throw new FileNotFoundException(file);
}
public void paint (Graphics g) {
Dimension d = size();
g.drawImage(image, 0, 0, d.width, d.height, Color.white, this);
}
}
liefert das Toolkit-Objekt, das für das aktuelle Window zuständig ist. Die
Klasse Toolkit ist abstrakt und verknüpft die A WT-Klassen mit ihren konkreten
Realisierungen durch Peer-Klassen.
getToolkit()
startet das Laden eines Image-Objekts. Ein MediaTracker überwacht die
ihm angezeigten Ladevorgänge in Gruppen.
getImage()
Ein Image reagiert möglicherweise langsam auf Nachrichten. Deshalb wird jeweils ein
ImageObserver wie this bei drawImage() angegeben, der imageUpdate()
implementieren muß, denn diese Methode wird bei Abschluß der gewünschten
Operation aufgerufen. Die Technik ist mächtig, aber nicht überzeugend dokumentiert.
Ein Image kann (langsam) auf getWidth() und getHeight() antworten. Experimente
führten aber nicht zu befriedigenden Resultaten. Deshalb sollte das Icon explizit
zunächst mit resize() und später durch Änderungen am Window dimensioniert
werden.
liefert die aktuelle Größe einer Component als Dimension mit den Feldern width
und height.
size()
3-29
Menu und TextArea
Menu und TextArea
Beispiel J dient zum Experimentieren mit Menüs und einem TextArea-Objekt. In der
TextArea kann ein Menü symbolisch beschrieben und durch Knopfdruck dann im Frame
eingebaut werden. Der Scanner ist sehr primitiv — Namen und Operatoren müssen
durch Zwischenraum getrennt werden.
Eine TextArea verwaltet einen mehrzeiligen, optional editierbaren Text, dessen
Attribute allerdings nur insgesamt manipuliert werden können.
Ein Frame kann eine MenuBar mit Menu-Objekten aufnehmen, die ihrerseits MenuItemund CheckboxMenuItem-Objekte enthalten. Da ein Menu als MenuItem gilt, können die
Menüs kaskadiert werden. Sie sind außerdem abreißbar.
Windows 95 unterstützt allerdings weder Abreißen noch CheckboxMenuItem. Motif
erlaubt beides, aber dafür gibt es ernsthaft Ärger, wenn man eine existente MenuBar zu
ersetzen versucht.
3-30
MenuItem und CheckboxMenuItem
Beide Klassen verwalten Strings und reagieren mit ACTION_EVENT. Sie werden für
dieses Beispiel instrumentiert und Unterstriche werden in Zwischenraum verwandelt:
class MyMenuItem extends MenuItem {
protected Trace tracer;
MyMenuItem (Trace tracer, String label) {
super(label.replace(’_’,’ ’));
this.tracer = tracer;
}
public boolean postEvent (Event e) {
tracer = tracer.trace(this, e);
return super.postEvent(e);
}
}
class MyCheckboxMenuItem extends CheckboxMenuItem {
protected Trace tracer;
MyCheckboxMenuItem (Trace tracer, String label) {
super(label.replace(’_’,’ ’));
this.tracer = tracer;
}
public boolean postEvent (Event e) {
tracer = tracer.trace(this, e);
return super.postEvent(e);
}
}
Die Menüs bilden eine eigene Klassenhierarchie auf der Basis von MenuComponent an
Stelle der sonst üblichen Component.
Events werden hier durch postEvent() verarbeitet, zur Dekodierung wird deshalb bei
Erzeugung der instrumentierten Objekte schon ein Trace-Objekt eingetragen.
3-31
MyMenuBar
ist instrumentiert und enthält einen Konstruktor , der die symbolische
Beschreibung durch rekursiven Abstieg bezüglich eines StringTokenizer-Objekts
implementiert:
MyMenuBar
import java.awt.*;
import java.util.*;
class MyMenuBar extends MenuBar {
/** menubar: { [?] name :|= entries } */
protected Trace tracer;
MyMenuBar (Trace tracer, StringTokenizer st)
throws NoSuchElementException {
this.tracer = tracer;
while (st.hasMoreTokens()) {
String name = st.nextToken();
String del = st.nextToken();
if ("?".equals(name)) {
MyMenu m = new MyMenu(tracer, del, st.nextToken(), st);
add(m); setHelpMenu(m);
} else
add(new MyMenu(tracer, name, del, st));
}
}
public boolean postEvent (Event e) {
tracer = tracer.trace(this, e);
return super.postEvent(e);
}
}
MenuBar
benötigt selbst keine Argumente zur Konstruktion.
Mit add() werden Menu-Objekte in die MenuBar eingetragen. Ein eingetragenes Menu
kann mit setHelpMenu() markiert werden — symbolisch markiert das ? als Präfix.
3-32
MyMenu
MyMenu
ist instrumentiert und verarbeitet die symbolische Beschreibung im Konstruktor:
class MyMenu extends Menu {
/** entries: { - | [!] + name | [!] name [:|= entries] } . */
protected Trace tracer;
MyMenu (Trace tracer, String name, String del, StringTokenizer st)
throws NoSuchElementException {
super(name.replace(’_’,’ ’), ":".equals(del));
this.tracer = tracer;
name = st.nextToken();
while (! ".".equals(name))
if ("-".equals(name)) {
addSeparator();
name = st.nextToken();
} else {
MenuItem m;
boolean off = "!".equals(name);
if (off)
name = st.nextToken();
if ("+".equals(name)) {
m = new MyCheckboxMenuItem(tracer, st.nextToken());
name = st.nextToken();
} else {
del = st.nextToken();
if ("=".equals(del) || ":".equals(del)) {
m = new MyMenu(tracer, name, del, st);
name = st.nextToken();
} else {
m = new MyMenuItem(tracer, name);
name = del;
}
}
add(m);
if (off) m.disable();
}
}
public boolean postEvent (Event e) {
tracer = tracer.trace(this, e);
return super.postEvent(e);
}
}
benötigt den Namen als Konstruktor-Argument, der in der MenuBar erscheinen soll.
Außerdem kann angegeben werden, ob das Menu abreißbar sein soll — symbolisch
markieren den Unterschied = oder : zwischen Menu-Namen und Inhalt; der Inhalt wird
durch . abgeschlossen.
Menu
fügt einen MenuItem, addSeparator() einen Trenner in ein Menu ein — letzteres
symbolisch ein Minuszeichen.
add()
disable()
inaktiviert einen MenuItem — symbolisch dient dafür ! als Präfix.
Ein Menu kann CheckboxMenuItem — symbolisch + und ein Name — Menu und MenuItem
enthalten — symbolisch dadurch unterschieden, daß für ein Menu dem Namen ein
Trenner und ein Inhalt folgen müssen.
3-33
Hauptprogramm
Der Aufbau des Fensters ist etwas aufwendig:
/** A class to test menus */
class J extends MyFrame {
TextArea ta;
J () {
String s =
"File = New ! Open_... - List : All Some . .\n" +
"Tear : header + above ! center ! + bottom .\n" +
"? Help = Help_Contents Using_Help - About_MenuBar .";
StringTokenizer st = new StringTokenizer(s);
setMenuBar(new MyMenuBar(this, st));
Panel p;
add("North", p = new Panel());
p.setLayout(new GridLayout(2,1));
Label l;
p.add(l = new Label("menubar: { [?] name :|= entries }"));
l.setFont(new Font("Courier", Font.PLAIN, 12));
p.add(l = new Label("entries: { - | [!] [+] name [:|= entries] } ."));
l.setFont(new Font("Courier", Font.PLAIN, 12));
add("Center", ta = new TextArea(s));
ta.setFont(new Font("Courier", Font.PLAIN, 12));
add("South", new Button("Build new menu"));
}
public boolean handleEvent (Event e) {
if (e.target instanceof Button && e.id == Event.ACTION_EVENT) {
setMenuBar(new MyMenuBar(this,
new StringTokenizer(ta.getText())));
pack(); show();
}
return super.handleEvent(e);
}
}
Mit setMenuBar() wird die MenuBar beim Frame eingetragen.
Ein Panel mit GridLayout und zwei Label-Objekte dienen dazu, die Grammatik oben im
Fenster zu zeigen.
Im Hauptbereich des Fensters ist die TextArea, unten ein Button, dessen ACTION_EVENT
dazu führt, daß eine neue MenuBar berechnet und dargestellt wird, wobei man eine
NoSuchElementException eigentlich abfangen sollte.
Font
Man kann bei jeder Component Schriftart und -größe einstellen, wie Farben gibt sie das
an ihr Graphics-Objekt weiter.
Wie man allerdings wirklich zu einer Schriftart kommt, bleibt etwas undurchsichtig.
Angeblich sind vordefinierte Namen wie Helvetica, TimesRoman, Courier, Dialog und
DialogInput portabel abgebildet worden, andere Abbildungen soll man in den SystemProperties definieren können. Zur Schriftart gehören -größe in Point und Stil als
Konstante.
3-34
Scrollbar
Scrollbar
Beispiel K dient zum Experimentieren mit einem Scrollbar-Objekt. Das TextField
reflektiert den Stand der Scrollbar und umgekehrt.
Windows 95 läßt nicht zu, daß die definierten Maximum- und Minimum-W erte der
Scrollbar wirklich mit dem Schieber erreicht werden; außerdem erfolgt der
Schiebevorgang bei Loslassen nochmals.
Eine Scrollbar dient zur direkten Manipulation einer stetigen Integer -Größe. Sie kann
vertikal oder horizontal angeordnet werden und generiert je nach Plattform
verschiedene Events:
import java.awt.*;
/* A class to test a scrollbar connected to a textfield */
class K extends Panel {
TextField tf;
Scrollbar sb;
K () {
setLayout(new BorderLayout());
add("Center", tf = new TextField("50"));
add("South",
sb = new Scrollbar(Scrollbar.HORIZONTAL, 50, 10, 0, 100));
}
public boolean handleEvent (Event e) {
try {
switch (e.id) {
case Event.ACTION_EVENT:
sb.setValue(Integer.parseInt(tf.getText()));
break;
case Event.SCROLL_LINE_UP:
case Event.SCROLL_LINE_DOWN:
case Event.SCROLL_PAGE_UP:
case Event.SCROLL_PAGE_DOWN:
case Event.SCROLL_ABSOLUTE:
tf.setText(""+sb.getValue());
}
} catch(Exception ex) {
System.err.println(ex);
}
return super.handleEvent(e);
}
}
Die verschiedenen Werte — Maximum, Minimum, Balkenlänge, LINE- und PAGEInkrement — können über Nachrichten kontrolliert werden, nur die Ausrichtung wird
bei der Konstruktion fixiert. Ob es PAGE-Manipulation gibt, hängt von der Plattform ab.
3-35
Die Beispiele als Applets
Die Beispiele als Applets
Jedes dieser Beispiele kann in einem Java-fähigen Browser wie das folgende
Scrollbar-Beispiel mit einem LoaderApplet besichtigt und ausgeführt werden.
Ausgewählte Events erscheinen im folgenden TraceApplet:
Dies wird — ausschnittsweise — durch folgenden HTML-Code erreicht:
<applet code="LoaderApplet.class" width=120 height=60>
<param name=class value=K>
<param name=trace value="|trace">
</applet>
<applet code="TraceApplet.class" width=500 height=200 name=trace>
<param name=ACTION_EVENT value=1>
...
</applet>
Das LoaderApplet erfährt als Parameter class, welche Klasse instantiiert werden soll.
Der Parameter trace kann eine Datei, mit | ein TraceApplet oder mit - die StandardAusgabe als Ziel der Event-Decodierung festlegen.
Das TraceApplet muß mit name zum Parameter trace passend benannt werden. Ein
Parameter all schaltet alle Events ein, außerdem kann der Zustand eines Events
einzeln reversiert werden.
3-36
Implementierung — LoaderApplet
Im Konstruktor müssen die Parameter ausgewertet werden, die ein Applet mit
getParameter() beschaffen kann:
import java.applet.Applet;
import java.awt.*;
import java.io.*;
public class LoaderApplet extends Applet implements Trace {
protected PrintStream out = null;
// trace=- or trace=fnm
protected String applet = null;
// trace=|applet
protected Trace tracer = null;
// resolved
protected MyFrame frame = null;
// if class is MyFrame
public void init () {
String cnm = getParameter("class");
String trace = getParameter("trace");
try {
if (trace != null && trace.length() > 0)
if ("-".equals(trace))
// trace=- to stdout
out = System.out;
else if (trace.charAt(0) == ’|’) // trace=|name to applet
applet = trace.substring(1);
else
// trace=path to file
out = new PrintStream(new FileOutputStream(trace));
Component c = (Component)Class.forName(cnm).newInstance();
if (c instanceof MyFrame) {
frame = (MyFrame)c;
if (out != null)
frame.out = out;
else if (applet != null)
frame.tracer = this;
frame.setTitle(cnm); frame.pack();
} else {
setLayout(new BorderLayout());
add("Center", c);
}
} catch(Exception e) {
showStatus(cnm+": "+e); // covered by ’applet started’...
}
}
Anschließend wird wie in awt das Ziel der Trace notiert und die Component des
Beispiels instantiiert.
Applet
stammt von Panel ab und kann einen Frame aufbauen oder ein Panel enthalten.
Wenn das LoaderApplet einen Frame kontrolliert, muß er bei den verschiedenen
Zustandsänderungen im Applet berücksichtigt werden:
3-37
public void start () {
if (frame != null) {
frame.validate(); frame.show();
}
}
public void stop () {
if (frame != null)
frame.hide();
}
public void destroy () {
if (frame != null)
frame.dispose();
}
public boolean handleEvent (Event e) {
trace(this, e);
return super.handleEvent(e);
}
public Trace trace (Object o, Event e) {
if (out != null) {
awt.trace(out, o, e);
return this;
}
if (applet == null || applet.length() == 0)
return this;
if (tracer == null)
synchronized (getClass()) {
Applet a;
while ((a = getAppletContext().getApplet(applet)) == null) {
showStatus("waiting for "+applet);
try {
getClass().wait();
} catch(InterruptedException ex) {
showStatus(ex.toString());
}
}
if (a instanceof Trace)
tracer = (Trace)a;
else
applet = null;
}
return tracer == null ? this : tracer.trace(o, e);
}
}
Wenn für die Ausgabe kein PrintStream zur Verfügung steht und ein Applet verwendet
werden soll, das Trace implementiert, sucht das LoaderApplet mit Hilfe des
AppletContext nach einem Applet mit dem als Parameter angegebenen Namen und
blockiert, bis ein geeignetes Applet gefunden wird. Dieses wird dann aufgerufen und
sollte sich als Resultat für zukünftige Aufrufe von trace() liefern.
3-38
Implementierung — TraceApplet
Ein TraceApplet enthält eine PrintArea, die einen PrintStream kontinuierlich in einer
TextArea darstellt, und eine EventList, mit der Events ausgeblendet werden können.
Das TraceApplet erzeugt eine PrintArea und eine EventList und sorgt dafür, daß etwa
wartende LoaderApplet-Objekte nicht mehr blockiert sind und nach ihm suchen
können. Anschließend werden die Parameter ausgewertet und an die EventList
übermittelt.
import java.applet.*;
import java.awt.*;
import java.io.*;
/** An applet to selectively decode events */
public class TraceApplet extends Applet implements Trace {
protected PrintArea pa;
protected EventList el;
protected Thread t;
/** Applet interface */
public void init () {
setLayout(new BorderLayout());
try {
add("Center", pa = new PrintArea());
add("East", el = new EventList());
Class loaderApplet = Class.forName("LoaderApplet");
synchronized(loaderApplet) {
loaderApplet.notifyAll();
}
} catch(Exception e) {
showStatus(e.toString());
}
if (getParameter("all") != null)
el.all();
List list = el.eventList();
for (int n = list.countItems(); n-- > 0; )
if (getParameter(list.getItem(n)) != null)
if (list.isSelected(n))
list.deselect(n);
else {
list.select(n);
list.makeVisible(n);
}
}
Zum Schluß ist der erste Bereich der EventList sichtbar, in dem per Parameter
Selektionen erfolgten
3-39
Die PrintArea soll unabhängig von der EventList immer dann aktiv werden, wenn ein
neuer Event dargestellt werden kann. Für sie wird deshalb ein eigener Thread
aufgebaut, den das Applet parallel zu sich selbst in Betrieb hält.
Der Zugriff auf diesen Thread erfolgt exklusiv:
public synchronized void start () {
if (t == null) {
t = new Thread(pa);
t.start();
} else if (! t.isAlive())
t.resume();
notifyAll();
}
public synchronized void stop () {
if (t != null && t.isAlive())
t.suspend();
}
/** Trace interface */
public synchronized Trace trace (Object o, Event e) {
while (t == null || ! t.isAlive())
try {
wait();
} catch(InterruptedException ex) {
showStatus(ex.toString());
}
if (el.isSelected(e.id))
awt.trace(pa.out(), o, e);
return this;
}
}
.
Wenn die EventList es zuläßt, wird ein Event durch awt.trace() dekodiert und dem
PrintStream übermittelt, den die PrintArea beobachtet.
3-40
Implementierung — EventList
Eine EventList stellt alle Event-Namen in einer List zur Auswahl dar. Zwei Knöpfe, all
und none, selektieren alle Einträge oder keinen.
import java.awt.*;
import java.util.Hashtable;
/** a class to select events */
class EventList extends Panel {
protected Hashtable table;
// Event.id -> index
protected List list;
// GUI
protected Button all, none;
// quick access
EventList () {
setLayout(new BorderLayout());
Panel p;
add("South", p = new Panel());
p.setLayout(new GridLayout(1,2));
p.add(all = new Button("all"));
p.add(none = new Button("none"));
table = new Hashtable();
add("Center", list = new List(5, true));
int n = 0;
table.put(new Integer(Event.ACTION_EVENT), new Integer(n++));
list.addItem("ACTION_EVENT");
Da später Werte von Event.id abgefragt werden sollen, speichert man die ListPositionen zu diesen Werten in einem Hashtable als Paare von Integer-Objekten.
public boolean handleEvent (Event e) {
if (e.target == list)
return true;
if (e.id == Event.ACTION_EVENT) {
if (e.target == all)
all();
else
none();
return true;
}
return super.handleEvent(e);
}
public void all () {
for (int n = list.countItems(); n -- > 0; )
if (list.isSelected(n)) ; else // bug in linux port...
list.select(n);
}
public void none () {
for (int n = list.countItems(); n -- > 0; )
list.deselect(n);
}
public boolean isSelected (int eventId) {
Integer index = (Integer)table.get(new Integer(eventId));
return index == null ? false : list.isSelected(index.intValue());
}
public List eventList () {
return list;
}
}
handleEvent()
implementiert die Wirkung der beiden Knöpfe mit den gleichen
3-41
Methoden all() und none(), die auch im TraceApplet aufgerufen werden.
fragt die List über den Hashtable ab. eventList() vereinfacht die
Auswertung der Parameter im TraceApplet, macht aber eigentlich interne Information
sichtbar.
isSelected()
3-42
Implementierung — PrintArea
Eine PrintArea enthält eine TextArea, in der ein PrintStream kontinuierlich verfolgt
wird, sowie einen Knopf zum Löschen.
import java.awt.*;
import java.io.*;
/** A class to run as thread to display lines from a PrintStream */
public class PrintArea extends Panel implements Runnable {
protected TextArea ta;
// output area
protected PrintStream out;
// stream into this...
protected DataInputStream in;
// ...read from here
public PrintArea () throws IOException {
setLayout(new BorderLayout());
add("Center", ta = new TextArea());
add("South", new Button("clear"));
ta.setEditable(false);
ta.setFont(new Font("Courier", Font.PLAIN, 10));
PipedOutputStream po = new PipedOutputStream();
PipedInputStream pi = new PipedInputStream(po);
out = new PrintStream(po);
in = new DataInputStream(pi);
}
public PrintStream out () {
// return stream into this
return out;
}
public void run () {
// runs the display
String line;
try {
while ((line = in.readLine()) != null)
ta.appendText(line+"\n");
in.close(); out.close();
} catch(Exception e) {
ta.appendText("\ni/o error: "+e);
}
in = null; out = null;
}
public boolean action (Event e, Object what) {
ta.setText("");
return true;
}
}
Der Trick liegt darin, daß der PrintStream als eine Hälfte einer Pipeline realisiert ist, die
in einem eigenen Thread gelesen und in die TextArea transferiert wird.
Die PrintArea ist gut wiederverwendbar und kann durch das Pipe-Interface sehr leicht
getestet und mit anderen Oberflächen-Elementen verbunden werden.
Herunterladen