Previous Page TOC Index Next Page See Page

12

Erstellen von Benutzeroberflächen
mit dem AWT

In den letzten vier Tagen haben Sie sich auf die Erstellung von Applets konzentriert, die sehr einfache Dinge bewirken: Text anzeigen, eine Animation oder einen Sound abspielen oder eine Interaktion mit dem Benutzer. An diesem Punkt angelangt, möchten Sie sicherlich etwas anspruchsvollere Applets entwickeln, die sich wie richtige Anwendungen verhalten und in eine Webseite eingebettet sind – Applets, die langsam aber sicher wie Anwendungen mit grafischen Benutzeroberflächen aussehen und Schaltflächen, Menüs, Textfelder und andere Elemente aufweisen.

Für die Realisierung genau dieser Aspekte in Java-Applets und -Anwendungen wurde das Abstract Windowing Toolkit von Java – kurz AWT – entwickelt. Sie haben die ganze Zeit über bereits das AWT benutzt, wie Sie vielleicht schon aufgrund der Klassen, die Sie importiert haben, vermutet haben. Die Applet-Klasse und die meisten Klassen, mit denen Sie diese Woche gearbeitet haben, sind alle Bestandteile von AWT. Darüber hinaus können Sie das AWT in autonomen Anwendungen einsetzen, so daß Sie alles, was Sie in dieser Woche soweit gelernt haben, weiterhin anwenden können. Wenn Ihnen der Umgebung des Web-Browser zu eingeschränkt erscheint, können Sie Ihr AWT-Wissen verwenden und damit beginnen, richtige Java-Anwendungen zu schreiben.

Das AWT bietet folgendes:

Das AWT ist ein umfangreiches Thema, und wenn man es in einem Tag abhandeln wollte, würde das ein sehr langer Tag werden. Somit haben wir uns für eine Aufteilung auf zwei Tage entschieden: heute werden Sie die grundlegende Dinge lernen, zum Beispiel etwas über einige einfache AWT-Komponenten, wie man sie in Applets stellt, wie man sie unter Verwendung von Layout-Managern anordnet und wie man ihnen zur Handhabung von Eingaben Ihres Benutzers Ereignisse hinzufügt.

Morgen werden Sie die AWT-Lektion beenden und mehr über Komponenten sowie über den Applet-Rahmen hinausgehende Aktivitäten wie Erstellung von Fenstern und Dialogen und ausgereiften AWT-Anwendungen lernen.

AWT-Übersicht

AWT basiert auf dem Grundkonzept, daß ein grafisches Java-Programm einen Satz verschachtelter Komponenten darstellt, beginnend vom äußersten Fenster bis zur kleinsten Komponente der Benutzeroberfläche. Komponenten können Elemente beinhalten, die man auf dem Bildschirm sieht, beispielsweise Fenster, Menüleisten, Schaltflächen und Textfelder, sie können darüber hinaus auch Container beinhalten, die ihrerseits wiederum andere Komponenten beinhalten. Abbildung 12.1 zeigt eine Beispielseite eines Java-Browser mit verschiedenen Komponenten, die alle über das AWT verwaltet werden.

siehe Abbildung

Abbildung 12.1:
AWT-
Komponenten

Diese Verschachtelung von Komponenten in Containern innerhalb von anderen Komponenten bildet eine Hierarchie von Komponenten, vom kleinsten Kontrollfeld in einem Applet bis zum Hauptfenster am Bildschirm. Die Komponentenhierarchie bestimmt die Anordnung von Elementen am Bildschirm und innerhalb anderer Elemente, die Reihenfolge, in der sie erscheinen, und (in der Version 1.02 des AWT) wie Ereignisse zwischen Komponenten weitergegeben werden.

Die wichtigsten Komponenten, mit denen Sie im AWT arbeiten können, sind:

Die Klassen im java.awt-Paket wurden so geschrieben und organisiert, daß sie die abstrakte Struktur von Containern, Komponenten und einzelnen Elementen einer Benutzeroberfläche abbilden. Abbildung 12.2 zeigt einen Teil der Klassenhierarchie, mit den wichtigsten AWT-Klassen. Die Wurzel der meisten AWT-Komponenten ist die Klasse Component, die grundlegende Anzeige- und Ereignishandhabungsfunktionen bereitstellt. Die Klassen Container, Canvas, TextComponent und viele andere Klassen der Benutzeroberfläche erben von Component. Durch Vererbung von der Klasse Container können Objekte andere AWT-Komponenten – insbesondere die Klassen Panel und Window –, enthalten. Beachten Sie, daß die Klasse java.applet.Applet zwar in einem eigenen Paket lebt, aber von Panel erbt, so daß Ihre Applets einen integralen Bestandteil der Komponentenhierarchie im AWT-System bilden.

siehe Abbildung

Abbildung 12.2:
Teil einer AWT-Klassenhierarchie

Eine Anwendung mit einer grafischen Benutzeroberfläche, die Sie unter Verwendung des AWT schreiben, kann so komplex sein, wie Sie nur wollen, mit Dutzenden von ineinander verschachtelten Containern und Komponenten. AWT wurde so ausgelegt, daß jede Komponente ihren Part im globalen AWT-System spielen kann, ohne dupliziert werden oder das Verhalten anderer Systemteile übernehmen zu müssen.

Zusätzlich zu den Komponenten selbst beinhaltet das AWT außerdem eine Gruppe von Layout-Managern. Layout-Manager bestimmen die Anordnung der verschiedenen Komponenten bei der Bildschirmanzeige und ihre entsprechend unterschiedlichen Größen. Da die das AWT verwendenden Java-Applets und -Anwendungen auf unterschiedlichen Systemen mit unterschiedlichen Anzeigen, Fonts und Auflösungen laufen können, ist die beliebige Plazierung einer speziellen Komponente an einer bestimmten Stelle im Fenster nicht möglich. Der Layout-Manager hilft Ihnen dabei, Layouts der Benutzeroberfläche zu erstellen, die dynamisch angeordnet und unabhängig davon, wo das Applet oder die Anwendung läuft, angezeigt werden können.

Die Basiskomponenten der Benutzeroberfläche

Eines der tollen Merkmale von Applets ist, daß Sie – da sie bereits (aufgrund ihrer Vererbung) AWT-Container sind – sofort damit beginnen können, ihnen andere AWT-Komponenten hinzuzufügen, ohne daß Sie viel über die komplizierteren Teile des AWT wissen müssen. Fangen wir also genau damit an: in diesem Abschnitt erlernen Sie die Grundlagen zur Erstellung und Benutzung der sechs Basiskomponenten der Benutzeroberfläche im AWT: Labels, Schaltflächen, Kontrollfelder, Optionsschaltflächen, Auswahlmenüs und Textfelder.

Beachten Sie, daß sich dieser Abschnitt auf die Komponenten hinsichtlich ihrer Erscheinung und Verwendung konzentriert. Im weiteren Verlauf dieser Lektion wird Ihnen gezeigt, wie Sie diesen Komponenten Ereignisverhalten hinzufügen können.


Größtenteils gibt es bei diesen Komponenten kaum Unterschiede zwischen Java 1.02 und 1.1. Allerdings wurden einige Methoden und Konstruktoren in 1.1 umbenannt. Die Änderungen wurden mit entsprechenden Vermerken versehen. Die alten Versionen werden auch in Java 1.1 weiterhin funktionieren, aber Sie erhalten bei dem Versuch, ihre Programme zu übersetzen, »Mißbilligungswarnungen«.

Einfügen von Applet-Komponenten

Für jede Komponente der Benutzeroberfläche, um die es in dieser (oder auch in der nächsten) Lektion geht, ist die entsprechende Erstellungsmethode die gleiche: Sie erstellen zuerst die Komponente, dann fügen Sie sie in das Applet oder Panel, in dem sie sich befindet, ein, so daß sie am Bildschirm angezeigt wird. Um eine Komponente (z.B. Ihr Applet) in ein Panel zu stellen, verwenden Sie die add()-Methode:

public void init() {

Button b = new Button("OK");
add(b);
}

Hier bezieht sich die add()-Methode auf das gegenwärtige Applet, anders ausgedrückt, »füge mir dieses Element hinzu«. Sie können Elemente auch in andere Panels und Container einfügen, also verschachtelte Komponenten innerhalb anderer Komponenten. Diesem Thema werden wir uns morgen intensiver widmen.

Beachten Sie, daß das Einfügen einer Komponente in ein Panel oder Applet nichts über ihre Position auf dem Bildschirm aussagt. Die entsprechende Positionierung der Komponente hängt von dem für das Panel definierten Layout-Manager ab. Um die Dinge vorerst einfach zu gestalten, wurden in diesen Beispielen einige unterschiedliche Layouttypen verwendet, die dem Applet ein jeweils besseres Aussehen verleihen. Über Panels und Layouts erfahren Sie mehr im nächsten Abschnitt.

Labels

Im Vergleich zu normalen Textketten (die Sie unter Verwendung von drawString() in der paint()-Methode zeichnen) weist ein Label folgende Vorteile auf:


Ein Label ist eine nicht editierbare Zeichenkette, die zur Beschriftung anderer AWT-Komponenten dient.

Um ein Label zu erstellen, verwenden Sie folgende Konstruktoren:

Sie können die Schrift des Label selbst mit der Methode setFont() ändern, indem entweder das Label selbst zur Änderung der individuellen Beschriftung oder die umgebende Komponente zur Änderung aller Beschriftungen aufgerufen wird. Mit folgendem einfachen Code werden ein paar Labels in Helvetica Bold erstellt. Abbildung 12.3 zeigt, wie diese Labels auf dem Bildschirm aussehen.


Dieser Code benutzt die setLayout()-Methode zur Erzeugung eines neuen Layout-Managers. Machen Sie sich über diese Zeile jetzt keine Gedanken; im nächsten Abschnitt werden Sie mehr über den Layout-Manager lernen.
import java.awt.*;


public class LabelTest extends java.applet.Applet {

public void init() {
setFont(new Font ("Helvetica", Font.BOLD, 14));
setLayout(new GridLayout(3,1));
add(new Label("aligned left", Label.LEFT));
add(new Label("aligned center", Label.CENTER));
add(new Label("aligned right", Label.RIGHT));
}
}

siehe Abbildung

Abbildung 12.3:
Drei Label mit verschiedenen Ausrichtungen

Nachdem Sie ein neues Label-Objekt erstellt haben, können Sie in der Label-Klasse definierte Methoden verwenden, um verschiedene Attribute für das Label-Objekt zu holen oder zu setzen – wie in Tabelle 12.1 gezeigt.

Tabelle 12.1: Label-Methoden.

Methode

Aktion

getText()

Gibt eine Zeichenkette aus, die den Text dieses Label enthält

setText(String)

Ändert den Text dieses Label

getAlignment()

Gibt eine, die Ausrichtung dieses Label darstellende Ganzzahl zurück:


0 entspricht Label.LEFT


1 entspricht Label.CENTER


2 entspricht Label.RIGHT

setAlignment(int)

Ändert die Ausrichtung dieses Label auf die angegebene Ganzzahl; verwenden Sie die in der getAlignment()-Methode aufgeführten Klassenvariablen.

Schaltflächen

Als zweite Komponente der Benutzeroberfläche werden wir uns Schaltflächen ansehen. Schaltflächen (Buttons) sind einfache Komponenten der Benutzeroberfläche, die durch Anklicken bestimmte Aktionen auf Ihrer Oberfläche ausführen. Ein Taschenrechner-Applet könnte beispielsweise Schaltflächen für jede Ziffer und jeden Operator haben, ein Dialogfeld dagegen für OK und Abbrechen.


Eine Schaltfläche ist eine Komponente der Benutzeroberfläche, die durch »Anklicken« (gewählt) mit der Maus die Ausführung von Aktionen auslöst.

Um eine Schaltfläche zu erstellen, benutzen Sie einen der folgenden Konstruktoren:

Nachdem Sie ein Schaltflächenobjekt erstellt haben, können Sie den Wert für seine Beschriftung mit der getLabel()-Methode holen und die Beschriftung mit der setLabel(String)-Methode setzen.

In Abbildung 12.4 sehen Sie ein paar einfache Schaltflächen, die mit folgendem Code erstellt wurden:

public class ButtonTest extends java.applet.Applet {


public void init() {
add(new Button("Rewind"));
add(new Button("Play"));
add(new Button("Fast Forward"));
add(new Button("Stop"));
}
}

siehe Abbildung

Abbildung 12.4:
Vier Schaltflächen in Netscape

Kontrollfelder

Kontrollfelder sind Komponenten einer Benutzeroberfläche, die zwei Stati haben: ein und aus (oder angekreuzt und nicht angekreuzt, gewählt und nicht gewählt, true und false usw.). Im Gegensatz zu Schaltflächen lösen Kontrollfelder nicht direkt Aktionen auf einer Benutzeroberfläche aus, sondern werden statt dessen zur Anzeige optionaler Features einer anderen Aktion verwendet.

Kontrollfelder können auf zwei Arten benutzt werden:

Den letzteren Typ von Kontrollfeldern nennt man Optionsfelder oder Kontrollfeldgruppen. Sie werden im nächsten Abschnitt beschrieben.


Kontrollfelder sind Komponenten einer Benutzeroberfläche, die zur Bereitstellung von Optionen gewählt oder nicht gewählt (angekreuzt oder nicht angekreuzt) werden können. Sich gegenseitig nicht ausschließende Kontrollfelder können unabhängig von anderen Kontrollfeldern angekreuzt oder nicht angekreuzt werden.

Kontrollfelder, die sich gegenseitig ausschließend, werden auch als Optionsfelder bezeichnet. Sie bestehen in Gruppen, wobei nur jeweils ein Feld aus der Gruppe angekreuzt werden kann.


Kontrollfelder für sich gegenseitig nicht ausschließende Optionen können durch Benutzung der Checkbox-Klasse erstellt werden. Sie können ein Kontrollfeld auch mit einem der folgenden Konstruktoren erstellen:



In Java 1.1 ist der Konstruktor Checkbox(String, null, boolean) »mißbilligt« worden (er existiert zwar noch, seine Verwendung wird aber nicht empfohlen). Verwenden Sie zur Erstellung eines im voraus gewählten Kontrollfeldes in Java 1.1 den Constructor Checkbox(String, boolean). Zur Erstellung eines Kontrollfeldes mit einer Gruppe können Sie eine 1.1-Version dieses Konstruktors unter Umkehrung der beiden letzten Argumente verwenden. Einzelheiten dazu finden Sie im nächsten Abschnitt.

Abbildung 12.5 zeigt, wie Sie ein paar einfache Kontrollfelder (nur das Feld Underwear ist gewählt) mit dem folgenden Code erstellen können. (Beachten Sie, daß das vierte Kontrollfeld den 1.1-Konstruktor einsetzt; um diesen Code für 1.02 zu modifizieren, fügen Sie eine null zwischen den beiden Argumenten ein.)

import java.awt.*;


public class CheckboxTest extends java.applet.Applet {

public void init() {
setLayout(new FlowLayout(FlowLayout.LEFT));
add(new Checkbox("Shoes"));
add(new Checkbox("Socks"));
add(new Checkbox("Pants"));
add(new Checkbox("Underwear", true));
add(new Checkbox("Shirt"));
}
}

siehe Abbildung

Abbildung 12.5:
Fünf Kontrollfelder, eines davon gewählt

Tabelle 12.2 listet einige der Kontrollfeldmethoden auf.

Tabelle 12.2: Kontrollfeldmethoden

Methode

Aktion

getLabel()

Gibt eine die Beschriftung dieses Kontrollfeldes beinhaltende Zeichenkette aus

setLabel(String)

Ändert den Text der Beschriftung des Kontrollfeldes

getState()

Gibt true oder false aus, je nach dem, ob das Kontrollfeld selektiert ist

setState(boolean)

Ändert den Status des Kontrollfeldes in gewählt (true) oder nicht gewählt (false)

Optionsfelder

Optionsfelder (Radio Buttons) sehen genauso aus wie Kontrollfelder (und werden faktisch ebenso aus der Checkbox-Klasse erstellt), jedoch kann jeweils nur eine Option gewählt werden. Um eine Reihe von Optionsfelder zu erstellen, legen Sie zuerst eine Instanz von CheckboxGroup an:

CheckboxGroup cbg = new CheckboxGroup();

Dann erstellen und fügen Sie die einzelnen Optionsfelder aus der CheckBox-Klasse ein. Je nach dem, ob Sie Java 1.1 einsetzen oder nicht, stehen Ihnen dafür zwei Wege zur Verfügung:

Hier zwei, unter Verwendung von Java 1.02 in die cbg-Kontrollfeldgruppe eingefügte, Optionsfelder:

add(new Checkbox("Yes", cbg, true);

add(new Checkbox("No", cbg, false);

Hier dieselben, unter Verwendung von Java 1.1 eingefügten, Kontrollfelder:

add(new Checkbox("Yes", true, cbg);

add(new Checkbox("No", false, cbg);

Beachten Sie, da in den Optionsfelder per Definition nur jeweils ein Feld gewählt werden kann, daß das letzte, der Gruppe zuzufügende true-Feld dasjenige ist, das per Default gewählt wird. Wenn Sie eine Gruppe von Feldern erstellen und kein Feld selektieren, werden sie zu Beginn alle als deselektiert erscheinen, und die erste vom Benutzer getroffene Auswahl wird zum Default werden.

Es folgt ein einfaches Beispiel einer Gruppe von Optionsfeldern, die mit Java 1.1 erstellt wurden. (Zur Umsetzung dieses Beispiels in 1.02, führen Sie eine Umkehrung der letzten beiden Argumente für jeden Kontrollfeld-Konstruktor durch.) Das Ergebnis zeigt Abbildung 12.6.

import java.awt.*;


public class CheckboxGroupTest extends java.applet.Applet {

public void init() {
setLayout(new FlowLayout(FlowLayout.LEFT));
CheckboxGroup cbg = new CheckboxGroup();

add(new Checkbox("Red", false, cbg,));
add(new Checkbox("Blue", false, cbg));
add(new Checkbox("Yellow", false, cbg));
add(new Checkbox("Green", true, cbg));
add(new Checkbox("Orange", false, cbg));
add(new Checkbox("Purple", false, cbg));
}
}

siehe Abbildung

Abbildung 12.6:
Sechs Optionsfelder (sich gegenseitig ausschließende Kontrollfelder), eines davon gewählt

Alle in Tabelle 12.2 im vorherigen Abschnitt gezeigten Kontrollfeldmethoden können auch bei den Optionsfeldern eingesetzt werden. Darüber hinaus können Sie die Methoden getCheckboxGroup() und setCheckboxGroup() (in der Checkbox()-Klasse definiert) anwenden, um auf die Gruppe eines beliebigen Kontrollfeldes zuzugreifen und sie zu ändern.

Schließlich können die in CheckboxGroup definierten Methoden getCurrent()und setCurrent(Checkbox) benutzt werden, um das momentan ausgewählte Optionsfeld zu holen oder zu setzen.

Auswahlmenüs

Das Auswahlmenü ist eine komplexere Komponente einer Benutzeroberfläche als Label, Schaltflächen oder Kontrollfelder. Auswahlmenüs sind Pop-up-(oder Pull-down-)Listen mit Elementen, aus denen Sie auswählen können. Das Menü zeigt dann Ihre Auswahl auf dem Bildschirm an. Die Funktion eines Auswahlmenüs ist bei den Plattformen dieselbe, abhängig von der Plattform kann seine Erscheinung allerdings unterschiedlich sein.


Die Terminologie ist hier ein bißchen falsch. Auswahlmenüs sind Auswahllisten ähnlicher als tatsächlichen Pop-up-Menüs. Java 1.1 hat eine richtige Pop-up-Menü-Komponente, die sich als bessere Komponente zur Erstellung von Menüfunktionen erweist. Über Pop-up-Menüs werden Sie morgen mehr erfahren.

Beachten Sie, daß jeweils nur ein Element aus den Auswahlmenüs gewählt werden kann. Wenn Sie mehrere Elemente aus dem Menü wählen möchten, verwenden Sie dazu ein Listenfeld (Listenfelder werden in der morgigen Lektion behandelt).


Auswahlmenüs sind Pop-up-Elementlisten, aus denen Sie ein Element auswählen können.

Um ein Auswahlmenü zu erstellen, legen Sie zuerst eine Instanz der Choice-Klasse an und fügen dann einzelne Elemente dem Objekt hinzu. Verwenden Sie in Java 1.02 die addItem()-Methode mit einer Zeichenkette, um der Liste ein Element hinzuzufügen. In Java 1.1 verwenden Sie statt dessen die add()-Methode mit einer Zeichenkette. Hier ein in Java 1.02 integriertes Auswahlmenü mit drei Elementen:

Choice c = new Choice();

c.addItem("Green");
c.addItem("Red");
c.addItem("Blue");

Hier dasselbe Menü in Java 1.1 integriert:

Choice c = new Choice();

c.add("Green");
c.add("Red");
c.add("Blue");

Nachdem Sie das Auswahlmenü erstellt haben, fügen Sie das Ganze unter Verwendung von add() in gewohnter Weise in das Panel ein.

Hier ein einfaches Programm (das den 1.1-Code benutzt), das ein Auswahlmenü von Früchten erstellt. Abbildung 12.7 zeigt das Ergebnis (mit der geöffneten Liste).

import java.awt.*;


public class ChoiceTest extends java.applet.Applet {

public void init() {
Choice c = new Choice();
c.add("Apples");
c.add("Oranges");
c.add("Strawberries");
c.add("Blueberries");
c.add("Bananas");
add (c);
}
}

siehe Abbildung

Abbildung 12.7:
Auswahlmenü

Ungeachtet dessen, ob Sie das Auswahlmenü bereits in ein Panel eingefügt haben, können Sie mit dem Hinzufügen von Elementen zu diesem Menü mit der addItem()-Methode fortfahren. Tabelle 12.3 zeigt einige weitere Methoden, die im Zusammenhang mit Auswahlmenüs nützlich sind.

Tabelle 12.3: Methoden für Auswahlmenüs

Methode

Aktion

getItem(int)

Gibt die Zeichenkette des Elements an der angegebenen Stelle aus (Elemente in einem Auswahlmenü beginnen bei 0, wie bei Arrays)

countItems()

Gibt die Anzahl der im Menü enthaltenen Elemente aus (nur Java 1.02; benutzen Sie getItemCount() in 1.1)

getItemCount()

Gibt die Anzahl der im Menü enthaltenen Elemente aus (nur Java 1.1)

getSelectedIndex()

Gibt die Indexposition des ausgewählten Elementes an

getSelectedItem()

Gibt das momentan ausgewählte Element als Zeichenkette aus

select(int)

Wählt das Element an der angegebenen Stelle

select(String)

Wählt das Element mit der angegebenen Zeichenkette

Textfelder

Im Gegensatz zu den bisher beschriebenen Komponenten der Benutzeroberfläche, bei denen Sie aus mehreren Optionen auswählen oder eine Aktion durchführen können, ermöglichen Ihnen Textfelder die Eingabe und Bearbeitung von Text. Im allgemeinen bestehen Textfelder nur aus einer einzelnen Zeile und haben keine Bildlaufleisten; Textbereiche, über die Sie im Laufe des heutigen Tages mehr lernen werden, eignen sich besser für größere Textmengen.

Textfelder unterscheiden sich von Labels dadurch, daß sie editiert werden können; Labels eignen sich gut für reine Textdarstellung, Textfelder hingegen für die Aufnahme von Texteingaben vom Benutzer.


Textfelder stellen einen Bereich zur Verfügung, in dem Sie eine einzelne Textzeilen eingeben und editieren können.

Um ein Textfeld zur erstellen, benutzen Sie einen der folgenden Konstruktoren:

Folgender Beispielcode erzeugt ein Textfeld mit einer Breite von 30 Zeichen und der Zeichenkette "Enter Your Name" als Anfangsinhalt:

TextField tf = new TextField("Enter Your Name", 30);

add(tf);


Textfelder beinhalten nur das editierbare Feld selbst. Sie müssen in der Regel eine Beschriftung (Label) in ein Textfeld einbeziehen, um anzugeben, was in das Textfeld gehört.

Sie können auch ein Textfeld erstellen, das die eingegebenen Zeichen verbirgt – beispielsweise für Paßwortfelder. Hierfür erstellen Sie zuerst das Textfeld; dann verwenden Sie in Java 1.02 die setEchoCharacter()-Methode oder in Java 1.1 die setEchoChar()-Methode, um das zu verbergende Zeichen am Bildschirm zu setzen. Im folgenden ein Beispiel für 1.02:

TextField tf = new TextField(30);

tf.setEchoCharacter('*');

Und hier das gleiche in Java 1.1:

TextField tf = new TextField(30);

tf.setEchoChar('*');

Wenn Sie den folgenden Java 1.1-Code benutzen, können Sie drei Textfelder (und Label) wie in Abbildung 12.8 gezeigt erstellen:

import java.awt.*;


public class TextFieldTest extends java.applet.Applet {
public void init() {
setLayout(new GridLayout(3,2,5,15);
add(new Label("Enter your Name"));
add(new TextField("your name here", 45));
add(new Label("Enter your phone number"));
add(new TextField(12));
add(new Label("Enter your password"));
TextField t = new TextField(20);
t.setEchoChar('*'); // nur bei 1.1! Bei 1.02 setEchoCharacter() verwenden
add(t);
}

siehe Abbildung

Abbildung 12.8:
Drei Textfelder für
Benutzereingaben

Der im ersten Feld stehende Text ("your name here") wurde im Code initialisiert; der Text wurde in die verbleibenden beiden Felder direkt vor Aufnahme des in Abbildung 12.8 gezeigten Bildschirms eingegeben.

Textfelder erben von der Klasse TextComponent und haben eine ganze Reihe von Methoden, die sowohl von dieser Klasse geerbt als auch in einer eigenen Klasse definiert sind. Sie sind nützlich für alle Arten von Java-Programmen. Tabelle 12.4 enthält eine Auswahl dieser Methoden

Tabelle 12.4: Methoden für Textfelder

Methode

Aktion

getText()

Gibt den Text des Feldes (als Zeichenkette) aus

setText(String)

Setzt die angegebene Zeichenkette in das Feld

getColumns()

Gibt die Breite des Textfeldes aus

select(int, int)

Wählt denText zwischen den zwei ganzzahligen Positionen (Positionen beginnen bei 0)

selectAll()

Wählt den gesamten Text im Feld aus

isEditable()

Gibt true oder false aus, je nach dem, ob der Text editierbar ist

setEditable(boolean)

true (der Standardwert) ermöglicht Editieren des Textes; false friert den Text ein

getEchoChar()

Gibt das Ersatzzeichen für das eingegebene Zeichen aus

setEchoCharacter(char)

Setzt das Ersatzzeichen für das eingegebene Zeichen
(nur 1.02)

setEchoChar(char)

Setzt das Ersatzzeichen für das eingegebene Zeichen
(nur 1.1)

echoCharIsSet()

Gibt true oder false aus, je nach dem, ob das Feld mit einem Ersatzzeichen (Masking) belegt ist

Panels und Layout

AWT-Panels können Komponenten der Benutzeroberfläche oder andere Panels enthalten. Die Frage lautet, wie diese Komponenten angeordnet und am Bildschirm angezeigt werden.

In anderen Fenstersystemen werden Komponenten der Benutzeroberfläche meist anhand von Pixelmaßen im Code angeordnet – Setzen eines Textfeldes beispielsweise auf die Position 10,30 – ebenso wie Sie Grafikoperationen benutzen, um Vierecke und Ovale am Bildschirm auszugeben. Da Java jedoch plattformübergreifend konzipiert ist, kann Ihr Design auf der Benutzeroberfläche im AWT in vielen verschiedenen Fenstersystemen auf unterschiedlichen Bildschirmen und mit vielen verschiedenen, unterschiedliche Schriftmetrik aufweisenden Fonts angezeigt werden. Deshalb brauchen wir flexiblere Methoden, um Komponenten so am Bildschirm anordnen zu können, daß das Layout auf jeder Plattform gut aussieht.

Für gerade diesen Zweck verfügt Java über verschiedene Layout-Manager, Einsätze und Tips, die jede Komponente zur Unterstützung der dynamischen Auslegung des Bildschirms bereitstellen kann.

Layout-Manager: Übersicht

Das Aussehen der AWT-Komponenten am Bildschirm wird von zwei Aspekten bestimmt: Von der Art des Einfügens der Komponenten in das Panel, in dem sie sich befinden (entweder gemäß der Reihenfolge oder mit der add()-Methode eingefügten Argumenten), und dem Layout-Manager, den das Panel für das Layout am Bildschirm benutzt. Der Layout-Manager bestimmt, wie Bildschirmbereiche aufgeteilt und Komponenten im Panel angeordnet werden.


Der Layout-Manager bestimmt, wie AWT-Komponenten dynamisch am Bildschirm angeordnet werden.

Jedes Panel am Bildschirm kann einen eigenen Layout-Manager haben. Durch Verschachteln von Panels innerhalb von Panels und Verwendung des entsprechenden Layout-Managers für jedes Panel können Sie Ihre Benutzeroberfläche zum Gruppieren und Anordnen von Komponenten funktionell und attraktiv für unterschiedliche Fenstersysteme und Plattformen auslegen. Wie Panels verschachtelt werden, lernen Sie in einem späteren Lektionsteil.

Das AWT beinhaltet fünf grundlegende Layout-Manager: FlowLayout, GridLayout, BorderLayout, CardLayout, und GridBagLayout. Um einen Layout-Manager für ein bestimmtes Panel zu erstellen, erzeugen Sie eine Instanz des Layout-Managers und verwenden dann die setLayout()-Methode für das Panel. Das folgende Beispiel setzt den Layout-Manager für das gesamte umgebende Applet-Panel:

public void init() {

setLayout(new FlowLayout());
}

Das Setzen des Standard-Layout-Managers wird, genau wie das Erstellen von Komponenten einer Benutzeroberfläche, vorzugsweise in die Initialisierung des Applet eingebunden, und ist deshalb hierin beinhaltet.

Wenn der Layout-Manager gesetzt ist, können Sie mit dem Einfügen von Komponenten in das Panel beginnen. Die Reihenfolge, in der die Komponenten hinzugefügt werden, oder die Argumente, die Sie zum Einfügen dieser Komponenten verwenden, ist meist, je nach dem, welcher Layout-Manager jeweils aktiv ist, entscheidend. Lesen Sie die Informationen über die jeweiligen Layout-Manager und wie sie Komponenten im entsprechenden Panel darstellen.

In den folgenden Abschnitten werden die fünf grundlegenden Layout-Manager von Java-AWT geschrieben.

Die FlowLayout-Klasse

Die FlowLayout-Klasse stellt das einfachste Layout dar. Mit diesem Layout werden die Komponenten nacheinander zeilenweise in das Panel eingefügt. Paßt eine Komponente nicht in eine Zeile, wird sie automatisch auf die nächste Zeile umbrochen. Das FlowLayout hat eine Ausrichtung, die die Ausrichtung aller Zeilen vorgibt. Standardmäßig ist jede Zeile zentriert.


FlowLayout ordnet Komponenten zeilenweise von links nach rechts an. Die Zeilen werden entweder nach links, rechts oder zentriert ausgerichtet.

Um ein einfaches FlowLayout mit zentrierter Ausrichtung zu erstellen, verwenden Sie in der Panel-Initialisierung folgende Codezeile (da dies das Panel-Standardlayout ist, können Sie, wenn Sie wollen, diese Zeile auslassen):

setLayout(new FlowLayout());

Das Layout ist gesetzt. Die Position der Elemente wird durch die Reihenfolge, in der Sie sie dem Layout zufügen, bestimmt. Der folgende Code erzeugt eine einfache Zeile mit sechs Schaltflächen in einem zentrierten FlowLayout. Abbildung 12.9 zeigt das Ergebnis.

import java.awt.*;


public class FlowLayoutTest extends java.applet.Applet {

public void init() {
setLayout(new FlowLayout());
add(new Button("One"));
add(new Button("Two"));
add(new Button("Three"));
add(new Button("Four"));
add(new Button("Five"));
add(new Button("Six"));
}
}

siehe Abbildung

Abbildung 12.9:
Sechs, mit einem
FlowLayout-Manager angeordnete,
Schaltflächen

Um ein FlowLayout mit einer rechten oder linken Ausrichtung zu erstellen, fügen Sie die Klassenvariable FlowLayout.RIGHT oder FlowLayout.LEFT als Argument ein:

setLayout(new FlowLayout(FlowLayout.LEFT));

Mit Hilfe des FlowLayout können Sie auch horizontale und vertikale Abstandswerte setzen. Der Abstand ist die Zahl der Pixel zwischen Komponenten in einem Panel. Standardmäßig sind die horizontalen und vertikalen Abstandswerte drei Pixel, was sehr eng ist. Der horizontale Abstand ist links und rechts zwischen Komponenten, der vertikale oben und unten. Um den Abstand zu erhöhen, fügen Sie in den FlowLayout-Konstruktor ganzzahlige Argumente ein. Sie können einen Abstand von 30 Punkten in horizontaler und 10 in vertikaler Ausrichtung wie folgt einfügen. Abbildung 12.10 zeigt das Ergebnis.

setLayout(new FlowLayout(FlowLayout.LEFT, 30, 10));

siehe Abbildung

Abbildung 12.10:
FlowLayout mit einem Abstand von 10 Punkten

Die Klasse GridLayout

GridLayouts bieten mehr Kontrolle über die Anordnung von Komponenten in einem Panel. Mit einem GridLayout können Sie den Panel-Bereich in Zeilen und Spalten aufteilen. Jede Komponente, die Sie dann in das Panel einfügen, wird in einer Zelle des Rasters von links oben nach rechts unten in das Raster eingefügt (hier ist die Reihenfolge, in der die add()-Methode auf-gerufen wird, für das Bildschirmlayout relevant).

Um ein GridLayout zu erstellen, geben Sie die Anzahl der gewünschten Zeilen und Spalten in einer neuen Instanz der GridLayout-Klasse an. Hier ein GridLayout mit drei Zeilen und zwei Spalten. Abbildung 12.11 zeigt das Ergebnis.

import java.awt.*;


public class GridLayoutTest extends java.applet.Applet {

public void init() {
setLayout(new GridLayout(3,2);
add(new Button("One"));
add(new Button("Two"));
add(new Button("Three"));
add(new Button("Four"));
add(new Button("Five"));
add(new Button("Six"));
}
}

siehe Abbildung

Abbildung 12.11:
Sechs Schaltflächen, Anzeige durch Verwendung eines GridLayouts mit drei Zeilen und zwei Spalten

Auch bei GridLayouts können Sie den horizontalen und vertikalen Abstand zwischen den Komponenten bestimmen. Hierfür fügen Sie die entsprechenden PixelWerte ein:

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

In Abbildung 12.12 sehen Sie ein GridLayout mit einem horizontalen Abstand von 10 und einem vertikalen von 30 Pixel.

siehe Abbildung

Abbildung 12.12:
Ein Rasterlayout mit
horizontalen und
vertikalen Abständen

Die Klasse BorderLayout

BorderLayouts verhalten sich anders als Flow- und GridLayouts. Wenn Sie eine Komponente in ein Panel einfügen, das auf einem BorderLayout basiert, müssen Sie seine Anordnung als geographische Richtung angeben: Nord, Süd, Ost, West oder Mitte. (Siehe Abbildung 12.13.) Die Komponenten rund um die Kanten werden in der benötigten Größe ausgelegt. Falls es eine Komponente in der Mitte gibt, erhält sie den restlichen Platz zugeteilt.

siehe Abbildung

Abbildung 12.13:
Anordnung der
Komponenten in einem
BorderLayout

Um ein BorderLayout zu erstellen, verfahren Sie wie bei den anderen Layouts; fügen Sie dann die einzelnen Komponenten mit einer speziellen, zwei Argumente beinhaltenden add()-Methode ein. Das erste Argument ist eine Zeichenkette, die die Position der Komponente im Layout bezeichnet, und das zweite die einzufügende Komponente:

add("North", new TextField("Title", 50));

Sie können diese Form von add() auch für andere Layout-Manager verwenden; das Argument für die Zeichenkette wird einfach ignoriert, falls es nicht benötigt wird.

Mit folgendem Code erstellen Sie das in Abbildung 12.13 gezeigte BorderLayout:

import java.awt.*;


public class BorderLayoutTest extends java.applet.Applet {

public void init() {
setLayout(new BorderLayout());
add("North", new Button("One"));
add("East", new Button("Two"));
add("South", new Button("Three"));
add("West", new Button("Four"));
add("Center", new Button("Five"));
}
}

Auch bei BorderLayouts sind horizontale und vertikale Abstände möglich. Beachten Sie, daß sich die Nord- und Südkomponenten über die gesamte Kante des Panel erstrecken, so daß der Abstand auf Kosten des Platzes für die Komponenten in Ost, West und Mitte entsteht. Um Abstände in ein BorderLayout einzufügen, tragen Sie, wie bei den anderen Layout-Managern, die Pixel-Werte in den Konstruktor ein:

setLayout(new BorderLayout(10, 10));

Die Klasse CardLayout

CardLayout unterscheiden sich von anderen Layouts. Wenn Sie in einen der anderen Layout-Manager Komponenten einfügen, erscheinen diese Komponenten sofort am Bildschirm. CardLayout werden verwendet, um jeweils eine Art Diaschau der Komponenten zu erzeugen. Falls Sie je mit HyperCard auf dem Macintosh gearbeitet haben, kennen Sie das Prinzip von CardLayout.

Die Komponenten, die Sie beim Erstellen eines CardLayout in das äußere Panel einfügen, werden im allgemeinen als weitere Container-Komponenten – normalerweise sind es Panels, behandelt. Sie können dann für die einzelnen Karten je ein anderes Layout verwenden, so daß jeder Bildschirm anders aussieht.


Cards (Karten) in einem CardLayout sind unterschiedliche Panels, die nacheinander eingefügt und angezeigt werden. Wenn Sie sich ein Kartenspiel vorstellen, werden Sie verstehen, was gemeint ist; es kann jeweils nur eine Karte angezeigt werden, aber Sie können zwischen den Karten wechseln.

Jede Karte, die Sie in das Panel einfügen, wird benannt. Um jeweils zwischen den Container-Karten zu blättern, können Sie in der CardLayout-Klasse definierte Methoden verwenden, mit denen Sie zu einer benannten Karte gelangen, vorwärts oder rückwärts, oder zur ersten oder letzten Karte blättern. Natürlich steht Ihnen zum Aufrufen dieser Methoden eine Gruppe von Schaltflächen zur Verfügung, die Ihnen das Navigieren durch das CardLayout erleichtern.

Mit den folgenden zwei einfachen Teilen eines Codes wird ein CardLayout mit drei Karten erstellt:

setLayout(new CardLayout());

//Karten hinzufügen
Panel one = new Panel()
add("first", one);
Panel two = new Panel()
add("second", two);
Panel three = new Panel()
add("third", three);

//navigieren
show(this, "second"); //Sprung zu Karte namens "second"
show(this, "third"); //Sprung zu Karte namens "third"
previous(this); //Zurück zur zweiten Karte
first(this); //Sprung zur ersten Karte

Die Klasse GridBagLayout

GridBagLayouts haben wir uns bis zum Schluß aufgehoben, da sie zwar das stärkste Instrument in der Verwaltung von AWT-Layouts sind, jedoch gleichzeitig auch äußerst kompliziert.

Genau das gewünschte Layout zu erhalten, kann sich bei Verwendung einer der vier anderen Layout-Manager manchmal recht schwierig gestalten, ohne viele Panels ineinander verschachteln zu müssen. GridBags bieten eine allgemeineren Zwecken dienliche Lösung. Wie GridLayouts ermöglichen Ihnen GridBagLayouts die Anordnung Ihrer Komponenten in einem rasterähnlichen Layout. Allerdings bieten Ihnen GridBag-Layouts zusätzlich die Möglichkeit, die Weite einzelner Zellen im Raster, das Proportionen zwischen Zeilen und Spalten sowie die Anordnung von Komponenten innerhalb der Zellen im Raster zu kontrollieren.

Zur Erstellung eines GridBagLayouts benutzen Sie zwei Klassen: GridBagLayout, die den gesamten Layout-Manager bereitstellt, und die Klasse GridBagConstraints, die die Eigenschaften jeder Komponente im Raster bestimmt – seine Anordnung, Maße, Ausrichtung, usw. Das Verhältnis zwischen GridBag, Rahmenbedingungen und jeder Komponente bestimmt das gesamte Layout.

Zur Erstellung eines GridBagLayouts in seiner einfachsten Form gehen Sie folgendermaßen vor:

1. Erstellen Sie ein GridBagLayout-Objekt, und definieren Sie es in gleicher Weise wie andere Layout-Manager als den derzeitigen Layout-Manager!

Der folgende einfache Code erstellt das Layout und erzeugt dann Rahmenbedingungen für eine einzelne Schaltfläche. (Machen Sie sich über die verschiedenen Werte der Rahmenbedingungen keine Sorgen; darüber reden wir später in diesem Abschnitt.)

// Layout einrichten 

GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints constraints = new GridBagConstraints();
setLayout(gridbag);

// Constarints für die Schaltfläche definieren
Button b = new Button("Save");
constraints.gridx = 0;
constraints.gridy = 0;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 30;
constraints.weighty = 30;
constraints.fill = GridBagConstraints.NONE;
constraints.anchor = GridBagConstraints.CENTER;

// Constraints zu Layout und Button hinzufügen
gridbag.setConstraints(b, constraints);
add(b);

Das Setzen der Rahmenbedingungen für jede Komponente ist wirklich der langweiligste Teil bei diesem Verfahren. (Wie Sie anhand des Beispiels sehen können, müssen Sie alle Rahmenbedingungen für jede Komponente, die Sie in das Panel einfügen wollen, setzen.) Hinzu kommt, daß Rahmenbedingungen wirklich nicht so einfach sind; sie haben viele verschiedene Werte, von denen viele zueinander in Beziehung stehen, was bedeutet, daß die Änderung eines Wertes direkte Auswirkungen auf andere haben kann.

Bei den zahlreichen Rahmenbedingungen ist es hilfreich, planmäßig vorzugehen und sich mit jeder Rahmenbedingung einzeln zu beschäftigen. Für diese Vorgehensweise stehen fünf Schritte zur Verfügung, denen wir nun im einzelnen folgen.

Erster Schritt: Rasterentwurf

Der erste Schritt zur Erstellung von GridBagLayouts besteht in einem Entwurf auf Papier. Die Erstellung des Designs Ihrer Benutzeroberfläche in Form einer Skizze vorab – noch bevor Sie auch nur eine Codezeile schreiben – wird auf lange Sicht gesehen enorm hilfreich sein, wenn Sie versuchen herauszufinden, wo was hinkommt. Wenden Sie sich also einmal von Ihrem Editor ab, nehmen Sie »Papier und Bleistift«, und entwerfen Sie das Raster.

Abbildung 12.14 zeigt das Panel-Layout, das Sie in diesem Beispiel konstruieren. Abbildung 12.15 zeigt dasselbe Layout mit einem aufgesetzten Raster. Ihr Layout wird ein diesem ähnliches Raster haben, in dem Zeilen und Spalten einzelne Zellen bilden.

siehe Abbildung

Abbildung 12.14:
GridBagLayout

siehe Abbildung

Abbildung 12.15:
Das GridBagLayout gemäß Abbildung 12.14, aber mit aufgesetztem Raster

Denken Sie beim Zeichnen Ihres Rasters daran, daß jede Komponente ihre eigene Zelle haben muß. Nur jeweils eine Komponente kann in dieselbe Zelle gesetzt werden. Umgekehrt stimmt das nicht, allerdings kann eine Komponente mehrere Zellen in den X- oder Y-Ausrichtungen umranden (wie bei der Schaltfläche OK in der unteren Zeile, die zwei Spalten einfaßt). Beachten Sie, daß die Labels und Textfelder in Abbildung 12.15 ihre eigenen Raster haben und die Schaltfläche zwei Spaltenzellen einfaßt.

Setzen Sie vorerst Ihre Arbeit auf dem Papier fort, und beschriften Sie die Zellen mit ihren X- und Y-Koordinaten. Sie werden später sehen, wie nützlich das ist. Es handelt sich nicht um Pixelkoordinaten sondern um Feldkoordinaten. Die Zelle links oben ist 0,0. Die nächste Zelle rechts davon in der oberen Zeile ist 1,0. Die Zelle rechts von dieser ist 2,0. In der nächsten Zeile ist die Zelle ganz links 1,0, die nächste Zelle in dieser Zeile ist 1,1 usw. Beschriften Sie Ihre Zellen auf dem Papier mit diesem Nummern; Sie werden sie später benötigen, wenn Sie den Code für dieses Beispiel erstellen. In Abbildung 12.16 sehen sind die Zahlen für jede Zelle des Beispiels angezeigt.

siehe Abbildung

Abbildung 12.16:
Das GridBagLayout gemäß Abbildung 12.14 mit Feldkoordinaten

Zweiter Schritt: Rastererstellung in Java

Wenden Sie sich jetzt wieder Java zu und beginnen mit der Realisierung des auf dem Papier vorbereiteten Layouts. Wir konzentrieren uns zuerst ausschließlich auf das Layout – damit das Raster und die Proportionen stimmen. Dabei ist es hilfreich, nicht mit den Elementen der Benutzeroberfläche zu arbeiten. Schaltflächen dienen als Platzhalter für die Elemente im Layout, bis alles richtig eingerichtet ist. Danach werden die Schaltflächen in richtige Elemente umgewandelt.

Um Ihr Pensum an Eigenarbeit zum Einrichten all dieser Rahmenbedingungen zu reduzieren, können Sie mit der Definition einer Hilfsmethode beginnen, die mehrere Werte entgegennimmt und die Rahmenbedingungen für diese Werte setzt. buildConstraints() erhält dazu sieben Argumente: ein GridBagConstraints-Objekt und sechs Ganzzahlen, die für die GridBagConstraints-Instanzvariablen gridx, gridy, gridwidth, gridheight, weightx, und weighty stehen. Was deren Funktion ist, werden Sie in Kürze lernen; im Moment beschäftigen wir uns mit dem Code der Hilfsmethode, den Sie im weiteren Verlauf dieses Beispiels einsetzen:

void buildConstraints(GridBagConstraints gbc, int gx, int gy,

int gw, int gh, int wx, int wy) {
gbc.gridx = gx;
gbc.gridy = gy;
gbc.gridwidth = gw;
gbc.gridheight = gh;
gbc.weightx = wx;
gbc.weighty = wy;
}

Wenden wir uns jetzt der init()-Methode zu. In dieser wird das gesamte Layout aufgebaut. Es folgt die grundlegende Definition der Methode, in der Sie das GridBagLayout als Layout-Manager definieren und ein Rahmenbedingungs-Objekt erzeugen (eine Instanz von GridBagConstraints):

public void init() {

GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints constraints = new GridBagConstraints();
setLayout(gridbag);

constraints.fill = GridBagConstraints.BOTH;
}

Noch ein kurzer erklärender Hinweis: Die letzte Zeile, die den Wert für constraints.fill setzt, wird später entfernt (und erklärt). Sie dient dem Zweck, daß die Komponenten die gesamte, sie beinhaltende Zelle füllen, was Ihnen die Möglichkeit gibt zu sehen, was vor sich geht. Fügen Sie sie jetzt hinzu, und Sie werden ihren späteren Zweck verstehen.

Fügen Sie jetzt die Platzhalter-Schaltflächen in das Layout ein (denken Sie daran, daß Sie sich momentan auf einfache Rasterorganisation konzentrieren und deshalb Schaltflächen als Platzhalter für echte, später hinzuzufügende Elemente der Benutzeroberfläche verwenden). Beginnen Sie mit einer einzelnen Schaltfläche, damit Sie ein Gefühl für das Setzen der Rahmenbedingungen entwickeln. Dieser Code kommt in die init()-Methode direkt im Anschluß an die Zeile setLayout:

//Label Namen

buildConstraints(constraints, 0, 0, 1, 1, 100, 100);
Button label1 = new Button("Name:");
gridbag.setConstraints(label1, constraints);
add(label1);

Diese vier Zeilen richten die Rahmenbedingungen für ein Objekt ein, erstellen eine neue Schaltfläche, teilen der Schaltfläche die Rahmenbedingungen zu und fügen sie dann in das Panel ein. Beachten Sie, daß die Rahmenbedingungen für eine Komponente in dem Objekt GridBagConstraints gespeichert werden, was bedeutet, daß die Komponente nicht einmal vorhanden sein muß, um ihre Rahmenbedingungen einzurichten.

Nun können Sie sich den Einzelheiten widmen: Welches sind die Werte für die Rahmenbedingungen, die Sie in die Hilfsmethode buildConstraints() eingebunden haben?

Die ersten beiden ganzzahligen Argumente sind die Werte gridx und gridy der Rahmenbedingungen. Sie stellen die Feldkoordinaten der diese Komponente beinhaltenden Zelle dar. Erinnern Sie sich, daß Sie diese Komponenten in Schritt 1 in Ihre Skizze geschrieben haben? Da Sie die Zellen sozusagen schon mit Nummern auf dem Papier versehen haben, müssen Sie jetzt nur noch die richtigen Werte einsetzen. Beachten Sie bei einer mehrere Zellen umfassenden Komponente, daß die Koordinaten der Zelle sich auf die Zelle in der obersten linken Ecke beziehen.

Hier ist diese Schaltfläche in der obersten linken Ecke, und somit sind ihre Werte für gridx und gridy (die ersten beiden Argumente für buildConstraints()) 0 bzw. 0.

Die zweiten beiden ganzzahligen Argumente sind gridwidth und gridheight. Sie stellen keine Pixelbreiten und -höhen der Zelle dar, sondern die Anzahl der Zellen, die diese Komponente umfaßt: gridwidth für die Spalten und gridheight für die Zeilen. In unserem Beispiel umfaßt diese Komponente nur eine Zelle, und somit ist der Wert für beide 1.

Die letzten beiden ganzzahligen Argumente stehen für weightx und weighty. Sie dienen zum Einrichten der Proportionen der Zeilen und Spalten – bzw. zur Angabe deren Breite und Tiefe. Weights kann sehr verwirrend sein, deshalb setzen Sie beide Werte jetzt einfach auf 100. Mit Weights werden wir uns in Schritt 3 beschäftigen.

Nachdem Sie die Rahmenbedingungen erstellt haben, können Sie sie mit der Methode setConstraints() an ein Objekt anbinden. Diese Methode wird in GridBagLayout definiert und arbeitet mit zwei Argumenten: der Komponente (hier eine Schaltfläche) und den Rahmenbedingungen für diese Schaltfläche. Zum Schluß können Sie die Schaltfläche in das Panel einfügen.

Wenn Sie die Rahmenbedingungen gesetzt und einer Komponente zugeordnet haben, können Sie das Objekt GridBagConstraints zur Einrichtung der Rahmenbedingungen für das nächste Objekt erneut verwenden. Hierfür duplizieren Sie diese vier Zeilen für jede Komponente im Raster, wobei Sie unterschiedliche Werte für die Methode buildConstraints() verwenden. Um Platz zu sparen, werden Ihnen nur die buildConstraints()-Methoden für die letzten vier Zellen gezeigt.

Die zweite einzufügende Zelle ist die das Textfeld für den Namen beinhaltende Zelle. Die Koordinaten für diese Zelle sind 1,0 (zweite Spalte, erste Zeile); auch in diesem Fall wird nur eine Zelle eingefaßt, und die Weights sind (für den Moment) auch jeweils 100:

buildConstraints(constraints, 1, 0, 1, 1, 100, 100);

Die nächsten beiden Komponenten, ein Label und ein Textfeld, sind mit den beiden vorherigen beinahe identisch; den einzigen Unterschied bilden die Koordinaten der Zelle. Das Paßwort-Label ist auf 0,1 (erste Spalte, zweite Zeile) und das Paßworttextfeld auf 1,1 (zweite Spalte, zweite Zeile) gesetzt:

buildConstraints(constraints, 0, 1, 1, 1, 100, 100);

buildConstraints(constraints, 1, 1, 1, 1, 100, 100);

Zum Schluß benötigen Sie die Schaltfläche OK, welches ein, zwei Zellen auf der unteren Zeile des Panel umfassende Komponente ist. In diesem Fall sind die Zellkoordinaten die ganz links und ganz oben angeordnete Zelle am Anfang der Einfassung (0,2). Im Gegensatz zu den vorherigen Komponenten setzen Sie hier gridwidth und gridheight auf einen von 1 verschiedenen Wert, da diese Zelle mehrere Spalten umfassen kann. gridweight ist 2 (sie umfaßt zwei Spalten), und die gridheight ist 1 (sie umfaßt nur eine Zeile):

buildConstraints(constraints, 0, 2, 2, 1, 100, 100);

Haben Sie es verstanden? Nun haben Sie die Anordnung der Rahmenbedingungen für alle Komponenten, die Sie in das GridLayout einfügen möchten, gesetzt. Sie müssen allerdings auch die Rahmenbedingungen jeder Komponente dem Layout-Manager zuordnen und dann jede Komponente in das Panel einfügen. Abbildung 12.17 zeigt das bisherige Ergebnis. Beachten Sie, daß Sie sich hier keine Gedanken um die genauen proportionalen Verhältnisse machen müssen, oder darum, sicherzustellen, daß alles richtig arrangiert ist. Was Sie jetzt im Auge behalten sollten ist sicherzustellen, daß das Raster funktioniert, daß Sie die richtige Anzahl von Zeilen und Spalten angegeben haben, daß die Spannweiten korrekt sind und daß nichts Auffälliges (Zellen am falschen Platz, sich überlappende Zellen oder ähnliches) zu erkennen ist.

siehe Abbildung

Abbildung 12.17:
GridBagLayout – erster Arbeitsgang

Dritter Schritt: Festlegen der Proportionen

Im nächsten Schritt geht es um die Festlegung der Proportionen von Zeilen und Spalten im Verhältnis zu anderen Zeilen und Spalten. Nehmen wir an, Sie möchten beispielsweise den Platz für die Labels (Name und Paßwort) kleiner als für die Textfelder gestalten. Außerdem möchten Sie die Höhe der Schaltfläche OK im unteren Teil auf lediglich die halbe Höhe der beiden darüber angebrachten Textfelder reduzieren. Verwenden Sie die Rahmenbedingungen weightx und weighty zur Anordnung der Proportionen der Zelle in Ihrem Layout.

Der einfachste Weg, mit weightx und weighty umzugehen, ist es sich ihre Werte als Prozentsätze der Gesamtbreite und -höhe des Panel vorzustellen. Diese können entweder 0 oder eine andere Zahl sein, falls die Gewichtung oder Höhe von einer anderen Zelle gesetzt wurde. Deshalb sollten die Werte für weightx und weighty aller Komponenten eine Gesamtsumme von 100 ergeben.


Eigentlich sind die Werte für weightx und weighty keine Prozentsätze; es sind einfach Proportionen – und können einen beliebigen Wert annehmen. Bei der Berechnung der Proportionen werden alle Werte in eine Richtung aufsummiert, so daß jeder einzelne Wert im proportionalen Verhältnis zur dieser Gesamtsumme steht (anders gesagt, durch die Gesamtsumme geteilt, um tatsächlich einen Prozentsatz zu erhalten). Da dieses ganze Verfahren nicht intuitiv ist, ist es wesentlich einfacher, das ganze als Prozentsätze zu betrachten und sicherzustellen, daß das Ergebnis ihrer Summe 100 ist, womit wir auf der sicheren Seite sind.

Welche Zellen erhalten also Werte und welche erhalten 0? Mehrere Zeilen und Spalten umfassende Zellen sollten immer 0 sein, in der Richtung, in der die Einfassung erfolgt. Darüber hinaus liegt die Entscheidung einfach in der Wahl eines Werts für eine Zelle, wonach dann alle anderen Zellen in dieser Zeile oder Spalte 0 sein sollten.

Schauen wir uns einmal die fünf Aufrufe von buildConstraints(), die im vorhergehenden Schritt ausgeführt wurden, an:

buildConstraints(constraints, 0, 0, 1, 1, 100, 100); //Name

buildConstraints(constraints, 1, 0, 1, 1, 100, 100); //Textfeld Namen
buildConstraints(constraints, 0, 1, 1, 1, 100, 100); //Paßwort
buildConstraints(constraints, 1, 1, 1, 1, 100, 100); //Textfeld Paßwort
buildConstraints(constraints, 0, 2, 2, 1, 100, 100); //OK-Schaltfläche

Die letzten beiden Argumente müssen Sie bei jedem Aufruf an buildConstraints entweder in einen Wert oder 0 umändern. Beginnen wir mit der X-Ausrichtung (die Proportionen der Spalten), welches das zweitletzte Argument in dieser Liste ist.

Wenn Sie sich Abbildung 12.15 noch einmal anschauen (die Illustration des Panel mit dem aufgesetzten Raster), werden Sie feststellen, daß der Umfang der zweiten Spalte bei weitem größer als der der ersten ist. Nehmen wir an, Sie wollen die theoretischen Prozentsätze für diese Spalten auswählen, und nehmen wir weiter an, daß Sie den ersten Prozentsatz mit 10 und den zweiten mit 90 festlegen (alles rein theoretische Annahmen – so sollten auch Sie vorgehen). Diese beiden angenommenen Prozentsätze können Sie nun Zellen zuordnen. Sie können der Zelle nicht beliebige Werte mit der Schaltfläche OK zuordnen, weil die Zelle beide Spalten einfaßt und deshalb hier nicht mit Prozentsätzen gearbeitet werden kann. Fügen Sie sie also den ersten beiden Zellen den Label Namen und das Textfeld Namen hinzu:

buildConstraints(constraints, 0, 0, 1, 1, 10, 100); //Name

buildConstraints(constraints, 1, 0, 1, 1, 90, 100); //Textfeld Namen

Und was ist mit den Werten der verbleibenden zwei Zellen, dem Label Paßwort und dem Textfeld dafür? Da mit dem Label Namen und dem Namensfeld die Proportionen der Spalten bereits festgelegt wurden, müssen sie hier nicht neu gesetzt werden. Geben Sie diesen beiden und der Zelle für das OK-Feld die Werte 0:

buildConstraints(constraints, 0, 1, 1, 1, 0, 100); //Paßwort

buildConstraints(constraints, 1, 1, 1, 1, 0, 100); //Textfeld Paßwort
buildConstraints(constraints, 0, 2, 2, 1, 0, 100); //OK-Schaltfläche

Beachten Sie in diesem Fall, das der Wert 0 nicht bedeutet, das die Zellenbreite 0 ist. Bei diesen Werten handelt es ich um Proportionen, nicht Pixel-Werte. Eine 0 bedeutet einfach, daß die entsprechende Proportion an anderer Stelle gesetzt wurde, und 0 bedeutet eigentlich so viel wie »entsprechend anpassen.«

Die Gesamtsumme aller weightx-Rahmenbedingungen ist jetzt 100, und Sie können sich nun den weighty-Eigenschaften widmen. Hier gibt es drei Zeilen. Wenn Sie einen Blick auf das von Ihnen gezeichnete Raster werfen, sieht es so aus, als ob etwa 20 Prozent auf die Schaltfläche und die restlichen 40 (40 Prozent pro Feld) auf die Textfelder verteilt sind. Sie müssen den Wert, wie bei den X-Werten, jeweils nur für eine Zelle pro Zeile setzen (die beiden Labels und die Schaltfläche), während alle anderen Zellen als weightx 0 haben.

Hier die endgültigen fünf Aufrufe an buildConstraints() mit den entsprechenden Gewichtungen:

buildConstraints(constraints, 0, 0, 1, 1, 10, 40); //Name

buildConstraints(constraints, 1, 0, 1, 1, 90, 0); //Textfeld Namen
buildConstraints(constraints, 0, 1, 1, 1, 0, 40); //Paßwort
buildConstraints(constraints, 1, 1, 1, 1, 0, 0); //Textfeld Paßwort
buildConstraints(constraints, 0, 2, 2, 1, 0, 20); //OK-Schaltfläche

Abbildung 12.18 zeigt das Ergebnis mit den richtigen Proportionen.

siehe Abbildung

Abbildung 12.18:
GridBagLayout, zweiter Arbeitsgang

Dieser Schritt zielt darauf ab, ein paar einfache Proportionen für die räumliche Anordnung der Zeilen und Zellen am Bildschirm zu erstellen, wodurch Ihnen eine grobe Einschätzung der Größe der verschiedenen Komponenten ermöglicht wird, allerdings sollten Sie an dieser Stelle mit vielen Versuchen und Fehlern rechnen.

Vierter Schritt: Komponenten einfügen und anordnen

Wenn das Layout und die Proportionen entsprechend vorbereitet sind, können Sie die Platzhalter-Schaltflächen durch richtige Label und Textfelder ersetzen. Da Sie hierfür bereits alles vorbereitet haben, sollte das problemlos funktionieren, richtig? Nicht ganz. Abbildung 12.19 zeigt Ihnen das Resultat, wenn Sie dieselben vorherigen Rahmenbedingungen benutzen und die Schaltflächen durch richtige Komponenten ersetzen.

siehe Abbildung

Abbildung 12.19:
GridBagLayout – fast geschafft

Dieses Layout kommt der Sache nahe, aber es sieht sonderbar aus. Die Textfelder sind zu hoch, und die Schaltfläche OK dehnt die Breite der Zelle.

Was jetzt noch fehlt, sind die Rahmenbedingungen, die für die Anordnung der Komponenten in der Zelle sorgen. Es gibt davon zwei: fill und anchor.

Die Rahmenbedingung fill legt für Komponenten, die sich in jede Richtung ausdehnen können (wie Textfelder und Schaltflächen), die Ausdehnungsrichtung fest. fill kann einen von vier, als Klassenvariablen in der Klasse GridBagCon-straints definierte Werten haben:


Denken Sie daran, daß dieses Layout dynamisch ist. Sie richten nicht die tatsächlichen Pixel-Maße von Komponenten ein, sondern bestimmen, in welcher Richtung sich diese Elemente bei einem bestimmten Panel von beliebiger Größe ausdehnen können.

Die Standard-Rahmenbedingung für fill ist für alle Komponenten NONE. Warum also füllen die Textfelder und Label die Zellen? Gehen Sie in Gedanken noch einmal zurück zum Beginn des Codes für dieses Beispiel, wo der init()-Methode folgende Zeile hinzugefügt wurde:

constraints.fill = GridBagConstraints.BOTH;

Jetzt verstehen Sie ihre Bedeutung. Bei der endgültigen Version dieses Applet entfernen Sie diese Zeile und fügen jeder eigenständigen Komponente die fill-Werte hinzu.

Die zweite, für das Aussehen einer Komponente in der Zelle maßgebliche Rahmenbedingung ist anchor. Diese Rahmenbedingung gilt nur für Komponenten, die nicht die gesamte Zelle füllen, und weist das AWT an, wo die Komponente innerhalb der Zelle auszurichten ist. Die möglichen Werte für die anchor-Rahmenbedingung sind GridBagConstraints.CENTER, wodurch die Komponente innerhalb der Zelle sowohl vertikal als auch horizontal ausgerichtet wird, oder einer von acht Ausrichtungswerten: GridBagConstraints.NORTH, GridBagConstraints.NORTHEAST, GridBagConstraints.EAST, GridBagConstraints.SOUTHEAST, GridBagConstraints.SOUTH, GridBagConstraints.SOUTHWEST, GridBagConstraints.WEST, oder GridBagConstraints.NORTHWEST. Der Standardwert für Anchor ist GridBagConstraints.CENTER.

Diese Rahmenbedingungen setzen Sie genau wie alle anderen: indem Sie die Instanzvariablen im GridBagConstraints-Objekt ändern. Sie können die Definition von buildConstraints() zur Aufnahme von zwei weiteren Argumenten (es handelt sich um int-Werte) ändern, oder Sie können diese einfach innerhalb der init()-Methode setzen. Es empfiehlt sich letzteres.

Behandeln Sie Standard-Werte mit Vorsicht. Denken Sie daran, daß, bedingt durch die erneute Verwendung desselben GridBagConstraints-Objektes für jede Komponente, ein paar Werte übrig bleiben können, nachdem Sie die Bearbeitung einer Komponente abgeschlossen haben. Wenn andererseits der Wert der fill- oder anchor-Eigenschaft von einem Objekt der gleiche ist wie bei der vorherigen Komponente, haben Sie den Vorteil, das Objekt nicht zurücksetzen müssen.

Für ein entsprechendes Beispiel nehmen wir drei Änderungen an den fill- und anchor-Eigenschaften der Komponenten vor:

Der entsprechende Code hierfür wird Ihnen im folgenden gezeigt; der vollständige Code für das Beispiel ist am Ende dieses Abschnitts aufgeführt. Sie können die vorgenommenen Änderungen erkennen.Objekte einfuegen

Fünfter Schritt: »Zurechtbasteln«

Dieser Schritt wurde der Liste hinzugefügt, da die Erfahrung bei der Erstellung von GridBagLayouts gezeigt hat, daß, auch wenn man alle erforderlichen Schritte ausführt, das entstandene Layout sich meist doch nicht so ganz richtig darstellt, und »Herumspielen« mit den verschiedenen Werten bzw. entsprechendes »Zurechtbasteln« der Rahmenbedingungen notwendig ist, um letztendlich das gewünschte Ergebnis zu erhalten. Das ist vollkommen in Ordnung; das Ziel der obigen vier Schritte war es, die endgültige Anordnung so gut wie möglich vorzubereiten und nicht jedes Mal ein perfektes Layout entwerfen.

Der Code

Listing 12.1 zeigt den vollständigen Code für das Panel-Layout, das Sie in diesem Abschnitt erstellt haben. Wenn jetzt noch Unklarheiten bestehen, sollten Sie diesen Code zeilenweise durchzuarbeiten, um ein klares Verständnis der verschiedenen Teile sicherzustellen.

Listing 12.1: Das Panel mit dem engültigen GridBagLayout

 1:import java.awt.*;

2:
3:public class GridBagTestFinal extends java.applet.Applet {
4:
5: void buildConstraints(GridBagConstraints gbc, int gx, int gy,
6: int gw, int gh,
7: int wx, int wy) {
8: gbc.gridx = gx;
9: gbc.gridy = gy;
10: gbc.gridwidth = gw;
11: gbc.gridheight = gh;
12: gbc.weightx = wx;
12: gbc.weighty = wy;
14: }
15:
16: public void init() {
17: GridBagLayout gridbag = new GridBagLayout();
18: GridBagConstraints constraints = new GridBagConstraints();
19: setLayout(gridbag);
20:
21: // Namenslabel
22: buildConstraints(constraints, 0, 0, 1, 1, 10, 40);
23: constraints.fill = GridBagConstraints.NONE;
24: constraints.anchor = GridBagConstraints.EAST;
25: Label label1 = new Label("Name:", Label.LEFT);
26: gridbag.setConstraints(label1, constraints);
27: add(label1);
28:
29: // Textfeld Namen
30: buildConstraints(constraints, 1, 0, 1, 1, 90, 0);
31: constraints.fill = GridBagConstraints.HORIZONTAL;
32: TextField tfname = new TextField();
33: gridbag.setConstraints(tfname, constraints);
34: add(tfname);
35:
36: // Paßwortlabel
37: buildConstraints(constraints, 0, 1, 1, 1, 0, 40);
38: constraints.fill = GridBagConstraints.NONE;
39: constraints.anchor = GridBagConstraints.EAST;
40: Label label2 = new Label("Password:", Label.LEFT);
41: gridbag.setConstraints(label2, constraints);
42: add(label2);
43:
44: // Textfeld Paßwort
45: buildConstraints(constraints, 1, 1, 1, 1, 0, 0);
46: constraints.fill = GridBagConstraints.HORIZONTAL;
47: TextField tfpass = new TextField();
48: tfpass.setEchoCharacter(‘*’);
49: gridbag.setConstraints(tfpass, constraints);
50: add(tfpass);
51:
52: // OK-Schatlfläche
53: buildConstraints(constraints, 0, 2, 2, 1, 0, 20);
54: constraints.fill = GridBagConstraints.NONE;
55: constraints.anchor = GridBagConstraints.CENTER;
56: Button okb = new Button("OK");
57: gridbag.setConstraints(okb, constraints);
58: add(okb);
59: }
60:}

ipadx und ipady

Bevor wir zum Abschluß der GridBag-Layouts kommen (sind wir noch nicht fertig?), müssen noch zwei weitere Rahmenbedingungen erwähnt werden: ipadx und ipady. Diese beiden Rahmenbedingungen kontrollieren das Auffüllen – hier geht es um den zusätzlichen Raum rund um eine einzelne Komponente. Standardmäßig ist kein zusätzlicher Raum um die Komponenten vorgegeben (was man am besten bei Komponenten, die ihre Zellen füllen, sehen kann).

Mit ipadx fügt man jeder Seite der Komponente Platz hinzu, und mit ipady fügt man entsprechenden Platz oben und unten ein.

Eckeinsätze (Insets)

Bei der Erstellung eines neuen Layout-Managers (oder bei der Verwendung von ipadx und ipady in GridBagLayouts) erzeugte horizontale und vertikale Abstände dienen zur Bestimmung des Platzes zwischen Komponenten in einem Panel. Eck-einsätze hingegen werden zur Festlegung des Randes um das Panel selbst benutzt. Die Klasse Insets bietet Eckeinsatzwerte für oben, unten, links und rechts, die dann verwendet werden, wenn das Panel gezeichnet wird.


Eckeinsätze (Insets) dienen zur Bestimmung des Platzes zwischen den Kanten eines Panels und seinen Komponenten.

Um Ihrem Layout einen Eckeinsatz hinzuzufügen, überschreiben Sie entweder die Methode insets() oder getInsets() in Ihrer Applet-Klasse oder anderen Panel-Klasse. Verwenden Sie für Java Version 1.02 insets(); und für Version 1.1 getInsets(). Sie erfüllen den gleichen Zweck, lediglich der Name wurde geändert.

Erstellen Sie mit der Methode insets() oder getInsets()ein neues Insets-Objekt, wobei der Konstruktor für die Insets-Klasse vier ganzzahlige Werte annimmt, für die Eckeinsätze oben, unten, links und rechts im Panel stehen. Die Methode insets() sollte dann das Insets-Objekt ausgeben. Es folgt ein Version 1.02-Code zum Einfügen von Eckeinsätzen für ein GridLayout mit den Werten: 10 oben und unten und 30 links und rechts. Abbildung 12.20 zeigt das Ergebnis.

public Insets insets() {

return new Insets(10, 30, 10, 30);
}

siehe Abbildung

Abbildung 12.20:
Ein Panel mit Eckeinsätzen mit 10 Pixel oben und unten und 30 Pixel links und rechts

Eingabefokus und mauslose Operationen (nur 1.1)

Eine der Neuheiten von Java 1.1 ist die mauslose Operation, das heißt, Sie können verschiedene AWT-Komponenten ohne Einsatz der Maus selektieren oder aktivieren. Dieses Merkmal ist besonders hilfreich bei Systemen ohne Maus oder für »Power User«, die die Benutzung von Tastaturbefehlen der Maus vorziehen.

Mit dem Eingabefokus können alle Standard-AWT-Komponenten aktiviert oder benutzt werden. Eingabefokus steht einfach für die zur Zeit aktive Komponente. Diese nimmt Bearbeitungs- und Aktivierungsbefehle entgegen. Den Eingabefokus kann jeweils nur einer Komponente zugewiesen sein; ist eine Zuweisung erfolgt, wird diese visuell angezeigt (beispielsweise sind Schaltflächen dann mit einer punktierten Linie eingerahmt). Wurde einer Komponente der Fokus zugewiesen, stehen Ihnen zur Bearbeitung oder Aktivierung dieser Komponente verschiedene Tasten zur Verfügung (Pfeil-Tasten, Eingabetaste, Leertaste), wobei diese Tasten allerdings je nach Plattform unterschiedlich sein können (einige Plattformen unterstützen den Eingebefokus für verschiedene Elemente gar nicht).

Um den Fokus zwischen Komponenten umzuschalten (eine sogenannte Focus Traversale), benutzen Sie die Tabulatortaste, um vorwärts und die Kombination Shift-Tabulatortaste um rückwärts zu springen. Die Reihenfolge, in der der Fokus von einer Komponente auf eine andere wechselt, wird durch die Reihenfolge festgelegt, in der diese Komponenten ihrer umgebenden Komponente hinzugefügt wurden. Zur Änderung der Fokusreihenfolge für eine Gruppe von Komponenten ändern Sie die Reihenfolge der Verwendung von add(), um sie in eine Komponente einzufügen.

Eine Komponente kann den Eingabefokus durch Verwendung der in der Komponentenklasse definierten, und daher allen AWT-Komponenten zur Verfügung stehenden Methode requestFocus() auch speziell anfordern.

Handhabung von Ereignissen der Benutzeroberfläche im 1.02-Ereignismodell

Falls Sie mit dem Bearbeiten der heutigen Lektion an dieser Stelle aufgehört haben, verfügen Sie über das Wissen, um ein Applet zu erstellen, das viele kleine Komponenten der Benutzeroberfläche enthält, mit dem entsprechenden Layout-Manager ansprechend am Bildschirm ausgelegt ist sowie Abstände und Eckein-sätze hat. Allerdings wäre dann Ihr Applet ziemlich fade, weil die Komponenten der Benutzeroberfläche eigentlich nichts tun, wenn man sie anklickt oder die entsprechende Taste drückt.

Damit Ihre Komponenten der Benutzeroberfläche etwas bewirken, ist die Verknüpfung mit einem Code zur Handhabung von Ereignissen erforderlich. Denken Sie an die gestrige Lektion bezüglich Maus- und Tastaturereignissen – genauso werden durch Ereignisse der Benutzeroberfläche Reaktionen Ihrer Applets oder Anwendung auf Eingaben durch den Benutzer hervorgerufen. Allerdings spielen sich die Ereignisse für AWT-Komponenten auf einer höheren Ebene ab; Schaltflächen beispielsweise verwenden Aktionsereignisse, die durch Anklicken der Schaltfläche ausgelöst werden. Sie brauchen sich über mouseDown oder mouseUp, oder wo diese eingesetzt wurden, keine Gedanken zu machen; die Komponente übernimmt diese Aufgabe für Sie.

Wie Sie bereits bei den Ereignissen der gestrigen Lektion gelernt haben, ist die Erstellung und Handhabung von Ereignissen der Benutzeroberfläche, je nach dem, ob Sie das 1.02- oder 1.1-Ereignismodell verwenden, unterschiedlich. Alles was Sie über die jeweiligen Modellunterschiede gestern gelernt haben, trifft auch für die heutige Lektion zu; die Unterschiede liegen in den Details. Im diesem Abschnitt wird die Handhabung von Ereignissen der Benutzeroberfläche für das Modell 1.02 beschrieben. Der nächste Abschnitt behandelt dann das gleiche Thema für das Modell 1.1.

Konzentrieren wir uns heute auf die, von den sechs grundlegenden Komponenten der Benutzeroberfläche erzeugten Ereignisse, mit denen Sie sich bereits beschäftigt haben. Eigentlich erzeugen lediglich fünf von ihnen Ereignisse (Labels sind statisch). Diese sechs Komponenten der Benutzeroberfläche können fünf Ereignisarten erzeugen:

Handhabung Aktionsereignisse

Aktionsereignisse sind die bei weitem am häufigsten eingesetzten Ereignisse der Benutzeroberfläche, und daher wird für ihre Handhabung eine spezielle Methode angewandt, genau wie bei den grundlegenden Maus- und Tastaturereignismethoden, die wir gestern durchgenommen haben.

Um ein von einer beliebigen Komponente der Benutzeroberfläche erzeugtes Aktionsereignis abzufangen, definieren Sie in Ihrem Applet oder Klasse eine action()-Methode mit folgender Signatur:

public boolean action(Event evt, Object arg) {

...
}

Diese action()-Methode dürfte angesichts der bereits gelernten grundlegenden Ereignismethoden für Maus und Tastatur vertraut aussehen. Wie jene Methoden wird sie an das Ereignisobjekt, das die jeweilige Aktion darstellt, weitergegeben. Außerdem erhält sie ein zusätzliches Objekt (bei diesem Code den Parameter arg), das ein beliebiger Klassentyp sein kann.

Das zweite Argument für die Aktionsmethode hängt von der Komponente der Benutzeroberfläche ab, durch die die Aktion erzeugt wird. Die Basisdefinition ist ein, durch die Komponente der Benutzeroberfläche bestimmtes »beliebiges arbiträres Argument« zur Weitergabe zusätzlicher Informationen, die Ihnen später bei der Bearbeitung der Aktion nützlich sein können. Tabelle 12.5 zeigt die zusätzlichen Argumente für jede Komponente der Benutzeroberfläche.

Tabelle 12.5: Aktionsargumente für jede Komponente der Benutzeroberfläche

Komponente

Argumenttyp

Enthält

Schaltfläche

Zeichenkette

Das Label der Schaltfläche

Kontrollfelder

boolesch

Immer true

Optionsfelder

boolesch

Immer true

Auswahlmenüs

Zeichenkette

Das Label des gewählten Elements

Textfelder

Zeichenkette

Textinhalt des Feldes

Als erstes müssen Sie innerhalb der action()-Methode testen, welche Komponente der Benutzeroberfläche die Aktion erzeugt hat (im Gegensatz zu Ereignissen der Maus oder Tastatur, wo es nicht so eine große Rolle spielt, da alle unterschiedlichen Komponenten Aktionen erzeugen können). Um dies zu vereinfachen, beinhaltet das Event-Objekt, das Sie beim Aufruf von action() erhalten, eine target-Instanzvariable, die eine Referenz zu dem Objekt, das das Ereignis aufgenommen hat, enthält. Sie können den instanceof-Operator wie folgt benutzen, um herauszufinden, von welcher Komponente das Ereignis ausging:

public boolean action(Event evt, Object arg) {

if (evt.target instanceof TextField)
return handleText(evt.target);
else if (evt.target instanceof Choice)
return handleChoice(arg);
...

return false;
}

In diesem Beispiel könnte action() entweder durch ein TextField oder ein Auswahlmenü erzeugt worden sein; die if-Anweisungen bestimmen, von welchem der beiden das Ereignis erzeugt wurde und rufen zur entsprechenden Bearbeitung eine andere Methode auf (handleText() oder hier handleChoice()). (Weder handleText() noch handleChoice() sind AWT-Methoden. Es wurden einfach zwei beliebige Hilfsmethoden ausgewählt. Die Erzeugung dieser Hilfsmethoden ist allgemein üblich, um zu verhindern, daß action() mit viel Code vollgestopft wird.)

Wie bei anderen Ereignismethoden, gibt action() einen booleschen Wert aus, und Sie sollten true ausgeben, falls action() das Ereignis selbst behandelt, oder false, falls das Ereignis weitergegeben (oder ignoriert) wird. In unserem Beispiel haben Sie die Kontrolle an die Methode handleText() oder handleChoice() weitergeben, und es ist deren Aufgabe, true oder false auszugeben, Sie können also false ausgeben (Sie erinnern sich, daß Sie true nur dann ausgeben, wenn diese Methode das Ereignis verarbeitet hat).

Zusätzliche Komplikationen können dann auftreten, wenn viele Komponenten mit denselben Klassen vorkommen – beispielsweise eine große Anzahl von Schaltflächen. Sie können alle Aktionen erzeugen und sind alle Instanzen von Button. Hier brauchen wir das zusätzliche Argument: Sie können die Labels, Elemente oder den Inhalt der Komponenten zur Bestimmung der das Ereignis erzeugenden Komponente und einfache Zeichenkettenvergleiche zur ihrer Auswahl verwenden. (Vergessen Sie dabei nicht, das Argument in den richtigen Objekt-Typ zu casten.)

public boolean action(Event evt, Object arg) {

if (evt.target instanceof Button) {
String labl = (String)arg;
if (labl.equals("OK"))
// OK-Schaltfläche handhaben
else if (labl.equals("Cancel"))
// Cancel-Schaltfläche handhaben
else if (labl.equals("Browse"))
// Browse-Schaltfläche handhaben
...
}


Und wie ist das bei Kontroll- und Optionsfeldern? Da ihr zusätzliches Argument immer true ist, würde entsprechendes Testen nicht viel nützen. Im allgemeinen sollten Sie auf ein gewähltes Kontroll- oder Optionsfeld nicht reagieren. Normalerweise können Kontroll- und Optionsfelder frei vom Benutzer gewählt oder nicht gewählt werden, und ihre Werte werden dann an anderer Stelle überprüft (beispielsweise bei Anklicken einer Schaltfläche).
Um eine Reaktion Ihres Programms auf die Auswahl eines Kontroll- oder Optionsfeldes zu initiieren, können Sie, anstatt das zusätzliche Argument zu verwenden, die Methode getLabel() benutzen, um das Label des Kontrollfeldes im Rumpf der action()-Methode zu ermitteln. (Praktisch alle Komponenten verfügen über ähnliche Methoden; die Anwendung gestaltet sich nur leichter, wenn die Information als zusätzliches Argument übergegeben wird.)

Im Abschnitt »Beispiel: Hintergrundfarbwechsler« erstellen Sie ein einfaches AWT-basiertes Applet, das Ihnen die Verwendung der action()-Methode in einer richtigen Anwendung zeigt.

Handhabung anderer Ereignisse

Wie bereits erwähnt, stellen Aktionsereignisse die bei weitem am häufigsten vorkommenden Ereignisse der Benutzeroberfläche dar, mit denen Sie sich im Hinblick auf die Komponenten, die in dieser Lektion behandelt wurden, beschäftigen werden. Allerdings können Sie vier weitere Ereignissarten in Ihrem eigenen Programm benutzen: Liste gewählt, Liste nicht gewählt, Fokuserhalt und ohne Fokusverlust.

Für die Ereignisse Fokuserhalt und Fokusverlust können Sie die Methoden gotFocus() und lostFocus() verwenden, die in gleicher Weise wie action()eingesetzt werden. Hier ihre Signatur:

public boolean gotFocus(Event evt, Object arg) {

...
}

public boolean lostFocus(Event evt, Object arg) {
...
}

Für die Ereignisse Liste gewählt und Liste nicht gewählt stehen keine Methoden zur Verfügung, die einfach überschreiben können. Für diese Ereignisse müssen Sie handleEvent() wie folgt verwenden:

public boolean handleEvent(Event evt) {

if (evt.id == Event.LIST_SELECT)
handleSelect(Event);
else if (evt.id == Event.LIST_DESELECT)
handleDeselect(Event);
else return super.handleEvent(evt);
}

In diesem Codebruchstück sind Event.LIST_SELECT und Event.LIST_DESELECT die offiziellen Ereigniskennzeichen für die Ereignisse Liste gewählt und Liste nicht gewählt. Hier wurde die Kontrolle einfach an die zwei Handler-Methoden handle-Select() und handleDeselect() weitergegeben, die theoretisch an anderer Stelle definiert sind. Beachten Sie den Aufruf an super.handleEvent() in der unteren Zeile; dieser Aufruf sorgt dafür, daß andere Ereignisse elegant in der Hierarchie an die ursprüngliche handleEvent()-Methode weitergegeben werden.

Handhabung von Ereignissen der Benutzeroberfläche im 1.1-Ereignismodell

Ereignisse der Benutzeroberfläche konnten der Generalüberholung des Ereignismodells in Java 1.1 nicht entgehen; was Sie bereits über die Unterschiede in der Handhabung der Maus- und Tastaturereignisse in den beiden Modellen gelernt haben, gilt auch für die Ereignisse der Benutzeroberfläche. Zum Glück haben Sie sich bereits in der gestrigen Lektion mit dem neuen Modell befaßt und müßten sich in etwa vorstellen können, was auf Sie zukommt.

Listeners für einfache Komponenten der Benutzeroberfläche

Ereignisse, die Sie für die einfachen Komponenten der Benutzeroberfläche einsetzen können, sind in 1.1 gleich; sie sind lediglich in zwei unterschiedliche Ereignisklassen unterteilt (darunter auch ein paar neue). Jede Ereignisklasse besitzt eine entsprechende Listener-Schnittstelle, die einen Satz auszuführender Methoden beinhaltet. Tabelle 12.6 zeigt die Ereignisse, die Ereignis-Listener-Klassen, die sie einsetzen, und die Methoden, mit diesen Ereignissen umzugehen.

Tabelle 12.6: Ereignisse, Listeners und Ereignismethoden

Ereignis

Listener-Schnittstelle

Methode

Aktion

ActionListener

public void actionPerformed(Action-Event e)

Liste gewählt, Liste nicht gewählt

ItemListener

public void itemStateChanged(Item-Event e)

Fokuserhalt

FocusListener

public void focusGained(FocusEvent e)

Fokusverlust

FocusListener

public void focusLost(FocusEvent e)

Text geändert

TextListener

public void textValueChanged(Text-Event e)

Komponente eingefügt

ContainerListener

public void componentAdded
(ContainerListener e)

Komponente entfernt

ContainerListener

public void componentRemoved
(ContainerListener e)

Die drei hier aufgeführten neue Ereignisse gelten nur für 1.1:

Wie Sie bereits in der gestrigen Lektion bei Ereignissen für Maus und Tastatur gelernt haben, können Sie durch Realisierung der richtigen Schnittstelle in Ihrer Applet-Klasse oder durch Erzeugen einer separaten Klasse Ihre eigenen Listeners erstellen. Beachten Sie allerdings dabei, daß für die hier erwähnten Listeners nur ContainerListener und FocusListener die Adapter-Klassen besitzen (Container-Adapter bzw. FocusAdapter). Daher müssen Sie die Schnittstellen für ActionListener, ItemListener und TextListener manuell realisieren.

Vergessen Sie bei der Implementierung einer Listener-Schnittstelle nicht, daß Sie alle Methoden in dieser Schnittstelle implementieren müssen. (Da Aktions- oder Listenereignisse jeweils nur eine Methode pro Listener haben, ist die Durchführung relativ einfach.)

Im Rumpf Ihrer Methoden erhalten Sie durch Überprüfen des Objektes häufig zusätzliche Informationen über das Ereignis (bei Einzelereignissen beispielsweise darüber, ob es gewählt oder nicht gewählt wurde). (Für Informationen über einige der Ereignismerkmale werfen Sie einen Blick in die API-Dokumentation.) Eine der wirklich nützlichen Ereignismethoden ist die getSource()-Methode, deren Verwendung Ihnen eine Referenz zu der Komponente, die das Ereignis erhalten hat, gibt. Mit dieser Referenz können Sie dann die verschiedenen Komponentenmethoden zur Identifizierung der Komponente (oder ihrer Modifizierung) einsetzen:

public void actionPerformed(ActionEvent e) {

Button theSource = e.getSource;
if (theSource.getLabel().equals("OK")) {
// Behandlung von OK
} else if (theSource.getLabel().equals("Cancel")) {
// Behandlung von Cancel
}
}

Registrieren von Listeners der Benutzeroberfläche

Beim Registrieren Ihrer Listeners der Benutzeroberfläche können Sie aus drei Methoden auswählen: addActionListener(), addItemListener(), und addFocusListener(), die die Listeners ihrer Zuordnung entsprechend registrieren. Wie bei anderen Listeners ist auch hier die init()-Methode der beste Ort für den Einsatz dieser Methoden, was im allgemeinen direkt im Anschluß an den Code zur Erstellung der Komponente, die diesen Listener verwendet, erfolgt.

Beachten Sie, daß – im Gegensatz zu den Maus- und Tastaturmethoden, die im Applet registriert werden, – Sie im Fall von Komponenten-Listeners, den Listener bei jeder einzelnen, das Ereignis empfangenden Komponente registrieren müssen. Um beispielsweise ein Aktionsereignis einer Schaltfläche zuzuweisen, müssen Sie die Methode addActionListener() für dieses und für jedes andere Schaltflächenobjekt in Ihrem Applet aufrufen. Es folgt ein einfaches Beispiel, in dem jeweils die Klassen OKButtonAction und CancelButtonAction den ActionListener implementieren und den Code zur Handhabung jedes Schaltflächentyps beinhalten:

public void init() {

Button OKButton = new Button("OK");
OKButtonAction ok = new OKButtonAction();
OKButton.addActionListener(ok);

Button CancelButton = new Button("Cancel");
CancelButtonAction cancel = new CancelButtonAction();
CancelButton.addActionListener(cancel);

add(OKButton);
add(CancelButton);
}

Dieses Beispiel zeigt eigentlich zwei Listener-Klassen für jede Schaltfläche. Je nach dem wie Sie Ihren Code strukturieren möchten, stehen Ihnen entweder mehrere Listener-Klassen zur Verfügung, eine einzelne Listener-Klasse, um von jeder Komponente eine gesamte Klasse von Ereignissen zu verarbeiten, oder Sie können die gesamte Ereignisverarbeitung in das Applet stellen. Eine der angenehmen Eigenschaften des neuen 1.1-Ereignismodells ist, daß Ihnen für jede einzelne Komponente die Anordnung Ihres Ereigniscodes an der Ihnen passenden Stelle geboten wird: in Ihrer Hauptklasse, in einer Einzelklasse für alle Ereignisse, in für jeden Ereignistyp separaten Klassen oder in mehreren Listener-Klassen.

Unterschiede zwischen den Ereignissen der Benutzeroberfläche in 1.02 und 1.1

Zur Umwandlung Ihres alten Java-Codes auf das 1.1-Ereignismodell erweist sich eine Liste als hilfreich, die spezifischen Änderungen von alt auf neu beinhaltet.

Wie bei den Ereignissen von Maus und Tastatur sind alle Ereignisse der Benutzeroberfläche im 1.02-Modell Instanzen der Event-Klasse. Im Modell 1.1 sind Aktionsereignisse Instanzen von ActionEvent, Fokusereignisse (Fokuserhalt und Fokusverlust) Instanzen von FocusEvent und Liste gewählt und Liste nicht gewählt sind Instanzen des ItemEvent.

Im folgenden sind spezifische Änderungen, auf die Sie beim Code achten müssen, aufgeführt:

Bei jeder der neuen Ereignismethoden ist das einzige übergegebene Argument das Ereignisobjekt, das zusätzliche Argument entfällt. In vielen Fällen können Sie die Verwendung des zusätzlichen Argumentes durch Anwendung der für das Ereignisobjekt definierten Methode getSource() umgehen, die eine Instanz der das Ereignis erzeugenden Komponente ausgibt. Anschließend können Sie beliebige Kriterien verwenden (das Label oder eine andere Kennzeichnung), um über das weitere Vorgehen zu entscheiden.

Die Zielinstanzvariable (für Event-Objekte definiert, die die Komponente zurückgibt, die das Ereignis erhalten hat) sollte aus dem gleichen Grund durch getSource() ersetzt werden.

Und schließlich sollten Sie noch auf zwei neue Ereignisse achten: TextEvent, das das Ereignis Text geändert handhabt, und ContainerEvent, das für die Ereignisse Komponente eingefügt und Komponente entfernt zuständig ist. Tabelle 12.6 zeigt die Listeners und Methoden für diese neuen Ereignisse.

Beispiel: Hintergrundfarbwechsler

Aufgrund von Codebruchstücke allein die Zusammenhänge aller Teile zu verstehen, ist schwer. Machen wir uns also an die Lösung dieses Problems und erstellen ein einfaches AWT-Applet. Morgen, nachdem Sie mehr über die komplexeren Teile des AWT gelernt haben, werden wir uns mit einem etwas anspruchsvolleren Applet befassen, um den bisherigen Lerninhalt zu untermauern.

Das in Abbildung 12.21 dargestellte Applet, das Sie in diesem Abschnitt erstellen werden, kann noch nicht viel: es verwendet fünf Schaltflächen, die übersichtlich oben am Bildschirm angeordnet sind, wobei jede mit dem Namen einer Farbe beschriftet ist. Jede Schaltfläche ändert die Hintergrundfarbe des Applet in die auf der Schaltfläche vermerkte Farbe.

siehe Abbildung

Abbildung 12.21:
Das Applet ButtonActionsTest

In diesem Abschnitt werden Sie zwei Versionen dieses Applet erzeugen: eine, die das 1.02- und die andere, die das 1.1-Ereignismodell verwendet. Der erste Schritt in diesem Abschnitt besteht allerdings darin, den Code der Benutzeroberfläche, den beide Modelle miteinander gemein haben, zu erzeugen. Im allgemeinen ist dies der beste Weg zur Erstellung eines AWT-basierten Applet: Erzeugen Sie die Komponenten und das Layout, und stellen Sie sicher, daß alles richtig aussieht, bevor Sie sich an die Einbindung der Ereignisse und damit an die eigentliche Arbeit mit dem Applet machen.

Bei diesem Applet sind die Komponenten und das Layout ausgesprochen einfach gehalten. Das Applet beinhaltet fünf einfache, oben am Bildschirm in einer Reihe angeordnete Schaltflächen. Ein FlowLayout eignet sich für diese Anordnung am besten und erfordert wenig Arbeit.

Hier der Code der für dieses Applet erzeugten Klassenstruktur und init()-Methode. Das FlowLayout ist zentriert, und jede Schaltfläche hat einen Abstand von 10 Punkten. Anschließend müssen Sie lediglich die einzelnen Schaltflächen erstellen und hinzufügen.

import java.awt.*;


public class ButtonActionsTest extends java.applet.Applet {

Button redButton,blueButton,greenButton,whiteButton,blackButton;

public void init() {
setBackground(Color.white);
setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10));

redButton = new Button("Red");
add(redButton);
blueButton = new Button("Blue");
add(blueButton);
greenButton = new Button("Green");
add(greenButton);
whiteButton = new Button("White");
add(whiteButton);
blackButton = new Button("Black");
add(blackButton);
}

Auf den ersten Blick sieht dieser Code wahrscheinlich umfangreicher als notwendig aus. Sie könnten dagegen setzen, daß man zur Unterbringung der Schaltflächen eigentlich nicht alle Instanzvariablen benötigt. Das Ganze ist in der Tat ein bißchen undurchsichtig; da das Applet bereits erstellt ist, ist die geeignete Schreibweise bekannt, und die Instanzvariablen sorgen später für Erleichterung (haben Sie Vertrauen). Es wird häufiger vorkommen, daß, wenn Sie Ihre eigenen Applets schreiben, der von Ihnen ursprünglich für die Benutzeroberfläche geschriebene Code nicht gut funktioniert und Sie ihn entsprechend ändern müssen. Das macht überhaupt nichts! Je mehr Applets Sie schreiben, desto leichter werden Sie verstehen, wie letztendlich alles zusammenpaßt.

Ereigniscode einfügen (1.02)

An dieser Stelle stehen Ihnen zwei Möglichkeiten zur Verfügung, Ihrem Applet den letzten Schliff zu geben: eine für das 1.02- und eine zweite für das 1.1-Ereignismodell. In diesem Abschnitt hier erstellen Sie die 1.02-Version. Im nächsten Abschnitt gehen Sie noch einmal zu diesem Gerüst der Benutzeroberfläche zurück und fügen die Ereignisse für das 1.1-Modell ein.

Das Anklicken von Schaltflächen löst Aktionsereignisse aus. Wie Sie bereits wissen, verwenden Sie zur Handhabung eines Aktionsereignisses die Methode action(). Diese action()-Methode wird hier folgendes auslösen:

Bevor wir uns dem Schreiben der action()-Methode widmen, ist noch eine weitere designbezogene Entscheidung zu treffen. Im wesentlichen sind die letzten drei Schritte – bis auf kleine Unterschiede – für jede Schaltfläche identisch, so daß es wirklich Sinn macht, sie in einer eigenen Methode unterzubringen. Sie heißt changeColor() und wird zur Vereinfachung der Logik in action() beitragen.

Auf dieser Grundlage gestaltet sich die action()-Methode einfach:

public boolean action(Event evt, Object arg) {

if (evt.target instanceof Button) {
changeColor((Button)evt.target);
return true;
} else return false;
}

Diese action()-Methode unterscheidet sich wenig von den einfachen, im Abschnitt über Aktionen erzeugten. Der erste Schritt beinhaltet die Verwendung von evt.target, um sicherzustellen, daß die Komponente eine Schaltfläche ist. An dieser Stelle geben Sie die Kontrolle an die noch zu schreibende Methode changeColor() weiter und geben true aus. Falls das Ereignis keine Schaltfläche ist, geben Sie false aus.

Beachten Sie das eine Argument für changeColor(). Mit diesem Argument geben Sie das eigentliche Schaltflächenobjekt, das das Ereignis erhalten hat, an die Methode changeColor()weiter. (Das Objekt in evt.target ist eine Instanz der Klasse Object; es muß also in einen Button-Objekt gecastet werden, damit Sie es als Schaltfläche verwenden können.) Ab hier wird die Methode changeColor() dies handhaben.

Apropos changeColor(), machen wir weiter und definieren jetzt diese Methode. Die Methode changeColor() ist hauptsächlich darauf ausgerichtet festzustellen, welche Schaltfläche angeklickt wurde. Sie erinnern sich, daß das zusätzliche Argument bei action() das Label der Schaltfläche war. Obwohl Sie mit einem Zeichenkettenvergleich in changeColor() herausfinden können, welche Schaltfläche angeklickt wurde, stellt das nicht die eleganteste Lösung dar und macht Ihren Ereigniscode in zu starkem Maße von der grafischen Benutzeroberfläche abhängig. Falls Sie ein Schaltflächen-Label ändern möchten, müssen Sie noch einmal zurückgehen und auch Ihren Ereigniscode neu bearbeiten. In diesem Applet können Sie somit das zusätzliche Argument vollkommen ignorieren.

Wie wissen Sie nun, welche Schaltfläche angeklickt wurde? An dieser Stelle kommen die Istanzvariablen der Schaltfläche ins Spiel. Das in der Zielinstanzvariablen des Ereignisses enthaltene Objekt – das Sie an changeColor() weitergegeben haben – ist eine Instanz von Button, und eine dieser Instanzvariablen enthält eine Referenz zu genau demselben Objekt. Sie müssen die beiden nur in changeColor() vergleichen, um zu sehen, ob sie dasselbe Objekt darstellen, den Hintergrund einstellen und neu zeichnen, und zwar folgendermaßen:

void changeColor(Button b) {

if (b == redButton) setBackground(Color.red);
else if (b == blueButton) setBackground(Color.blue);
else if (b == greenButton) setBackground(Color.green);
else if (b == whiteButton) setBackground(Color.white);
else setBackground(Color.black);

repaint();
}

Anklicken einer Schaltfläche von der Benutzeroberfläche aus ruft action()auf, action() ruft changeColor()auf und changeColor() stellt den entsprechenden Hintergrund ein. Ganz einfach! Listing 12.2 zeigt das fertige Applet.

Listing 12.2: Das fertige ButtonActionsTest.-Applet (Version 1.02)

 1: import java.awt.*;

2:
3: public class ButtonActionsTest extends java.applet.Applet {
4:
5: Button redButton,blueButton,greenButton,whiteButton,blackButton;
6:
7: public void init() {
8: setBackground(Color.white);
9: setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10));
10:
11: redButton = new Button("Red");
12: add(redButton);
13: blueButton = new Button("Blue");
14: add(blueButton);
15: greenButton = new Button("Green");
16: add(greenButton);
17: whiteButton = new Button("White");
18: add(whiteButton);
19: blackButton = new Button("Black");
20: add(blackButton);
21: }
22:
23: public boolean action(Event evt, Object arg) {
24: if (evt.target instanceof Button) {
25: changeColor((Button)evt.target);
26: return true;
27: } else return false;
28: }
29:
30: void changeColor(Button b) {
31: if (b == redButton) setBackground(Color.red);
32: else if (b == blueButton) setBackground(Color.blue);
33: else if (b == greenButton) setBackground(Color.green);
34: else if (b == whiteButton) setBackground(Color.white);
35: else setBackground(Color.black);
36:
37: repaint();
38: }
39:}

Ereigniscode einfügen (1.1)

Zur Behandlung dieses Abschnittes gehen wir zur unbearbeiteten Benutzeroberfläche zurück und fangen noch einmal an. Dieses Mal fügen Sie den Ereigniscode gemäß dem 1.1-Ereignismodell ein. Das Ergebnis ist dasselbe, lediglich der angewandte Code ist unterschiedlich.

Zur Handhabung von Schaltflächenaktionen müssen Sie die ActionListener-Schnittstelle implementieren. In der gestrigen Lektion wurde Ihnen erklärt, daß Sie entweder das Applet umwandeln können, so daß es seine eigene Schnittstelle ist, oder eine separate Klasse erzeugen können, die als der entsprechende Listener dient. Da Sie ersteres in einem gestrigen Beispiel ausgeführt haben, probieren wir dieses Mal letzteres und erstellen eine separate Listener-Klasse.

Ein Hinweis bezüglich Handler-Klassen

Bis zu dieser Stelle des Buches besaßen die von Ihnen erstellten Applets und Programme nur eine Klasse, so daß dieser neue Schritt der Erzeugung mehrerer Klassen verwirrend sein könnte. Tatsache ist, daß Ihr Applet so viele Klassen wie erforderlich haben kann; Sie sind nicht auf die eine Klasse beschränkt. Komplexe Applets haben im allgemeinen eine einzelne Applet-Klasse (die von java.applet.-Applet abgeleitet ist) und eine beliebige Anzahl »Handler«-Klassen. So lange sich die zusätzlichen Klassen im gleichen Verzeichnis wie Ihre Applet-Klasse befindet, müssen Sie sie nicht speziell von anderer Stelle importieren oder besondere Aktionen ausführen. Sie können sie durch den Namen aufrufen, neue Instanzen von ihnen erstellen, ihre Variablen setzen und ihre Methoden aufrufen, als seien sie Klassen des Java-Standardpakets.

Der zweite, bei der Verwendung mehrerer Klassen zu beachtende Punkt ist die Positionierung des Quellcodes. Am Anfang dieses Buches wurde vorgeschlagen, daß jede Klassendefinition in ihrer eigenen .java-Datei enthalten sein sollte, wobei der Name der Quelldatei mit dem Klassennamen identisch ist. Diesen Weg zu gehen ist zwar gut, aber aus technischer Sicht nicht erforderlich.

Die Regel besagt eigentlich, daß Sie beliebig viele Klassendefinitionen in eine einzige Quelldatei eingeben können, so lange nur eine dieser Klassen als öffentliche Klasse deklariert wird (hat ein public-Schlüsselwort am Beginn der Definition). Die .java-Datei wird dann nach der öffentlichen Klasse benannt. Wenn Sie diese eine Quelldatei übersetzten, wird das Java-Übersetzungsprogramm für jede Klassendefinition in der Datei separate .class-Dateien erzeugen.

Einige Programmierer bevorzugen diese, durch Eingabe mehrerer Klassen in eine einzelne Quelldatei gekennzeichnete Arbeitsweise, damit nur eine Datei bearbeitet werden muß. Bei Java wird die Eingabe jeder Klasse in ihre eigene Quelldatei bevorzugt, da es diese Weise leichter ist nachzuvollziehen ist, wo sich die einzelnen Dinge befinden. Die Existenz separater Quelldateien bedeutet weiterhin, daß Sie nur geänderte Dateien übersetzen können, falls Ihre Entwicklungsumgebung diese Fähigkeit unterstützt.

Die Organisation Ihrer Quelldateien bleibt Ihnen überlassen. Am Tag 15 werden Sie mehr über Klassen, Modifiers, Zugriffskontrolle und Klassendesign erfahren.

Listener-Klasse erstellen

Widmen wir uns jetzt der Erstellung der Listener-Klasse. ActionListener hat keine Adapter-Klasse, so daß Sie die Schnittstelle manuell implementieren müssen. Da die Schnittstelle nur eine Methode hat, ist dies eine einfache Aufgabe. Es folgt der Klassenrahmen für eine Listener-Klasse genannt HandleButton:

import java.awt.*;

import java.awt.event.*;

public class HandleButton implements ActionListener {

public void actionPerformed(ActionEvent e) {
}
}

Halten Sie hier kurz inne, um den nächsten Schritt zu überdenken: was kommt in die Methode actionPerformed()? Sie könnten die gleiche Methode wie für 1.02 anwenden, wo Sie einen einzelnen Listener für alle Schaltflächen haben und dann nur if-else-Blöcke verwenden, um herauszufinden, welche Schaltfläche das Ereignis erhält. Oder Sie könnten etwas noch Interessanteres und Effizienteres tun, das sich die Vorteile des neuen Modells und die Existenz dieser zusätzlichen Listener-Klasse voll zunutze macht.

Können Sie nicht einfach – anstatt ein Listener-Objekt zu erzeugen, das diesen einen Listener für alle Schaltflächen registriert, und dann if-else zur Entscheidung heranzuziehen, welche Schaltfläche das Ereignis erhalten hat – mehrere Listener-Objekte aus dieser einen Klasse erzeugen, von denen jedes das spezielle Ereignis für die bestimmte Schaltfläche handhabt? Wobei es hier nicht darum geht, für jede Schaltfläche separate Listener-Klassen zu erzeugen (RedHandleButton, GreenHandleButton, usw.). Statt dessen können Sie eine einzelne HandleButton-Klasse erzeugen, die so allgemein gehalten ist, daß Sie sie den Anforderungen jeder Schaltfläche, die sie einsetzen muß, genau anpassen können. Diese Anpassung können Sie bei der Erzeugung und Registrierung der Listeners im Haupt-Applet durchführen.

Und so wird die Methode angewandt. HandleButton ist eine eigene Klasse und besitzt alle grundlegenden Eigenschaften der Klassen, die Sie in der vergangenen Woche kennengelernt haben. Als erstes muß sie einen auf das ihr zugewiesene Applet verweisenden Zeiger korrekt bedienen, um somit die Änderung des Hintergrundes zu bewirken. Fügen Sie also der HandleButton-Klasse eine Instanzvariable für die Applet-Klasse hinzu (beachten Sie, daß der Typ die für das Applet erzeugte Klasse ist und nicht die generische Applet-Klasse):

ButtonActionsTest theApplet;

Zweitens, wenn die Klasse zur Handhabung aller Schaltflächenereignisse allgemein gehalten sein soll, stellt nur die Farbe für die Änderung des Hintergrundes einen echten Unterschied zwischen den Listener-Objekten dar. Diese Farbe ergibt ein perfektes Attribut. Fügen Sie also für die Farbe auf folgende Weise eine Instanzvariable hinzu:

Color theColor;

Jetzt haben Sie also eine Klasse mit zwei Instanzvariablen. Wie setzt man nun diese Variablen? Einer der besten Wege, Variablen in einer Klasse zu initialisieren ist, einen Konstruktor für sie zu erzeugen und diesen Instanzvariablen dann Werte zuzuweisen, wenn das Objekt erstellt wird. Sie erinnern sich an Konstruktoren von Tag 5, »Arrays, Bedingungen und Schleifen«? Der folgende eignet sich gut für diese Klasse:

HandleButton(ButtonActionsTest a, Color c) {

theApp = a;
theColor = c;
}

Fast geschafft. Der letzte Schritt besteht darin, den Inhalt der actionPerformed()-Methode zu erzeugen. Wenn die Listener-Objekte, der einzelnen Schaltflächen bereits das zu ändernde Applet und die entsprechend Farbe kennen (aus den Werten der Instanzvariablen), ist kein Test mehr erforderlich. Die einzelnen Objekte müssen lediglich zwei Aufgaben ausführen: den Hintergrund einstellen und repaint() aufrufen. Diese Methoden können Sie mit dem Applet-Objekt aufrufen:

public void actionPerformed(ActionEvent e) {

theApp.setBackground(theColor);
theApp.repaint();
}

Listeners erstellen und registrieren

HandleButton haben wir abgehandelt; es ist ein generischer Action-Listener, der jeder Schaltfläche individuell angepaßt und zugewiesen wird. Gehen wir zurück zum ButtonActionsTest-Applet, um genau dies zum Abschluß zu bringen.

Innerhalb von init() steht für das Applet eine Gruppe von Zeilen zur Erstellung und Einfügung der Schaltflächen in das Applet zur Verfügung. Um die Listeners zu erzeugen und zu registrieren, müssen Sie für jede Schaltfläche ein paar Zeilen hinzufügen.

Beginnen Sie mit einer vorläufigen Variablen zur Unterbringung des Listener-Objektes. Diese Variable ist vom Typ HandleButton (der gerade erzeugten Listener-Klasse):

HandleButton he;

Die erste Schaltfläche: Die »rote« Schaltfläche. Die Schaltfläche haben Sie bereits mit der folgenden Zeile erzeugt:

redButton = new Button("Red");

Erzeugen Sie jetzt eine Instanz des HandleButton zur Handhabung der »roten« Schaltfläche. Der für HandleButton definierte Konstruktor benötigt zwei Argumente: das Applet und eine Instanz von Color. Wir können ihn verwenden, um eine Referenz auf das Applet und Color.red für ein Color-Objekt, das die Farbe Rot darstellt, zu erzeugen:

he = new HandleButton(this, Color.red);

Anschließend können Sie diesen Listener einfach der Schaltfläche zuweisen und die Schaltfläche dem Applet hinzufügen:

redButton.addActionListener(he);

add(redButton);

Die gleiche Vorgehensweise gilt für die folgende »blaue« Schaltfläche. Erzeugen Sie für diese Schaltfläche eine andere Instanz von HandleButton, und initialisieren Sie sie mit einem blauen Color-Objekt, registrieren Sie sie, und fügen Sie die Schaltfläche dem Applet hinzu:

blueButton = new Button("Blue");

he = new HandleButton(this, Color.blue);
blueButton.addActionListener(he);
add(blueButton);

Gehen Sie im weiteren Verlauf bei den anderen drei Schaltflächen in exakt der gleichen Weise vor, und letztendlich werden alle fünf Schaltflächen in Ihrem Applet integriert sein, wobei jede Schaltfläche zur Handhabung ihrer Ereignisse über ihr eigenes Listener-Objekt verfügt. Wenn Sie anschließend eine bestimmte Schaltfläche auswählen, wird das Ereignis an das für diese Schaltfläche zuständige Objekt weitergegeben, das bereits innerhalb seiner theColor-Instanzvariablen die richtige Farbe besitzt und somit die Farbe ändern kann. Im Applet selbst ist abgesehen vom Code der grafischen Benutzeroberfläche nichts vorhanden; das gesamte Verhalten ist in der Listener-Klasse gekapselt. Zwei Klassen, von denen jede unabhängig von der anderen zum Einfügen weiterer Schaltflächen oder Verhaltensänderung modifiziert werden kann. Nett, oder?

An dieser Stelle sollte noch angemerkt werden, daß die Ausführung dieses Applet in der hier etwas komplizierteren Form mit mehreren Listener-Objekten einfach aus dem Grunde erfolgte, um die Arbeitsweise von Listener-Objekten deutlich zu machen. Sie hätten das Applet auch leicht mit einem, allen fünf Schaltflächen zugewiesenen Listener-Objekt erstellen, und die Methode HandleButton hätte eine Gruppe von if...else-Anweisungen zur Identifizierung der das Ereignis auslösenden Schaltfläche verwenden können. Es wären zwar weniger Objekte vorhanden, aber jedes Ereignis würde aus Testgründen etwas mehr Zeit benötigen.

Einer der großen Vorteile des neuen 1.1-Ereignismodells ist allerdings die Fähigkeit, daß verschiedene Listener-Objekte Ereignisse für unterschiedliche Komponenten handhaben können, und während Sie immer kompliziertere Programme erstellen, werden Sie feststellen, daß diese Art der Organisation Sinn macht. Behalten Sie das für die Zukunft im Gedächtnis.

Listing 12.3 zeigt den endgültigen Code für das ButtonActionsTest-Applet, und Listing 12.4 zeigt den HandleButton-Code.

Listing 12.3: Das ButtonActionsTest-Applet (Version 1.1)

 1: import java.awt.*;

2:
3: public class ButtonActionsTest extends java.applet.Applet {
4:
5: Button redButton,blueButton,greenButton,whiteButton,blackButton;
6:
7: public void init() {
8: setBackground(Color.white);
9: setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10));
10:
11: HandleButton he;
12:
13: redButton = new Button("Red");
14: he = new HandleButton(this, Color.red);
15: redButton.addActionListener(he);
16: add(redButton);
17: blueButton = new Button("Blue");
18: he = new HandleButton(this, Color.blue);
19: blueButton.addActionListener(he);
20: add(blueButton);
21: greenButton = new Button("Green");
22: he = new HandleButton(this, Color.green);
23: greenButton.addActionListener(he);
24: add(greenButton);
25: whiteButton = new Button("White");
26: he = new HandleButton(this, Color.white);
27: whiteButton.addActionListener(he);
28: add(whiteButton);
29: blackButton = new Button("Black");
30: he = new HandleButton(this, Color.black);
31: blackButton.addActionListener(he);
32: add(blackButton);
33: }
34:}

Listing 12.4: Die HandleButton-Klasse.

 1:    import java.awt.*;

2: import java.awt.event.*;
3:
4: public class HandleButton implements ActionListener {
5:
6: Color theColor;
7: ButtonActionsTest theApp;
8:
9: HandleButton(ButtonActionsTest a, Color c) {
10: theApp = a;
11: theColor = c;
12: }
13:
14: public void actionPerformed(ActionEvent e) {
15: theApp.setBackground(theColor);
16: theApp.repaint();
17: }
18:}

Zusammenfassung

Das Java Abstract Windowing Toolkit oder AWT ist ein Paket mit Java-Klassen und Schnittstellen zur Entwicklung eines ausgereiften Zugriffs auf eine fensterbasierte grafische Benutzeroberfläche mit Mechanismen zur Anzeige von Text, Grafiken, Ereignismanagement, Text und grafischen Grundformen, Komponenten der Benutzeroberfläche und plattformunabhängigen Layouts. Applets bilden ebenso einen Bestandteil des AWT.

Heute haben Sie einen ersten größeren Vorgeschmack auf die Erstellung von Benutzeroberflächen für Ihre Applets unter Einsatz des AWT bekommen, indem Sie sechs grundlegende Komponenten der Benutzeroberfläche kennengelernt haben (Labels, Schaltflächen, Kontrollfelder, Optionsfelder, Auswahlmenüs und Textfelder). Darüber hinaus beinhaltete die Lektion Layout-Manager, mit denen Sie die Komponenten so auf dem Bildschirm plazieren können, daß sie, abhängig von der Plattform und dem Fenstersystem, unter dem Ihr Applet läuft, neu formatiert werden können. Und schließlich haben Sie Ihren Komponenten sowohl im Modell 1.02 als auch im Modell 1.1 Ereignisse hinzugefügt, um echte Reaktionen der von Ihnen erstellten grafischen Benutzeroberfläche auf Benutzereingaben auszulösen.

Noch nicht erschöpft? Es gibt noch viel zu lernen. Morgen erfahren Sie mehr über die andere Hälfte des AWT, sowie über weitere Komponenten, wie man Komponenten ineinander verschachtelt und wie man Dialoge und Fenster erstellt, mit denen Sie ausgereifte AWT-Anwendungen, die auf der Arbeitsfläche der Benutzeroberfläche selbst ablaufen, erzeugen können.

Fragen und Antworten

F Das Arbeiten mit Layout-Managern gefällt mir wirklich nicht; entweder sind sie zu einfach oder zu kompliziert (GridBagLayout). Auch wenn ich noch so viel herumbastle, sehen meine Applets niemals so aus, wie ich sie haben möchte. Alles was ich möchte, ist die Größe meiner Komponenten festlegen und sie in einer X- und Y-Position auf den Bildschirm setzen. Kann ich das?

A Die Antwort ist »ja« – trotzdem noch eine Erläuterung.

setLayout(null);

Button myButton (new Button("OK");
mybutton.reshape(10, 10, 30, 15);

F Ich habe mir die AWT-Klassen angesehen und ein Paket namens Peer entdeckt. Außerdem wird an vielen Stellen in der API-Dokumentation auf die Peer-Klassen verwiesen. Was bewirken diese Klassen?

A Peers sind für die plattformspezifischen Teile des AWT zuständig. Wenn Sie beispielsweise ein AWT-Fenster erstellen, haben Sie eine Instanz der Window-Klasse, die allgemeine Fenstereigenschaften bereitstellt. Daneben gibt es eine Instanz der WindowPeer-Klasse, die ein sehr spezifisches Fenster für diese Plattform – ein Motiv-Fenster unter XWindows, ein Macintosh-Fenster für den Macintosh oder ein Fenster für Windows 95 erstellt. Diese »Peer«-Klassen handhaben auch die Kommunikation zwischen dem Fenstersystem und dem Java-Fenster selbst. Durch Trennen der allgemeinen Komponenteneigenschaften (AWT-Klassen) von der eigentlichen Systemimplementierung und -aussehen (Peer-Klassen) können Sie sich auf das Verhalten Ihrer Java-Anwendung konzentrieren und die plattformspezifischen Einzelheiten der Java-Implementierung überlassen.

F Das zum Schluß mit dem Action-Listener für jede Schaltflächenfarbe aufgeführte Beispiel erschien einfach übertrieben. Ich habe es verstanden, und sehe auch ein, warum die Idee der Aufteilung des Listener-Codes in separate Klassen gut ist, bin aber der Meinung, daß dies für solch ein einfaches Applet nicht notwendig war.

A War’s auch nicht. Für ein derart kleines und einfaches Applet könnte man den schnelleren Weg nehmen und eine der dem Model 1.02 ähnliche Verfahrensweise anwenden – das Applet zu seinem eigenen Listener machen und if...else-Anweisungen für die Ermittlung der Schaltfläche des Ereignisses verwenden. Der Grund für die Erstellung des Applet in dieser Form war es aufzuzeigen, wie Sie das Layout der grafischen Benutzeroberfläche (»Look« des Applets) und den Code zur Handhabung der Ereignisse (»Feel« des Applet) vollständig voneinander trennen können. Wenn Sie mit der Erstellung immer größerer Anwendungen beginnen, wird diese Fähigkeit der Aufteilung des Ereigniscodes in unterschiedliche Klassen und der erneuten Verwendung des Codes durch objektorientiertes Design immer wichtiger werden.

F Ich habe eine neue Schaltflächenklasse, die ich im Aussehen unterschiedlich zu den Standard-AWT-Schaltflächenobjekten in 1.02 definiert habe. Ich möchte Aufrufe auf diese Schaltfläche rückführen (d.h. eine arbiträre Funktion bei Anklicken der Schaltfläche ausführen), kann aber nicht herausfinden, wie ich mit Java eine arbiträre Methode ausführen kann. In C++ verfüge ich nur über einen auf eine Funktion weisenden Zeiger. In Small-talk würde ich perform verwenden: Wie kann ich das in Java durchführen?

A In Java 1.02 können Sie es nicht; Aktionen der Schaltfläche werden durch ein action()-Ereignis getriggert, das in derselben Klasse wie die Schaltfläche enthalten sein muß. Sie müssen Ihre Schaltflächenklasse jedes Mal ableiten, wenn Sie für diese Schaltfläche ein anderes Verhalten erzeugen möchten. Dies ist einer der Gründe für die Erstellung des Java-Ereignismodells 1.1; es ist wesentlich einfacher und effizienter, Ihre eigenen Komponenten zu erzeugen, wenn der Ereigniscode nicht zu stark an den Code der grafischen Benutzeroberfläche gebunden ist.


(c) 1997 SAMS
Ein Imprint des Markt&Technik Buch- und Software- Verlag GmbH
Elektronische Fassung des Titels: Java in 21 Tagen, ISBN: 3-8272-2009-2

Previous Page Page Top TOC Index Next Page See Page