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.