vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 2

Tag 14

Fortgeschrittene Benutzeroberflächen mit dem AWT

Dies ist der letzte Tag, an dem Sie etwas über das Abstract Windowing Toolkit lernen. Ob Sie dies als gute oder schlechte Nachricht werten, hängt wahrscheinlich davon ab, wie vertraut der Umgang mit den Klassen des AWT für Sie geworden ist.

Wenn Sie glauben, daß dies eine gute Nachricht ist, dann werden Sie sich in bezug auf das AWT besser fühlen, wenn Sie heute einige seiner fortgeschritteneren Features kennengelernt haben.

Sie bauen auf allem, was Sie an den vorangegangenen Tagen über Komponenten, Layout-Manager und Ereignisse der Benutzerschnittstelle gelernt haben, auf und erhalten eine Einführung in einige neue Konzepte:

Fenster, Frames und Dialogfelder

Zusätzlich zu den Grafiken, Ereignissen, Komponenten der Benutzeroberfläche und Layoutmechanismen bietet AWT Möglichkeiten zur Erstellung von Elementen der Benutzeroberfläche außerhalb eines Applets oder Browsers: Fenster, Menüs und Dialogfelder. Damit können Sie ausgewachsene Anwendungen als Teil Ihres Applets oder als unabhängige Java-Applikationen erstellen.

Die AWT-Fensterklassen

Die Java-AWT-Klassen zur Erstellung von Fenstern und Dialogfeldern erben von einer einzigen Klasse: Window. Die Window-Klasse, die selbst wiederum von Container erbt (und daher eine standardmäßige AWT-Komponente ist), liefert das allgemeine Verhalten für alle fensterähnlichen Elemente. Im allgemeinen verwenden Sie keine Instanzen von Window, sondern setzen Instanzen von Frame oder Dialog ein.

Die Frame-Klasse bietet ein Fenster mit einer Titelleiste, einer Schließen-Schaltfläche und anderen, plattformabhängigen Fenstermerkmalen. In diesen Frames können Sie auch Menüleisten einfügen. Dialog hingegen ist eine eingeschränktere Form von Frame, die normalerweise nicht über einen Titel verfügt. FileDialog, eine Subklasse von Dialog, bietet ein Standard-Dateiauswahldialogfeld (das normalerweise wegen der Sicherheitsbeschränkungen nur innerhalb von Java-Applikationen genutzt werden kann).

Wenn Sie ein neues Fenster oder Dialogfeld in Ihr Applet oder Ihre Applikation aufnehmen wollen, erstellen Sie Subklassen zu den Klassen Frame und Dialog.

Frames

Frames sind Fenster, die von dem Applet oder Browser unabhängig sind, der das Applet enthält. Es handelt sich hierbei um eigenständige Fenster mit eigenen Titeln, Schaltflächen zum Verkleinern, Vergrößern und Schließen sowie Menüleisten. Sie können Frames für Ihre eigenen Applets erstellen, um Fenster zu erzeugen, oder Frames in Java-Applikationen nutzen, um den Inhalt dieser Applikation aufzunehmen.


Ein Frame ist ein plattformspezifisches Fenster mit einem Titel, einer Menüleiste, Schaltflächen zum Verkleinern, Vergrößern und Schließen sowie anderen Fensterfunktionen.

Um einen Frame zu erstellen, benutzen Sie einen der folgenden Konstruktoren:

Da Frame von Window abgeleitet ist, dieses wiederum von Container und jenes wiederum von Component, werden Frames grundsätzlich so wie andere AWT-Komponenten erstellt und eingesetzt. Rahmen sind Container wie Panels, so daß Sie andere Komponenten mit der add()-Methode einfügen können. Das Standardlayout für Frames ist BorderLayout. Hier ein Beispiel für die Erstellung eines Frames, das Festlegen des Layouts und Hinzufügen von zwei Schaltflächen:

win = new Frame("My Cool Window");
win.setLayout(new BorderLayout(10, 20));
win.add("North", new Button("Start"));
win.add("Center", new Button("Move"));

Um die Größe für einen neuen Frame festzulegen, verwenden Sie die resize()-Methode mit der Breite und Höhe des neuen Fensters. Diese Codezeile beispielsweise ändert die Fenstergröße auf 100 Pixel Breite und 200 Pixel Höhe:

win.resize(100, 200);

Beachten Sie jedoch folgendes: Da unterschiedliche Systeme eine unterschiedliche Vorstellung davon haben, was ein Pixel ist und diese Pixel in unterschiedlicher Auflösung darstellen, ist es schwierig, ein Fenster zu erstellen, das die »richtige« Größe bei jeder Plattform hat. Fenster, die auf einer Plattform in Ordnung sind, können auf einer anderen viel zu groß oder viel zu klein sein. Ein Ausweg aus diesem Problem ist, die Methode pack() anstatt resize() zu verwenden. Die pack()-Methode, die keine Argumente hat, erstellt ein Fenster in kleinstmöglicher Größe auf der Basis der aktuellen Größe der Komponenten im Fenster sowie des eingesetzten Layout-Managers und der eingesetzten Eckeinsätze. Im folgenden Beispiel werden zwei Schaltflächen erstellt und in ein Fenster eingefügt. Das Fenster wird dann auf die kleinstmögliche Größe reduziert, die diese Schaltflächen enthalten kann.

win = new Frame("My Other Cool Window");
win.setLayout(new FlowLayout()));
win.add("North", new Button("OK"));
win.add("Center", new Button("Cancel"));
win.pack();

Ein neu erstelltes Fenster ist zunächst unsichtbar. Sie müssen die show()-Methode anwenden, um dieses Fenster am Bildschirm anzeigen zu lassen (und Sie können es mit hide() wieder verbergen):

win.show();

Wenn Sie Pop-up-Fenster aus einem Applet heraus aktivieren, kann der Browser auf irgendeine Art deutlich machen, daß das Fenster kein reguläres Browser-Fenster ist - normalerweise erscheint dann eine Warnung im Fenster selbst. In Netcape sagt eine gelbe Leiste unten in jedem Fenster Untrusted Java Window. Diese Warnung soll den Benutzer informieren, daß das Fenster aus einem Applet kommt und nicht vom Browser selbst. (Denken Sie daran, daß die Frame-Klasse Fenster erzeugt, die genauso aussehen wie ein normales Fenster.) Dies soll davor bewahren, ein schlechtes Applet zu erstellen, das den Benutzer beispielsweise zur Eingabe des Paßworts auffordert. Sie können nichts gegen diese Warnung unternehmen - sie bleibt immer, wenn Sie Fenster mit Applets zusammen benutzen.

Die Listings 14.1 und 14.2 zeigen die Klassen, die ein einfaches Applet mit einem Pop-up-Fenster erzeugen. Sowohl das Applet als auch das Fenster sind in Abbildung 14.1 dargestellt. Das Applet verfügt über zwei Schaltflächen: eine zum Anzeigen und eine zum Verbergen des Fensters. Der Fensterframe selbst, der aus einer Subklasse erstellt wird, die ich BaseFrame1 genannt habe, enthält ein Label: This is a Window. Ich werde mich weiterhin auf dieses Fenster und dieses Applet beziehen, damit Sie besser verstehen, was passiert, und es später für Sie leichter wird.

Listing 14.1: Der gesamte Quelltext von PopUpWindow.java

 1: import java.awt.*;
 2:
 3: public class PopUpWindow extends java.applet.Applet {
 4:     Frame window;
 5:     Button open, close;
 6:
 7:     public void init() {
 8:         open = new Button("Open Window");
 9:         add(open);
10:         close = new Button("Close Window");
11:         add(close);
12:
13:         window = new BaseFrame1("A Pop Up Window");
14:         window.resize(150,150);
15:     }
16:
17:     public boolean action(Event evt, Object arg) {
18:         if (evt.target instanceof Button) {
19:             String label = (String)arg;
20:             if (label.equals("Open Window")) {
21:                 if (!window.isShowing())
22:                     window.show();
23:             } else {
24:                 if (window.isShowing())
25:                     window.hide();
26:             }
27:             return true;
28:         } else
29:             return false;
30:     }
31: }

Listing 14.2: Der gesamte Quelltext von BaseFrame1.java

 1: import java.awt.*;
 2:
 3: class BaseFrame1 extends Frame {
 4:     String message = "This is a Window";
 5:     Label l;
 6:
 7:     BaseFrame1(String title) {
 8:         super(title);
 9:         setLayout(new BorderLayout());
10:
11:         l = new Label(message, Label.CENTER);
12:         l.setFont(new Font("Helvetica", Font.PLAIN, 12));
13:         add("Center", l);
14:    }
15:
16:    public Insets getInsets() {
17:        return new Insets(20,0,25,0);
18:    }
19: }

Nachdem Sie beide Klassen kompiliert haben, kann das Applet mit dem folgenden HTML-Code getestet werden:

<applet code="PopUpWindow.class" height=200 width=200>
</applet>


Abbildung 14.1:
Fenster

Dieses Beispiel ist aus zwei Klassen gebildet: Die erste, PopupWindow, ist die Applet- Klasse, die das Pop-up-Fenster erzeugt und steuert. In der init()-Methode dieser Klasse, und hier insbesondere in den Zeilen 7 bis 15 im Listing 14.1, fügen Sie zwei Schaltflächen in das Applet ein, die das Fenster steuern. Dann wird das Fenster selbst erstellt, in seiner Größe verändert und angezeigt.

Die Steuerung in diesem Applet wird aktiv, wenn eine der Schaltflächen aktiviert wird. An dieser Stelle kommt die zweite Klasse ins Spiel. Die action()-Methode in den Zeilen 17-30 von Listing 14.1 verarbeitet die Klicks auf diese Schaltfläche, die Aktionsereignisse erzeugt. In dieser Methode wird mit der Schaltfläche Open Window einfach nur das Fenster angezeigt, wenn es verborgen ist (Zeilen 20 bis 22 in Listing 14.1), und es wird verborgen, wenn es angezeigt ist (Zeilen 23 bis 25).

Das Pop-up-Fenster selbst ist ein spezieller Frame mit Namen BaseFrame1. Bei diesem Beispiel ist der Frame recht einfach: er nutzt ein BorderLayout und zeigt ein Label in der Mitte des Frames. Beachten Sie, daß die Initialisierung des Frames in einem Konstruktor erfolgt, und nicht über eine init()-Methode. Da Frames normale Objekte und keine Applets sind, müssen Sie sie auf konventionelle Art und Weise initialisieren.

Im Konstruktor von BaseFrame1 sehen Sie, daß die erste Zeile (Zeile 8) einen Aufruf des Konstruktors der übergeordneten Klasse zu BaseFrame1 enthält. Wie Sie schon während Tag 6 gelernt haben, ist dieser Aufruf der erste Schritt bei der Initialisierung einer neuen Klasse. Vergessen Sie diesen Schritt nicht, wenn Sie Ihre eigenen Klassen erstellen - schließlich wissen Sie nie, welche wichtigen Dinge die Superklasse in dem Konstruktor macht.

Dialogfelder

Dialogfelder ähneln in ihrer Funktionsweise den Frames insoweit, daß ein neues Fenster am Bildschirm eingeblendet wird. Jedoch sind Dialogfelder als Übergangsfenster gedacht - z.B. Fenster, die Warnungen ausgeben, Sie nach bestimmten Informationen fragen etc. Dialoge haben für gewöhnlich keine Titelleisten und zeigen auch viele andere allgemeine Fenstermerkmale nicht (Sie können jedoch ein Dialogfeld mit einer Titelleiste erstellen). Dialogfelder können als nicht in ihrer Größe veränderbar oder auch modal angelegt werden. (Modale Dialogfelder verhindern die Eingabe in einem anderen, derzeit angezeigten Fenster, bis das Dialogfeld wieder geschlossen ist.)


Dialogfelder sind Übergangsfenster, die dazu dienen, den Benutzer über Ereignisse zu informieren oder Eingaben vom Benutzer anzufordern. Im Gegensatz zu Frames haben Dialogfelder im allgemeinen keine Titelleiste oder Schaltfläche zum Schließen des Felds.

Ein modales Dialogfeld verhindert die Eingabe in einem anderen, derzeit angezeigten Fenster, bis das Dialogfeld geschlossen ist. Ein modales Dialogfenster können Sie nicht auf Symbolgröße reduzieren, und es können parallel keine anderen Fenster in den Vordegrund geholt werden. Das modale Dialogfeld muß erst geschlossen sein, bevor Sie im System weiterarbeiten können. Typische Beispiele für modale Dialogfelder sind Warnungen und Alarme.

Das AWT bietet zwei Arten von Dialogfeldern: die Dialog-Klasse, die ein allgemeines Dialogfeld enthält, und FileDialog, womit ein plattformspezifischer Datei-Browser erzeugt wird.

Dialogobjekte

Dialoge werden fast genauso erstellt und eingesetzt wie Fenster. Um einen allgemeinen Dialog zu erstellen, benutzen Sie einen der folgenden Konstruktoren:

Ein Dialogfenster ist, wie auch ein Frame, ein Panel, in dem Sie Komponenten der Benutzeroberfläche anordnen und zeichnen sowie Grafikoperationen ausführen können. Wie auch andere Fenster, ist das Dialogfeld zunächst unsichtbar. Sie können jedoch mit show() und hide() angezeigt bzw. wieder verborgen werden.

Fügen wir jetzt in das Beispiel mit dem Pop-up-Fenster ein Dialogfeld ein. Von den zwei Klassen in diesem Applet muß nur BaseFrame1 geändert werden. Hier ändern Sie die Klasse dahingehend, daß sie eine Schaltfläche Set Text und eine neue Klasse, TextDialog, enthält, die ein Texteingabedialogfeld ähnlich dem in Abbildung 14.2 gezeigten enthält.


Abbildung 14.2:
Das Dialogfeld Enter Text


Im Codebeispiel auf der CD ist diese Version des Applets von der vorherigen Version getrennt. Ich habe eine neue Klasse namens BaseFrame2 für diesen Teil des Beispiels erstellt, sowie eine neue Klasse PopupWindowDialog.java, die das Applet darstellt, zu dem dieses Fenster gehört. Außerdem habe ich PopupActions2.java als die Klasse erstellt, die die Aktionen handhabt. Mit PopupWindowDialog.html kann diese Version des Applets angezeigt werden.

Um das Dialogfeld in die BaseFrame1-Klasse einzufügen, sind nur geringfügige Änderungen erforderlich. Als erstes ändern Sie den Namen der Klasse von BaseFrame1 in BaseFrame2. Dann brauchen Sie eine Instanzvariable, die den Dialog enthält, weil Sie sich in der gesamten Klasse darauf beziehen werden:

TextDialog dl;

In der Konstruktor-Methode von BaseFrame2 können Sie das Dialogfeld erzeugen (als Instanz der neuen Klasse TextDialog, die Sie in ein paar Minuten anlegen werden), es der Instanzvariablen dl zuweisen und in seiner Größe ändern (wie in den folgenden beiden Codezeilen gezeigt). Das Dialogfeld soll noch nicht angezeigt werden, weil es erst eingeblendet werden soll, wenn die Schaltfläche Set Text aktiviert wird.

dl = new TextDialog(this, "Enter Text", true);
dl.resize(150,100);

Als nächstes erstellen Sie die Schaltfläche Set Text in ähnlicher Form wie andere Schaltflächen, und dann fügen Sie sie in BorderLayout an der Position "South" ein (d.h. direkt unterhalb des Labels).

Button b = new Button("Set Text");
add("South", b);

Nachdem Sie ein TextDialog-Objekt und die Set-Text-Schaltfläche der Klasse BaseFrame2 hinzugefügt haben, müssen Sie noch die folgende Methode zur Ereignisbehandlung einfügen:

public boolean action(Event evt, Object arg) {
    if (evt.target instanceof Button) {
        dl.show();
        return true;
    } else
        return false;
    }

Dies zeigt das TextDialog-Objekt dl an, sobald irgendein Button-Objekt in dem Frame angeklickt wurde. In diesem Beispiel gibt es nur eine Schaltfläche bzw. Button- Objekt - Set Text.

window = new BaseFrame2("A Pop Up Window");

Das restliche Verhalten steckt in der TextDialog-Klasse, deren Code Sie in Listing 14.3 sehen.

Listing 14.3: Der gesamte Quelltext von TextDialog.java

 1: import java.awt.*;
 2:
 3: class TextDialog extends Dialog {
 4:     TextField tf;
 5:     BaseFrame2 theFrame;
 6:
 7:     TextDialog(Frame parent, String title, boolean modal) {
 8:         super(parent, title, modal);
 9:
10:         theFrame = (BaseFrame2)parent;
11:         setLayout(new BorderLayout(10,10));
12:         setBackground(Color.white);
13:         tf = new TextField(theFrame.message,20);
14:         add("Center", tf);
15:
16:         Button b = new Button("OK");
17:         add("South", b);
18:     }
19:
20:     public Insets insets() {
21:         return new Insets(30,10,10,10);
22:     }
23:
24:     public boolean action(Event evt, Object arg) {
25:         if (evt.target instanceof Button) {
26:             String label = (String)arg;
27:             if (label == "OK") {
28:                 hide();
29:                 theFrame.l.setText(tf.getText());
30:             }
31:             return true;
32:         } else
33:             return false;
34:     }
35: }

Bei diesem Code sind ein paar Punkte zu beachten. Zunächst einmal achten Sie darauf, daß im Gegensatz zu den anderen beiden Fenstern in diesem Applet die Ereignisbehandlung innerhalb der Klasse stattfindet, so daß das Dialogfeld als seine eigene Ereignisbehandlung dient. Manchmal ist es sinnvoll, den Code Ereignisbehandlung separat zu schreiben, doch manchmal ist es einfacher, alles zusammenzulassen. In diesem Fall ist das Element TextDialog einfach genug, so daß es einfacher ist, alles zusammenzustellen.

Trotzdem sind in diesem Dialogfeld viele Elemente mit denen der BaseFrame2-Klasse identisch. Beachten Sie, daß der Konstruktor für TextDialog mit dem Konstruktor der Superklasse Dialog identisch ist, denn trotz der Tatsache, daß TextDialog einem Objekt verbunden ist, dessen Klasse BaseFrame2 ist, müssen die Dialogfelder einem Frame- Objekt zugeordnet werden. Es ist leichter, den Konstruktor allgemeiner zu erstellen und ihn anschließend zu spezialisieren, nachdem der Konstruktor der Superklasse aufgerufen wurde - und genau das geschieht in den Zeilen 8 und 10 in Listing 14.3. Zeile 8 enthält die Verzweigung zum Konstruktor der Superklasse, um den Dialog mit dem Frame zu verbinden. In Zeile 10 werden die Instanzvariablen auf die jeweilige Instanz der Frame-Klasse gesetzt, die in der Klasse BaseFrame2 definiert wurden.

Der Rest des TextDialog-Konstruktors richtet einfach nur das übrige Layout ein: ein Textfeld und eine Schaltfläche in einem BorderLayout. Mit der getInsets()-Methode werden ein paar Eckeinsätze hinzugefügt, und schließlich behandelt die action()-Methode die Aktion der OK-Schaltfläche dieses Dialogfelds. In der action()-Methode passieren zwei Dinge: In Zeile 28 wird das Dialogfeld verborgen und freigegeben, und in Zeile 29 wird der Wert des Labels im Eltern-Frame auf den neuen Textwert geändert.

So viele Klassen für ein einfaches Applet! Die verschiedenen Fenster und die zugehörigen Ereignisklassen machen das Applet so kompliziert. An dieser Stelle sollten Sie aber bereits damit vertraut sein, daß jedes Teil eines Applets seine eigenen Komponenten und Aktionen hat, und wie alle diese Teile zusammenspielen. Sollte jedoch noch immer Verwirrung herrschen, können Sie den Beispielcode auf der CD durcharbeiten, um ein besseres Gefühl dafür zu bekommen, wie alles zusammengehört.

Applets und Dialogfelder zusammenfügen

Dialogfelder können nur einem Frame zugewiesen werden. Um ein Dialogfeld zu erstellen, müssen Sie eine Instanz der Frame-Klasse an eine der Konstruktor-Methoden des Dialogfelds übergeben. Das würde bedeuten, daß Sie keine Dialogfelder erstellen können, die mit Applets verbunden sind. Da Applets keine expliziten Frames haben, können Sie der Dialog-Klasse kein Frame-Argument übergeben. Aber mit ein wenig trickreichem Code können Sie eines Frame-Objekts habhaft werden, das das Applet enthält (häufig im Browser- oder Appleviewer-Fenster) und das Objekt dann als Frame für das Dialogfeld nutzen.

Bei diesem Code wird die getParent()-Methode eingesetzt, die für alle AWT-Komponenten definiert ist. Die getParent()-Methode gibt das Objekt zurück, in dem dieses Objekt enthalten ist. Das Eltern-Objekt aller AWT-Applikationen muß also ein Frame sein. Applets verhalten sich genauso. Durch wiederholten Aufruf von getParent() müßten Sie schließlich in der Lage sein, eine Frame-Instanz zu erhalten. Hier nun der trickreiche Code, den Sie in Ihr Applet einfügen können:

Object anchorpoint = getParent()
while (! (anchorpoint instanceof Frame))
anchorpoint = ((Component)anchorpoint).getParent();

In der ersten Zeile erstellen Sie eine lokale Variable namens anchorpoint, die den schließlich gefundenen Rahmen für das Applet aufnimmt. Das Objekt, das anchorpoint zugewiesen wird, kann eine von vielen Klassen sein. Sie deklarieren den Typ also als Object.

Die zweite Zeile in diesem Code ist eine while-Schleife, die für jedes neue Objekt in der Aufwärtskette getParent() aufruft, bis sie ein Frame-Objekt erreicht. Da die getParent() -Methode nur für Objekte definiert ist, die von Component erben, müssen Sie hier daran denken, daß Sie den Wert von anchorpoint jedesmal in Component stellen müssen, damit die getParent()-Methode funktionieren kann.

Nach dem Austritt aus der Schleife wird das Objekt in der anchorpoint-Variablen eine Instanz der Frame-Klasse (oder einer ihrer Subklassen). Anschließend können Sie ein Dialog-Objekt erstellen, das diesem Rahmen beigefügt wird, in dem Sie anchorpoint noch einmal ausgeben, um sicherzustellen, daß Sie wirklich ein Frame-Objekt haben:

TextDialog dl = new TextDialog((Frame)anchorpoint,
"Enter Text", true);

Dateidialog-Objekte

Die FileDialog-Klasse bietet ein Standard-Dialogfeld Datei öffnen/speichern, mit dem Sie auf das lokale Dateisystem zugreifen können. Diese FileDialog-Klasse ist systemunabhängig, jedoch wird je nach Plattform der normale Dialog für das Öffnen oder Speichern von Dateien angezeigt.


Bei Applets ist es vom Browser abhängig, ob Sie Instanzen von FileDialog einsetzen können. Die meisten Browser erzeugen lediglich einen Fehler, wenn Sie es versuchen. FileDialog ist bei Stand-alone-Anwendungen wesentlich nützlicher.

Um einen Dateidialog zu erstellen, benutzen Sie die folgenden Konstruktoren:

Nachdem Sie eine FileDialog-Instanz erstellt haben, zeigen Sie sie mit show() an:

FileDialog fd = new FileDialog(this, "FileDialog");
fd.show();

Wenn der Benutzer eine Datei im Datei-Dialogfeld auswählt und es dann schließt, können Sie auf den vom Benutzer gewählten Dateinamen zugreifen, indem Sie die Methoden getDirectory() und getFile() verwenden. Beide Methoden geben Zeichenketten zurück, die die Werte angeben, die der Leser gewählt hat. Sie können die Datei dann mit den Stream- und File-Handler-Methoden (über die Sie nächste Woche mehr erfahren werden) öffnen und sie auslesen bzw. in sie schreiben.

Fensterereignisse

Dies sind nun die letzten Ereignisse, die Sie in AWT bearbeiten können: die Ereignisse für Fenster und Dialogfelder. (In bezug auf Ereignisse sind Dialogfeld und Fenster gleich.) Fensterereignisse entstehen, wenn sich der Zustand eines Fensters in irgendeiner Form ändert: wenn das Fenster verschoben, seine Größe geändert, auf Symbolgröße reduziert, in den Vordergrund geholt oder geschlossen wird. In einer wohlerzogenen Applikation werden Sie zumindest einige dieser Ereignisse behandeln wollen - z.B. um Threads zu stoppen, wenn ein Fenster auf Symbolgröße reduziert wird, oder für Aufräumarbeiten, wenn das Fenster geschlossen wird.

Sie können handleEvent() verwenden, um auf einzelne Ereignisse, die in Tabelle 13.8 genannt sind, zu prüfen. Dazu verwenden Sie die normale switch-Anweisung mit der Instanzvariablen id.

Tabelle 14.1: Fensterereignisse

Ereignisname

Wann es auftritt

WINDOW_DESTROY

Wird erzeugt, wenn ein Fenster über das Feld Schließen oder den Menüpunkt Schließen zerstört wird

WINDOW_EXPOSE

Wird erzeugt, wenn ein Fenster hinter einem anderen Fenster hervorgeholt wird

WINDOW_ICONIFY

Wird erzeugt, wenn das Fenster auf Symbolgröße reduziert wird

WINDOW_DEICONIFY

Wird erzeugt, wenn das Fenster aus Symbolgröße wiederhergestellt wird

WINDOW_MOVED

Wird erzeugt, wenn das Fenster verschoben wird

Menüs

Es bleibt jetzt nur noch ein Element der Benutzeroberfläche im AWT zu behandeln: Menüs.

Eine Menüleiste ist eine Zusammenstellung von Menüs. Ein Menü enthält dazu eine Zusammenstellung von Menüpunkten, die Namen haben und manchmal optional über Tastenkürzel verfügen. Das AWT bietet Klassen für alle diese Menüelemente, darunter MenuBar, Menu, und MenuItem.

Menüs und Menüleisten


Eine Menüleiste ist ein Satz von Menüs, die an der Oberkante eines Fensters erscheinen. Da sich Menüleisten immer auf Fenster beziehen, können Sie keine Menüleisten in Applets erstellen (aber wenn das Applet ein unabhängiges Fenster öffnet, kann dieses Fenster eine Menüleiste besitzen).

Um eine Menüleiste für ein bestimmtes Fenster zu erzeugen, legen Sie eine neue Instanz der Klasse MenuBar an:

MenuBar mbar = new MenuBar();

Um diese Menüleiste als Standardmenü des Fensters festzulegen, verwenden Sie die setMenuBar()-Methode (definiert in der Frame-Klasse):

window.setMenuBar(mbar);

Sie können individuelle Menüs (Datei, Bearbeiten etc.) in die Menüleiste einfügen, indem Sie sie erzeugen und dann in die Menüleiste mit der Methode add() einfügen. Das Argument für den Menu-Konstruktor ist der Name des Menüs, wie er in der Menüleiste auftritt.

Menu myMenu = new Menu("File");
mbar.add(myMenu);

Bei einigen Systemen kann ein spezielles Hilfemenü definiert werden, das auf der rechten Seite der Menüleiste steht. Sie können ein bestimmtes Menü mit der setHelpMenu()-Methode als Hilfemenü definieren. Das angegebene Menü muß bereits dem Menü selbst hinzugefügt worden sein, bevor Sie es zu einem Hilfemenü machen können.

Menu helpmenu = new Menu("Help");
mbar.add(helpmenu);
mbar.setHelpMenu(helpmenu);

Falls Sie den Benutzer aus irgendeinem Grund daran hindern möchten, ein bestimmtes Menü auszuwählen, können Sie den Befehl disable() auf das Menü anwenden (bzw. den enable()-Befehl, um es wieder verfügbar zu machen):

myMenu.disable();

Menüoptionen

Sie können vier verschiedene Optionsarten in die Menüs einfügen:

Menüoptionen erstellen

Normale Menüoptionen werden in ein Menü mit der MenuItem-Klasse eingefügt. Zunächst erstellen Sie eine neue Instanz von MenuItem und fügen diese anschließend mit der add()-Methode in die Menu-Komponente ein:

Menu myMenu = new Menu("Tools");
myMenu.add(new MenuItem("Info"));
myMenu.add(new MenuItem("Colors"));

Untermenüs können einfach durch Erstellen einer neuen Instanz von Menu und deren Hinzufügen in das erste Menü erstellt werden. Danach können Sie Optionen in das Menü einfügen:

Menu submenu = new Menu("Sizes");
myMenu.add(submenu);
submenu.add(new MenuItem("Small"));
submenu.add(new MenuItem("Medium"));
submenu.add(new MenuItem("Large"));

Die CheckBoxMenuItem-Klasse erzeugt eine Menüoption mit einem Kontrollfeld, wodurch der Zustand des Menüs ein- bzw. ausgeschaltet werden kann. (Einmaliges Anklicken bewirkt die Auswahl des Kontrollfelds, nochmaliges Anklicken bewirkt die Abwahl des Kontrollfelds.) Menüoptionen mit Kontrollfeld werden auf die gleiche Art erzeugt, wie normale Menüoptionen:

CheckboxMenuItem coords =
new CheckboxMenuItem("Show Coordinates");
myMenu.add(coords);

Um schließlich einen Separator in das Menü einzufügen (eine Linie, mit der Sie Optionsgruppen in einem Menü trennen), erstellen Sie eine Menüoption mit einem einfachen Bindestrich (-) als Label und fügen diese dann ein. Die spezielle Menüoption wird als Trennlinie ausgegeben. Die beiden nächsten Zeilen Java-Code erzeugen eine Separator-Menüoption, und fügen Sie diese in das Menü myMenu ein:

MenuItem msep = new MenuItem("-");
myMenu.add(msep);

Jede Menüoption kann mit der disable()-Methode deaktiviert und mit enable() wieder aktiviert werden. Deaktivierte Menüoptionen können nicht ausgewählt werden.

MenuItem item = new MenuItem("Fill");
myMenu.addItem(item);
item.disable();

Menüereignisse

Die Auswahl einer Menüoption oder die Auswahl eines Tastenkürzels für eine Menüoption führt dazu, daß ein Aktionsereignis generiert wird. Sie können das Ereignis mit der action()-Methode behandeln, wie Sie es in den letzten Tagen gemacht haben.

Zusätzlich zu den Aktionsereignissen erzeugen CheckBoxMenuItems Listenauswahl- und Listenabwahlereignisse, die über handleEvent() behandelt werden.

Bei der Verarbeitung von Ereignissen, die von Menüoptionen und Menüoptionen mit Kontrollfeld erzeugt wurden, müssen Sie an folgendes denken: Da CheckboxMenuItem eine Subklasse von MenuItem ist, brauchen Sie diese Menüoption nicht als Sonderfall zu behandeln. Diese Aktion handhaben Sie genauso wie andere Aktionsmethoden.

AWT-Stand-alone-Applikationen erstellen

Obwohl Sie nächste Woche lernen, wie Sie grafische Benutzeroberflächen mit den neuen Swing-Klassen erstellen, besitzen Sie bereits die meisten Fähigkeiten, um eine Java-1.02-Applikation zu erstellen.

Der Grund ist, daß Sie außer in ein paar einfachen Codezeilen und in den verschiedenen Umgebungen keine großen Unterschiede zwischen einem Java-Applet und einer grafischen Java-Applikation finden werden. Alles das, was Sie bis jetzt über das AWT gelernt haben, einschließlich der Grafikmethoden, Animationstechniken, Ereignisse, Komponenten der Benutzeroberfläche sowie Fenster und Dialogfelder, kann genauso in Java-Applikationen wie in Applets eingesetzt werden. Und Applikationen haben den Vorteil, »nicht mehr im Sandkasten zu stecken« - sie unterliegen nicht den Sicherheitsbeschränkungen wie Applets. Mit Applets können Sie (fast) alles tun, was Sie wollen.

Wie erstellen wir also eine grafische Java-Applikation? Der Code dafür ist nahezu trivial. Ihre Hauptanwendungsklasse muß von Frame abgeleitet sein. Werden Threads genutzt (für die Animation oder andere Verarbeitungsarten), muß auch Runnable implementiert sein:

class MyAWTApplication extends Frame implements Runnable {
...
}

Innerhalb der main()-Methode Ihrer Applikation erstellen Sie eine neue Instanz Ihrer Klasse - weil Ihre Klasse Frame erweitert, und damit erhalten Sie ein neues AWT-Fenster, das Sie in seiner Größe ändern und wie ein beliebiges anderes AWT-Fenster anzeigen können.

In der Konstruktor-Methode Ihrer Klasse richten Sie die üblichen AWT-Funktionen für Fenster ein, wie Sie es normalerweise in der init()-Methode eines Applets täten: Legen Sie den Titel fest, fügen Sie den Layout-Manager ein, erstellen Sie Komponenten wie eine Menüleiste oder andere Elemente der Benutzeroberfläche und fügen sie ein, starten Sie ein Thread etc.

Hier ein ganz einfaches Applikationsbeispiel:

import java.awt.*;

class MyAWTApplication extends Frame {

    MyAWTApplication(String title) {
        super(title);
        setLayout(new FlowLayout());
        add(new Button("OK"));
        add(new Button("Reset"));
        add(new Button("Cancel"));
    }

    public static void main(String args[]) {
        MyAWTApplication app = new MyAWTApplication("Hi! I'm an application");
        app.resize(300,300);
        app.show();
    }
}

Meistens können Sie die diese Woche erlernten Methoden einsetzen, um Ihre Applikation zu steuern und zu verwalten. Nur die für Applets spezifischen Methoden können Sie nicht verwenden (d.h. solche, die in java.applet.Applet definiert sind, darunter Methoden zum Erhalt von URL-Informationen und zum Abspielen von Audio- Clips). Weitere Einzelheiten hierzu finden Sie in der API-Dokumentation zu dieser Klasse.

Und noch einen Unterschied zwischen Applikationen und Applets sollten Sie kennen: Wenn Sie ein Fensterschließereignis behandeln, müssen Sie neben dem Verbergen oder Zerstören des Fensters auch System.exit(0) aufrufen, um das System zu informieren, daß Ihre Applikation beendet wurde:

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

Komplettes Beispiel: RGB/HSB-Konverter

Wir haben bisher viel Theorie und kleine Beispiele durchgearbeitet und wenden uns nun einem größeren Beispiel zu, in dem die bisher gelernten Teile zusammengesetzt werden. Das folgende Beispiel-Applet zeigt die Erstellung des Layouts, das Verschachteln von Panels, Erstellen von Komponenten der Benutzeroberfläche und die Ereignisbehandlung sowie den Einsatz mehrerer Klassen, die in einem einzelnen Applet zusammengesetzt werden. Kurz gesagt: das ist das komplexeste Applet, das Sie bisher erstellt haben.

Abbildung 14.3 zeigt das Applet, das Sie in diesem Beispiel erstellen. Das ColorTest- Applet ermöglicht es Ihnen, Farben auf der Basis von RGB-Werten (Rot, Grün und Blau) und HSB-Werten (Farbton, Sättigung und Helligkeit) auszuwählen.


Abbildung 14.3:
Das ColorTest-Applet


Schnell eine kurze Zusammenfassung zur Farbtheorie, für den Fall, daß Sie nicht damit vertraut sind: RGB definiert eine Farbe nach ihren Rot-, Grün- und Blau-Werten. Kombinationen dieser Werte können fast jede beliebige Farbe des Spektrums erzeugen. (Rot, Grün und Blau sind sogenannte additive Farben; so werden z.B. verschiedene Farben auf Ihrem Bildschirm und Fernseher dargestellt.)

HSB steht für Farbton (Hue), Sättigung (Saturation) und Helligkeit (Brightness) und bildet eine andere Art der Farbangabe. Der Farbton ist die eigentliche Farbe im Spektrum (stellen Sie sich das als Wert auf einem Farbrad vor). Die Sättigung ist die Menge dieser Farbe: eine geringe Sättigung ergibt Pastellfarben, Farben mit hoher Sättigung sind lebendiger und »farbiger«. Helligkeit ist die Helligkeit bzw. Dunkelheit der Farbe. Keine Helligkeit ist Schwarz, volle Helligkeit ist Weiß.

Eine einzelne Farbe kann entweder durch ihre RGB-Werte oder durch ihre HSB-Werte dargestellt werden, und mathematische Algorithmen können eine Konvertierung zwischen beiden Werten vornehmen. Das ColorTest-Applet liefert einen grafischen Konverter zwischen diesen beiden.

Das ColorTest-Applet hat drei Hauptteile: eine Farbbox auf der linken Seite und zwei Gruppen mit Textfeldern auf der rechten Seite. Die erste Feldergruppe zeigt die RGB- Werte, die rechte die HSB-Werte an. Durch Ändern der Werte in einem der Textfelder wird die Farbbox aktualisiert, so daß jeweils die Farbe angezeigt wird, die in den Feldern gewählt wurde.

Dieses Applet nutzt zwei Klassen:

Arbeiten wir nun das Beispiel Schritt für Schritt durch, weil es kompliziert ist und leicht verwirren kann. Am Ende dieser Lektion steht der gesamte Code dieses Applets.

Entwerfen und Erstellen des Applet-Layouts

Die beste Art, mit einem Applet zu beginnen, das AWT-Komponenten beinhaltet, ist, sich zuerst um das Layout und dann um die Funktionalität zu kümmern. Beim Layout sollten Sie mit dem äußersten Panel beginnen und sich dann nach innen durcharbeiten.

Sie können sich die Arbeit vereinfachen, indem Sie alle Panels Ihres Benutzeroberflächendesigns zuerst auf Papier aufzeichnen. Das hilft Ihnen bei der Anordnung der Panels innerhalb des Applets oder Fensters und der besten Ausnutzung von Layout und Platz. Papierentwürfe sind selbst dann hilfreich, wenn Sie kein GridBagLayout verwenden, aber sicherlich nützlich, wenn Sie ein GridBagLayout nutzen. (Für dieses Applet nutzen Sie ein einfaches GridLayout.)

Abbildung 14.4 zeigt das ColorTest-Applet mit einem darübergelegten Raster, so daß Sie eine Vorstellung davon bekommen können, wie die Panels und die eingebetteten Panels funktionieren.


Abbildung 14.4:
Die Panels und Komponenten des ColorTest-Applets

Beginnen wir mit dem äußersten Panel - dem Applet selbst. Dieses Panel besteht aus drei Teilen: der Farbbox auf der linken Seite, den RGB-Textfeldern in der Mitte und den HSB-Feldern auf der rechten Seite.

Da das äußerste Panel das Applet selbst ist, ist die ColorTest-Klasse die Klasse des Applets. Sie wird von Applet abgeleitet. Außerdem importieren Sie hier die AWT- Klassen. (Beachten Sie, daß wir der Einfachheit halber das komplette Paket importieren, da wir hier soviel davon nutzen.)

import java.awt.*;

public class ColorTest extends java.applet.Applet {
  //...
}

Dies Applet hat drei Hauptelemente, die verfolgt werden müssen: die Farbbox und die beiden untergeordneten Panels. Jedes dieser beiden Sub-Panels bezieht sich auf etwas anderes, aber grundsätzlich sind sie gleich und verhalten sich gleich. Anstatt hier viel Code in dieser Klasse zu kopieren, können Sie die Gelegenheit nutzen und eine weitere Klasse für diese Sub-Panels erstellen, wobei Sie Instanzen dieser Klasse hier im Applet verwenden und alles miteinander über Methoden kommunizieren lassen. Schon bald definieren wir die neue Klasse namens ColorControls.

Jetzt müssen Sie jedoch erst wissen, wie Sie alle drei Teile des Applets handhaben, damit Sie sie bei Änderungen aktualisieren können. Erstellen wir also drei Instanzenvariablen: eine vom Typ Canvas für die Farbbox und die anderen beiden vom Typ ColorControls für die Kontroll-Panels:

ColorControls RGBcontrols, HSBcontrols;
Canvas swatch;

Jetzt können Sie mit der init()-Methode fortfahren, in der die gesamte Initialisierung und das Layout stattfinden. Sie arbeiten in drei Schritten:

1. Erstellen Sie das Layout für die großen Teile des Panels. Ein FlowLayout wäre zwar möglich, jedoch eignet sich ein GridLayout mit einer Zeile und drei Spalten besser.

2. Erstellen und initialisieren Sie die drei Komponenten dieses Applets: ein Zeichenbereich für die Farbbox und zwei Sub-Panels für die Textfelder.

3. Fügen Sie diese Komponenten in das Applet ein.

Der erste Schritt ist das Layout. Benutzen Sie ein GridLayout mit einem Abstand von 10 Punkten, um die Komponenten voneinander zu trennen:

setLayout(new GridLayout(1, 3, 5, 10));

Der zweite Schritt ist das Erstellen der Komponenten - zuerst den Zeichenbereich. Sie haben eine Instanzvariable, die diesen enthält. Jetzt erstellen Sie den Zeichenbereich und initialisieren den Hintergrund mit Schwarz:

swatch = new Canvas();
swatch.setBackground(Color.black);

Sie müssen hier auch zwei Instanzen des noch nicht existierenden ColorControls-Panels einfügen. Da Sie diese Klasse noch nicht angelegt haben, wissen Sie nicht, wie die Konstruktoren für diese Klasse aussehen werden. In diesem Fall fügen Sie hier einfach ein paar Platzhalter-Konstruktoren ein und füllen die Einzelheiten später aus.

RGBcontrols = new ColorControls(...)
HSBcontrols = new ColorControls(...);

Der dritte Schritt ist das Einfügen aller drei Komponenten in das Applet-Panel, und zwar so:

add(swatch);
add(RGBcontrols);
add(HSBcontrols);

Da Sie gerade am Layout arbeiten, fügen Sie als zusätzlichen Freiraum einen Eckeinsatz ein - 10 Punkt an allen Kanten:

public Insets getInsets() {
return new Insets(10, 10, 10, 10);
}

Soweit mitgekommen? Dann müßten Sie jetzt drei Instanzenvariablen, eine init()- Methode mit zwei unvollständigen Konstruktoren und eine getInsets()-Methode in Ihrer ColorTest-Klasse haben. Fahren wir nun fort mit der Erstellung des Layouts für die Subpanels in der ColorControls-Klasse, damit Sie die Konstruktoren angeben und das Layout abschließen können.

Definieren der untergeordneten Panels

Die ColorControls-Klasse enthält das Verhalten für das Layout und die Handhabung der Sub-Panels, die die RGB- und HSB-Farbwerte darstellen. ColorControls braucht keine Subklasse von Applet zu sein, da es sich nicht um ein Applet, sondern ein Panel handelt. Definieren Sie es so, daß es eine Subklasse von Panel ist:

import java.awt.*

class ColorControls extends Panel {
...
}

Die ColorControls-Klasse benötigt eine Reihe von Instanzvariablen, damit die Informationen vom Panel zurück zum Applet gelangen können. Die erste dieser Instanzvariablen ist eine Rückverzweigung zur Klasse des Applets, die dieses Panel enthält. Da die äußere Applet-Klasse die Aktualisierung der einzelnen Panels steuert, muß dieses Panel eine Möglichkeit haben, dem Applet mitzuteilen, daß sich etwas geändert hat. Und um eine Methode in dem Applet aufzurufen, müssen Sie einen Bezug zum Objekt herstellen. Also ist die erste Instanzenvariable eine Referenz zu einer Instanz der Klasse ColorTest:

ColorTest applet;

Stellen Sie sich vor, daß die Applet-Klasse alles aktualisiert, dann ist diese Klasse an den einzelnen Textfeldern in diesem Sub-Panel interessiert. Sie müssen also für jedes dieser Textfelder eine Instanzvariable erstellen:

TextField tfield1, tfield2, tfield3;

Jetzt können Sie sich an den Konstruktor für diese Klasse machen. Da diese Klasse kein Applet ist, verwenden Sie zur Initialisierung nicht init(), sondern eine Konstruktor-Methode. Innerhalb des Konstruktors machen Sie das, was Sie in init() machen würden: das Layout für das untergeordnete Panel sowie die Textfelder erstellen und sie in das Panel einfügen.

Das Ziel ist hier, die ColorControls-Klasse allgemein genug zu halten, so daß Sie sie sowohl für die RGB- als auch für die HSB-Felder nutzen können. Diese beiden Panels unterscheiden sich nur durch die Beschriftung für den Text. Das sind die drei Werte, die Sie haben müssen, bevor Sie das Objekt erzeugen können. Sie können diese drei Werte über die Konstruktoren in ColorTest übergeben. Aber Sie brauchen noch mehr: den Bezug zum übergeordneten Applet, den Sie ebenfalls über den Konstruktor erhalten können.

Damit haben Sie jetzt vier Argumente für den grundlegenden Konstruktor der ColorControls -Klasse. Die Signatur für den Konstruktor sieht so aus:

ColorControls(ColorTest parent,
String l1, String l2, String l3) {
}

Lassen Sie uns mit der Definition dieses Konstruktors beginnen, indem Sie zunächst der applet-Instanzvariablen den Wert von parent zuweisen:

applet = parent;

Als nächstes erstellen Sie das Layout für dieses Panel. Sie können hier auch wieder, wie beim Applet-Panel, ein GridLayout für diese Sub-Panels verwenden, aber diesmal hat das Raster drei Zeilen (je eine für jedes Textfeld mit Beschriftung) und zwei Spalten (eine für die Beschriftung und eine für die Felder). Und definieren Sie wieder einen Abstand von 10 Pixeln zwischen den Komponenten im Raster:

setLayout(new GridLayout(3,2,10,10));

Jetzt können Sie die Komponenten erstellen und in das Panel einfügen. Zuerst erstellen Sie die Textfeld-Objekte (initialisiert mit dem String "0") und weisen sie den entsprechenden Instanzenvariablen zu:

tfield1 = new TextField("0");
tfield2 = new TextField("0");
tfield3 = new TextField("0");

Dann fügen Sie diese Felder und die zugehörigen Label in das Panel ein, wobei Sie die verbleibenden Parameter im Konstruktor als Beschriftungstext in den Labels verwenden:

add(new Label(l1, Label.RIGHT));
add(tfield1);
add(new Label(l2, Label.RIGHT));
add(tfield2);
add(new Label(l3, Label.RIGHT));
add(tfield3);



Damit ist der Konstruktor für die Sub-Panel-Klasse ColorControls abgeschlossen. Sind Sie mit dem Layout fertig? Noch nicht ganz. Fügen Sie noch einen Eckeinsatz im Sub-Panel ein - nur an der oberen und unteren Kante - das sieht besser aus. Die Eckeinsätze fügen Sie genauso ein, wie Sie es schon bei der ColorTest-Klasse getan haben, nämlich mit der getInsets()-Methode:

public Insets getInsets() {
return new Insets(10, 10, 0, 0);
}

Sie haben es fast geschafft. 98% der Grundstruktur sind fertig, und es ist nur noch ein Schritt auszuführen. Dazu gehen Sie zurück zu ColorTest und ergänzen die Platzhalter-Konstruktoren für das Sub-Panel, damit sie mit dem tatsächlichen Konstruktor für ColorControls übereinstimmen.

Der Konstruktor für ColorControls, den Sie gerade erstellt haben, hat vier Argumente: das ColorTest-Objekt und drei Labels (Zeichenketten). Erinnern Sie sich noch, wie Sie die init()-Methode für ColorTest erstellt haben? Sie haben zwei Platzhalter für das Erstellen neuer ColorControls-Objekte eingefügt. Diese Platzhalter ersetzen Sie jetzt mit der richtigen Version. Und vergessen Sie dabei nicht, die vier Argumente einzufügen, die der Konstruktor braucht: das ColorTest-Objekt und die drei Strings. Das ColorTest-Objekt können Sie an diese Konstruktor-Aufrufe mit dem Schlüsselwort this übergeben:

RGBcontrols = new ColorControls(this, "Red", "Green", "Blue");
HSBcontrols = new ColorControls(this, "Hue", "Saturation", "Brightness");


Bei allen Initialisierungswerten in diesem Beispiel habe ich die Zahl 0 gewählt (eigentlich den String "0"). Für die Farbe Schwarz ist sowohl der RGB- als auch der HSB- Wert 0, und daher habe ich diese Vorgabe gewählt. Wenn Sie das Applet mit einer anderen Farbe initialisieren wollen, können Sie die ColorControls-Klasse so umschreiben, daß sie auch Initialisierungswerte nutzt, um die Label zu initialisieren. Dies hier war nur ein kürzeres Beispiel.

Ereignisbehandlung

Mit dem fertigen Layout können Sie die Ereignisbehandlung und die Aktualisierungsvorgänge zwischen den verschiedenen Komponenten festlegen, so daß das Applet reagieren kann, wenn der Benutzer mit dem Applet interagiert.

Die Aktionsereignisse des Applets treten auf, wenn der Benutzer in einem der Textfelder einen Wert ändert. Durch das Aktionsereignis eines Textfelds wird die Farbe verändert, das Farbfeld mit der neuen Farbe entsprechend aktualisiert und in den Feldern des anderen Sub-Panels werden die Werte angezeigt, die die neue Farbe repräsentieren.

Die übergeordnete ColorTest-Klasse ist eigentlich für die Aktualisierung zuständig, da Sie alle Sub-Panels überwacht. Sie sollten allerdings Ereignisse in dem Subpanel verfolgen und annehmen, in denen diese auftreten. Da sich das eigentliche Aktionsereignis des Applets auf ein Textfeld bezieht, können Sie die Methode action() verwenden, um das Ereignis in der ColorControls-Klasse abzufangen:

public boolean action(Event evt, Object arg) {
    if (evt.target instanceof TextField) {
        applet.update(this);
        return true;
    }
    else return false;
}

In der action()-Methode stellen Sie sicher, daß das Aktionsereignis tatsächlich von einem Textfeld ausgelöst wurde (da lediglich Textfelder zur Verfügung stehen, können Aktionsereignisse auch nur von diesen stammen; es ist aber generell sinnvoll, einen derartigen Test durchzuführen). Wenn dem so ist, rufen Sie die Methode update() auf, die in der Klasse ColorTest definiert ist, um das Applet mit den neuen Werten zu aktualisieren. Da das äußere Applet für die gesamte Aktualisierung zuständig ist, benötigen Sie diese Verbindung zurück zum Applet - auf diese Weise können Sie die richtige Methode zur richtigen Zeit aufrufen.

Aktualisieren des Ergebnisses

Jetzt kommt der schwierige Teil: die eigentliche Aktualisierung auf der Basis der neuen Werte, gleichgültig welches Textfeld geändert wurde. Bei diesem Schritt definieren Sie die update()-Methode in der ColorTest-Klasse. Diese update()-Methode nimmt ein einzelnes Argument an: die ColorControls-Instanz, die den geänderten Wert enthält. (Sie erhalten das Argument aus den Ereignismethoden im ColorControls-Objekt.)


Fürchten Sie nicht, daß diese update()-Methode die update()-Methode des Systems stört? Sie tut es nicht, denn wie Sie sich erinnern, können Methoden den gleichen Namen, jedoch unterschiedliche Signaturen und Definitionen haben. Da diese update()- Methode nur ein Argument vom Typ ColorControls hat, beeinflußt sie nicht die übrigen update()-Versionen. Normalerweise sollten alle Methoden namens update() im Grunde dieselbe Aufgabe haben. Das ist hier nicht der Fall, aber das ist nur ein Beispiel.

Die update()-Methode ist für das Aktualisieren aller Panels im Applet zuständig. Um zu wissen, welches Panel zu aktualisieren ist, müssen Sie wissen, welches Panel geändert wurde. Das finden Sie heraus, indem Sie testen, ob das übergebene Argument mit den untergeordneten Panels, die Sie in den Instanzvariablen RGBcontrols und HSBcontrols gespeichert haben, identisch ist:

void update(ColorControls controlPanel) {

if (controlPanel == RGBcontrols) {  // RGB geändert, HSB aktualisieren
...
} else {  // HSB geändert, RGB aktualisieren
...
}
}

Dieser Test ist der Kern der update()-Methode. Beginnen wir mit dem ersten Fall - einer Zahl, die in den RGB-Textfeldern geändert wurde. Anhand dieser neuen RGB- Werte müssen Sie ein neues Color-Objekt erstellen und die Werte im HSB-Panel aktualisieren. Damit Sie nicht so viel tippen müssen, können Sie ein paar lokale Variablen deklarieren, in die Sie einige Grundwerte stellen. Die Werte der Textfelder sind Zeichenketten, deren Werte Sie dazu heranziehen können, die getText()-Methode zu nutzen, die in den TextField-Objekten des ColorControls-Objekts definiert wurden. Da Sie bei dieser Methode meistens mit Ganzzahlwerten arbeiten, können Sie diese Zeichenketten abfragen, sie in Ganzzahlen konvertieren und in lokalen Variablen speichern (value1, value2, value3). Mit dem folgenden Code erledigen Sie diese Aufgabe (das sieht komplizierter aus als es ist):

int value1 = Integer.parseInt(controlPanel.tfield1.getText());
int value2 = Integer.parseInt(controlPanel.tfield2.getText());
int value3 = Integer.parseInt(controlPanel.tfield3.getText());

Wenn Sie die lokalen Variablen definieren, brauchen Sie auch eine für das neue Color -Objekt:

Color c;

Nehmen wir an, eines der Textfelder auf der RGB-Seite des Applets hat sich geändert, und Sie fügen den Code in den if-Teil der update()-Methode ein. Sie müssen ein neues Color-Objekt erstellen und die HSB-Seite des Panels aktualisieren. Dieser erste Teil ist einfach. Bei drei RGB-Werten können Sie ein neues Color-Objekt erstellen, wobei Sie diese Werte als Argumente für den Konstruktor nutzen:

c = new Color(value1, value2, value3);

Dieser Teil des Beispiels ist nicht sehr robust. Er basiert auf der Annahme, daß der Benutzer nichts anderes als ganze Zahlen zwischen 0 und 255 in die Textfelder eingibt. Eine bessere Version wäre ein Test, mit dem sichergestellt wird, daß keine Dateneingabefehler passieren. Aber ich wollte das Beispiel kurz halten.

Jetzt konvertieren Sie die RGB-Werte nach HSB. Standardalgorithmen können eine auf RGB basierende Farbe in eine HSB-Farbe konvertieren, aber Sie brauchen sie nicht nachzusehen. Die Color-Klasse verfügt über eine Klassenmethode namens RGBtoHSB() , die Sie benutzen können. Diese Methode erledigt die Arbeit für Sie - oder zumindest die meiste Arbeit. Die RGBtoHSB()-Methode wirft jedoch zwei Probleme auf:

Aber keines dieser Probleme ist unüberwindbar. Sie müssen lediglich einige Zeilen Code hinzufügen. Beginnen wir mit dem Aufruf von RGBtoHSB() mit den neuen RGB- Werten. Die Methode gibt ein Array mit floats zurück, und daher müssen Sie eine lokale Variable (HSB) erstellen, die die Ergebnisse der RBGtoHSB()-Methode speichert. (Denken Sie daran, daß Sie auch ein leeres float-Array als viertes Argument für RGBtoHSB() erstellen und übergeben müssen.)

float[] HSB = Color.RGBtoHSB(value1, value2, value3, (new float[3]));

Jetzt konvertieren Sie diese Fließkommawerte, die zwischen 0.0 und 1.0 liegen, in Werte zwischen 0 und 100 (für Sättigung und Helligkeit) bzw. 0 und 360 für den Farbton, indem Sie die entsprechenden Zahlen multiplizieren und die Werte dem array wieder zuweisen:

HSB[0] *= 360;
HSB[1] *= 100;
HSB[2] *= 100;

Jetzt haben Sie alle gewünschten Zahlen. Der letzte Teil der Aktualisierung ist, diese Werte wieder in die Textfelder einzutragen. Natürlich sind diese Werte noch immer Fließkommazahlen, und Sie müssen sie in int-Werte casten, bevor sie in Zeichenketten umgewandelt und gespeichert werden:

HSBcontrols.tfield1.setText(String.valueOf((int)HSB[0]));
HSBcontrols.tfield2.setText(String.valueOf((int)HSB[1]));
HSBcontrols.tfield3.setText(String.valueOf((int)HSB[2]));

Die Hälfte haben Sie geschafft. Der nächste Teil des Applets ist derjenige, der die RGB-Werte aktualisiert, wenn sich ein Textfeld auf der HSB-Seite geändert hat. Das geschieht im else-Teil des großen if...else-Abschnitts, wo diese Methode definiert und festgelegt wird, was aktualisiert wird.

Es ist eigentlich einfacher, RGB-Werte aus HSB-Werten zu erzeugen, als andersherum. Eine Klassenmethode in der Color-Klasse, die getHSBColor()-Methode, erzeugt ein neues Color-Objekt aus den drei HSB-Werten. Wenn Sie ein Color-Objekt haben, können Sie die RGB-Werte daraus leicht herausziehen. Der Trick ist natürlich, daß getHSBColor drei Fließkommaargumente annimmt, wohingegen die Werte, die Sie haben, Ganzzahlwerte sind, mit denen ich lieber arbeite. Bei diesem getHSBColor()- Aufruf müssen Sie jetzt die Ganzzahlwerte aus den Textfeldern in floats-Werte casten und sie durch den entsprechenden Konvertierungsfaktor dividieren. Das Ergebnis von getHSBColor ist ein Color-Objekt, und deshalb können Sie das Objekt einfach der lokalen Variablen c zuweisen, damit Sie es später weiterverwenden können:

c = Color.getHSBColor((float)value1 / 360,
(float)value2 / 100, (float)value3 / 100);

Sind alle Elemente des Color-Objekts gesetzt, müssen die RGB-Werte für das Aktualisieren aus dem Color-Objekt extrahiert werden. Diese Arbeit erledigen für Sie die Methoden getRed(), getGreen() und getBlue(), die in der Color-Klasse definiert sind:

RGBcontrols.tfield1.setText(String.valueOf(c.getRed()));
RGBcontrols.tfield2.setText(String.valueOf(c.getGreen()));
RGBcontrols.tfield3.setText(String.valueOf(c.getBlue()));

Schließlich müssen Sie noch, unabhängig davon, ob sich der RGB- oder HSB-Wert geändert hat, die Farbbox auf der linken Seite aktualisieren, damit sie die neue Farbe darstellt. Da das neue Color-Objekt in der Variablen c gespeichert ist, können Sie die setBackground-Methode zur Änderung der Farbe nutzen. Denken Sie aber daran, daß setBackground den Bildschirm nicht automatisch neu zeichnet, so daß Sie auch ein repaint() aufrufen müssen:

swatch.setBackground(c);
swatch.repaint();

Das war`s! Jetzt kompilieren Sie die beiden Klassen ColorTest und ColorControls, erstellen eine HTML-Datei, um das ColorTest-Applet zu laden, und probieren es aus.

Der komplette Quellcode

Listing 14.4 enthält den vollständigen Quellcode für die Applet-Klasse ColorTest, und Listing 14.5 zeigt den Quellcode für die Hilfsklasse ColorControls. Meist ist es anhand eines kompletten Quelltextes einfacher, sich vorzustellen, was in einem Applet abläuft - wenn alles zusammensteht und Sie die Methodenaufrufe nachvollziehen und sehen können, welche Werte übergeben werden. Beginnen wir mit der init()-Methode im ColorTest-Applet.

Listing 14.4: Der gesamte Quelltext von ColorTest.java

 1: import java.awt.*;
 2:
 3: public class ColorTest extends java.applet.Applet {
 4:     ColorControls RGBcontrols, HSBcontrols;
 5:     Canvas swatch;
 6:
 7:     public void init() {
 8:         setLayout(new GridLayout(1, 3, 5, 15));
 9:         swatch = new Canvas();
10:         swatch.setBackground(Color.black);
11:         RGBcontrols = new ColorControls(this, "Red",
12:             "Green", "Blue");
13:         HSBcontrols = new ColorControls(this, "Hue",
14:             "Saturation", "Brightness");
15:         add(swatch);
16:         add(RGBcontrols);
17:         add(HSBcontrols);
18:     }
19:
20:     public Insets getInsets() {
21:         return new Insets(10, 10, 10, 10);
22:     }
23:
24:     void update(ColorControls controlPanel) {
25:         int value1 = Integer.parseInt(controlPanel.tfield1.getText());
26:         int value2 = Integer.parseInt(controlPanel.tfield2.getText());
27:         int value3 = Integer.parseInt(controlPanel.tfield3.getText());
28:         Color c;
29:         if (controlPanel == RGBcontrols) {  // RGB geändert, HSB aktualisieren
30:             c = new Color(value1, value2, value3);
31:             float[] HSB = Color.RGBtoHSB(value1, value2,
32:                 value3, (new float[3]));
33:             HSB[0] *= 360;
34:             HSB[1] *= 100;
35:             HSB[2] *= 100;
36:             HSBcontrols.tfield1.setText(String.valueOf((int)HSB[0]));
37:             HSBcontrols.tfield2.setText(String.valueOf((int)HSB[1]));
38:             HSBcontrols.tfield3.setText(String.valueOf((int)HSB[2]));
39:         } else {  // HSB geändert, RGB aktualisieren
40:             c = Color.getHSBColor((float)value1 / 360,
41:                 (float)value2 / 100, (float)value3 / 100);
42:             RGBcontrols.tfield1.setText(String.valueOf(c.getRed()));
43:             RGBcontrols.tfield2.setText(String.valueOf(c.getGreen()));
44:             RGBcontrols.tfield3.setText(String.valueOf(c.getBlue()));
45:         }
46:         swatch.setBackground(c);
47:         swatch.repaint();
48:     }
49: }

Listing 14.5: Der gesamte Quelltext von ColorControls.java

 1: import java.awt.*;
 2:
 3: class ColorControls extends Panel {
 4:     ColorTest applet;
 5:     TextField tfield1, tfield2, tfield3;
 6:
 7:     ColorControls(ColorTest parent,
 8:         String l1, String l2, String l3) {
 9:
10:         applet = parent;
11:         setLayout(new GridLayout(3,2,10,10));
12:         tfield1 = new TextField("0");
13:         tfield2 = new TextField("0");
14:         tfield3 = new TextField("0");
15:         add(new Label(l1, Label.RIGHT));
16:         add(tfield1);
17:         add(new Label(l2, Label.RIGHT));
18:         add(tfield2);
19:         add(new Label(l3, Label.RIGHT));
20:         add(tfield3);
21:
22:     }
23:
24:     public Insets getInsets() {
25:         return new Insets(10, 10, 0, 0);
26:     }
27:
28:     public boolean action(Event evt, Object arg) {
29:         if (evt.target instanceof TextField) {
30:             applet.update(this);
31:             return true;
32:         }
33:         else return false;
34:     }
35: }

Nachdem Sie diese beiden Klassen kompiliert haben, kann das ColorTest-Applet auf einer Webseite mit dem folgenden HTML-Code geladen werden:

<applet code="ColorTest.class" width=475 height=100>
</applet>

Zusammenfassung

Vier Tage lang sich auf ganz bestimmte Elemente der Sprache Java zu konzentrieren ist eine lange Zeit. Das Abstract Windowing Toolkit ist allerdings ein wesentlicher Bestandteil des Werkzeugkastens eines Java-Programmierers.

Sie sind nun in der Lage, grafische Benutzeroberflächen für ein Applet zu entwerfen oder können sogar eine Applikation erstellen, die die Techniken des AWT und von Java 1.02 verwendet. In der letzten Woche des Buches werden Sie lernen, wie Sie einige dieser Aufgaben mit den Swing-Klassen von Java 1.2 bewältigen.

Egal, ob mit Tränen in den Augen oder freudestrahlend Abschied nehmen, werden Sie morgen vom AWT ablassen und beginnend mit dem morgigen Tag sich neuen Themen zuwenden.

Gute Arbeit! - Dieses Lob haben Sie sich redlich verdient.

Fragen und Anworten

Frage:
Bei der Diskussion der Stand-alone-Applikationen habe ich den Eindruck gewonnen, daß absolut kein Unterschied zwischen einem Applet und einer Applikation besteht. Wieso?

Antwort:
Sowohl Applets als auch Applikationen verwenden genau dieselben Prozeduren im AWT, um Komponenten aufzubauen und anzuzeigen sowie Ereignisse zu behandeln. Die Unterschiede liegen nur darin, daß Applikationen von main() initialisiert und in ihrem eigenen Fenster angezeigt werden, und daß Applets von init() und start() initialisiert bzw. gestartet werden. Bei der großen Ähnlichkeit zwischen Applets und Applikationen, können Sie 99% dessen, was Sie hinsichtlich Applets gelernt haben, bei Applikationen verwenden. Und weil Applets die main()-Methode ignorieren, sollte sie in einer Klasse existieren, gibt es keinen Grund, daß Sie nicht ein einzelnes Programm erstellen, das genausogut als Applet und als Applikation läuft.

Frage:
Ich habe eine Stand-alone-Applikation erstellt. Aber wenn ich auf das Schließen- Schaltfeld klicke, passiert nichts. Was muß ich machen, damit meine Anwendung wirklich beendet wird?

Antwort:
Behandeln Sie im 1.02-Ereignismodell das Ereignis WINDOW_CLOSE. Als Reaktion auf dieses Ereignis rufen Sie hide() auf, wenn das Fenster später wieder auftreten kann, oder rufen Sie destroy() auf, um es ein für alle Mal loszuwerden. Führt das Fensterschließereignis zum kompletten Beenden Ihrer Applikation, rufen Sie auch System.exit() auf.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Java 2 in 21 Tagen, ISBN: 3-8272-5578-3