10 Das AWT

Kommen wir nun zu dem für viele Programmierer wahrscheinlich wichtigsten Thema einer Programmiersprache - der Kommunikation mit dem Anwender. In Java wird das hauptsächlich über ein eigenes Konzept realisiert - das AWT (Abstract Window Toolkit - in einigen Quellen wird es auch Abstract Windowing Toolkit genannt). Obwohl für viele weitergehende Aufgaben Dinge wie Netzwerkzugriffe, Datenbankzugriffe und -management oder Datenträgeroperationen unabdingbar sind, wird für viele Programmierer die Kommunikation mit dem Programmanwender das zentrale Thema sein. Besonders die Programmierer, die keine Großprojekte bearbeiten, sondern alleine oder in einem kleinen Team kleinere Anwendungen (wichtiges Stichwort in Java: Applets) erstellen, werden großen Wert auf die Benutzerkommunikation legen.

10.1 Was ist das Abstract Window Toolkit?

Das AWT beinhaltet zur Kommunikation mit dem Anwender im Wesentlichen ein API (Application Programming Interface), über das allgemeine Komponenten der Anwenderschnittstelle wie Buttons oder Menüs plattformunabhängig genutzt werden können. Um es nochmals zu betonen: plattformunabhängig! Damit hat Java ein erhebliches Problem zu lösen. Eine Benutzerschnittstelle kann ziemlich unterschiedlich aussehen und zu bedienen sein. Es kann sich um grundsätzlich verschiedene Konzepte handeln. Man unterscheidet erst einmal, ob es sich um eine befehlzeilenorientierte oder grafische Schnittstelle zum Anwender handelt. Aber nehmen Sie diesen Ansatz nicht zu ernst. Ich werde nicht ernsthaft befehlzeilenorientierte Benutzerschnittstellen wie DOS oder alte UNIX-Varianten als Basis heranziehen :-). Selbstverständlich hat Java von Anfang an eine grafische Verbindung zum Anwender geplant (wobei eine Kommunikation auf Befehlszeilenebene natürlich auch möglich ist - denken Sie nur an System.out zur Ausgabe oder zugehörigem System.in zur Eingabe).

Damit ist das Problem, eine portierbare und plattformunabhängige grafische API zu entwickeln, jedoch noch lange nicht gelöst. Die wichtigsten grafischen Benutzerschnittstellen der EDV-Welt unterschieden und unterscheiden sich massiv. Da gibt es als Vertreter der großen Anwendermasse die Windows-Oberfläche. Oder besser - die Windows-Oberflächen, denn es gibt mehrere Varianten, die sich zum Teil massiv unterscheiden. Natürlich kocht(e) IBM mit dem API des OS/2-Präsentationsmanagers ein eigenes Süppchen und Apple will (wollte - wenn man neuere Entwicklungen beobachtet?) bei der Mac-Oberfläche überhaupt keinen Verdacht aufkommen lassen, große Ähnlichkeiten zu den unausgereiften Produkten der Wintel-Konkurrenz zu haben.

Wie sollte nun ein gemeinsames API aussehen, auf dem sich Anwender aller Welten zurechtfinden können? Es gibt im Grunde vier Lösungsansätze:

Alle drei Ansätze haben Vor- und Nachteile. Die erste Idee ist relativ einfach und kann leicht auf allen Plattformen unterstützt werden, wird jedoch in der Leistungsfähigkeit erhebliche Einschränkungen gegenüber den jeweiligen Basis-Oberflächen der Plattformen zur Folge haben.

Die Vereinigungsmenge aller Möglichkeiten der Basis-Oberflächen in einem einzigen API (wie in Variante zwei) beinhaltet zwar keinerlei Einschränkungen der Leistungsfähigkeit, aber es kann zu Konflikten bei der zu wählenden Realisierung kommen (z.B. so wie in Windows oder in OS/2), das System wird sehr kompliziert und evtl. nicht auf allen Plattformen einwandfrei zu realisieren sein. Zudem wird es für den Anwender schwer durchschaubar (was sonst geht, funktioniert zwar, aber es gibt noch weitergehende, unbekannte Aktionen).

Die dritte Idee bringt für Anwender die wenigsten Umstellungen. Ein Anwender hätte in der Java-Oberfläche das gleiche Look and Feel wie in den gewohnten Anwendungen, die das systemeigene bzw. native API verwenden. Der Nachteil dieser Variante ist (und eigentlich ebenso der ersten beiden), dass sie nicht wirklich plattformunabhängig ist. Man kann nicht alle Oberflächen der EDV-Welt berücksichtigen und wie sollen Java-Programme auf Exoten-Plattformen aussehen? Wer wirklich plattformunabhängig sein will, muss ebenso dafür eine Lösung bereitstellen. Schlimmer noch ist, dass der dritte Ansatz gegenüber Neuerungen sehr unflexibel ist. Außerdem wird man abhängig von API-Herstellern. Wenn beispielsweise Microsoft wieder einmal beschließt, eine Windows-Version abzulösen und dort wieder einmal zig Veränderungen der API notwendig sind, muss das Java-API diese Veränderungen umgehend nachvollziehen.

Es fehlt bisher noch der vierte Denkansatz. Wie wäre es, sich alle Plattformen, die unterstützt werden sollen, anzusehen, die besten Komponenten zu identifizieren und dann diejenigen zu realisieren, die auf allen Plattformen Sinn machen und ein vollständiges API gewährleisten? Natürlich würde man sich bei einem solchen Ansatz nicht 100 % für ein bestehendes Look and Feel entscheiden, sondern ein eigenes Look and Feel erzeugen, das die besten Ideen aus den zugrunde liegenden Plattformen beinhaltet. Genau diesen Ansatz hatte Sun bei Java ursprünglich gewählt - ein allgemeingültiges API, mit dem es Ihnen ermöglicht werden soll, Java-Anwendungen weitgehend übergangslos in die unterschiedlichsten Umgebungen einzubetten - das AWT.

Diese vierte Lösung hat Sun jedoch nicht nur Lob eingebracht. Da das AWT von Java in der Version 1.0 relativ klein und allgemein gehalten wurde, konnten Java-Applikationen am Anfang nur eine Auswahl der unter Windows bekannten Elemente bieten. Hauptargument der Kritiker war, dass damit über 80 % der angeschlossenen Rechner (basierend auf der WINTEL-Plattform) zu Gunsten von weniger als 20 % der Internet-Teilnehmer auf die vollständige Windowsfunktionalität und deren Aussehen bei Java-Applikationen verzichten mussten. Dem AWT 1.0 wird gerne unterstellt, dass es recht rustikal aussieht.

Ob dieser Kritikpunkt nun berechtigt war oder nicht (nicht immer ist der Massengeschmack ein Garant für Qualität - ich verzichte auf Beispiele) - das AWT hat in der Version 1.1 sehr weitreichende Erweiterungen erfahren. Darunter fallen im Wesentlichen eine einheitliche Druckerschnittstelle, damit Applikationen darüber plattformunabhängig drucken können (auch dies war in der Version 1.0 noch nicht gegeben), schnelleres und einfacheres Scrolling (bzw. die prinzipiell schnellere Reaktion auf Ereignisse), bessere Grafikmöglichkeiten sowie flexiblere Unterstützung von Schrift.

Die Unterstützung von Pop-up-Menüs und der Zwischenablage waren weitere wichtige Erweiterungen der ersten AWT-Erweiterung. Die folgenden APIs haben Schritt für Schritt zugelegt und viele weitere neue Funktionalitäten und Erweiterungen folgen lassen. Die Java Foundation Classes (JFC) beinhalten nun Java 2D, diverse UI-Components (UI steht für User Interface - Benutzerschnittstelle), Zugriffsmöglichkeiten auf Fremdtechnologien (Accessibility), Drag&Drop, ein Input Method Framework zur Eingabe und Verwendung von Text in beliebigen Sprachen (etwa Japanisch, Chinesisch oder Koreanisch) über verschiedenste Eingabeelement und Application Services. Im Bereich der Performance und Stabilität wurde das AWT im Laufe der Zeit erheblich verbessert. Nicht zuletzt hat sich Sun der Kritik angenommen, dass eine überall einheitlich aussehende Oberfläche nicht unbedingt sinnvoll ist und mit dem Swing-Konzept (eingeführt in Java 1.1, aber erst im JDK 1.2 in der nun aktuellen Form vollständig implementiert) eine Erweiterung des bisherigen Aussehens der Oberfläche geschaffen. Dieses Konzept implementiert einen Satz von GUI-Komponenten mit anpassungsfähigem Look and Feel. Man hat also im Nachhinein - trotz der Bedenken - den dritten Denkansatz als Alternative zum AWT realisiert.

Swing ist vollständig in 100 % purem Java implementiert und basierte ursprünglich auf dem JDK 1.1 Lightweight UI Framework. Das Aussehen und die Reaktionen von GUI Komponenten passen sich automatisch an jede unterstützte Betriebssystemplattform (Windows, Solaris, Macintosh) an und erweitern die AWT-Möglichkeiten um einem Satz von Oberflächen-Interaktionselementen (Baumansichten, Listboxen, usw.).

Swing ist keine Ablösung des AWTs. Beide Welten existieren parallel und können sogar vermischt werden (was aber nicht zu empfehlen ist). Bei der Entwicklung einer Oberfläche können Sie entscheiden, ob Sie sich der ursprünglichen Idee von Sun anschließen (also eine Oberfläche ohne Swing erstellen) oder sich der automatisch anpassungsfähigen Oberfläche widmen. Wir werden uns Swing im nächsten Kapitel widmen und uns hier auf das reine AWT beschränken.

Der Begriff Framework umfasst einen ganzen Satz von Elementen für eine grafische Oberfläche. Neben den Elementen, die die eigentliche Kommunikation mit dem Anwender regeln (UI-Elemente), wird ein vollständiger Rahmen (Bibliotheken) für eine Applikation bereitgestellt.

Ein wichtiger und berechtigter Kritikpunkt am dem AWT 1.0 war das sehr starr ausgelegte Eventhandling (also die Reaktion auf Ereignisse). Es wurde ab der Version 1.1 völlig überarbeitet und damit wesentlich flexibler. Das 1.1-Eventmodell arbeitet mit so genannten Delegates, d.h. Objekte, die die »Delegierten« von anderen Objekten sind. Ein Fenster erhält zum Beispiel ein Delegate-Objekt, an das eine Menge von Methoden geschickt wird, die das Delegate dann abarbeiten kann. Je nach dem Rückgabewert der Methode in dem Delegate wird das Fensterobjekt dann unterschiedlich reagieren. Das Delegate-Konzept an sich ist nicht neu und bereits ziemlich ausgereift, denn es wird schon seit Jahren unter dem Betriebsystem Nextstep, aber auch unter Windows NT und in der Entwicklungsumgebung OpenStep verwendet.

Ein jedoch ebenfalls nicht zu unterschätzendes Problem des neueren Eventhandlings ist der gänzlich unterschiedliche Aufbau zu dem Modell 1.0. Insbesondere in der Vergangenheit gab es diverse komplexe Entwicklungen, die bereits nach dem alten Verfahren erstellt wurden und nur mit viel Aufwand auf das neue Konzept umgestellt werden konnten. Des Weiteren unterstützen selbst 2001 die meisten Browser das neue Eventmodell und die damit assoziierten Methoden bzw. neuen Varianten von bereits vorhandenen Methoden noch nicht direkt (die Referenzierung über das <OBJECT>-Tag außen vor gelassen). Wer eine maximale Verbreitung anvisiert und bei Applets auf Nummer sicher gehen will, muss noch einige Zeit bei dem alten Eventmodell bleiben. Es lässt sich also nicht leugnen, dass es derzeit noch für beide Eventmodelle Argumente gibt, sodass wir uns mit beiden Modellen beschäftigen werden und sie gegenüberstellen.

Wir werden - wie bei dem Kapitel über Grafik und Animation - im ersten Abschnitt des Kapitels das AWT und vor allem das Eventhandling der ersten Generation als Basis verwenden. Zum einen hat diese Technik - wie wir gerade gesehen haben - noch eine massive Existenzberechtigung, zum anderen sind diese die Grundlage für die neuen Techniken und die Erweiterungen werden sich ganz einfach ableiten lassen. Bestehende Methoden werden oft unverändert übernommen oder einfach durch eine neue Variante erweitert. Andere Methoden erhalten (etwa für das Swing-Konzept) eine direkte Partnermethode für das neue Konzept. In diesem Fall werden die »alten« Methoden als deprecated bezeichnet (in der Dokumentation erhalten Sie immer einen Verweis auf die neue Lösung). In Swing wird beispielsweise oft einfach eine neue Klasse geschaffen, die bis auf ein vorangestelltes J mit dem Namen der Klasse identisch ist, die im bisherigen Konzept die Aufgaben erledigte. Die direkten Partnermethoden für das neue Konzept müssen dann aus dieser neuen Klasse verwendet und evtl. etwas veränderte Argumente genommen werden.

10.2 Woraus besteht das AWT?

Das AWT besteht vorwiegend aus Komponenten, die der Endanwender (also der Entwickler einer Java-Applikation oder eines Applets) benutzt, indem er daraus seine Applikation baut1. Daneben gibt es Container, in denen die Komponenten integriert werden müssen, damit eine vollständige und sinnvolle Anwenderschnittstelle erstellt werden kann. Ein dritter bedeutender Bestandteil des AWT ist die Technik der Layoutmanager. Und »last but not least« muss das AWT einen Mechanismus zur Verfügung stellen, um auf Ereignisse reagieren zu können, die von dem Anwender über Komponenten ausgelöst werden.

Das AWT basiert auf dem Grundkonzept, dass jedes Java-Fenster mehrere verschachtelte Ebenen besitzt, die aus Komponenten aufgebaut sind. Dies beginnt mit dem äußersten Fenster und endet mit dem kleinsten eigenständigen Bestandteil der Benutzeroberfläche. Diese Verschachtelung bildet eine Hierarchiestruktur. Die Klassen des AWT-Pakets sind so entwickelt worden, dass ihre Struktur eine vollständige Benutzeroberfläche abbilden können. Die Wurzel von fast allen AWT-Komponenten ist die Klasse Component. Darin enthalten sind die grundlegenden Anzeige- und Eventhandling-Funktionalitäten.

10.2.1 Was sind AWT-Komponenten?

Die Komponenten sind die Elemente, über die eine Interaktion mit dem Endanwender konkret realisiert wird. Sie kennen sie aus den unterschiedlichen grafischen Benutzerschnittstellen.

Die von Anfang an vorhandenen Komponenten des AWT sind folgende:

Die hier aufgeführten Komponenten kann man nach ihrer Funktion in drei logische Bereiche unterteilen.

1. Komponenten der Benutzeroberfläche: Dazu zählen alle typischen Elemente einer Benutzeroberfläche (etwa Schaltflächen, Kontrollfelder usw.).
2. Zeichenbereiche: Dabei handelt es sich um die Fläche auf dem Bildschirm, auf der in der Regel Zeichnungen ausgegeben werden.
3. Fensterkomponenten: Dies sind alle Komponenten, die sich um den Aufbau und die Struktur der Fenster kümmern. Also Bildlaufleisten, Rahmen, Menüleisten und Dialogfelder. Diese Komponenten trennt man logisch von den Komponenten der Benutzeroberfläche, da sie logisch unabhängig von der integrierten Anwendung betätigt werden (das Verschieben von Fensterinhalt über Bildlaufleisten beispielsweise löst noch keine Aktion in der Applikation aus).

10.2.2 Was versteht man unter AWT-Container?

Die Container sind eine Ordnungsstruktur für Gruppen von Komponenten, die gemeinsam verwaltet werden. Das mag jetzt erst einmal sehr abstrakt klingen, ist aber eigentlich ganz logisch. Eine Schaltfläche ohne ein Fenster, in der sie integriert ist, macht wenig Sinn. Genauso wenig hat die Komponente »Bildlaufleiste« ohne ein Fenster, das über sie gescrollt werden kann, eine nützlich Funktion. Die Komponenten müssen mittels Container in verwaltbaren Gruppen organisiert werden. In Java können nur diejenigen Komponenten im AWT verwendet werden, die auch in einem Container enthalten sind. Streng genommen enthalten Container auch keine Komponenten, sie sind selbst die allgemeinste Form von Komponenten. Das bedeutet, dass ein Container neben »normalen« Komponenten durchaus auch andere Container enthalten kann. Die Container im AWT sind:

10.2.3 Container und Applets

Es mag in einem Applet so aussehen, dass man dort keinen Container verwendet. Der Eindruck trügt, den die Applet-Klasse ist eine Unterklasse der Panel-Klasse. Insbesondere stellt bei Applets der Browser das Hauptfenster und die Menüleiste (also die Fensterkomponenten) bereit.

10.2.4 Der Aufbau der AWT-Klassenhierarchie

Am deutlichsten wird der Zusammenhang der wichtigsten Bestandteile der AWT-Klassenhierarchie, wenn man die Klassen in einem Hierarchiediagramm ansieht. In der Klassenhierarchie-Anzeige der API-Dokumentation von Sun wird es sehr schön angezeigt.

Abbildung 10.1:  Button ist eine Subklasse von Component

10.2.5 Was ist ein AWT-Layoutmanager?

Wir hatten bisher festgehalten, dass man Komponenten verwendet, um die Interaktion mit dem Anwender zu realisieren, und Container als spezielle Komponenten, um diese dann gemeinsam zu verwalten. Unter dieser Verwaltung ist größtenteils die gemeinsame Abspeicherung der Komponenten gemeint. Was nicht darunter fällt, ist die Anordnung der Komponenten innerhalb eines Containers. Sie müssen sich selbstverständlich darum kümmern, wo die Komponenten innerhalb eines Containers dargestellt werden.

Wer bisher mit anderen Sprachen programmiert hat, kennt wahrscheinlich die übliche Technik. Über Koordinatenangaben werden die einzelnen Elemente einer Struktur (etwa eines Dialogfensters) platziert, evtl. auch visuell.

In Java werden Sie anders vorgehen. Unter Java werden Sie nicht mehr genau angeben, wo eine hinzugefügte Komponente in einem Container platziert werden soll. Die genaue Platzierung regeln die Layoutmanager. Sie weisen das AWT (und auch Swing) darüber nur noch an, wo Ihre Komponenten im Verhältnis zu den anderen Komponenten stehen sollen. Der Layoutmanager findet - angepasst an die jeweilige Situation - automatisch heraus, an welche Stelle die Komponenten am besten passen.

Wie dieses sicher sehr abstrakt klingende Verfahren genau funktioniert, werden wir uns etwas später in der Praxis ansehen. Vereinfacht gesagt, bedeuten Layoutmanager für einen Programmierer die Abgabe von Kontrolle über Größe und Position von Komponenten. Dieses Wegnehmen der absoluten Kontrolle vom Programmierer und die Übergabe an das System mag bei vielen Entwicklern erst einmal eine Aversion auslösen, hat jedoch ganz massive Vorteile. Es erleichtert die Erstellung von plattformunabhängiger Software erheblich. Wenn Sie Komponenten mit absoluten Koordinaten positionieren, kann in unterschiedlichen Auflösungen ein völliges Chaos entstehen. Eine Applikation, die mit absoluten Koordinaten unter einer 1280 x 1024 Auflösung perfekt aussieht, wird unter einer 640 x 480 Auflösung wichtige Bestandteile nicht anzeigen oder zumindest verunstaltet. Layoutmanager fangen dies genial und einfach ab.

Damit kein Missverständnis entsteht: Layoutmanager kümmern sich nur um Komponenten und nicht um Grafikelemente. Diese werden auch unter Java vom Programmierer absolut positioniert.

Das AWT stellt erst einmal fünf verschiedene Typen von Layoutmanagern zur Verfügung:

  • Flow
  • Border
  • Grid
  • Card
  • GridBag

Mehr dazu folgt etwas weiter unten.

10.3 Container

Wir beschränken uns bei der Abhandlung des AWTs meist darauf, AWT-Komponenten in Applets einzufügen oder abstrakt von Containern zu sprechen. Aber was bedeuten Container grundsätzlich? Die Beschränkung auf Applets hat in vielen Beispielen nur rein didaktische Gründe oder dient der einfacheren Beschreibung.

Ein Container ist im weitesten Sinn ein Objekt, das darin enthaltenen Komponenten eine gewisse Grundfunkionalität zur Verfügung stellt und die in ihm enthaltenen Komponenten gemeinsam verwaltet.

Wir haben den Begriff im Zusammenhang mit Applets/Browsern schon früher kennen gelernt. Im AWT sind Container zwar etwas anders zu verstehen, erfüllen aber im Wesentlichen den gleichen Zweck. Ein Applet ist im Sinn des AWTs auf jeden Fall ein Container.

Das AWT verfügt neben dem Applet über drei weitere Container.

  • Panels
  • Frames
  • Dialoge

Schauen wir uns diese drei Container genauer an.

10.3.1 Panels

Bei einem Panel handelt es sich um einen reinen Container, der keine sichtbaren Bestandteile besitzt. Es handelt sich um kein eigenes Fenster. Einziger Zweck eines Panels ist es, Komponenten in einem Fenster anzuordnen. Es gibt also nur sehr wenig, was Sie mit einem Panel machen können, ohne es in einem anderen Container (etwa einem Applet) einzusetzen. Dennoch sind Panels äußerst wichtig, wie wir gleich sehen werden. Sie können verschachtelt werden.

Erzeugen von Panels

Sie erzeugen ein Panel mit folgender Syntax:

Panel [Panelname] = new Panel();

Beispiel:

Panel meinPanel = new Panel();

Hinzufügen von Panels in Container

Da Panels wie gesagt kein eigenes Fenster besitzen, machen sie hauptsächlich Sinn innerhalb von anderen Containern, die selbst eine sichtbare Oberflächenstruktur aufweisen (zumindest in der letzten Ebene). Sie können ein Panel einem anderen Container mit der public Component add(Component comp)-Methode hinzufügen. Beispiel:

add(meinPanel);

Uns begegnet hier zum ersten Mal die add()-Methode, mit der Komponenten einem Container hinzugefügt werden. Von dieser werden Sie eine Vielzahl von Varianten finden. Beachten Sie, dass Sie die korrekte Variante aufrufen, wenn wir sie verwenden (besonders, wenn nicht explizit die Variante angegeben ist).

Verschachteln von Panels

Panels können anderen Panels hinzugefügt werden. Das heißt, Sie können Panels verschachteln. Dies ist besonders deshalb wichtig, weil damit Programmierer eine (relative) Kontrolle über die Position und Größe von Komponenten zurück erlangen. Allgemein ist üblich, dass die Layoutmanager das Layout eines Haupt-Panels festlegen und in dessen Teilbereichen weitere Panels eingefügt werden, die dann die konkreten Komponenten (Buttons, Labels usw.) enthalten. Verschachtelt werden Panels einfach, indem die add()-Methode des übergeordneten Panels aufgerufen wird.

Beispiel:

Panel hauptPanel, subPanel1, subPanel2; 
subPanel1 = new Panel(); // 1. verschachteltes Panel
subPanel2 = new Panel(); // 2. verschachteltes Panel
hauptPanel = new Panel(); // Hauptpanel 
hauptPanel.add(subPanel1); // Kind vom Hauptpanel 
hauptPanel.add(subPanel2); // 2. Kind vom Hauptpanel

Panels lassen sich in beliebig viele Ebenen verschachteln, solange es Sinn macht.

In verschiedenen Beispielen im weiteren Verlaufs des AWT-Kapitels werden wir Panels in der Praxis einsetzen und auch verschachtelte Panels verwenden.

10.3.2 Frames

Frames ist ein Begriff, der vielen Lesern aus dem Zusammenhang mit dem World Wide Web bekannt sein dürfte. Seit dem HTML-Standard 4.0 gehören Frames zum offiziellen HTML-Dialekt. Um den Begriff zu klären, wollen wir uns von HTML aus nähern. Vor der Einführung der Frame-Technologie in HTML gab es nur die Möglichkeit, eine HTML-Datei in einen Browser zu laden und diese dann im vollständigen Anzeigefenster des Browsers darzustellen. Mit der ursprünglich von der Firma Netscape entwickelten Frame-Technologie ist es dagegen möglich, den Anzeigebereich des Browsers in verschiedene, frei definierbare Segmente aufzuteilen. Diese frei definierbaren Segmente werden Frames genannt und so lässt sich auch die Definition auf unseren Fall übertragen. Ein Frame ist ein voll funktionsfähiges Fenstersegment bzw. Fenster mit eigenem Titel und Icon. Frames können Pulldown-Menüs haben und verschieden gestaltete Mauszeiger verwenden. Jeder Frame kann eigene Inhalte enthalten. Die Situation lässt sich gut mit einer üblichen grafischen Oberfläche wie Windows und der dortigen Fenstertechnik vergleichen. Frames im Java-Sinn sind also Fenster, wie Sie sie aus nahezu allen grafischen Benutzerschnittstellen gewohnt sein dürften.

Erzeugen von Frames

Allgemein gilt, dass Sie mit der Window-Klasse des AWT grafische Fenster für Java-Applikationen erstellen können. Die Window-Klasse enthält grundlegende Eigenschaften für Fenster. In der Praxis wird jedoch selten die Window-Klasse direkt verwendet, sondern deren Subklassen Frame und Dialog. Da es sich um Subklassen handelt, können natürlich auch mit diesen Klassen voll funktionstüchtige Fenster erstellt werden. darüber hinaus haben sie noch weitergehende Eigenschaften.

Zum Erzeugen von Frames stehen Ihnen wieder verschiedene Konstruktoren zur Verfügung. Die einfachste Variante sieht so aus:

public Frame()

Sie können damit einen Frame erzeugen, der anfangs nicht sichtbar ist und keinen Titel hat. Beispiel: Frame mFrame = new Frame();

Eine etwas kompliziertere Version vergibt für den Rahmen des Frames einen Titel bei der Erzeugung, jedoch der Frame ist anfänglich immer noch unsichtbar: public Frame(String title)

Beispiel: Frame mFrame = new Frame("Titel des Frames");

Beide Konstruktoren zum Erzeugen von Frames haben diese unsichtbar gelassen. Damit sind die Frames bisher noch nicht zu gebrauchen. Frames müssen vor dem Gebrauch noch sichtbar gemacht werden.

Frames dimensionieren, anzeigen, ausblenden und löschen

Der erste sinnvolle, aber nicht unbedingt notwendige Schritt besteht darin, einem Frame eine Größe zu geben. Dazu diente ursprünglich die Methode

public void resize(int width, int height).

Das erste Argument gibt die Breite des Frames in Pixel an, das zweite Argument die Höhe in Pixel .

Beispiel:

mFrame.resize(400, 200); // 400 Pixel breit und 200 hoch

Diese Methode gibt es zwar immer noch, sie gilt aber schon lange als deprecated. Statt dessen verwendet man zur Dimensionierung von Frames die Methode setSize(), die es in zahlreichen Varianten gibt. Zwei der wichtigsten Varianten (Bestandteil der Klasse Component) sind folgende:

public void setSize(Dimension d)
public void setSize(int width, int height)

Im ersten Fall übergibt man ein Dimensions-Objekt als Parameter, der zweite Fall ist analog der resize()-Methode.

Grundsätzlich wurden mit der Zeit Methoden zum Setzen von Eigenschaften allesamt neu eingeführt, sodass sie mit set beginnen. Passend dazu beginnen Methoden zum Abfragen von Eigenschaften mit get.

Dimensionieren legt zwar die Größe eines Frames fest, sichtbar ist der Frame aber immer noch nicht. Das erfolgt nun beispielsweise mit der uns auch schon bekannten Methode public void show().

Beispiel: meinFrame.show();

Diese Methode gilt als deprecated und soll im Prinzip durch public void setVisible(boolean b)ersetzt werden. Der Parameter ist ein boolean Wert. Ist er true, wird das Fenster angezeigt, ist er false, wird es versteckt. Damit benötigen Sie dann auch nur eine Methode, mit der Fenster angezeigt und wieder unsichtbar gemacht werden können. Die vorher zum Ausblenden von Fenstern verwendete Methode public void hide() kann also auch als deprecated abgelegt werden.

Wenn ein Fenster-Objekt angezeigt wird, ohne dass Sie vorher eine Größe festgelegt haben, wird nur ein Fenster mit Titelzeile, aber ohne einen Anzeigebereich geöffnet. Sie können das Fenster aber dann mit der Maus vergrößern.

Verstecken bzw. unsichtbar machen bedeutet nicht, dass ein Frame-Objekt damit zerstört worden ist. Wenn Sie ein Frame-Objekt endgültig beseitigen wollen, können Sie es mit der public void dispose()-Methode zerstören (die Methode gehört zu Window). Damit werden dann alle vom Frame belegten Ressourcen freigegeben.

Beispiel: meinFrame.dispose();

Sofern ein Programm beendet werden soll, ist der Aufruf von System.exit(int status); sinnvoller.

Überprüfen, ob bereits angezeigt

Wenn Sie einen Frame anzeigen oder schließen wollen, ist es sinnvoll, vorher zu überprüfen, ob das Fenster bereits angezeigt wird. Dies können Sie mit der Methode public boolean isShowing() kontrollieren. Diese gibt einen booleschen Wert zurück, den Sie wie üblich auswerten können.

Beispiel:

if (!meinFrame.isShowing()) {
// tue etwas sinnvolles
}

Frames beeinflussen und überprüfen

Selbstverständlich lassen sich Frames vielfältig beeinflussen bzw. Eigenschaften von Frames abfragen:

Die Methode public void setTitle(String title) ändert den Titel des Frames, der oben in einem Rahmen angezeigt wird.

Beispiel: meinFrame.setTitle("Neuer Titel");

Mit der Schwester-Methode public String getTitle() können Sie den Titel eines Frames einlesen.

Beispiel: String frameTitel = meinFrame.getTitle();

Die Frame-Klasse verfügt über unterschiedliche Mauszeigerformen, die Sie mit der Methode public void setCursor(int cursorType) dazu benutzen können, den Mauszeiger innerhalb eines Frames zu ändern. Dabei kann das Argument der Methode durch in der Klasse definierte, meist sprechende Konstanten ersetzt werden.

Beispiel: meinFrame.setCursor(Frame.HAND_CURSOR);

Folgende Mauszeiger sind unter dem alten Java-Modell verfügbar:

Frame.DEFAULT_CURSOR, Frame.CROSSHAIR_CURSOR, Frame.TEXT_CURSOR, Frame.WAIT_CURSOR, Frame.HAND_CURSOR, Frame.MOVE_CURSOR, Frame.N_RESIZE_CURSOR, Frame.NE_RESIZE_CURSOR, Frame.E_RESIZE_CURSOR, Frame.SE_RESIZE_CURSOR, Frame.S_RESIZE_CURSOR, Frame.SW_RESIZE_CURSOR, Frame.W_RESIZE_CURSOR und Frame.NW_RESIZE_CURSOR.

Unter dem neuen Java-Modell stehen Ihnen die Mauszeiger immer noch zur Verfügung. Sie müssen sie nur über Cursor.[Konstante] adressieren, also beispielsweise Cursor.CROSSHAIR_CURSOR.

Natürlich lassen sich diese Werte abfragen. Dafür gibt es die Methode public int getCursorType(). Sie wird einen dieser Werte oder einen Integerwert zurückgeben, der dann den aktuellen Mauszeigertyp anzeigt. Damit kann man dann etwas Sinnvolles anfangen.

Beispiel:

if (mFrame.getCursorType() == Frame.W_RESIZE_CURSOR) {
// tue was sinnvolles
} 
else {
// tue was anderes sinnvolles
}

Sie können die potenzielle Größenanpassung eines Frames durch den Anwender an- oder abstellen. Dazu gibt es die Methode public void setResizable(boolean resizable). Das Argument true lässt den Frame in der Größe veränderbar, false deaktiviert diese Möglichkeit.

Beispiel: meinFrame.setResizable(false);

Wir werden jetzt zwei Beispiele anführen, die die gerade besprochenen Elemente in der Praxis zeigen. Wir werden zwar ein wenig vorgreifen, da wir dort u.a. Schaltflächen in einen Container einfügen und diesen Vorgang erst im Folgenden genauer behandeln. Außerdem werden die action()-Methode aus dem Eventhandling 1.0 bzw. Bestandteile des Eventhandling 1.1 verwendet, wobei wir grundsätzlich mit so genannten anonymem Adaptern arbeiten. Die Beispiele dürften dennoch leicht zu verstehen sein. Probieren Sie sie einfach mal aus. Wenn Sie bezüglich der Hintergründe der Ereignisbehandlung vorgreifen wollen, können Sie die Details auf Seite 574 finden. Das erste Beispiel verwendet einige als deprecated gekennzeichnete Methoden, wird aber einwandfrei funktionieren.

Für die meisten besprochenen AWT-Situationen werden wir eine Ereignisbehandlung nach dem Modell 1.0 und (!) dem Modell 1.1 durchspielen.
import java.awt.*;
public class AppletMitFolgeFenster extends java.applet.Applet {
  Frame meinFrame;
  public void init() {
// Füge zwei Schaltflächen dem Container hinzu
    add(new Button("Öffne Fenster"));
    add(new Button("Schließe Fenster"));
// Erstelle Frame
    meinFrame = new Frame("Sub-Fenster");
// Lege Framegröße fest
    meinFrame.resize(250,150);
}
// Reaktionen auf Schaltflächen
  public boolean action(Event evt, Object arg) {
    if (evt.target instanceof Button) {
      String label = (String)arg;
// Wenn Schaltfläche "Öffne Fenster"
     if (label.equals("Öffne Fenster")) {
// Überprüfe, ob Frame noch nicht angezeigt
  if (!meinFrame.isShowing()) 
    meinFrame.show(); // Zeige Frame
      }
// Wenn Schaltfläche "Schließe Fenster"
   else if (label.equals("Schließe Fenster")) {
// Überprüfe, ob Frame angezeigt
  if (meinFrame.isShowing())
    meinFrame.hide(); // Verstecke Frame
      }
      return true;  
    }
    else return false;
  }  }

Abbildung 10.2:  Ein Folgefenster, das von einem Applet geöffnet wird

Das zweite Beispiel arbeitet mit dem Eventmodell 1.1 und einer eigenständigen Java-Applikation mit grafischer Oberfläche, auf der eine Schaltfläche platziert wird. Bei einem Klick auf die Schaltfläche wird das Programm wieder beendet. Beachten Sie, dass wir hier den Titel und die Größe des Fensters erst nach der Erzeugung festlegen, ebenso die Beschriftung des Buttons.

import java.awt.*;
import java.awt.event.*;
public class AWTTe extends Frame {
  Button b = new Button();
  public static void main(String[] args) {
    new AWTTe();
  }
  public AWTTe() {
      initial();
  }
  private void initial()  {
    b.setLabel("Ende");
    b.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        b_actionPerformed(e);
      }
    });
    setLayout(new FlowLayout());
    setTitle("Mein Fenster");
    add(b);
    setSize(200,100);
    setVisible(true);
  }
  void b_actionPerformed(ActionEvent e) {
   System.exit(0);
  }  }

Abbildung 10.3:  Ein Fenster mit Schließ-Button

10.3.3 Dialoge

Eng verwandt mit der Klasse Frame ist die Klasse Dialog. Unter einem Dialog versteht man in fast allen grafischen Benutzeroberflächen ein Popup-Fenster. So auch unter Java. Ein Dialogfenster verfügt über nicht so viele Funktionalitäten wie ein Frame. Dialoge werden im Wesentlichen für standardisierte Eingaben oder Meldungen eingesetzt.

Dialoge werden entweder modal oder non-modal erzeugt: Der Begriff »modal« bedeutet, dass das Dialogfeld andere Fenster blockiert, während es angezeigt wird.

Erzeugen eines allgemeinen Dialogfeldes

Um ein allgemeines Dialogfeld zu erzeugen, stehen Ihnen im Wesentlichen zwei Konstruktoren zur Verfügung. Beide geben an, ob der Dialog zum Zeitpunkt der Erstellung modal oder non-modal ist. Dies kann nach der Erstellung nicht mehr geändert werden.

Der Konstruktor Dialog(Frame, boolean) erzeugt ein Dialogfeld ohne Titel, das mit dem angegebenen Frame (das erste Argument) verbunden ist. Das zweite Argument gibt an, ob der Dialog modal (Wert true) oder non-modal (Wert false) ist.

Beispiel: Dialog meinDlg = new Dialog(meinFrame, true);

Der Konstruktor Dialog(Frame, String, boolean) erzeugt ein Dialogfeld mit einem zusätzlichen Titel.

Beispiel: Dialog meinDlg = new Dialog(meinFr, "Stop"; false);

Grundsätzlich unterscheidet sich die Verwendung eines Dialogfensters nicht sonderlich von der eines Frames. Nur muss immer zuerst ein Rahmen vorhanden sein, um einen Dialog daran aufzuhängen. Ein Dialog kann als Folge nicht direkt zu einem Applet gehören (der Konstruktor benötigt jedes Mal ein Frame-Objekt als Argument). Ein Applet kann jedoch einen Rahmen erstellen, zu dem der Dialog gehören kann. Da ein Dialog nicht direkt zu einem Applet gehören kann, kann er nur dann an einem Rahmen »aufgehängt« werden, wenn Sie in dem Applet einen »Dummy«-Rahmen als Elternteil des Dialogs einsetzen. Damit können allerdings keine modalen Dialoge erzeugt werden.

Ansonsten verhält sich ein Dialog wie ein gewöhnlicher Frame, was z.B. die Methoden zum Anzeigen und Verstecken betrifft. Auch die Methoden void setResizable(boolean), boolean isResizable(), void setTitle(String) und String getTitle() hat die Klasse Dialog mit der Frame-Klasse gemeinsam. Zusätzlich gibt die public boolean isModal()-Methode den Wert true aus, wenn der Dialog modal ist (andernfalls false).

Spielen wir ein Beispiel mit einem Dialog durch. Das Beispiel basiert auf dem vorherigen Frame-Beispiel mit dem Eventmodell 1.1. Dieses Mal verwenden wir nur zwei Java-Dateien, die sich im selben Verzeichnis befinden sollen (also innerhalb desselben anonymen Pakets).

import java.awt.*;
import java.awt.event.*;
public class Dialog1 extends Dialog {
  Button d = new Button();
  public Dialog1(
  Frame frame, String title, boolean modal) {
    super(frame, title, modal);
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
      initial();
  }
  void initial() {
    d.setLabel("Schliessen");
    d.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        d_actionPerformed(e);
      }
    });
    setLayout(new FlowLayout());
    add(d);
  }
protected void processWindowEvent(WindowEvent e) {
  if (e.getID() == WindowEvent.WINDOW_CLOSING) {
   cancel();
  }
  super.processWindowEvent(e);
}
 void cancel() {
    dispose();
  }
 void d_actionPerformed(ActionEvent e) {
  dispose();
 }  }


import java.awt.*;
import java.awt.event.*;
public class AWTTe2 extends Frame {
  Button b = new Button();
  Button c = new Button();
  Dialog1 dl = new Dialog1(
  this,"Mein Dialog",true);
  public static void main(String[] args) {
    new AWTTe2();
  }
  public AWTTe2() {
      initial();
  }
  private void initial()  {
    b.setLabel("Ende");
    b.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        b_actionPerformed(e);
      }
    });
    setLayout(new FlowLayout());
    setTitle("Mein Fenster");
    add(b);
    c.setLabel("Dialog öffnen");
    c.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        c_actionPerformed(e);
      }
    });
    add(c);
    setSize(300,400);
    setVisible(true);
  }
  void b_actionPerformed(ActionEvent e) {
   System.exit(0);
  }
  void c_actionPerformed(ActionEvent e) {
  dl.setSize(200,100);  
  dl.show();
  }  }

Abbildung 10.4:  Ein innerhalb eines Fensters aufgerufenes Dialogfenster

Das Beispiel stellt ein Fenster mit zwei Buttons bereit. Der eine beendet wieder das Programm, der andere öffnet ein Dialogfenster. Dieses beinhaltet einen weiteren Button, der die dispose()-Methode aufruft und das Dialog-Objekt damit zerstört (ohne das Programm zu beenden).

Dateidialoge

Neben den allgemeinen Dialogfenstern gibt es unter Java spezielle Dialoge, die für besondere Aufgaben vorbereitet sind. Sie stellen also eine Art Schablonen dar. Es gibt beispielsweise für den Zugriff auf das Dateisystem eines Rechners ein Dateidialogfenster. Dieses ist im Prinzip systemunabhängig, kann sich jedoch optisch von Plattform zu Plattform unterscheiden.

Dateidialogfelder machen meist nur bei eigenständigen Java-Applikationen Sinn. Bei Applets behindert die Sicherheitsbeschränkung des Java-Sandkastens in der Regel die Funktionalität. Zwar lässt sich das Dateidialogfeld anzeigen, die konkreten Dateizugriffe werden jedoch abgefangen. Dies gilt aber nicht immer, denn wenn das Applet maximale Rechte hat (sollte nicht oft vorkommen), kann man auch mit Applets Dateioperationen durchführen.

Um ein Dateidialogfeld zu erzeugen, stehen Ihnen u.a. zwei Konstruktoren zur Verfügung.

Der Konstruktor FileDialog(Frame, String) erzeugt ein Dialogfeld vom Typ »Datei öffnen« mit dem durch String bezeichneten Titel, das mit dem aktuellen Rahmen (das erste Argument) verbunden ist. Es wird also ein Dateidialogfeld zum Laden einer Datei erzeugt.

Beispiel:

FileDialog mDlg = new FileDialog(meinFr, "Datei öffnen");

Der Konstruktor FileDialog(Frame, String, int) erzeugt ebenfalls ein Dateidialogfeld mit einem Titel, das mit dem aktuellen Rahmen (das erste Argument) verbunden ist. Der wesentliche Unterschied ist, dass das dritte Argument benutzt wird, um zu bestimmen, ob das Dateifeld zum Laden oder Speichern einer Datei dient. Die möglichen Ausprägungen für das dritte Argument sind FileDialog.LOAD und FileDialog.SAVE. Dies sind Konstanten, die Integerwerten entsprechen.

Beispiel:

FileDialog meinDlg = new FileDialog(
  meinFr, "Datei öffnen", FileDialog.LOAD);

Um es ganz deutlich zu betonen - das Dateidialogfeld besitzt nicht die Funktionalität, dass bei Auswahl einer Datei und Betätigen der entsprechenden Schaltfläche eine Datei geöffnet oder gespeichert wird. Statt dessen passiert Folgendes: Wenn eine Datei ausgewählt wird, kann die ausgewählte Datei mit den Methoden getDirectory() und getFile() bestimmt werden. Beide Methoden haben als Rückgabewerte Zeichenketten, die die ausgewählte Datei angeben. Konkret öffnen oder speichern müssen Sie die Dateien dann mit den Methoden, die wir im Kapitel über die Ein- und Ausgabe in Java behandeln.

Fensterereignisse

Für diverse standardisierte Fensterereignisse gibt es in der Event-Klasse einige Konstanten, die im Rahmen des Eventhandlings verwendet werden können. Schauen wir uns einmal einige dieser Standard-Ereignisse an.

Konstante Beschreibung
WINDOW_DESTROY Das Fenster wird geschlossen, z.B. mit dem Schließbutton oder durch Beenden des Containers.
WINDOW_EXPOSE Das Fenster wird in den Vordergrund geholt.
WINDOW_MOVED Das Fenster wird verschoben.
WINDOW_ICONIFY Das Fenster wird zum Symbol verkleinert.
WINDOW_DEICONIFY Das Fenster wird auf Normalgröße wiederhergestellt.

Tabelle 10.1:   Fensterereignisse

10.4 Schaltflächen

Schaltflächen oder Buttons sind die elementarsten Komponenten für die Interaktion zwischen Anwender und Anwendung. Ob ein Befehl bestätigt werden soll, ob eine Aktion abgebrochen oder ob Hilfe geholt werden soll - Buttons regeln in grafischen Benutzerschnittstellen das Prozedere. Es gibt keine ernst zu nehmende interaktive Anwendung, die auf Buttons verzichten kann (und wenn es nur die Bestätigung des Beenden-Vorgangs ist).

Schaltflächen werden in Java ganz einfach erzeugt. Sie benötigen nur folgende Syntax: Button [Buttonname] = new Button();

Beispiel: Button meinButton = new Button();

Mit dieser Syntax wird eine Schaltfläche ohne Label (d.h. ohne eine Beschriftung innerhalb der Schaltfläche - etwa »OK«) erzeugt. Normalerweise benötigen Sie jedoch eine Schaltfläche, die ein Label besitzt. Dies funktioniert ähnlich einfach. Wir haben einmal die bereits in obigen Beispielen verwendete Methode setLabel() zur Verfügung, um nachträglich eine Beschriftung zu setzen. Es gibt aber auch noch einen Konstruktor, der die Beschriftung bereits bei der Erzeugung setzt. Sie müssen nur als Argument in Hochkommata eingeschlossen die Beschriftung mit angeben:

Button [Buttonname] = new Button([String]);

Beispiel: Button meinButton = new Button("Alles klar!");

Das war es! Mehr ist nicht notwendig, um eine Schaltfläche in Java zu erzeugen. Es gibt keine weiteren Optionen. Fertig sind Sie jedoch noch nicht. Die so erzeugte Schaltfläche muss noch in einen Container platziert werden und sollte normalerweise eine gewisse Funktionalität haben (Selbstzweck ist für einen Button meist nicht ausreichend).

Platzieren wir zuerst den Button in einem Container. Die einfache Syntax add(meinButton); reicht im einfachsten Fall aus, um die eben erzeugte Schaltfläche dem Container hinzuzufügen.

Sie können also entweder zwei Schritte nacheinander ausführen:

Button meinButton = new Button("Alles klar!"); 
add(meinButton);

Alternativ funktioniert es ebenso in einer Zeile:

add(new Button("Alles klar!"));

Im zweiten Fall arbeiten wir mit einem anonymen Objekt. Bei der expliziten Benennung einer Variablen (zwei Schritte), kann das Objekt später noch über den Namen der Variable beeinflusst werden.

Die bisherigen Beispiele zum AWT haben ja bereits mit Buttons gearbeitet, sodass wir hier auf ein einfaches Beispiel verzichten können.

Grundsätzlich kann zur Laufzeit eines Programms oder Applets die Bezeichnung eines Buttons (oder anderer verwandter Komponenten) geändert oder abgefragt werden. Gerade wenn Sie eine Schaltfläche ohne Beschriftung erzeugen, bietet es sich an, die Bezeichnung eines Buttons zu ändern. Dazu dient (wie wir bereits wissen) die Methode public void setLabel(String label).

Beispiel: meinButton.setLabel("Doch nicht OK!");

Vollkommen analog können Sie die Bezeichnung für einen Button einlesen. Dazu gibt es die Methode public String getLabel().

Beispiel: String LabelvomButton = meinButton.getLabel();

Schaltflächen können (wie die meisten anderen Komponenten) zu Laufzeit aktiviert und deaktiviert werden. Dazu gibt es beispielsweise die Methode disable() zum Deaktivieren des Buttons oder sogar noch allgemeiner einer Komponente.

Beispiel: meinButton.disable();

Die Methode enable() macht einen gesperrten Button (bzw. allgemein eine Komponente) wieder auswählbar.

Beispiel: meinButton.enable();

Beide Methoden gelten als deprecated. Statt dessen steht die Methode public void setEnabled(boolean b) zur Verfügung. Wenn der Parameter true ist, ist eine Komponente aktiviert, sonst deaktiviert (also wieder nur eine Methode zum Ein- und Ausschalten eines Zustands). In diesem Zusammenhang ist auch die Methode public boolean isEnabled() zu nennen, mit der getestet werden kann, ob eine Komponente aktiviert ist oder nicht.

Um noch mal vorzuwarnen - ein etwas umfangreicheres Thema ist die Reaktion der Applikation auf Ereignisse von Komponenten. Unsere bisherigen Beispiele haben dieses Verfahren schon gezeigt, aber das muss natürlich noch genauer besprochen werden. Wir haben an dieser Stelle das Dilemma, dass Schaltflächen (und natürlich ebenso die anderen Komponenten) in der Regel erst dann Sinn machen, wenn deren Betätigung etwas bewirkt. Dies soll im Rahmen des Abschnitts über die beiden Ereignismodelle (siehe Seite 574) noch detaillierter erfolgen. Wir wollen dennoch mit einigen Erklärungen vorarbeiten.

Die meisten Komponenten innerhalb einer AWT-Oberfläche der 1.0-Version besitzen eine action()-Methode, die aufgerufen wird, wenn bei einer Komponente eine Aktion ausgeführt wird. Bei einer Schaltfläche wird diese action()-Methode aufgerufen, wenn sie ausgelöst (angeklickt) wird.

An dieser Stelle soll noch einmal explizit darauf hingewiesen werden, dass die Methoden des Eventhandlings 1.0 (etwa auch mouseDown(), mouseDrag(), mouseEnter(), mouseExit(), mouseMove(), mouseUp()) als deprecated gelten und nur dann zu verwenden sind, wenn man nach dem Eventmodell 1.0 programmiert.

Wenn eine Aktion bei einer Komponente ausgeführt wird, ruft das AWT (Eventmodell 1.0) die handleEvent()-Methode auf. Dabei wird bei einer Schaltfläche der Ereignistyp von ACTION_EVENT übergeben. Die Standard-handleEvent()-Methode ruft die action()-Methode bei der Komponente auf. Standardmäßig kann die Komponente selbst keine Aktion bearbeiten. Deshalb wird jedes Ereignis an die handleEvent()-Methode im Eltern-Container übergeben. Dort wird die action()-Methode dann aufgerufen und entweder bearbeitet oder von dem nächst höheren Container in der Hierarchie ignoriert. Eine Komponente signalisiert mit der Weitergabe des Wertes true aus der Ereignisbehandlungsmethode, dass sie das Ereignis bearbeitet hat. Der Wert false bedeutet bei der Ereignisbehandlungsmethode, dass die Komponente das Ereignis nicht verarbeiten konnte. In diesem Fall sollte das Ereignis dann in der Elternhierarchie weiter nach oben gegeben werden. Die Syntax der action()-Methode ist bei allen Komponenten identisch:

public boolean action(Event ereignis, Object welcheAktion)

  • ereignis steht für das Ereignis, das bei der Komponente aufgetreten ist
  • welcheAktion steht für das, was geschehen ist.

Bei Schaltflächen ist die Art der Aktion (welcheAktion) ganz einfach auszuwerten. Es ist das Label, also die Beschriftung der Schaltfläche, die ausgelöst worden ist. Der ereignis-Parameter enthält weitere Informationen, die für die Aktion spezifisch sind. Darunter fallen z.B. Informationen über die Komponente, bei der die Aktion aufgetreten ist (event.target), oder den Zeitpunkt, zu dem die Aktion ausgelöst wurde (event.when).

Mit dem instanceof-Operator können Sie die event.target-Variable überprüfen, um sicherzustellen, dass die Aktion auch für das gewünschte Objekt erfolgt. Dazu kontrolliert man, dass event.target instanceof Button den Wert true ergibt.

Sie wissen nun, wie Sie einen Button erzeugen und kennen die action()-Methode. Gehen wir ein konkretes Beispiel an. Dabei wollen wir die vorher besprochenen Methoden zum Setzen und Abfragen von der Button-Beschriftung anwenden. Das Beispiel verändert die Beschriftung eines Buttons in einer switch-Anweisung. Im Detail passiert Folgendes:

1. Zuerst wird ein leerer Button erzeugt.
2. Danach wird er im Container platziert.
3. Nun weist man dem Button eine Beschriftung zu.
4. Wenn die Schaltfläche gedrückt wird, wird die Methode public boolean action(Event evt, Object welcheAktion) aufgerufen.
5. Zuerst überprüft die Methode, ob die Aktion, die die action()-Methode ausgelöst hatte, auch vom dem Button ausgelöst wurde.
6. Danach wird über die Informationen der Komponente, bei der die Aktion aufgetreten ist (evt.target) eine neue Instanz von Button in der action()-Methode erzeugt, um darüber die Beschriftung ändern zu können. Dies ist erforderlich, da sonst (in unserem Fall) nicht direkt auf die Beschriftung des Buttons über setLabel() zugegriffen werden kann.
7. Über die Angabe welcheAktion kann jedoch direkt die Beschriftung ausgewertet werden. Dies folgt im nächsten Schritt. Es wäre aber ebenso möglich, mit getLabel() zu arbeiten. In diesem Fall kann man vollständig auf die Angabe welcheAktion verzichten. Statt dessen muss man diese Zeile verwenden: String buttonBeschriftung = meinButton_in_der_actionmethode.getLabel();.
8. Im letzten Schritt wird je nach aktueller Beschriftung die Beschriftung der Schaltfläche wechselweise umbenannt.
import java.awt.*;
public class ButtonTest extends java.applet.Applet {
 public void init() {
Button meinButton = new Button(); 
add(meinButton);  
meinButton.setLabel("Alles klar!!");
 }
public boolean action(Event evt, Object welcheAktion) {
if (!(evt.target instanceof Button)) { 
return false; 
}
Button meinButton_in_der_actionmethode = (Button)evt.target;
String buttonBeschriftung = (String) welcheAktion;
if (buttonBeschriftung == "Alles klar!!") {
meinButton_in_der_actionmethode.setLabel(
"Doch nicht?"); 
}
else {
meinButton_in_der_actionmethode.setLabel("Alles klar!!"); 
}
return true;
}  }

Abbildung 10.5:  Die Schaltfläche hat sich verändert.

Abbildung 10.6:  Das frisch  gestartete Applet

10.5 Labels

Ein Label ist die wohl einfachste Form einer Komponente in einer Benutzeroberfläche. Unwichtig ist sie dennoch beileibe nicht. Es handelt sich um Textketten, die ausschließlich dafür benutzt werden, um andere Komponenten zu beschriften.

Zwar besitzen alle Komponenten als Ableitung der Component-Klasse eine action()-Methode, aber da Beschriftungen normalerweise nur angezeigt werden, wird die action()-Methode eines Labels niemals aufgerufen und auch sonstige Methoden machen im Zusammenhang mit Labels selten Sinn. Das gilt selbstverständlich auch für das Eventhandling 1.1.

Labels sind normalen Textketten auf Panels sehr ähnlich, haben jedoch den Vorteil, dass Labels neben dem reinen Text Layoutangaben des jeweiligen Panels einhält und dass es nicht jedes Mal nachgezeichnet werden muss, wenn das Panel neu gezeichnet wird. Ferner können Labels sehr leicht ausgerichtet werden.

Wie bei fast allen Komponenten gibt es verschiedene Möglichkeiten, ein Label zu erstellen. Die einfachste Variante ist, ein leeres, linksausgerichtetes Label zu erzeugen. Dies funktioniert über den leeren Konstruktor Label() mit folgender Syntax:

Label [Label] = new Label()

Beispiel: Label leeresLabel = new Label();

Ein leeres Label ist normalerweise kaum sonderlich dienlich, weil es einfach nichts zu sehen gibt. Dennoch kann es Sinn machen, wenn man zur Laufzeit das Label erst dynamisch füllen möchte. Dies funktioniert mit der Methode public void setText(String text). Die Abfrage erfolgt wieder analog mit public String getText().

Zur Beschriftung von Komponenten dienen allgemein entweder setText() oder setLabel(). Beachten Sie, dass für Labels setText() verwendet wird, während beispielsweise für Buttons setLabel() Anwendung findet.

Wenn Sie ein Label bereits vorher mit Text versehen wollen, gehen Sie wieder vor wie bei Schaltflächen - nur mit dem Konstruktor Label(String) mit folgender Syntax:

Label [Labelvariable] = new Label([Text])

Beispiel: Label meinLabel = new Label("Labeltext");

Labels können links, rechts oder zentriert ausgerichtet sein. Sowohl zur Laufzeit mit der Methode public void setAlignment(int alignment), wobei für die Ausrichtung Label.LEFT, Label.RIGHT und Label.CENTER verwendet werden können (auch diese Methode kann bei beliebigen Komponenten zum Einsatz kommen, wo eine Beschriftung ausgerichtet werden kann). Dazu passend liefert public int getAlignment() die Ausrichtung wieder zurück. Die Ausrichtung kann auch direkt schon bei der Erzeugung angegeben werden. Der dritte Konstruktor Label(String, int) erzeugt ein Label mit einer Textkette und einer bestimmten Ausrichtung. Die verfügbaren Ausrichtungen sind wie bei der Ausrichtungsmethode Label.LEFT, Label.RIGHT und Label.CENTER. Dabei entsprechen die Konstanten den Integerwerten 0 (Label.LEFT), 1 (Label.CENTER) oder 2 (Label.RIGHT), die natürlich ebenfalls verwendet werden kann. Die Sache sieht also beispielsweise so aus:

Label mLabel1 = new Label("Labeltext", Label.LEFT); 
Label mLabel2 = new Label("Labeltext", Label.RIGHT); 
Label mLabel3 = new Label("Labeltext", Label.CENTER);

Um Label in einem Container platzieren, verwenden Sie wieder die add()-Methode.

Schauen wir uns die Label-Technik nun einmal in einem Beispiel an. Das Beispiel fügt drei Labels mit unterschiedlichen Techniken in den Container (ein Applet) ein. Label 3 wird mittels zwei verschiedener Button manipuliert. Eine Schaltfläche verändert den Labeltext, die andere Schaltfläche die Ausrichtung.

import java.awt.*;
public class Label1 extends java.applet.Applet {
Label mL1 = new Label();
public void init() {
 add(new Label("Text 1")); 
 Label mL2 = new Label("Text 2");
 add(mL2);
 mL1.setText("Text 3");
 add(mL1);
 Button mB1 = new Button("Wechsel Label3"); 
 add(mB1); 
 Button mB2 = new Button(
  "Label3 rechts ausrichten"); 
 add(mB2); 
}
public boolean action(Event evt, Object welcheAktion) {
if (!(evt.target instanceof Button)) { 
return false; 
}
String bBeschr = (String) welcheAktion;
if (bBeschr == "Wechsel Label3") {
mL1.setText("Text 4");
}
if (bBeschr == "Label3 rechts ausrichten") {
mL1.setAlignment(Label.RIGHT);
}
return true;
}  }

Zu Verdeutlichung sehen wir uns die drei Situationen im Appletviewer an.

Abbildung 10.7:  Original-Label

Abbildung 10.8:  Die Ausrichtung des dritten Labels ist verändert.

Abbildung 10.9:  Der Text des dritten Labels hat sich geändert.

10.6 Kontrollkästchen und Optionsfelder

Kontrollkästchen und Optionsfelder (Checkbutton/Checkbox und Radiobutton) sind jedem Anwender von grafischen Oberflächen bekannt. Es ist aber vielen Anwendern (besonders nicht so versierten) oft nicht klar, worin der genaue Unterschied besteht (zumal oft für beide zusammen der Oberbegriff Optionsfelder verwendet wird). Sie sind sehr eng verwandt. Beide gleichen Buttons, mit der Einschränkung, dass Sie nur auf »Ja« oder »Nein« gesetzt werden dürfen.

Der entscheidende Unterschied zwischen Kontrollkästchen und Optionsfeldern ist der, dass Kontrollkästchen unabhängig von anderen Kontrollkästchen auf Ja oder Nein gesetzt werden können. Es können kein, ein oder mehrere Kontrollkästchen auf Ja gesetzt werden. Kontrollkästchen können auch alleine auftreten. Optionsfelder werden dahingegen immer mindestens paarweise verwendet und sind immer von den anderen Optionsfelder in speziellen wechselseitig sich ausschließenden Gruppierungen angeordnet, bei denen jeweils immer nur genau ein Button auf Ja geschaltet sein kann und muss. Der englische Name Radiobutton macht die Funktionalität sehr deutlich, den bei einem Radio kann man ebenfalls immer nur einen Sender einstellen und genau einer ist auch immer eingestellt (zumindest bei einem sinnvoll »konfigurierten« Radio).

Auf den meisten Plattformen werden Kontrollkästchen viereckig dargestellt, während Optionsfelder rund gezeigt werden. Wir schauen uns die beiden unterschiedlichen Komponenten getrennt an. Zuerst wenden wir uns den Kontrollkästchen zu.

10.6.1 Kontrollkästchen

Kontrollkästchen unterscheiden sich ein wenig von unseren bisherigen Fällen. Ein Kontrollkästchen hat zwei Bestandteile - ein Label zur Bezeichnung und einen Zustand. Das Label ist der Text, der neben dem Kontrollkästchen angezeigt wird, der Zustand ist eine boolesche Variable, die angibt, ob die Box angeschaltet wurde oder nicht. Standardmäßig ist die Box ausgeschaltet und ihr Wert ist false oder off. Ausgewählte Optionen werden auch optisch so dargestellt.

Die Syntax zum Erzeugen eines Kontrollkästchens kann wieder mit verschiedenen Konstruktoren erfolgen. Sie können ein Kontrollkästchen ohne Label erzeugen, indem Sie den leeren Konstruktor Checkbox() verwenden.

Beispiel: Checkbox meineCheckbox = new Checkbox();

Solche Objekte müssen Sie dann zur Laufzeit sinnvoll einstellen.

Um ein Kontrollkästchen direkt mit einer Beschriftung zu erstellen, können Sie den Konstruktor Checkbox(String) verwenden:

Beispiel: Checkbox mCbox = new Checkbox("Selektier mich");

Die Variante drei mit dem Konstruktor Checkbox(String, null, boolean) erzeugt ein Kontrollkästchen, das je nach gesetzter boolescher Zustandsvariable vorselektiert (true) ist oder nicht (false). Das mittlere Argument ist bei Kontrollkästchen immer auf null zu setzen. Es dient als Platzhalter für ein Gruppenargument, das nur bei Radiobuttons auftritt.

Beispiel: Checkbox meineCheckbox =
  new Checkbox("Selektier mich ", null, true)

Kontrollkästchen müssen wie alle Komponenten nach der Erzeugung erst in einem Container platziert werden. Dazu dient wieder die add()-Methode. Ein kleines Beispiel (ohne Eventhandling) demonstriert die Verwendung.

import java.awt.*;
public class CheckBoxTest extends java.applet.Applet {
  public void init() {
    add(new Checkbox("Visual Basic"));
    add(new Checkbox("C/C++"));
    add(new Checkbox("Delphi"));
    add(new Checkbox("Java", null, true));
    add(new Checkbox("HTML"));
  }  }

Abbildung 10.10:  Kontrollkästchen - Java ist vorselektiert

Den Status von Kontrollkästchen kann man programmtechnisch leicht überprüfen und setzen. Mit der Methode public boolean getState() können Sie überprüfen, ob ein Kontrollkästchen angeklickt wurde. Beispiel:

if (meineCheckbox.getState()) { 
// Kontrollkästchen selektiert
} 
else { 
// Kontrollkästchen nicht selektiert
}


Die Methode public void setState(boolean state) setzt den Status (true für gesetzt), und die bereits bekannte getLabel()-Methode fragt das Label des jeweiligen Kontrollkästchens ab.

10.6.2 Optionsfelder

Die Vorgehensweise zur Erstellung von Optionsfeldern unterscheidet sich ein wenig von der Erzeugung von Kontrollkästchen. Ein Kontrollkästchen hat nur zwei Bestandteile - ein Label zur Bezeichnung und einen Zustand. Radiobuttons sind zwar nur ein Spezialfall der Kontrollkästchen, aber bei einem Optionsfeld muss noch zwingend eine Checkbox-Gruppe erstellt werden, in die Sie dann die Optionsfelder hinzufügen. Es gibt keine eigene Radiobutton-Klasse.

Um Optionsfelder zu erstellen, legen Sie als ersten Schritt eine Instanz einer solchen Checkbox-Gruppe an. Dazu dient der Konstruktor CheckboxGroup(), der keinerlei Argumente besitzt.

Beispiel: CheckboxGroup mCboxGruppe = new CheckboxGroup()

Wenn Sie eine Gruppe erzeugt haben, erstellen Sie die einzelnen zugehörigen Optionsfelder und fügen sie der Gruppe hinzu. Dies funktioniert im Wesentlichen wie bei Kontrollkästchen, nur wird jetzt auf jeden Fall der Konstruktor benötigt, wo wir die Gruppe angeben können und müssen.

Beispiel: Checkbox meineCheckbox = new Checkbox(
  "Selektier mich ", mCboxGruppe, true)

Um Optionsfelder in einem Container zu platzieren, wenden sie dann einfach wieder die add()-Syntax an.

Beispiel: add(meineCheckbox);

Eine wichtige Besonderheit von Radiobuttons unter Java ist, dass alle (!) Buttons in einer Gruppe deselektiert sein können. Zumindest so lange, bis der erste Eintrag ausgewählt wurde. Wenn Sie bei der Erstellung der Radiobuttons sämtliche Objekte mit dem Parameter false erzeugen, wird keiner der Radiobuttons vorselektiert. Das widerspricht natürlich den Gestaltungsregeln von Radiobuttons. Diese besagen ja, dass genau ein Button jeder Gruppe selektiert ist.

Abbildung 10.11:  Kein Radiobutton  ist selektiert

Die Geschichte ist nicht sonderlich problematisch. Sie müssen nur daran denken, sich selbst um eine Vorselektion zu kümmern.

Das nachfolgende Beispiel demonstriert die Verwendung in einer vollständigen Anwendung, wobei immer noch auf das konkrete Eventhandling verzichtet werden soll.

import java.awt.*;
public class CheckboxGroupTest extends java.applet.Applet {
public void init() {
 CheckboxGroup meineCheckboxGruppe = new CheckboxGroup();
 add(new Checkbox("Visual Basic", meineCheckboxGruppe , false));
 add(new Checkbox("C/C++", meineCheckboxGruppe , false));
 add(new Checkbox("Java", meineCheckboxGruppe , false));
 add(new Checkbox("Delphi", meineCheckboxGruppe , false));
 add(new Checkbox("HTML", meineCheckboxGruppe , true));
 }  }

Abbildung 10.12:  Optionsfelder - HTML ist vorselektiert

Optionsfelder können genau wie Kontrollkästchen gesetzt und überprüft werden. Die Methoden getState() bzw. setState() und getLabel() bzw. setLabel() funktionieren ebenfalls bei Optionsfeldern. Daneben können Sie die Methoden public CheckboxGroup getCheckboxGroup() und public void setCheckboxGroup(CheckboxGroup g) verwenden, um auf die Gruppe eines Optionsfeldes zuzugreifen und sie zu ändern. Des Weiteren stehen Ihnen mit den in der Optionsfeldergruppe definierten Methoden public Checkbox getCurrent() und public void setCurrent(Checkbox box) zwei Methoden zur Verfügung, um das momentan ausgewählte Optionsfeld zu holen und zu setzen.

10.6.3 Reaktionen auf Kontrollfelder und Radiobuttons

Die Reaktionen auf Kontrollfelder und Radiobuttons weisen einige Besonderheiten auf. Kontrollfelder und Radiobuttons verfügen wie alle Komponenten unter dem Eventmodell 1.0 über die action()-Methode. Diese wird bei einer Auswahl aufgerufen, sobald ein Feld selektiert worden ist. Der welcheAktion-Parameter der action()-Methode wird genau dann true liefern, wenn das Feld angeklickt worden ist, und genau dann false, wenn das Feld nicht selektiert ist.

Soweit nichts Besonders. Bei einem Radiobutton kann aber das Problem entstehen, dass er den Wert false erhält, wenn er angeklickt wird und bereits an ist. Deshalb sollten Sie immer vorher mit der getState()-Methode den Zustand des Radiobuttons oder der Checkbox überprüfen.

Um nun zu bestimmen, welches Feld angeklickt worden ist, können Sie die getLabel()-Methode verwenden. Eine solche action()-Methode bei einer Auswahl wird normalerweise ungefähr wie folgt aussehen:

public boolean action(Event evt, Object welcheAktion) 
// Überprüfung, ob ein Kontrollkästchen oder ein
// Radiobutton. Falls nicht Rückgabewert false
if (!(event.target instanceof Checkbox)) { 
return false; 
} 
// Instanz des Ereignisses
Checkbox welcheAuswahl = (Checkbox)evt.target;
.... tue etwas mit der Auswahl ...
}

Wenden wir uns einem konkreten Beispiel zu. Das Beispiel setzt je nach Auswahl des Radiobuttons die Hintergrundfarbe des Applets um.

import java.awt.*;
public class CheckAction extends java.applet.Applet {
 CheckboxGroup meineCheckboxGruppe = new CheckboxGroup();
 public void init() {
 add(new Checkbox("Blau", meineCheckboxGruppe , false));
 add(new Checkbox("Rot", meineCheckboxGruppe , false));
 add(new Checkbox("Grün", meineCheckboxGruppe , false));
 add(new Checkbox("Gelb", meineCheckboxGruppe , true));
// Vorbelegen der Hintergrundfarbe passend zur 
// Vorselektion
setBackground(Color.yellow);
 }
public boolean action(Event evt, Object welcheAktion) {
// Überprüfung, ob ein Kontrollkästchen oder 
// Radiobutton
// Falls nicht Rückgabewert false
if (!(evt.target instanceof Checkbox)) { 
return false; 
} 
// Instanz des Ereignisses
Checkbox welcheAuswahl = (Checkbox)evt.target;
boolean checkboxStatus = welcheAuswahl.getState(); 
if (welcheAuswahl.getLabel() == "Blau") { 
if (checkboxStatus) { 
setBackground(Color.blue);
} 
return true; 
} 
if (welcheAuswahl.getLabel() == "Rot") { 
if (checkboxStatus) { 
setBackground(Color.red);
} 
return true; 
} 
if (welcheAuswahl.getLabel() == "Grün") { 
if (checkboxStatus) { 
setBackground(Color.green);
} 
return true; 
} 
if (welcheAuswahl.getLabel() == "Gelb") { 
if (checkboxStatus) { 
setBackground(Color.yellow);
} 
return true; 
} 
return false; 
}  }

Abbildung 10.13:  Die Voreinstellung

Abbildung 10.14:  Blau wird als Hintergrundfarbe des Applets gewählt.

Schauen wir uns noch ein etwas komplexeres Beispiel an, das nach dem Eventmodell 1.1 auf Check- und Radiobuttons reagiert. Es werden jeweils Labels sichtbar bzw. unsichtbar gesetzt. Das Beispiel wird zeigen, dass das Eventmodell 1.1 nicht immer einfacher ist als das alte Modell. Das Beispiel arbeitet zudem mit verschachtelten Panels.

Wenn Sie mit Labels unter bestimmten Layouts wie dem Flowlayout arbeiten, sollten Sie beachten, dass sich die Länge des Labels dem Inhalt anpassen kann. Das kann dann ein Problem werden, wenn man einen kurzen Text schreibt (mit setText()) und danach einen längeren Text. Dieser wird unter gewissen Konstellationen nicht ganz angezeigt, wenn das Layout die Länge des Labels so einschränkt, dass eine Vergrößerung nicht möglich ist.
import java.awt.*;
import java.awt.event.*;
public class CheckRadio extends Frame {
  Panel pnl1 = new Panel();
  Panel pnl2 = new Panel();
  FlowLayout fl1 = new FlowLayout();
  FlowLayout fl2 = new FlowLayout();
  Checkbox chkbx1 = new Checkbox();
  CheckboxGroup chkbxGroup1 = new CheckboxGroup();
  Checkbox chkbx2 = new Checkbox();
  Checkbox chkbx3 = new Checkbox();
  Panel pnl3 = new Panel();
  FlowLayout fl3 = new FlowLayout();
  Label lb1 = new Label();
  Label lb2 = new Label();
  public CheckRadio() {
      initial();
     }
  public static void main(String[] args) {
    CheckRadio checkRadio = new CheckRadio();
  }
  private void initial() {
    pnl2.setLayout(fl1);
    pnl1.setLayout(fl2);
    chkbx1.setLabel("Text schreiben");
    chkbx1.setState(true);
    chkbx1.addItemListener(
  new java.awt.event.ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        chkbx1_itemStateChanged(e);
      }
    });
    chkbx2.setCheckboxGroup(chkbxGroup1);
    chkbx2.setLabel("Text schreiben");
    chkbx2.setState(true);
    chkbx2.addItemListener(
  new java.awt.event.ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        chkbx2_itemStateChanged(e);
      }
    });
    chkbx3.setCheckboxGroup(chkbxGroup1);
    chkbx3.setLabel("Text löschen");
    chkbx3.addItemListener(
  new java.awt.event.ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        chkbx3_itemStateChanged(e);
      }
    });
    pnl3.setLayout(fl3);
    lb1.setText("Kontrolle");
    lb2.setText("Radio");
    this.add(pnl1, BorderLayout.WEST);
    pnl1.add(chkbx1, null);
    this.add(pnl2, BorderLayout.EAST);
    pnl2.add(chkbx3, null);
    pnl2.add(chkbx2, null);
    this.add(pnl3, BorderLayout.CENTER);
    pnl3.add(lb2, null);
    pnl3.add(lb1, null);
    setSize(500,200);
    show();
  }
  void chkbx1_itemStateChanged(ItemEvent e) {
  if(chkbx1.getState())  {
  lb1.setVisible(true);
  }
  else  {
    lb1.setVisible(false);
  }
  }
  void chkbx3_itemStateChanged(ItemEvent e) {
  if(!chkbx2.getState())  {
  lb2.setText("");
  }
  }
  void chkbx2_itemStateChanged(ItemEvent e) {
  if(chkbx2.getState())  {
  lb2.setText("Radio");
  }
  }  }

Abbildung 10.15:  Die Voreinstellung

Abbildung 10.16:  Die Alternativen

Beachten Sie, dass das Programm keinen Mechanismus zum Beenden besitzt (es würde sonst noch komplexer). Sie können es auf Befehlszeilenebene mit CTRL+C beenden. Das gilt auch für einige noch folgende Beispiele.

Das Beispiel verwendet (wie nahezu alle Beispiele bezüglich des Eventmodells 1.1) explizit anonyme Klassen zum Eventhandling. An dieser Stelle soll uns vor allem interessieren, dass das Ereignis itemStateChanged(ItemEvent e) verwendet wird. Dies tritt auf, wenn sich der Status eines Radiobuttons oder eines Kontrollkästchens ändert. Die Methode getState() liefert den Status zurück, der dann ausgewertet wird. Beachten Sie, dass das Kontrollkästchen als An-/Aus-Schalter fungiert, während bei den Radiobuttons die gleiche Funktionalität über zwei Objekte realisiert wird. Zudem ist erwähnenswert, dass einmal das Label nur unsichtbar und wieder sichtbar gesetzt wird, während das andere Mal der Text explizit gelöscht und wieder geschrieben wird.

10.7 Auswahlmenüs und Listenfelder

Kommen wir nun zu einer etwas komplexeren Komponente einer Benutzeroberfläche - dem Auswahlmenü oder Auswahlfeld bzw. der Kombinationsliste oder dem einzeiligen Listenfeld, wie die Komponente oft auch genannt wird. Es handelt sich dabei um Popup- bzw. Pulldown-Menüs, die sich beim Anklicken öffnen und mehrere auszuwählende Optionen anzeigen, von denen dann eine Option ausgewählt werden kann. Eng verwandt damit sind mehrzeilige Listenfelder, bei denen mehrere Einträge bereits sichtbar sind und die sich nicht mehr aufklappen lassen (nur bei Bedarf scrollen). Ein wesentlicher Unterschied bei der Auswahl der Einträge ist, dass einzeilige Listenfelder nur die Auswahl eines Werts erlauben. Wenn mehr als eine Option gleichzeitig ausgewählt werden soll, müssen Listenfelder oder Kontrollkästchen verwendet werden.

10.7.1 Einzeilige Listenfelder

Um ein Auswahlmenü zu erzeugen, wird eine Instanz der Choice-Klasse erstellt. Es gibt keine Optionen für den zugehörigen Konstruktor Choice(). Deshalb sieht der Vorgang immer so aus: Choice [auswahlm] = new Choice()

Beispiel: Choice meinAuswahlmenue = new Choice();

Dann aber wird es etwas unterschiedlich zu den bisherigen Situationen. Es wird jetzt nicht einfach die add()-Methode verwendet, um das Objekt in einem Container zu platzieren. Erst muss das Objekt mit Einträgen gefüllt werden. Dazu benutzen Sie die public void addItem(String item)-Methode, um einzelne Optionen in der Reihenfolge hinzuzufügen, in der sie angezeigt werden sollen.

Beispiel:

meinAuswahlmenue.addItem("Visual Basic");
meinAuswahlmenue.addItem("C/C++");
meinAuswahlmenue.addItem("Java");
meinAuswahlmenue.addItem("Delphi");
meinAuswahlmenue.addItem("HTML");
Erst dann wird das Auswahlmenü in einem Container platziert - wie üblich mit der add()-
Methode. 
Beispiel: add(meinAuswahlmenue); 
Ein vollständiges Beispiel demonstriert die Anwendung:
import java.awt.*;
public class ChoiceTest extends java.applet.Applet {
public void init() {
Choice meinAuswahlmenue = new Choice(); 
meinAuswahlmenue.addItem("Visual Basic");
meinAuswahlmenue.addItem("C/C++");
meinAuswahlmenue.addItem("Java");
meinAuswahlmenue.addItem("Delphi");
meinAuswahlmenue.addItem("HTML");
add(meinAuswahlmenue); 
}  }

Abbildung 10.17:  Das aktivierte Auswahlmenü

Sie können übrigens dynamisch mit dem Hinzufügen von Optionen zu einem bestehenden Menü fortfahren. Das bedeutet, dass auch nach dessen Erstellung und Platzierung im Container die Einträge verändert, reduziert oder erweitert werden können. Es gibt dabei einige interessante Methoden, mit denen Sie Auswahlmenüs beeinflussen und überprüfen können.

  • Die Methode public void select(int pos) wählt das an der angegebenen Stelle befindliche Element aus. Dabei ist zu beachten, dass von 0 ab gezählt wird. In unserem oberen Beispiel wählt meinAuswahlmenue.select(2) »Java« aus.
  • Die Methode public void select(String str) wählt das Element mit der angegebenen Zeichenkette aus. In Analogie zu dem eben gewählten Beispiel selektiert meinAuswahlmenue.select("Java") auch diesen Eintrag.
  • Die public int getSelectedIndex()-Methode gibt die Position des ausgewählten Elements zurück. Dabei ist wieder zu beachten, dass von 0 ab gezählt wird.
  • Die public String getSelectedItem()-Methode gibt den Zeichennamen des momentan ausgewählten Elements zurück.
  • Die public String getItem(int index)-Methode gibt die Zeichenkette des Elements an diesem Index aus. Über String ausgewaehlterEintrag = meinAuswahlmenue.getItem(2) würde in der angegebenen Variable der Wert »Java« gespeichert.
  • Die public int countItems()-Methode bestimmt die Anzahl der im Menü enthaltenen Elemente.

Bei der Reaktion auf Auswahlmenüs kann man im Rahmen des Eventmodells 1.0 wie bei allen Komponenten auf die action()-Methode zurückgreifen. Diese wird bei einer Auswahl aufgerufen, sobald ein Feld selektiert worden ist, sogar dann, wenn die gleiche Auswahl wiederholt wird. Der welcheAktion-Parameter enthält den Namen des ausgewählten Elements, der in einer String-Variable gespeichert werden kann. Eine action()-Methode bei einem Auswahlmenü wird normalerweise ungefähr wie folgt aussehen:

String auswahl; 
public boolean action(Event evt, Object welcheAktion) 
if (!(event.target instanceof Choice)) { 
return false; 
} 
Choice welcheWahl = (Choice) evt.target; 
if (welcheWahl == meinAuswahlmenue) { 
auswahl = (String) welcheAktion; 
... tue etwas sinnvolles ...
return true; 
} 
return false; 
}

Wandeln wir unser Beispiel, bei dem die Hintergrundfarbe je nach Auswahl gesetzt wird, in eine Selektion über ein Auswahlmenü um.

import java.awt.*;
public class ChoiceAction extends java.applet.Applet {
public void init() {
Choice meinAuswahlmenue = new Choice(); 
meinAuswahlmenue.addItem("Blau");
meinAuswahlmenue.addItem("Rot");
meinAuswahlmenue.addItem("Grün");
meinAuswahlmenue.addItem("Gelb");
add(meinAuswahlmenue); 
// Vorbelegen der Hintergrundfarbe passend zur 
// Vorselektion
setBackground(Color.blue);
}
public boolean action(Event evt, Object welcheAktion) 
// Überprüfung, ob ein Kontrollkästchen oder ein 
// Radiobutton
// Falls nicht Rückgabewert false
{
if (!(evt.target instanceof Choice)) { 
return false; 
} 
// Instanz des Ereignisses
Choice welcheAuswahl = (Choice)evt.target;
if (welcheAuswahl.getSelectedItem() == "Blau") { 
setBackground(Color.blue);
return true; 
} 
if (welcheAuswahl.getSelectedItem() == "Rot") { 
setBackground(Color.red);
return true; 
} 
if (welcheAuswahl.getSelectedItem() == "Grün") { 
setBackground(Color.green);
return true; 
} 
if (welcheAuswahl.getSelectedItem() == "Gelb") { 
setBackground(Color.yellow);
return true; 
} 
return false; 
}  }

Abbildung 10.18:  Das aktivierte Auswahlmenü hat die Farbe verändert.

Das zweite Beispiel zu diesem Thema zeigt die Reaktion nach dem Eventmodell 1.1, dieses Mal nicht als Applet, sondern als eigenständige Applikation. Es kommt wieder das Ereignis itemStateChanged(ItemEvent e) zum Zug, dass wir eben schon angewendet haben.

import java.awt.*;
import java.awt.event.*;
public class ChoiceAction2 extends Frame {
  Choice meinAuswahlmenue = new Choice();
  public ChoiceAction2() {
      initial();
  }
  public static void main(String[] args) {
    ChoiceAction2 choiceAction2 = new ChoiceAction2();
  }
  void initial()  {
  meinAuswahlmenue.addItem("Blau");
  meinAuswahlmenue.addItem("Rot");
  meinAuswahlmenue.addItem("Grün");
  meinAuswahlmenue.addItem("Gelb");
  meinAuswahlmenue.addItemListener(new java.awt.event.ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        meinAuswahlmenue_itemStateChanged(e);
      }
    });
  this.add(meinAuswahlmenue, BorderLayout.NORTH);
  setSize(300,200);
  show();
}
  void meinAuswahlmenue_itemStateChanged(ItemEvent e) {
  if (meinAuswahlmenue.getSelectedItem() == "Blau") { 
   setBackground(Color.blue);
  } 
  if (meinAuswahlmenue.getSelectedItem() == "Rot") { 
  setBackground(Color.red);
  } 
  if (meinAuswahlmenue.getSelectedItem() == "Grün") { 
  setBackground(Color.green);
  } 
  if (meinAuswahlmenue.getSelectedItem() == "Gelb") { 
  setBackground(Color.yellow);
  } 
 }  }

Abbildung 10.19:  Das aktivierte Auswahlmenü hat die Farbe verändert.

Beachten Sie, dass das Programm wieder keinen Mechanismus zum Beenden besitzt. Sie können es auf Befehlszeilenebene mit CTRL+C beenden.

10.7.2 Mehrzeilige Listenfelder

Mehrzeilige Listenfelder sind nahe Verwandte der Auswahlmenüs. Es gibt - wie schon angedeutet - jedoch einige wichtige Unterschiede. Mit den Elementen der List-Klasse erzeugen Sie keine Popup- bzw. Pulldown-Menüs, sondern permanent sichtbare Listen. Insbesondere können scrollbare Wertebereiche erzeugt werden. Sofern nicht alle Einträge direkt angezeigt werden können, wird automatisch eine Bildlaufleiste generiert.

Ein weiterer Unterschied zu Auswahlmenüs ist der, dass nicht nur eine von sich gegenseitig ausschließenden Optionen ausgewählt werden kann, sondern auch bei entsprechender Konfiguration mehrere Werte ausgewählt werden können (sofern sie sich nicht gegenseitig ausschließen). Ein Listenfeld lässt sich entsprechend konfigurieren.

Zum Erzeugen von Listenfeldern legen Sie eine Instanz der List-Klasse an und fügen danach die gewünschten Einträge hinzu. Die List-Klasse besitzt zwei Konstruktoren. Der Konstruktor List() erzeugt eine Liste, die keine Mehrfachauswahl erlaubt.

Beispiel: List meineListe = new List();

Der zweite Konstruktor List(int, boolean) erzeugt eine Liste, in der die Anzahl der Listeneinträge festgelegt werden kann, die im Listenfenster zu einem beliebigen Zeitpunkt sichtbar sind (der Integer-Wert), und ob auch Mehrfachauswahlen erlaubt (der boolesche Wert auf true) oder verboten (der boolesche Wert auf false) sind. Machen wir uns das an zwei Beispielen kurz klar:

  • Eine Liste mit maximal 10 sichtbaren Einträgen und erlaubter Mehrfachauswahl:
    List meineListe = new List(10, true);
  • Eine Liste mit maximal 5 sichtbaren Einträgen ohne Mehrfachauswahl:
    List meineListe = new List(5, false);

Die Anzahl der maximal sichtbaren Einträge begrenzt jedoch nicht die Anzahl der maximal in der Liste erlaubten Einträge. Gibt es mehr Einträge, wird die Liste scrollbar dargestellt. Genausowenig erzwingt sie, dass mindestens so viele Einträge enthalten sind. Wenn weniger Einträge vorhanden sind, bleibt ein Bereich einfach leer.

Wenn die Liste erzeugt wurde, können Sie mit der schon von Auswahlmenüs bekannten addItem()-Methode neue Einträge in diese hinzufügen.

Beispiel:

meineListe.addItem("Blau"); 
meineListe.addItem("Rot"); 
meineListe.addItem("Grün"); 
meineListe.addItem("Gelb");

Es ist ebenfalls möglich, Elemente an einer bestimmten Position in der Liste hinzuzufügen, auch zur Laufzeit. Die Listenpositionen werden, von 0 beginnend, durchnummeriert. Wenn ein Element an der Position 0 hinzugefügt wird, ist es das erste in der Liste. Wenn es an der Position -1 eingefügt werden soll oder Sie eine Position auswählen, die höher ist als die in der Liste verfügbaren Positionen, wird das Element ans Ende der Liste gestellt.

Beispiel:

meineListe.addItem("Grün",0); // Einfügen an Position 0
meineListe.addItem("Grün",-1); // Am Ende einfügen

Listenfelder in einem Container zu platzieren bedeutet nichts Neues. Wie bisher dient zum Platzieren eines Listenfelds in einem Container die add()-Methode.

Beispiel: add(meineListe);

Zur Vollständigkeit wieder ein kleines Beispiel.

import java.awt.*;
public class Liste extends java.applet.Applet {
// 5 sichtbare Einträge, Mehrfachauswahl erlaubt
List meineListe = new List(5, true); 
public void init() {
meineListe.addItem("Blau"); 
meineListe.addItem("Rot"); 
meineListe.addItem("Grün"); 
meineListe.addItem("Gelb"); 
meineListe.addItem("Cyan"); 
meineListe.addItem("Pink"); 
meineListe.addItem("Braun"); 
meineListe.addItem("Schwarz"); 
add(meineListe);}
}

Abbildung 10.20:  Eine Auswahlliste mit Mehrfachauswahl, wo nicht alle Einträge angezeigt werden

Die Bildlaufleisten und Schieber zum Scrollen des Inhalts werden übrigens automatisch generiert und sind (wie gewohnt) nicht vorhanden, wenn weniger Inhalt anzuzeigen ist, als Platz zur Verfügung steht. Das nachfolgende Beispiele zeigt es.

import java.awt.*;
public class Liste2 extends java.applet.Applet {
// 5 sichtbare Einträge, 
// keine Mehrfachauswahl erlaubt
List meineListe = new List(5, false); 
public void init() {
// nur 4 Einträge
meineListe.addItem("Blau"); 
meineListe.addItem("Rot"); 
meineListe.addItem("Grün"); 
meineListe.addItem("Gelb"); 
add(meineListe);}  }

Abbildung 10.21:  Eine Auswahlliste ohne Mehrfachauswahl, wo alle Einträge sichtbar sind (keine Bild laufleisten)

Um Listenfelder zu beeinflussen und zu überprüfen, stellt die List-Klasse verschiedene Methoden bereit. Damit kann der Inhalt einer Liste manipuliert und verändert werden.

  • Die Methode public void replaceItem(String newValue, int index) ersetzt ein an der angegebenen Position (der Integer-Wert) vorhandenes Element durch ein neues Element (der String-Wert).
    Beispiel:
  • meineListe.replaceItem("Grün",1);
  • Die Methode ersetzt an der Position 1 den Wert durch »Grün«.
  • Die Methode public void delItem(int position) löscht ein Element in der Liste an der angegebenen Position.
    Beispiel:
  • meineListe.delItem(2); // löscht 3. Eintrag der Liste
  • Die public void delItems(int start, int end)-Methode löscht einen ganzen Bereich von Elementen aus der Liste. Der erste Integer-Wert gibt den Beginn und der zweite Integer-Wert das Ende der zu löschenden Elemente in der Liste an. Bei dieser Methode muss allerdings der Hinweis erfolgen, dass sie nicht nur - wie einige der anderen Methoden - als deprecated angeben wird, sondern von Sun explizit von der Verwendung abgeraten wird. Dennoch ein Beispiel:
  • meineListe.delItems(2, 5); /* Löschen von der Position 2 bis inklusive Position 5 */
  • Mittels der public void clear()-Methode können Sie alle Elemente in der Liste löschen.
    Beispiel:
  • meineListe.clear();
  • Die bereits bekannte Methode getSelectedIndex() gibt die Indexposition des aktuell ausgewählten Elements zurück. Sofern kein Element ausgewählt worden ist, wird -1 zurückgegeben.
    Beispiel:
  • int aktAusw = meineListe.getSelectedIndex();
  • Die bereits bekannte Methode getSelectedItem() gibt das ausgewählte Element als Zeichenkette zurück.
    Beispiel:
  • String aktAusw = meineListe.getSelectedItem();
  • Die Methode public int[] getSelectedIndexes() gibt bei Listen mit erlaubter Mehrfachauswahl alle Auswahlen zurück. Dabei muss beachtet werden, dass die Anzahl der zurückgegebenen Werte natürlich nicht von vornherein feststeht.
    Beispiel:
  • int aktuelleAuswah[] = meineListe.getSelectedIndexes();
  • Die Methode public String[] getSelectedItems() gibt eine Reihe von Zeichenketten zurück, die die ausgewählten Elemente enthalten.
    Beispiel:
  • String aktuelleAuswahlen[] = meineListe.getSelectItems();
  • Mittels der bekannten Methode select(int) können Sie ein beliebiges Element auswählen. Wenn die Liste keine Mehrfachauswahl zulässt, wird das vorher ausgewählte Element abgewählt.
    Beispiel:
  • mListe.select(2); // Wählt das 3. Element aus
  • Die Methode select(String) kennen wir auch schon. Sie arbeitet fast identisch. Damit können Sie ein mit String bezeichnetes Element auswählen.
    Beispiel:
  • meineListe.select("Blau"); /* Wählt das Element mit dem passenden Eintrag aus */
  • Die Methode public void deselect(int index) deselektiert das angegebene Element.
    Beispiel:
  • meineListe.deselect(2);
  • Diese Methode deselektiert das dritte Element der Liste.
  • Die bekannte Methode isSelected(int) überprüft, ob das Element mit dem angegebenen Index ausgewählt worden ist oder nicht und liefert ein boolesches Ergebnis zurück, das beispielsweise in Schleifen ausgewertet werden kann.
  • Beispiel:
  • if (meineListe.isSelected(2))
    {
    // tue etwas, wenn das 3. Element ausgewählt wurde
    }
  • Mit der Methode public void setMultipleSelections(boolean b) können Sie die Mehrfachauswahl dynamisch an- und abstellen.
    Beispiel:
  • meineListe.setMultipleSelections(true);
  • Die Methode bedeutet das Aktivieren der Multiselektion.
  • Passend dazu können Sie überprüfen, ob Mehrfachauswahl erlaubt ist. Dazu dient die public boolean allowsMultipleSelections()-Methode. Sie gibt den Wert true zurück, sofern Mehrfachauswahl erlaubt ist, andernfalls false.
    Beispiel:
  • if (meineListe.allowsMultipleSelections())
    {
    // tue etwas sinnvolles
    }
  • Die Methode public void makeVisible(int index) stellt sicher, dass ein mit dem Integer-Wert angegebenes Element in dem Listenfenster sichtbar ist.
    Beispiel:
  • meineListe.makeVisible(10);
  • Die Anweisung macht das Element Nr. 11 sichtbar.
  • Die uns bereits bekannte Methode countItems() gibt die Anzahl der Einträge in der Liste aus.
    Beispiel:
  • int anzahl = meineListe.countItems();

Die Reaktion von Listenfeldern auf Aktionen im Rahmen des Eventmodells 1.0 unterscheidet sich von den bisherigen Komponenten. Es wird nicht die action()-Methode aufgerufen. Statt dessen wird eine alternative Möglichkeit verwendet - die handleEvent()-Methode. Diese stellt dazu die Reaktionsmöglichkeit auf zwei Ereignisse der Listenfelder zur Verfügung:

  • Reaktion auf Listenauswahl
  • Reaktion auf Deselektion eines Listeneintrags

Die Syntax der handleEvent()-Methode kennen wir bereits:

public boolean handleEvent(Event evt)

Wir wollen dennoch auf ein Beispiel nach dem alten Eventmodell verzichten und gleich ein Beispiel nach dem Eventmodell 1.1 durchspielen. Die Details wiederholen sich.

import java.awt.*;
import java.awt.event.*;
public class Liste3 extends Frame {
  List meineListe = new List();
  public Liste3() {
        initial();
   }
  public static void main(String[] args) {
    Liste3 liste3 = new Liste3();
  }
  private void initial()  {
    meineListe.setMultipleMode(false);
    meineListe.addItemListener(
  new java.awt.event.ItemListener() {
      public void itemStateChanged(ItemEvent e) {
        meineListe_itemStateChanged(e);
      }  });
    meineListe.addItem("Blau"); 
    meineListe.addItem("Rot"); 
    meineListe.addItem("Grün"); 
    meineListe.addItem("Gelb"); 
    this.add(meineListe, BorderLayout.WEST);
    setSize(300,200);
    show();
  }
  void meineListe_itemStateChanged(ItemEvent e) {
   if(meineListe.getSelectedItem() == "Blau") { 
   setBackground(Color.blue);
  }
   if (meineListe.getSelectedItem() == "Rot") { 
  setBackground(Color.red);
  } 
   if (meineListe.getSelectedItem() == "Grün") { 
  setBackground(Color.green);
       } 
   if (meineListe.getSelectedItem() == "Gelb") { 
  setBackground(Color.yellow);
    } 
  }  }

Abbildung 10.22:  Reaktion auf die Auswahl

Beachten Sie, dass das Programm keinen Mechanismus zum Beenden besitzt. Sie können es auf Befehlszeilenebene mit CTRL+C beenden.

10.8 Textbereiche und Textfelder

Um Anwendern eine vernünftige Benutzeroberfläche zur Verfügung zu stellen, müssen Möglichkeiten zur Eingabe von freien Texten vorhanden sein. Das AWT verfügt deshalb über verschiedene Klassen, um Textdaten einzugeben - die Klasse TextField für die so genannten Textfelder und die Klasse TextArea für die Textbereiche.

Wesentlicher Unterschied zwischen beiden Elementen ist, dass Textbereiche Funktionalitäten zur Behandlung von größeren Textmengen zur Verfügung stellen. Textfelder begrenzen die Menge der einzugebenden Texts und können insbesondere nicht gescrollt werden. Sie eignen sich deshalb hauptsächlich für einzeilige Eingabefelder mit begrenzter Textmenge.

Dahingegen können in Textbereiche mehrere Zeilen verarbeitet werden, und Textbereiche sind scrollbar. Dementsprechend haben sie standardmäßig Bildlaufleisten. Trotz der Unterschiede besitzen beide Klassen viele ähnliche Methoden, da sie beide aus der gemeinsamen Klasse TextComponent abgeleitet sind.

Das Erzeugen von Textbereichen und Textfeldern ist sehr ähnlich. Sie müssen für einen Textbereich nur zusätzlich sowohl Spalten als auch Reihen (Zeilen) angeben, wenn Sie eine bestimmte Größe für einen Textbereich angeben. Wenden wir uns zunächst dem Textbereich zu.

10.8.1 Textbereiche

Zum Erstellen eines Textbereiches können Sie einen der folgenden Konstruktoren verwenden:

  • TextArea() erzeugt einen leeren Textbereich. Das Problem ist hier, dass damit weder die Anzahl von Spalten und Zeilen genau bestimmt ist. Beide sind erst einmal auf 0 festgelegt. Bevor dieser Textbereich in ein Panel eingefügt und dort angezeigt werden kann, muss die Anzahl von Spalten und Zeilen verändert werden.
  • Beispiel:
  • TextArea meinTextBereich = new TextArea();
  • Sie können ebenso einen Textbereich mit Text vorbelegen. Dazu dient der Konstruktor TextArea(String). Auch hier müssen Sie die genaue Größe des Textbereichs noch festlegen.
    Beispiel:
  • TextArea meinTextBereich =
    new TextArea("Vorbelegen oder nicht vorbelegen?");
  • Der Konstruktor TextArea(int, int) erzeugt einen leeren Textbereich mit einer bereits festgelegten Anzahl von Spalten und Zeilen.
    Beispiel:
  • TextArea meinTextBer = new TextArea(13, 42 );
    // 13 Zeilen, 42 Spalten
  • Sie können ebenso einen Textbereich mit einem Anfangstext und mit einer bereits festgelegten Anzahl von Spalten und Zeilen erzeugen. Dazu dient der Konstruktor TextArea(String, int, int).
    Beispiel:
  • TextArea meinTextBer =
    new TextArea("Belag", 13, 42 );
    // 13 Zeilen, 42 Spalten

10.8.2 Textfelder

Auch für Textfelder gibt es diverse Konstruktoren. Der Konstruktor TextField() ist die einfachste Möglichkeit, ein Textfeld zu erstellen. Dadurch wird wie im Fall der Textbereiche ein leeres Textfeld mit einer noch nicht spezifizierten Anzahl von Spalten (Default 0) erzeugt.

Zum Vorbelegen eines Textfeldes (mit einer noch nicht spezifizierten Anzahl von Spalten) dient der Konstruktor TextField(String).

Beispiel: TextField meinTextFeld = new TextField("Belag");

In der Regel ist es sinnvoll, die Anzahl von Spalten/Zeichen in dem Textfeld festzulegen. Das funktioniert mit dem Konstruktor TextField(int).

Beispiel: TextField mTxtFld = new TextField(42);

Wie bei den Textbereichen kann man die Angabe der Größe mit einer Vorbelegung kombinieren. Dazu dient der Konstruktor Textfield(String, int).

Beispiel: TextField mTxtFd = new TextField("Belag?", 42);
// Vorbelegt + 42 Spalten groß

Das Platzieren von Textbereichen und Textfeldern in einem Container funktioniert wie bei den meisten Komponenten mit der add()-Methode.

Schauen wir uns wieder ein kleines Beispiel an:

import java.awt.*;
public class Text1 extends java.applet.Applet {
// 3 Zeilen, 42 Spalten
TextArea meinTextBereich1 = new TextArea(3, 42 ); 
// 8 Zeilen, 20 Spalten, vorbelegt
TextArea meinTextBereich2 = new TextArea("Vorbelegen oder nicht orbelegen?", 8, 20 ); 
// 15 Spalten
TextField meinTextFeld1 = new TextField(15); 
// Vorbelegt und 27 Spalten groß
TextField meinTextFeld2 = new TextField("Vorbelegen oder nicht vorbelegen?", 27); 
public void init() {
add(meinTextFeld1);
add(meinTextBereich1);
add(meinTextFeld2);
add(meinTextBereich2);
}  }

Abbildung 10.23:  Textfelder

Textkomponenten lassen sich natürlich auch beeinflussen und überprüfen. Da sowohl Textbereiche als auch Textfelder von der abstrakten Klasse TextComponent erben, sind die meisten Methoden dieser Klasse in beiden Textkomponenten anzuwenden.

10.8.3 Gemeinsame Methoden

  • Die Methode public String getText() gibt den Textinhalt eines Textfeldes oder eines Textbereiches zurück.
    Beispiel:
  • String textinhalt = meinTextFeld.getText();
  • Die Methode public void setText(String t) fügt den mit String angegebenen Text in die Komponenten ein.
    Beispiel:
  • mTxtFd.setText("Schiebe Text in Textfeld");
  • Mit der Methode public String getSelectedText() können Sie herausfinden, welcher Text selektiert worden ist.
    Beispiel:
  • String ausgew = mTxtBereich.getSelectedText();
  • Eng mit der Methode getSelectedText() zusammenhängend sind die Methoden public int getSelectionStart() und public int getSelectionEnd(). Sie können damit herausfinden, wo eine Auswahl beginnt und wo sie aufhört. Beide Methoden liefern einen Integer-Wert, der die relativen Positionen (ganzzahlig) des selektierten Anfangs- und Endpunktes der Selektion für den gesamten Text angeben. Wenn die Auswahl beispielsweise am Anfang des Textes beginnt, gibt die Methode getSelectionStart() den Wert 0 zurück.
    Beispiel:
  • int auswahlStart, auswahlEnde;
    auswahlStart = meinTextFeld.getSelectionStart();
    auswahlEnde = meinTextFeld.getSelectionEnd();
  • Zum Selektieren eines bestimmten Textbereiches innerhalb einer Textkomponente mittels einer Methode gibt es wieder select(int, int). Diese Methode wählt den Text zwischen dem ersten Integerwert (Anfangspunkt der Selektion) und dem zweiten Integerwert (Endpunkt der Selektion) aus.
    Beispiel:
  • meinTextFeld.select(0, 42);
  • Die Methode selektiert die Zeichen vom Anfang bis zur Position 42 inklusive.
  • Zur Auswahl des gesamten Textes gibt es die Methode public void selectAll().
    Beispiel:
  • meinTextBereich.selectAll();
  • Die Methode selektiert alle Zeichen im Textbereich.
  • Nicht immer ist Text in einer Textkomponente editierbar. Zur Überprüfung können Sie die Methode public boolean isEditable() verwenden. Diese Methode liefert einen booleschen Wert zurück, der true ist, wenn die Komponente bearbeitet werden kann und false, wenn sie nicht bearbeitet werden kann.
    Beispiel:
  • boolean aenderbar;
    aenderbar = meinTextBereich.isEditable();
  • Wenn Sie selbst eine Textkomponente als nicht-editierbar definieren oder sie wieder als editierbar freigeben wollen, haben Sie die Schwestermethode public void setEditable(boolean b) zur Verfügung. Wenn Sie true als booleschen Wert verwenden (Standardwert), ist eine Textkomponente editierbar, bei Verwendung von false hingegen nicht (also read-only).
    Beispiel:
  • meinTextFeld.setEditable(false);
  • Mit der Methode public int getColumns() können Sie die Breite einer Textkomponente sowohl im Textbereich als auch im Textfeld in Zeichen bzw. Spalten erfragen (d.h. die Anzahl der sichtbaren Spalten und nicht wie viel Text da ist).
    Beispiel:
  • int breite = meinTextBereich.getColumns();

10.8.4 Spezielle TextField-Methoden

Textfelder besitzen einige spezielle Eigenschaften, die Textbereiche nicht haben. Dementsprechend gibt es einige Methoden, die nur dort Sinn machen.

  • Textfelder eignen sich insbesondere für die zur Verfügungstellung von Eingabemöglichkeiten von Passwörtern. Sie kennen sicher das Phänomen, dass bei der Eingabe von Passwörtern die eingegebenen Zeichen nicht auf dem Bildschirm angezeigt werden. Sie werden durch Alias-Zeichen (meist der Stern *) ersetzt. Man nennt diese Alias-Zeichen auch Echo-Zeichen. In der TextField-Klasse findet sich nun eine Methode, mit der Sie ein Echo-Zeichen setzen können, das anstelle des eingegebenen Zeichens auf dem Bildschirm dargestellt wird. Die Methode heißt public void setEchoCharacter(char c).
    Beispiel:
  • meinTextFeld.setEchoCharacter('*');
  • Die Anweisung ersetzt alle eingegebenen Zeichen durch *.
  • Wenn Sie das explizit verwendete Echozeichen für ein Feld abfragen wollen, können Sie die Methode public char getEchoChar() verwenden.
    Beispiel:
  • char echoChar = meinTextFeld.getEchoChar();
  • Bevor Sie ein Echozeichen abfragen, ist es sicher sinnvoll zu überprüfen, ob das Textfeld überhaupt ein Echozeichen besitzt. Die Methode public boolean echoCharIsSet() gibt true zurück, wenn das Textfeld mit einen Echozeichensatz belegt ist, sonst wird sie false zurückgegeben.
    Beispiel:
  • boolean echCh_janein = meinTextFeld.echoCharIsSet();

10.8.5 Spezielle TextArea-Methoden

Wie wir wissen, haben auch Textbereiche einige spezielle Eigenschaften, die Textfelder nicht besitzen. Einige davon dienen der Bearbeitung von Text und Realisieren das Einfügen, Anhängen und Ersetzen von Text.

  • Die Methode public void appendText(String str) hängt den mit str spezifizierten Text an das Ende eines Textbereichs an.
    Beispiel:
  • meinTextBereich.appendText("Häng das an das Ende des bisher im Textbereich vorhandenen Textes an.");
  • Die Methode public void insertText(String str, int pos) fügt an der mit int spezifizierten Stelle den mit String angegebenen Text ein.
    Beispiel:
  • meinTextBereich.insertText("Füge den Text am Anfang des Textbereiches ein.", 0);
  • Die Methode public void replaceText(String str, int start, int end) ersetzt die Teile des Textes, die sich zwischen der Anfangsposition (1. Integerwert) und der Endposition (2. Integerwert) befinden durch den mit dem String spezifizierten Text.
    Beispiel:
  • meinTextBereich.replaceText("Ersetze den Text zwischen dem Anfang des Textbereiches und der Position 42 (inkl.) durch diesen Text.", 0, 42);
  • Die Ermittlung von der Anzahl der Spalten ist bei beiden Textkomponenten identisch (die Methode getColumns()). Die Anzahl der Zeilen in einem Textbereich macht jedoch natürlich nur bei Textbereichen Sinn. Mit der Methode public int getRows() wird die Anzahl der Zeilen eines Textbereichs ermittelt. Diese ist in der Regel nicht identisch mit der Anzahl der im Textbereich befindlichen Textzeilen.
    Beispiel:
  • int laenge = meinTextBereich.getRows();

Reaktionen auf Ereignisse in Textbereichen und Textfeldern zählen nicht zu den häufig ausgewerteten Events. Bis auf wenige Ausnahmen gilt, dass Textbereiche überhaupt keine eigenständige Reaktion auf Ereignisse zeigen sollten. Dies wird bei genauerer Überlegung klar. Wie verhalten sich Textbereiche auf den grafischen Oberflächen, die Sie kennen? Die Ereignisse, die Sie für TextArea- oder TextField-Objekte erzeugen können, sind Tastatur- und Mausereignisse. Bei Eingabe einer bestimmten Tastenkombination oder einem Klick auf den Textbereich würde eine Reaktion erzeugt. Ist Ihnen dieses Verhalten bekannt? Wahrscheinlich nicht, denn üblicherweise werden die Reaktionen mit einer Schaltfläche oder einem Menü für den Anwender gekoppelt sein. Erst diese lösen dann eine Reaktion aus.

Wenn dennoch ein Ereignis direkt ausgewertet werden soll, sollte unter dem Eventmodell 1.0 beachtet werden, dass (wie auch die List-Klasse) die TextArea-Klasse nicht die action()-Methode verwendet. Im Prinzip steht Ihnen die handleEvent()-Methode zur Verfügung (die bei Listen zum Einsatz kommt). Sie sollten diese jedoch nicht verwenden. Die TextField-Klasse verwendet zwar die action()-Methode, allerdings nur dann, wenn der Anwender die Eingabetaste drückt. Aber auch hier gilt, dass eine Kopplung mit einer Schaltfläche mehr Sinn macht. Diese Bemerkungen gelten natürlich auch für das aktuelle Eventmodell.

10.9 Schieber und Bildlaufleisten

Die beiden AWT-Elemente Listen und Textbereiche besitzen standardmäßig die Eigenschaft, bei Bedarf nicht in den Anzeigebereich passenden Inhalt per Bildlaufleisten und Schieber zu scrollen. Die Bildlaufleisten und Schieber sind bereits in diese Elemente der Benutzeroberfläche integriert und werden nur dann zur Verfügung gestellt, wenn es notwendig ist. Sie können jedoch auch individuell einzelne Bildlaufleisten erstellen und mit diversen Komponenten koppeln. Die Scrollbar-Klasse, auf der diese Objekte basieren, besitzt eine Basisschnittstelle für den Bildlauf, der in verschiedenen Situationen verwendet werden kann.

Die Steuerelemente der Bildlaufleiste dienen zur Auswahl und Kontrolle eines Werts, der die gegenwärtige Position der Bildlaufleiste anzeigt. Dieser Positionswert kann zwischen einem Minimumwert und einen Maximumwert gesetzt werden.

Um diesen Positionswert zu ändern, können drei verschiedene Teile der Bildlaufleisten verwendet werden und damit drei verschiedene Arten der Positionsveränderung auswählen:

  • Die Positionsveränderung über die Pfeile an den beiden Enden der Bildlaufleiste aktualisieren die Position der Bildlaufleiste über eine feste Einheit in die eine oder andere Richtung. Meist ist als Standard eine Zeile eingestellt. Man nennt diese Methode deshalb auch Zeilenaktualisierung, und es handelt sich um eine relative Positionsveränderung von der aktuellen Position aus gesehen.
  • Die zweite Art der Positionsveränderung erfolgt über den Raum zwischen dem Schieber der Bildlaufleiste und dem Pfeil am jeweiligen Ende der Leiste. Wenn Sie dahin klicken, wird die Position um eine größere, feste Einheit verändert (Standardeinstellung 10 Zeilen oder gelegentlich auch eine Seite - deshalb hin und wieder seitenweise Aktualisierung genannt). Auch hierbei handelt es sich um eine relative Positionsveränderung von der aktuellen Position aus gesehen.
  • Die dritte Art der Positionsveränderung ist eine absolute Positionsveränderung. Diese absolute Aktualisierung erfolgt über den Schieber (auch Schiebeknopf oder Bildlauffeld genannt), indem er in die eine oder andere Richtung gezogen wird. Sie haben keine Steuerungsmöglichkeit darüber, wie sich die Positionswerte bei einer absoluten Aktualisierung ändern. Sie können höchstens die Maximum- und Minimumwerte steuern.

Eine Bildlaufleiste kann nur sich selbst aktualisieren. Dies geschieht vollkommen automatisch und braucht von Ihnen nicht ausgelöst zu werden. Sie müssen nur einen Minimumwert und einen Maximumwert festlegen. Eine Bildlaufleiste kann andererseits aber keine anderen Komponenten automatisch zum Scrollen veranlassen. Damit brauchen Sie auch keine Ereignisse der Bildlaufleiste zu verwalten, die eigentliche Funktionalität der Bildlaufleiste ist jedoch noch nicht gegeben. Wenn Sie möchten, dass über eine Bildlaufleiste der Bildinhalt eines Elements gescrollt wird (was ja meist der Sinn und Zweck einer Bildlaufleiste ist), müssen Sie über entsprechenden Code den Positionswert der Bildlaufleiste abfragen und dann den Bildinhalt entsprechend anpassen. Wir werden das in dem nachfolgenden Beispiel demonstrieren.

Zum Erzeugen von Bildlaufleisten stehen Ihnen drei Konstruktoren zur Verfügung.

  • Der Konstruktor Scrollbar() ist die einfachste Variante und erzeugt eine einfache vertikale Bildlaufleiste. Die anfänglichen Minimum- und Maximumwerte sind (0, 0).
    Beispiel:
  • Scrollbar mBildllst = new Scrollbar();
  • Der zweite Konstruktor Scrollbar(int) lässt die Angabe zu, ob es sich um eine vertikale oder horizontale Bildlaufleiste handelt. Für das Argument (ein Integerwert), das die Ausrichtung bezeichnet, können Sie die Konstanten Scrollbar.HORIZONTAL oder Scrollbar.VERTICAL angeben. Die anfänglichen Minimum- und Maximumwerte sind auch beim zweiten Konstruktor (0, 0).
    Beispiel:
  • Scrollbar mBdllst = new Scrollbar(Scrollbar.HORIZONTAL);
  • Der Konstruktor Scrollbar(int, int, int, int, int) erzeugt eine Bildlaufleiste mit einer vordefinierten Ausrichtung, Position, Größe sowie Minimum- und Maximumwerte. Das erste Argument ist die Ausrichtung der Bildlaufleiste und entspricht dem Argument des zweiten Konstruktors. Das zweite Argument ist der Anfangswert der Bildlaufleiste (also die Position des Schiebers) und sollte ein Wert zwischen Minimumwert (das vierte Argument) und Maximumwert (das fünfte Argument) der Bildlaufleiste sein. Das dritte Argument ist die Gesamtbreite oder -höhe der Bildlaufleiste (abhängig von der Ausrichtung).
    Beispiel:
  • Scrollbar meineBildlaufleiste = new Scrollbar(Scrollbar. HORIZONTAL,0,10,0,100);

Auch neu erstellte Bildlaufleisten müssen vor ihrer Verwendung einem Container hinzugefügt werden, was in diesem Fall dem Element entspricht, das gescrollt werden soll. Dazu dient wie üblich die add()-Methode.

Die Scrollbar-Klasse stellt einige Methoden zur Verfügung, mit denen die Werte von Bildlaufleisten überprüft und beeinflusst werden können.

  • Die Methode public int getMinimum() fragt den Minimumwert ab.
    Beispiel:
  • int mini = meineBildlaufleiste.getMinimum();
  • Die Methode public int getMaximum() fragt den Maximumswert ab.
    Beispiel:
  • int maxi = meineBildlaufleiste.getMaximum();
  • Mit public void setLineIncrement(int v) können Sie die Einheit setzen, um die eine Bildlaufleiste bei einer Positionsveränderung über die Pfeile an den beiden Enden der Bildlaufleiste verändert wird.
  • Beispiel:
  • meineBildlaufleiste.setLineIncrement(3);
  • Die Anweisung setzt die Einheit auf 3.
  • Mit public int getLineIncrement() können Sie die Einheit abfragen, um die eine Bildlaufleiste bei einer Positionsveränderung über die Pfeile an den beiden Enden der Bildlaufleiste verändert wird :
    Beispiel:
  • int zeilenverschg = mBildllst.getLineIncrement();
  • Die Methode public int getOrientation() fragt die Ausrichtung der Bildlaufleiste ab. 0 steht für vertikal (Scrollbar.VERTICAL) und 1 für horizontal (Scrollbar.HORIZONTAL). Sie können entweder die Konstanten verwenden oder den Integerwert.
    Beispiel:
  • if (meineBildlaufleiste.getOrientation() == Scrollbar.HORIZONTAL)
    {
    // tue was sinnvolles
    }
    else
    {
    // tue was sinnvolles
    }

Bezüglich der Ereignisbehandlung von Bildlaufleisten gibt es ein paar Feinheiten zu beachten. Die Scrollbar-Klasse verwendet im Eventmodell 1.0 - wie die List- und die TextArea-Klassen - die action()-Methode nicht. Sie müssen dort auf die handleEvent()-Methode zurückgreifen. Damit können Sie Veränderung einer Bildlaufleiste überprüfen. Die Ereignisse werden wie üblich mit evt.id an die handleEvent()-Methode übergeben. Die möglichen Werte der evt.id bei Ereignissen einer Bildlaufleiste sind folgende:

Wert von evt.id Beschreibung
Event.SCROLL_ABSOLUTE Der Schieber der Bildlaufleiste wird gezogen.
Event.SCROLL_LINE_DOWN Der obere (vertikale Bildlaufleiste) oder linke (horizontale Bildlaufleiste) Pfeilknopf wird angeklickt.
Event.SCROLL_LINE_UP Der untere (vertikale Bildlaufleiste) oder rechte (horizontale Bildlaufleiste) Pfeilknopf wird angeklickt.
Event.SCROLL_PAGE_DOWN Klick in den Bereich zwischen den Schieber und dem unteren (vertikale Bildlaufleiste) oder linken (horizontale Bildlaufleiste) Pfeil.
Event.SCROLL_PAGE_UP Klick in den Bereich zwischen dem Schieber und dem oberen (vertikale Bildlaufleiste) oder rechten (horizontale Bildlaufleiste) Pfeil.

Tabelle 10.2:   Ereignisse von Bildlaufleisten im Eventmodell 1.0

Das konkrete Ereignis ist oft gar nicht so von zentraler Bedeutung. In vielen Fällen genügt die Information, dass sich die Position der Bildlaufleiste verändert hat. Sie können dann einfach mithilfe der Methode public int getValue() die neue Position herausfinden.

Im Rahmen des Eventmodells 1.1 erfolgt die Behandlung von Ereignissen - wie dort allgemein üblich - wieder über das vereinheitlichte Verfahren. Allgemein gilt, dass eine Bildlaufleiste Adjustment-Ereignisse sendet. Die potenziellen Empfänger implementieren die Schnittstelle AdjustmentListener und registrieren sich über addAdjustmentListener. Im Allgemeinen wird dann auf das Ereignis adjustmentValueChanged reagiert. Nachfolgend finden Sie ein kleines Beispiel, das die Position des Schiebers einer Bildlaufleiste in einem Label anzeigt. Beachten Sie, dass der Rückgabewert der Methode getValue() ein int-Wert ist, der mit einem Wrapper in ein Integer-Objekt konvertiert wird, aus dem dann ein String extrahiert wird, damit die Methode setText() verwendet werden kann.

import java.awt.*;
import java.awt.event.*;
public class Bild extends Frame {
  BorderLayout borderLayout1 = new BorderLayout();
  Label label1 = new Label();
  Scrollbar scrollbar1 = new Scrollbar();
  public Bild() {
      initial();
  }
  public static void main(String[] args) {
    Bild bild = new Bild();
  }
  private void initial() {
    label1.setText("label1");
    this.setLayout(borderLayout1);
    scrollbar1.addAdjustmentListener(new java.awt.event.AdjustmentListener() {
      public void
   adjustmentValueChanged(AdjustmentEvent e) {
        scrollbar1_adjustmentValueChanged(e);
      }
    });
    add(label1, BorderLayout.CENTER);
    add(scrollbar1, BorderLayout.EAST);
    setSize(200,200);
    show();
  }
  void scrollbar1_adjustmentValueChanged(
  AdjustmentEvent e) {
  label1.setText((new Integer(scrollbar1.getValue())).toString());
  }  }

Abbildung 10.24:  Der Wert der Bild laufleiste wird im Label ausgegeben.

Das AWT stellt seit dem JDK 1.1 neben einfachen Bildlaufleisten noch eine weitere Komponente zur Verfügung, die einen Container für automatisches horizontales und vertikales Scrollen darstellt - ScrollPane. Es handelt sich dabei um ein spezielles Panel, das sich in folgenden Details von einem gewöhnlichen Panel unterscheidet:

  • Es kann nur genau eine Komponente aufnehmen.
  • Es verwendet keinen Layoutmanager.
  • Es kann eine Ausgabefläche verwalten, die über den angezeigten Bereich hinausgeht (ein so genannter virtueller Ausgabebereich).

Gerade der dritte Punkt stellt die Funktionalität dar, die zum Scrollen benötigt wird. Wenn ein in einem ScrollPane-Objekt dargestellter Inhalt nicht vollständig angezeigt werden kann, werden automatisch Scrollbars eingeblendet.

Für die Erzeugung eines ScrollPane-Objekts gibt es einmal den leeren Konstruktor und einen Kontruktor mit einem int-Wert, in dem der int-Wert drei Zustände des ScrollPane-Objekts festlegen kann. Diese können auch wieder über Konstanten festgelegt werden.

Konstante Beschreibung
ScrollPane.SCROLLBARS_AS_NEEDED Wenn notwendig, werden Scrollbars angezeigt.
ScrollPane.SCROLLBARS_NEVER Niemals Scrollbars anzeigen. Eine Verschiebung ist nur programmtechnisch möglich.
ScrollPane.SCROLLBARS_ALWAYS Immer Scrollbars anzeigen.

Tabelle 10.3:   ScrollPane-Konstanten

Das Hinzufügen eines ScrollPane-Objekts zu einem Container erfolgt wie üblich mit add(). Die Methode setSize() legt die sichtbare Größe des ScrollPane-Objekts fest (im Rahmen der Layouts des umgebenden Containers), die Größe des virtuellen Ausgabebereichs wird durch die enthaltene Komponente bestimmt. Betrachten wir ein kleines Beispiel (etwas künstlich, aber die Grundidee ist sicher zu erkennen).

import java.awt.*;
import java.applet.*;
public class ScrollPane1 extends Applet {
  ScrollPane scrollPane1 = 
    new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS);
  BorderLayout borderLayout1 = new BorderLayout();
  public void init() {
    setLayout(borderLayout1);
    scrollPane1.add(new Button("Test"));
    add(scrollPane1, BorderLayout.CENTER);
 }  }

Abbildung 10.25:  Der Button mit Scrollbars

10.10 Zeichenbereiche

Die meisten AWT-Komponenten bieten Möglichkeiten, darauf Zeichnenoperationen auszuführen oder Grafiken anzuzeigen. Zeichenbereiche sind jedoch AWT-Komponenten, die speziell zum Zeichnen da sind. Sie enthalten keine anderen Komponenten, akzeptieren demgegenüber jedoch Ereignisse. Außerdem lassen sich Animationen und Bilder in Zeichenbereichen darstellen. Insbesondere erlauben Zeichenbereiche, die Hintergrundfarbe zu beeinflussen. Die standardmäßig in allen Applets und AWT-Komponenten bereitstehende paint()-Methode zeichnet erst einmal nur in der aktuellen Hintergrundfarbe. Eine in einem Canvas-Objekt überschriebene Methode hat da natürlich viel mehr Möglichkeiten.

Zeichenbereiche werden erzeugt, indem Sie Instanzen der Canvas-Klasse generieren oder eine Klasse von Canvas ableiten.

Beispiel: Canvas meinZeichenbereich= new Canvas();

Mittels der add()-Methode wird der neu erstellte Zeichenbereich wieder in einen Container eingefügt.

Beispiel: add(meinZeichenbereich);

Wir spielen das Verfahren in einem einfachen Beispiel durch. Dazu verwenden wir zwei Klassen. Die eine Klasse wird als Erweiterung von Canvas gebildet und lädt ein Bild aus einer relativen Pfadangabe. Im Gegensatz zu den bisherigen Fällen verwenden wir für das gesamte Beispiel kein Applet, sondern eine eigenständige Applikation. Damit wird das Laden eines Bildes auch nicht mehr ganz so verlaufen, wie es bei einem Applet erfolgt. Zwar greifen wir wieder auf die Methode getImage() zurück, aber nicht die der Applet-Klasse. Wir verwenden die Methode public abstract Image getImage(String filename) aus der Klasse java.awt.Toolkit. Damit wir diese Methode anwenden können, benötigen wir allgemein bei Frames das Toolkit des Frames der Applikation. Dieses liefert die Methode public Toolkit getToolkit() aus java.awt.Window. In unserem Fall, wo wir eine Canvas-Komponente verwenden, können wir die Methode aus java.awt.Component verwenden, die für AWT-Komponenten deren Toolkit liefert.

import java.awt.*;
public class MeinZeichenBereich1 extends Canvas {
 Image  bild; 
 public void paint(Graphics g) {
  bild = getToolkit().getImage("images/Bild1.jpg"); 
  g.drawImage(bild, 0, 0, this); 
 }  }

Das eigentliche Java-Programm erzeugt ein Objekt der Klasse MeinZeichenBereich1 und fügt es der Oberfläche hinzu.

import java.awt.*;
public class CanvasTest extends Frame {
  BorderLayout bl1 = new BorderLayout();
  MeinZeichenBereich1 cv1 = new MeinZeichenBereich1();
  public CanvasTest() {
      initial();
  }
  public static void main(String[] args) {
    new CanvasTest();
  }
  void initial() {
    this.setLayout(bl1);
    this.add(cv1, BorderLayout.CENTER);
    setSize(500,400);
    show();
  }  }

Abbildung 10.26:  Der Zeichenbereich zeigt ein Bild .

Beachten Sie, dass das Programm keinen Mechanismus zum beenden besitzt. Sie können es auf Befehlszeilenebene mit CTRL+C beenden.

10.11 Menüs

Da jedes Fenster eine eigene Menüleiste besitzen kann, müssen wir uns natürlich damit beschäftigen. Immerhin zählen Menüs in grafischen Oberflächen zu den wichtigsten Interaktionselementen und kein halbwegs professionelles Programm kann darauf verzichten. Jede Menüleiste in Java kann mehrere Menüs enthalten und dort jedes Menü wieder beliebige Einträge (halt so, wie man Menüs kennt). Das AWT enthält dafür die Klassen MenuBar, Menu und MenuItem.

Das Erzeugen von Menüleisten besteht aus zwei zentralen Schritten. Einmal muss eine Menüleiste erzeugt werden. Als zweiter Schritt erfolgt die Erzeugung der konkreten Einträge im Menü.

Das Erzeugen einer Menüleiste erfolgt, indem Sie mit dem Konstruktor MenuBar() eine Instanz der Klasse MenuBar erstellen.

Beispiel: MenuBar meineMenueleiste = new MenuBar();

Um dann eine Menüleiste als Menü eines Fensters zu definieren, verwenden Sie die public void setMenuBar(MenuBar mb)-Methode.

Beispiel: meinFrame.setMenuBar(meineMenueleiste);

Bevor man eine Menüleiste sinnvoll verwenden kann, muss sie »mit Fleisch gefüllt« werden. Das soll heißen, es müssen Menüeinträge erzeugt und dann dem Menü hinzugefügt werden. Anschließend müssen noch die einzelnen Menüs mit Einträgen gefüllt werden. Aber beginnen wir mit dem ersten Schritt.

Das Erzeugen eines Menüeintrags erfolgt mit dem Konstruktor Menu(String).

Beispiel: Menu meinMenupunkt = new Menu("Hilfe");

Um die Menüeinträge in einer Menüleiste hinzuzufügen, wird wieder die add()-Methode verwendet.

Beispiel: meineMenueleiste.add(meinMenupunkt);

Selbstverständlich können Menüeinträge zur Laufzeit aktivierbar (default) oder für den Anwender gesperrt gesetzt werden. Dazu gibt es die üblichen Methoden public void disable() und public void enable(). Diese sind zwar deprecated, funktioniern aber. Alternativ gibt es wieder setEnabled().

Bei Menüeinträgen benötigt man in vielen Fällen die Information darüber, um welche Art von Eintrag es sich handelt - die so genannte Menüoption. Java unterscheidet vier Optionsarten, die allesamt in ein Menü eingefügt werden können.

1. Instanzen der Klasse MenuItem. Dies sind die gewöhnlichen (regulären) Menüeinträge.
2. Menüs mit eigenen Untermenüs
3. Trennlinien
4. Instanzen der Klasse CheckboxMenuItem. Damit werden umschaltbare (aus/an) Menüeinträge erzeugt.

Reguläre Menüeinträge werden mit dem Konstruktur der MenuItem-Klasse erzeugt und mit der add()-Methode eingefügt.

Beispiel: mMenpkt.add(new MenuItem("Über das Programm"));

Abbildung 10.27:  Reguläre Menüeinträge

Untermenüs werden hinzugefügt, indem eine neue Instanz von Menu erstellt und diese dann in das Obermenü eingefügt wird. Danach wird die Option in dieses Menü eingefügt.

Beispiel:

Menu untermenue = new Menu("Untermenü 1");
meinMenupunkt.add(untermenue);
untermenue.add(new MenuItem("Untereintrag 1"));

Abbildung 10.28:  Untermenüs

Trennlinien bilden ein wichtiges Gestaltungsmittel, um Optionsgruppen optisch voneinander zu trennen. Mehr Funktionalität besitzen sie nicht und daher ist der Einbau sehr einfach gehalten. Genau genommen ist er identisch mit der Vorgehensweise bei regulären Menüeinträgen. Sie verwenden nur das Argument - als Label. Dabei ist auffällig, dass sich der Trennstrich über die gesamte Breite des Menüs erstreckt, obwohl nur ein Zeichen - der Strich - als Argument verwendet wird (dies ist auch der deutliche Unterschied zu regulären Menüeinträgen).

Beispiel:

MenuItem meineTrennlinie = new MenuItem("-");
meinMenupunkt.add(meineTrennlinie);

Abbildung 10.29:  Trennlinien in  einem Menü

Umschaltbare Menüeinträge werden mit der Klasse CheckboxMenuItem realisiert. Damit wird ein umschaltbarer Menüeintrag mit einem Kontrollzeichen erzeugt. Anklicken mit der Maus schaltet einen Eintrag an, erneutes Anklicken wieder aus. Auch bei CheckboxMenuItems ist die Vorgehensweise analog wie bei regulären Menüeinträgen (es handelt sich um eine Subklasse von MenuItem). Es wird nur deren Konstruktor - CheckboxMenuItem(String) - verwendet.

Beispiel: CheckboxMenuItem meinSchalter =
new CheckboxMenuItem("Licht an/aus");

Anschließend wird der Eintrag wie üblich dem Menü mit der add()-Methode zugefügt.

Beispiel: meinMenupunkt.add(meinSchalter);

Abbildung 10.30:  Ein CheckboxMenuItem

Die Verwendung von umschaltbaren Menüeinträgen war in älteren Java-Versionen nicht unkritisch. Es konnte zu Fehlfunktionen und Ausnahmen kommen. In neuen Versionen - mit entsprechendem Eventhandling - gibt es keine Probleme.

Ein Menü macht wie die meisten AWT-Komponenten nur dann Sinn, wenn damit eine Aktion ausgelöst werden kann. Diese Aktion kann in dem Eventmodell 1.0 wie fast jede andere Aktion mit der action()-Methode behandelt werden. Sowohl reguläre Menüoptionen als auch CheckboxMenuItem besitzen eine Beschriftung, die als Argument bei der action()-Methode ausgewertet werden kann.

Beispiel:

public boolean action(Event evt, Object arg) {
if (evt.target instanceof MenuItem) {
String label = (String)arg;
if (label.equals("Über das Programm")) {
// tue etwas sinnvolles
}
else {  
// tue etwas sinnvolles
}  }

Auch am Ende des Menüabschnitts sollen wir zwei vollständige Beispiele stehen. Nummer eins arbeitet nach dem Eventhandling 1.0 (es handelt sich um ein Applet, wo das ja oft notwendig ist). Es wird nicht sonderlich umfangreich, jedoch die wichtigsten Schritte zu Erstellung einer Menüstruktur werden durchgeführt. Beachten Sie, dass die Checkbox zum »Aus- und Anschalten des Lichtes« in einigen JVMs eine Ausnahme auswirft. Dies ist bei dem Beispiel bewusst einkalkuliert.

import java.awt.*;
public class Menu1 extends java.applet.Applet {
  meinFenster mFrame;
  public void init() {
// Füge zwei Schaltflächen dem Container hinzu
    add(new Button("Öffne Fenster"));
    add(new Button("Schließe Fenster"));
// Erstelle Frame
    mFrame = new meinFenster("Sub-Fenster");
// Lege Framegröße fest
    mFrame.resize(350,150);
}
// Die action-Methode der Klasse menu1
// Reaktionen auf Schaltflächen
public boolean action(Event evt, Object arg) {
    String label = (String)arg;
    if (evt.target instanceof Button) {
  // Wenn Schaltfläche "Öffne Fenster"
      if (label.equals("Öffne Fenster")) {
  // Überprüfe, ob Frame noch nicht angezeigt
  if (!mFrame.isShowing()) 
    mFrame.show(); // Zeige Frame
      }
  // Wenn Schaltfläche "Schließe Fenster"
      else if (label.equals("Schließe Fenster")) 
  {
  // Überprüfe, ob Frame angezeigt
  if (mFrame.isShowing())
    mFrame.hide(); // Verstecke Frame
      }
      return true;  
      }
    else return false;
  }// Ende der action-Methode des Applets
} // Ende der menu1-Klasse
class meinFenster extends Frame {
  meinFenster(String title) {
// Menüleiste erzeugen
    MenuBar mML = new MenuBar();    
// Menüleiste am Rahmen verankern
    setMenuBar(mML);
// Menüpunkte erzeugen
    Menu mMP1 = new Menu("Farben");  
    Menu mMP2 = new Menu("Menüpunkt 2");  
    Menu mMP3 = new Menu("Menüpunkt 3");  
// Ein Untermenü erzeugen
   Menu uM = new Menu("Farbanpassung");
//Menüeinträge in der Menüleiste platzieren
    mML.add(mMP1);
    mML.add(mMP2);
    mML.add(mMP3);
// Einträge in Menüpunkt 1
    mMP1.add(new MenuItem("Rot"));
    mMP1.add(new MenuItem("Blau"));
    mMP1.add(new MenuItem("Grün"));
    mMP1.add(new MenuItem("Gelb"));
// Trennlinie
   MenuItem mTL = new MenuItem("-");
   mMP1.add(mTL);
// Untermenü dem ersten Menü hinzufügen
   mMP1.add(uM);
   uM.add(new MenuItem("Farbe"));
   uM.add(new MenuItem("Mono"));
// Menüeintrag 2 in der Menüleiste deaktivieren
   mMP2.disable();
// Einträge in Menüpunkt 3 - CheckboxMenuItems 
/* Beachten Sie, dass die Checkbox zum "Aus- und Anschalten des Lichtes" in einigen JVMs 
eine Ausnahme auswirft. Dies ist bei dem Beispiel bewusst einkalkuliert.*/
   CheckboxMenuItem meinSchalter2 = new CheckboxMenuItem("Ton an/aus");
   mMP3.add(new CheckboxMenuItem("Licht an/aus"));
   mMP3.add(meinSchalter2);
// 2. Eintrag im 3. Menüpunkt deaktivieren
   meinSchalter2.disable();
}
// Die Reaktion auf Ereignisse in der Klasse 
// meinFenster
public boolean action(Event evt, Object arg) {
String label = (String)arg;
// Reaktionen auf reguläre Menüeinträge
if (evt.target instanceof MenuItem)  {
   if (label.equals("Rot")) setBackground(Color.red);
   else if (label.equals("Blau")) setBackground(Color.blue);
   else if (label.equals("Grün")) setBackground(Color.green);
   else if (label.equals("Gelb")) setBackground(Color.yellow);
// Reaktionen auf CheckboxMenuItem
   if (evt.target instanceof CheckboxMenuItem)   {
     if (getBackground() != Color.black)
    setBackground(Color.black);
     else setBackground(Color.white);
    }
  repaint();
  return true;
  }
     else return false;
}// Ende der action-Methode des Frames
}// Ende der fenster-Klasse

Abbildung 10.31:  Das Applet mit Folgefenster und Menü

Abschließend soll ein Beispiel mit dem Eventhandling 1.1 demonstriert werden.

import java.awt.*;
import java.awt.event.*;
public class FrameMenu extends Frame {
  Label lb1 = new Label();
  MenuBar menuBar1 = new MenuBar();
  Menu menu1 = new Menu();
  Menu menu2 = new Menu();
  Menu menu3 = new Menu();
  MenuItem mI1 = new MenuItem();
  MenuItem mI2 = new MenuItem();
  MenuItem mI3 = new MenuItem();
  MenuItem mI4 = new MenuItem();
  MenuItem mI5 = new MenuItem();
  MenuItem mI6 = new MenuItem();
  MenuItem mI7 = new MenuItem();
  MenuItem mI8 = new MenuItem();
  public FrameMenu() {
    initial();
  }
  public static void main(String[] args) {
    FrameMenu fM = new FrameMenu();
    fM.setSize(300,300);
    fM.show();
  }
  private void initial() {
    lb1.setAlignment(1);
    lb1.setFont(
  new java.awt.Font("Dialog", 0, 42));
    lb1.setText("Alles wird gut");
    this.setMenuBar(menuBar1);
    menu1.setLabel("Farbe");
    menu2.setLabel("Groesse");
    menu3.setLabel("Text");
    mI1.setLabel("Rot");
    mI2.setLabel("Blau");
    mI3.setLabel("Schwarz");
    mI4.setLabel("12");
    mI5.setLabel("24");
    mI6.setLabel("42");
    mI7.setLabel("Alles wird gut");
    mI8.setLabel("Egal");
    mI1.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        mI1_actionPerformed(e);
      }
    });
    mI2.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        mI2_actionPerformed(e);
      }
    });
    mI3.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        mI3_actionPerformed(e);
      }
    });
    mI4.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        mI4_actionPerformed(e);
      }
    });
    mI5.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        mI5_actionPerformed(e);
      }
    });
    mI6.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        mI6_actionPerformed(e);
      }
    });
    mI7.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        mI7_actionPerformed(e);
      }
    });
    mI8.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        mI8_actionPerformed(e);
      }
    });
    menuBar1.add(menu1);
    menuBar1.add(menu2);
    menuBar1.add(menu3);
    menu1.add(mI1);
    menu1.add(mI2);
    menu1.add(mI3);
    menu2.add(mI4);
    menu2.add(mI5);
    menu2.add(mI6);
    menu3.add(mI7);
    menu3.add(mI8);
    this.add(lb1, BorderLayout.CENTER);
  }
  void mI1_actionPerformed(ActionEvent e) {
  lb1.setForeground(new Color(255,0,0));
  }
  void mI2_actionPerformed(ActionEvent e) {
  lb1.setForeground(new Color(0,0,255));
  }
  void mI3_actionPerformed(ActionEvent e) {
  lb1.setForeground(new Color(0,0,0));
  }
  void mI4_actionPerformed(ActionEvent e) {
  lb1.setFont(new java.awt.Font("Dialog", 0, 12));
  }
  void mI5_actionPerformed(ActionEvent e) {
  lb1.setFont(new java.awt.Font("Dialog", 0, 24));
  }
  void mI6_actionPerformed(ActionEvent e) {
  lb1.setFont(new java.awt.Font("Dialog", 0, 42));
  }
  void mI7_actionPerformed(ActionEvent e) {
  lb1.setText("Alles wird gut");
  }
  void mI8_actionPerformed(ActionEvent e) {
  lb1.setText("Egal wie schlimm es ist - es wird noch schlimmer");
  }
  }

Abbildung 10.32:  Das Original-Layout

Abbildung 10.33:  Veränderung über das Menü

Das Beispiel verändert die Farbe des Labels, dessen Schriftgröße und die Beschriftung.

10.12 Eine Schablone für alle Fälle

Wir wollen nun eine Schablone erstellen, die sowohl als Applet als auch als Standalone-Anwendung mit einer grafischen Oberfläche (also einer Java-Frame-Struktur) funktioniert. Das beinhaltet zwingend das Eventhandling 1.0. Da wir in der Schablone grundsätzlich nur Techniken des ersten Java-Konzepts verwenden, läuft diese Schablone sowohl in älteren Java-Umgebungen als auch unter neueren Java-Plattformen (hat aber zugegeben auch die Schwächen des alten Konzepts). Diese Schablone enthält die Grundfunktionalität für einen Aufruf als Applet als auch als eigenständige Applikation (die main()-Methode). Der Trick besteht darin, dass der nicht benötigte Anteil jeweils ignoriert wird.

import java.applet.*;
import java.awt.*;
public class AlleineFenster extends Applet
{
// STANDALONE APPLICATION 
// Die boolesche Variable StandAlone_ja wird auf 
// true gesetzt, wenn die Klasse als Standalone-
// Application gestartet wird
//---------------------------
private boolean StandAlone_ja= false;
public static void main(String args[]) {
ALFrame mFenst = new ALFrame("AlleineFenster");
mFenst.resize(300,300);
AlleineFenster apAF = new AlleineFenster();
mFenst.add("Center", apAF);
apAF.StandAlone_ja= true;
apAF.init();
apAF.start();
mFenst.show();
}
public AlleineFenster(){
}
public void init(){
 resize(300, 300);
}
public void destroy(){
}
public void paint(Graphics g){
}
public void start(){
}
public void stop(){
}  }
// Neue Klasse
class ALFrame extends Frame {
public ALFrame(String str){
super (str);
}
public boolean handleEvent(Event evt){
switch (evt.id) {
case Event.WINDOW_DESTROY:
dispose();
System.exit(0);
return true;
default:
return super.handleEvent(evt);
}   }  }

10.13 Layoutmanager

Es wird Ihnen bei unseren bisherigen AWT-Experimenten aufgefallen sein, dass wir uns (scheinbar) überhaupt nicht um die genauen Positionen der AWT-Komponenten innerhalb der Container gekümmert haben. Sie waren einfach »irgendwo« auf der Oberfläche platziert worden. Vielleicht haben Sie auch einmal die Größe eines Applets oder Fensters verändert. Dann sind plötzlich AWT-Komponenten an ganz anderen Stellen aufgetaucht als bei der Orginalgröße oder ihre Größe war verändert.

Dieses Verhalten ist kein Zufall und schon gar nicht eine Schwäche von Java. Im Gegenteil - es ist eine der großen Stärken des Java-AWT-Konzepts. Auch wenn man sich erst einmal mit der Konzeption vertraut machen muss.

Schauen wir uns als Basis zunächst andere Fensterkonzepte an. Dort geben Sie für die Komponenten der Benutzeroberfläche überwiegend exakte Koordinaten (meist in Pixel) an. Eine Koordinate für den Abstand vom oberen oder unteren Rand und eine Koordinate für den Abstand vom linken oder rechten Rand, also ein hardcodiertes Koordinaten-Tupel, wie wir es in Java bei Grafikoperationen auch tun. Ebenso wird die Größe eines Elements exakt festgelegt. Ein Programmierer hat vollständige Kontrolle über das Aussehen der Benutzeroberfläche. Oder nicht?

Doch, jedoch nur in genau der Bildschirmauflösung und der Größe des Java-Fensters, die er beim Generieren der Anwendung oder des Applets verwendet. Was ist, wenn der Anwender nun eine andere Auflösung fährt? Eine feinere Auflösung ist vielleicht vom ästhetischen Standpunkt ein kleines Problem, gleichwohl noch kein Beinbruch. Eine geringere Auflösung hingegen kann dazu führen, dass einige Komponenten der Oberfläche gar nicht angezeigt werden und damit mit der Maus nicht auswählbar sind. Die Funktionalität kann massiv beeinträchtigt werden.

Bei solchen konventionellen Fensterkonzepten muss ein Programmierer sämtliche denkbaren Auflösungen berücksichtigen (zumindest diejenigen, die er unterstützen möchte), diese zur Laufzeit abfragen und für jede Maske eine individuelle Oberfläche für jede Auflösung generieren. Ein erheblicher Aufwand und dennoch immer noch keine befriedigende Lösung. Denn was ist, wenn der Anwender die Fenstergröße verändert (sofern kein Vollbild vorliegt)? Vergrößern ist wieder nur ein ästhetisches Problem, aber Verkleinern kann zum Verdecken von Komponenten der Oberfläche führen. Nicht umsonst können Windows-Dialogfenster nicht in der Größe verändert werden.

Java löst diese Probleme auf eine äußerst intelligente Art und Weise. Das AWT-System kümmert sich weitgehend selbstständig um Größenanpassung und Positionierung von den Komponenten auf der Oberfläche. Je nach Plattform und Bedingungen werden die Komponenten auf die Oberfläche optimal angepasst. Die Technik nennt sich Layoutmanager.

Damit können Sie das AWT anweisen, wo Ihre Komponenten im Verhältnis zu den anderen Komponenten stehen sollen. Ein Layoutmanager findet nach gewissen Regeln heraus, an welche Stelle die Komponenten am besten passen und ermittelt die beste Größe der Komponenten.

10.13.1 Layout-Regeln

Das genaue Aussehen einer AWT-Oberfläche wird von drei Aspekten festgelegt.

1. Der Plattform und den genauen Bedingungen dort.
2. Der Reihenfolge, in der die AWT-Komponenten in einem Container eingefügt werden.
3. Der Art des Layoutmanagers.

Über Punkt eins kann ein Programmierer keine Aussagen machen und muss es unter Java auch nicht. Plattformunabhängigkeit ist ja gerade ein Highlight.

Der zweite Punkt ist naheliegend: Wer zuerst kommt, mahlt zuerst. Das soll heißen, dass zuerst eingefügte Komponenten auch vorher angeordnet werden.

Richtig interessant ist die Art des Layoutmanagers. Das AWT beinhaltet fünf verschiedene Typen von Layoutmanagern, die die Oberfläche unterschiedlich anordnen:

  • FlowLayout
  • GridLayout
  • BorderLayout
  • CardLayout
  • GridBagLayout

Diese Layoutmanager können für jeden grafischen Container festgelegt werden. Insbesondere kann jedes Panel einen eigenen Layoutmanager verwenden. Dies ist besonders für verschachtelte Panels interessant.

Um einen Layoutmanager für einen grafischen Container zu erstellen, können Sie die Methode public void setLayout(LayoutManager mgr) verwenden.

Beispiel:

public void init() {
setLayout(new CardLayout());
}

Alternativ geht es auch wieder so:

public void init() {
meinFlowLayout = new FlowLayout(); 
setLayout(meinFlowLayout); 
}

Das Setzen eines Layoutmanagers in einer Initialisierungsmethode ist nicht zwingend, jedoch meist angeraten, da dann die Anordnung der Komponenten von Anfang an auf den Layoutmanager ausgerichtet wird. Dies betrifft vor allem die Reihenfolge der Komponenten.

10.13.2 Die Layoutmanager im Detail

Schauen wir uns nun die Layoutmanager genauer an.

Die FlowLayout-Klasse

Die FlowLayout-Klasse stellt das einfachste Layout zur Verfügung. Eine FlowLayout-Klasse behandelt einen Container wie mehrere Spalten. Deren Größe ergibt sich aus der Größe der Komponenten. Die Komponenten werden in der Reihenfolge, in der sie in den Container eingefügt werden, spaltenweise einfach von links nach rechts anordnet, bis keine weiteren Komponenten mehr in eine Zeile passen. Dann geht es zur nächsten Zeile und analog weiter. Die Höhe der Zeilen wird von der Höhe der Elemente, die in der Reihe platziert sind, bestimmt. Schauen wir uns einmal ein kleines Beispiel als vollständigen Quelltext an.

import java.awt.*;
public class FlowLayoutTest extends java.applet.Applet {
public void init() {
setLayout(new FlowLayout()); 
Button meinButton1 = new Button("Rot"); 
add(meinButton1);     
Button meinButton2 = new Button("Blau"); 
add(meinButton2); 
Button meinButton3 = new Button("Grün"); 
add(meinButton3); 
Button meinButton4 = new Button("Pink"); 
add(meinButton4); 
Button meinButton5 = new Button("Rosa"); 
add(meinButton5); 
Button meinButton6 = new Button("Gelb"); 
add(meinButton6); 
Button meinButton7 = new Button("Cyan"); 
add(meinButton7); 
}  }

Abbildung 10.34:  Die Originalanordnung

Abbildung 10.35:  Größenveränderung des Applets macht den neuen, zeilenweisen Aufbau deutlich

Wenn wir die Reihenfolge, in der wir die Schaltflächen einfügen, im Quelltext umsortieren, wird auch das angezeigte Ergebnis abweichen.

Jeder Layoutmanager beinhaltet auch eine Ausrichtung. Die FlowLayout-Klasse versucht je nach Vorgabe, die Zeilen entweder links, rechts oder zentriert auszurichten. Die Standard-Ausrichtung für ein FlowLayout ist zentriert. Wenn die Ausrichtung verändert werden soll, kann das entweder mit der Methode setAlignment(int ausrichtung) erfolgen (0 ist links, 1 zentriert und 2 rechts bzw. die nachfolgend besprochenen Konstanten), aber auch schon bei der Erstellung durch einen passenden Konstruktor festgelegt werden.

Der FlowLayout-Layoutmanager ist der Standard-Layoutmanager für alle Applets.
  • Der einfachste Konstruktor ist FlowLayout(). Er erstellt ein Standard-FlowLayout (zentrierte Ausrichtung).
    Beispiel:
  • meinFlowLayout = new FlowLayout();
    setLayout(meinFlowLayout);
  • Für ein FlowLayout mit Ausrichtung verwenden Sie den Konstruktor FlowLayout(int). Dabei können Sie als Argument für die Ausrichtung die Konstanten FlowLayout.LEFT, FlowLayout.RIGHT und FlowLayout.CENTER verwenden.
    Beispiel:
  • mFlowLt = new FlowLayout(FlowLayout.LEFT);
  • Sie können mit FlowLayout Werte für den Zwischenraum in horizontaler und vertikaler Richtung angeben. Dafür gibt es den Konstruktor FlowLayout(int, int, int). Das erste Argument ist wieder die Ausrichtung, die Argumente zwei und drei sind die Werte für die Mindestgröße des horizontalen und vertikalen Raums, der zwischen den Komponenten bestehen bleibt. Die Zwischenräume werden in Pixel angegeben. Diese Angaben sind oft sehr sinnvoll, da die Standardabstände zwischen den Komponenten nur 3 Pixel betragen.
    Beispiel:
  • mFlowLt = new FlowLayout(FlowLayout.RIGHT, 15, 8);
  • Die Anweisung bedeutet: rechts ausgerichtet, horizontaler Zwischenraum 15 Pixel, vertikaler Zwischenraum von 8 Pixel.

Die GridLayout-Klasse

Bei diesem Layoutmanager wird ein Rasterlayout mit Zeilen und Spalten verwendet (eine Art virtuelle Tabellenstruktur). Ein Container wird wie ein Gitter mit gleich großen Teilen behandelt. Jede eingefügte Komponente wird in einer Zelle des Gitters platziert. Dabei ist wieder die Reihenfolge relevant, in der mit der add()-Methode eine Komponente hinzugefügt wurde. Die Anordnung beginnt immer oben links und geht dann wie bei FlowLayout von links nach rechts weiter.

Ein wesentlicher Unterschied zwischen dem GridLayout und dem FlowLayout ist, dass GridLayout jeder Komponente den gleichen Raum zur Verfügung stellt.

GridLayout ermöglicht in Verbindung mit verschachtelten Panels eine ziemlich genaue Kontrolle der Anordnung von Komponenten. Damit lassen sich Komponenten fast genauso genau auf der Oberfläche platzieren, wie bei der pixelgenauen Angabe, ohne jedoch deren Schwachpunkte zu übernehmen (die optimale Größenanpassung der Komponenten ist immer vorhanden).

Die GridLayout-Klasse besitzt natürlich einen leeren Default-Konstruktor. Nur sollten Sie beachten, dass ein GridLayout einem Gitter entspricht. Das bedeutet, eine Anzahl von Zeilen oder Spalten muss irgendwie festgelegt werden. Wenn Sie mit dem leeren Default-Konstruktor arbeiten, können Sie nach Erzeugung der Objekte diese Angaben machen (mit der Methode setColumns(int spalten) zum Festlegen der Spaltenanzahl oder setRows(int reihen) für die Anzahl der Zeilen). Oft ist es jedoch sinnvoll, diese Festlegung bereits beim Erstellen eines GridLayouts vorzunehmen. Das Format des dazu zu verwendenden GridLayout-Konstruktors ist GridLayout(int, int). Das erste Argument ist die Anzahl der Zeilen, das zweite Argument die Anzahl der Spalten.

Beispiel:

setLayout(new GridLayout(3, 4));

Dabei verhält sich der Layoutmanager wie folgt:

  • Die Angabe der Zeilenanzahl bewirkt die Berechnung der Anzahl der notwendigen Spalten, wenn Sie Komponenten einfügen. Sie sollten in diesem Fall 0 für die Spaltenzahl verwenden.
  • Die Angabe der Spaltenanzahl bewirkt die Berechnung der Anzahl der notwendigen Zeilen, wenn Sie Komponenten einfügen. Sie sollten in diesem Fall 0 für die Anzahl der Zeilen verwenden.
  • Wenn Sie sowohl für die Anzahl der Zeilen als auch der Spalten von Null verschiedene Werte angeben, werden nur die Angaben für die Zeilenzahl berücksichtigt. Die Anzahl der Spalten wird auf der Basis der Anzahl der Komponenten und der Zeilen berechnet - unabhängig davon, ob sie mit dem von Ihnen gesetzten Wert übereinstimmt oder nicht. GridLayout(5, 4) ist identisch mit GridLayout(5, 0) oder GridLayout(5, 2). Dies trifft auch zu, wenn sich auf Grund der Berechnung der Komponentenanzahl und der Anzahl der Zeilen eine Spaltenanzahl ergibt.

Abbildung 10.36:  GridLayout(3, 0) bei drei Komponenten

Abbildung 10.37:  GridLayout(3, 0) bei acht Komponenten - beachten Sie die leere Zelle rechts unten

Abbildung 10.38:  GridLayout(0, 4) bei acht Komponenten

Abbildung 10.39:  GridLayout(4, 4) bei acht Komponenten. Trotz Angabe von vier Spalten werden nur zwei verwendet.

Für die Festlegung des horizontalen und vertikalen Zwischenraums zwischen den Komponenten steht ein weiterer Konstruktor zur Verfügung. Analog zum FlowLayout erweitern Sie die Argumentenliste um die Argumente für die Mindestgröße des horizontalen und vertikalen Raumes, der zwischen den Komponenten bleibt. Der Konstruktor GridLayout(int, int, int, int) hat als erstes Argument die Anzahl der Zeilen, als zweites Argument die Anzahl der Spalten, Argument drei ist der horizontale und Argument vier der vertikale Mindestabstand. Die Zwischenräume werden in Pixel angegeben.

Beispiel:

setLayout(new GridLayout(3, 4, 10, 15));

Die GridBagLayout-Klasse und die GridBagConstraints-Klasse

Behandeln wir nun die GridBagLayout-Klasse. Ein Container wird wieder in ein Gitter mit gleich großen Zellen unterteilt. Die Bedeutung ist nicht annähernd so groß wie die von FlowLayout oder BorderLayout. Die GridBagLayout-Klasse ähnelt der GridLayout-Klasse, ist aber viel flexibler (und damit leider etwas komplizierter). Der Unterschied zum GridLayout besteht zum einen darin, dass nicht Sie, sondern der Layoutmanager die Anzahl der Zeilen und Spalten bestimmt. Der andere wichtige Unterschied ist der, dass eine Komponente mehr als eine Zelle belegen kann. Damit wird bei geschickter Anwendung eine kleine Komponente nicht überflüssig viel Platz verschwenden, während ihre »große Schwester« zusammengestaucht wird.

Kommen wir nun zu den Details. Der gesamte Bereich, den eine Komponente einnimmt (unter Umständen mehrere Zellen), wird Display Area genannt.

Wir hatten schon festgehalten, dass nicht Sie, sondern der Layoutmanager die Anzahl der Zeilen und Spalten entscheidet. Sie können jedoch eine grobe Richtung angeben, wie die Komponenten vom Layoutmanager angeordnet werden sollen. Dazu verwenden Sie eine eigene Klasse - die GridBagConstraints-Klasse. Bevor Sie einem Container eine Komponente hinzufügen, setzen Sie über verschiedene Variablen der Klasse die grobe Anordnung der Komponenten. Diese Variablen müssen wir uns zu Gemüte führen:

  • Es gibt Variablen, die auf den ersten Blick an absolute Koordinatenangaben erinnern - gridx und gridy. Es handelt sich aber dabei um die Koordinaten der Zelle, in der die nächste Komponente platziert werden soll (nicht die Komponente selbst, aber diese liegt damit natürlich indirekt ebenso fest). Die obere linke Ecke von GridBagLayout liegt bei 0, 0. Der Standardwert für gridx und gridy ist GridBagConstraints.RELATIVE. Dies bedeutet für gridx die erste rechte Zelle neben der letzten Komponente, die hinzugefügt worden ist, für gridy die Zelle, die unterhalb der letzten Komponente hinzugefügt worden ist.
  • Sie können ebenso die Höhe und Breite von Komponenten indirekt festlegen. Über die Variablen gridwidth und gridheight legen Sie fest, wie viele Zellen hoch und breit eine Komponente sein sollte. Der Standardwert ist für beide Variablen 1. Sie können als besonderes Feature festlegen, dass eine Komponente die letzte in der aktuellen Zeile ist (damit ist die Breite natürlich festgelegt). Dazu dient der Wert GridBagConstraint.REMAINDER für gridwidth.
  • Für gridheight bedeutet die Zuweisung dieses Wertes, dass diese Komponente die letzte in der Spalte sein sollte.
  • Der Wert fill gibt an, welche Dimension einer Komponente sich verändern soll, wenn eine Komponente kleiner als der Anzeigebereich ist. Gültige Werte sind NONE, BOTH, HORIZONTAL und VERTICAL. Der Standardwert GridBagConstraint.NONE veranlasst, dass die Größe der Komponente unverändert bleibt. GridBagConstraint.HORIZONTAL verbreitert die Komponente so, dass sie in der horizontalen Ebene den gesamten Anzeigebereich ausfüllt, in der Höhe aber unverändert bleibt. GridBagConstraint.VERTICAL macht das Entsprechende für die vertikale Ebene. GridBagConstraint.BOTH sorgt für eine Streckung der Komponenten in beide Richtungen so, dass sie den Anzeigebereich voll ausfüllt.
  • Die Werte ipadx und ipady definieren, um wie viele Pixel die Größe einer Komponente in x- und y-Richtung erweitert werden soll, um damit den Abstand zur nächstliegenden Komponente zu vergrößern. Die Pixel werden auf jeder Seite der Komponente hinzugefügt, was bedeutet, dass die Größe der Komponente immer um das Doppelte der angegebenen Pixel anwächst. Standard für ipadx und ipady ist jeweils 0.
  • Bei Insets handelt es sich um eine Instanz der (als veraltet erklärten) Insets-Klasse. Insets-Objekte definieren die Ränder für alle Seiten einer Komponente. Es wird angezeigt, wie viel Platz zwischen den Grenzen einer Komponente und den Kanten des Anzeigebereichs gelassen werden kann.
  • Das Feld anchor wird eingesetzt, wenn eine Komponente kleiner als ihr Anzeigebereich ist. Damit wird angegeben, wo die Komponente innerhalb des Anzeigebereichs platziert werden soll. Der Standardwert ist GridBagConstraint.CENTER (im Zentrum des Anzeigebereichs). Die Angaben GridbagConstraints.NORTH, GridBagConstraints.NORTHEAST, GridBagConstraints.EAST, GridBagConstraints.SOUTHEAST, GridBagConstraints- .SOUTH, GridBagConstraints.SOUTHWEST, GridBagConstraints.WEST, und GridBagConstraints.NORTHWEST sind wie bei der BorderLayout-Klasse zu verstehen und geben Richtungen wie bei einem Kompass an.
  • Die Werte weightx und weighty definieren eine Gewichtung für freien Raum innerhalb eines Containers und bestimmen die relative Größe der Komponenten. Eine Komponente mit weightx 2.0 nimmt doppelt so viel horizontalen Platz wie weightx 1.0 ein.

Die Erstellung eines GridBagLayout-Layouts erfolgt mit dem Konstruktor GridBagLayout().

Beispiel: GridBagLayout mGrdBgLt = new GridBagLayout();

Danach setzen Sie das Layout.

Beispiel: setLayout(meinGridBagLayout);

Dann wird eine Instanz von GridBagConstraints erzeugt.

Beispiel: GridBagConstraints Beding =
new GridBagConstraints();

Danach werden die genauen Bedienungen der Komponente festgelegt.

Beispiel:

Beding.weightx = 2.0; 
Beding.gridwidth = GridBagConstraints.RELATIVE; 
Beding.fill = GridBagConstraints.HORIZONTAL;

Der nächste Schritt ist der Aufbau der Beschränkungen in dem GridBag-Layout.

Beispiel: meinGridLayout.setConstraints(meinButton, Beding);

Im letzten Schritt wird die Komponente in den Container eingefügt.

Beispiel: add(meinButton);

Die BorderLayout-Klasse

Bei der BorderLayout-Klasse handelt es sich um ein Rahmenlayout. Es ist eines der wichtigsten Layouts in Java. Die Komponenten werden dem Container nach geografischen Aspekten zugeordnet (wie bei einem Kompass). Es gibt fünf Gebiete, wo eine Komponente platziert werden kann:

  • Zentrum (Center)
  • Nord (North)
  • Süd (South)
  • Ost (East)
  • West (West)

Wenn Sie unter dem BorderLayout dem Container Komponenten hinzufügen wollen, müssen Sie bei der add()-Methode einen dieser fünf Bereiche angeben. Dabei kann in jedem Bereich nur eine Komponente platziert werden. Das ist jedoch keine Einschränkung der Funktionalität. Man muss nur mit verschachtelten Panels arbeiten.

Die fünf Bereiche werden wie auf einem Kompass angeordnet. Nord ist oben im Container, Ost rechts, Süd unten und West links im Container. Das Zentrum befindet sich in der Mitte.

Die einfachste Form für ein BorderLayout erfolgt über den Konstruktor BorderLayout().

Beispiel: setLayout(new BorderLayout());

Optional können Sie wieder einen horizontalen und einen vertikalen Zwischenraum zwischen den Komponenten anzugeben. Dazu verwenden Sie wieder einen Konstruktor mit zwei Argumenten - BorderLayout(int, int)

Beispiel: setLayout(new BorderLayout(15,10));

Um nun einem Bereich des BorderLayouts eine Komponente hinzuzufügen, müssen Sie eine spezielle add()-Methode verwenden. Ursprünglich wurde folgende Methode verwendet: Das erste Argument dieser speziellen add()-Methode ist eine Zeichenkette, die die Position der Komponenten bezeichnet.

Beispiel: add("North", new Button("Rot"));

Die Anweisung fügt eine Schaltfläche im oberen Bereich hinzu. Alternativ gibt es eine neuere Variante, die aber den gleichen Effekt hat. Dabei wird zuerst die einzufügende Komponente angegeben und dann erst die Positionierung als int-Wert. Für diese Positionsangaben stellt die Klasse BorderLayout wieder sprechende Konstanten zur Verfügung.

Beispiel: add(label1, BorderLayout.CENTER);

Letztere add()-Methode kann auch für andere Layouts verwendet werden. In diesen Fällen wird einfach das Schlüsselwort null als Bereichsangabe gesetzt. Wenn eine Komponente mit der add()-Methode ohne eine Positionsangabe in einem BorderLayout hinzugefügt wird, wird sie nicht angezeigt.

Schauen wir uns auch dieses Layout in einem vollständigen Beispiel an.

import java.awt.*;
public class BorderLayoutTest extends java.applet.Applet {
public void init() {
setLayout(new BorderLayout()); 
Button meinButton1 = new Button("Rot"); 
add("North", meinButton1);     
Button meinButton2 = new Button("Blau"); 
add("West", meinButton2); 
Button meinButton3 = new Button("Grün"); 
add("South", meinButton3); 
}  }

Abbildung 10.40:  Die drei Komponenten sind angeordnet, der Rest bleibt leer.

Das nächste Beispiel verwendet nun fünf Komponenten:

import java.awt.*;
public class BorderLayoutTest2 extends java.applet.Applet {
public void init() {
setLayout(new BorderLayout(15,25)); 
Button meinButton1 = new Button("Rot"); 
add("North", meinButton1);     
Button meinButton2 = new Button("Blau"); 
add("West", meinButton2); 
Button meinButton3 = new Button("Grün"); 
add("South", meinButton3); 
Button meinButton4 = new Button("Gelb"); 
add("East", meinButton4); 
Button meinButton5 = new Button("Schwarz"); 
add("Center", meinButton5); 
}  }

Mit der BorderLayout-Klasse können Sie nur maximal eine Komponente in einem Bereich hinzufügen. Es gibt jedoch keinen Fehler. Wenn zwei Komponenten dem gleichen Bereich hinzugefügt werden, ist nur die zuletzt hinzugefügte zu sehen.

Die CardLayout-Klasse

Kartenlayouts unterscheiden sich signifikant von den bisher behandelten Layouts. Der wesentliche Unterschied besteht darin, dass von den eingefügten Komponenten immer nur eine Komponente sichtbar ist. Der CardLayout-Layoutmanager behandelt die dem Container hinzugefügten Komponenten wie einen Stapel von Karten. Jede Komponente wird auf einer eigenen Karte platziert, wobei wie bei einer Diashow immer maximal eine Karte angezeigt wird. Die Erstellung eines Kartenlayout erfolgt über den Konstruktor CardLayout().

Beispiel: setLayout(new CardLayout());

Sinn machen Kartenlayouts vor allem dann, wenn es sich bei den eingefügten Komponenten um Panels handelt. Damit kann pro »Karte« ein vollständiges, eigenes Layout definiert werden, das beim vor- und zurückblättern jeweils angezeigt wird. Da sich ein Panel (denken Sie an ein Fenster) aufteilen lässt, indem Panels ineinander geschachtelt werden, macht dieser Layoutmanager durchaus Sinn.

Nach Erstellung des Kartenlayouts fügen Sie mit einer speziellen Version der add()-Methode die Karten hinzu. Dabei wird jede Karte benannt. Das erste Argument dieser speziellen add()-Methode ist eine Zeichenkette, die den Namen der Karte bezeichnet. Die Ähnlichkeit zum BorderLayout ist zwar da, aber die Angabe des Argumentes hat eine andere Bedeutung. Wenn eine Karte mit der normalen add()-Methode (ohne eine Namensangabe) hinzugefügt wird, können Sie diese nicht so einfach über die in der CardLayout-Klasse definierte show()-Methode direkt ansprechen.

Beispiel:

Panel karte1 = new Panel();
add("Rot", karte1);
Panel karte2 = new Panel();
add("Gelb", karte2);
Panel karte3 = new Panel();
add("Blau", karte3);
show(this, "Gelb");

Auch bei diesem Layout gibt es wieder eine modernere Version der add()-Methode, die das Gleiche bewirkt. Dabei wir als erstes Argument die einzufügende Komponente angegeben.

Beispiel: add(Button1, "Button1");

Zum Anzeigen der »Karten« stehen Ihnen im CardLayout folgende Methoden zur Verfügung:

Methode Beschreibung
first() Anzeige der ersten Karte
last() Anzeige der letzten Karte
next() Anzeige der nächsten Karte
previous() Anzeige der vorherigen Karte
show() Anzeige der Karte über Angabe des Namens

Tabelle 10.4:   Methoden der CardLayout-Klasse

Schauen wir uns ein kleines Beispiel an, das bei jedem Klick auf einen Button diesen durch einen neuen austauscht.

import java.awt.*;
import java.awt.event.*;
public class Card extends Frame {
  CardLayout cardLayout1 = new CardLayout();
  Button b1 = new Button();
  Button b2 = new Button();
  Button b3 = new Button();
  public Card() {
      initial();
  }
  public static void main(String[] args) {
    Card frame1 = new Card();
    frame1.setSize(100,100);
    frame1.show();
  }
  private void initial() {
    b1.setLabel("button1");
    b1.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        b1_actionPerformed(e);  }  });
    setLayout(cardLayout1);
    b2.setLabel("button2");
    b2.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        b2_actionPerformed(e);  }  });
    b3.setLabel("button3");
    b3.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        b3_actionPerformed(e);  }  });
    add(b1, "button1");
    add(b2, "button2");
    add(b3, "button3");
  }
  void b1_actionPerformed(ActionEvent e) {
  cardLayout1.next(this);
  }
  void b2_actionPerformed(ActionEvent e) {
  cardLayout1.next(this);
  }
  void b3_actionPerformed(ActionEvent e) {
  cardLayout1.first(this);
  }  }

Abbildung 10.41:  Erster Zustand

Abbildung 10.42:  Der Button wurde ausgetauscht

10.14 Die Eventmodelle 1.0 und 1.1

Wir sind jetzt bereits an verschiedenen Stellen auf die Reaktionsmöglichkeiten unter Java eingegangen. Hier sollen noch einmal die wichtigsten Details zusammengefasst werden. Außerdem wird es Zeit, sich mit den beiden Eventmodellen von Java auseinander zu setzen und sie direkt gegenüberzustellen.

Java und das JDK bieten seit geraumer Zeit zwei verschiedene Modelle, wie auf Ereignisse durch den Anwender (etwa das Bedienen einer AWT-Komponente, aber auch die Betätigung einer beliebigen Taste) zu reagieren ist. Da gibt es einmal das Eventhandling, wie es in Java und dem JDK vor der Version 1.1 realisiert war. Das nachfolgende Eventhandling 1.1 ist in allen Folgeversionen aktuell. Die Reaktion auf Ereignisse hat sich in der JDK-Version 1.1 gegenüber seinem Vorgänger total verändert und nicht mehr viel mit diesem gemeinsam. Allerdings besitzen beide Konzepte noch ihre Existenzberechtigung. Das neue Konzept ist zwar in fast jeder Hinsicht besser, schneller und leistungsfähiger, aber für die weitere Verwendung des ersten Modells gibt es - wie wir bereits wissen - ein unschlagbares Argument. Viele Browser unterstützen bis jetzt das neuere Eventhandling noch nicht (zumindest, wenn die Applets mit dem allgemein üblichen <APPLET>-Tag in eine Webseite eingebunden werden). Deshalb muss das ältere Eventhandling-Konzept für diejenige Applets immer noch verwendet werden, die die Kompatibilität zu diesen Browsern sicherstellen müssen. Dabei gibt es auch keine großen Probleme, denn um die Abwärtskompatibilität sicherzustellen, unterstützt das neue JDK beide Verfahren. Sun rät allerdings explizit, die beiden Modelle nicht in einem Programm zu mischen. Wir werden hier beide Konzepte durchsprechen und überdies im direkten Vergleich gegenüberstellen.

10.14.1 Der AWT-Event-Handler 1.0

Eventhandling über eine grafische Benutzeroberfläche muss das Problem lösen, dass alle Elemente, die als Interface zwischen Benutzer und Anwenderprogramm dienen, portabel für alle unterstützten Betriebssysteme bleiben. Die Programmierung dieser Elemente auf der Ebene der jeweiligen Betriebssysteme erfolgt je nach Betriebssystem in einer anderen Weise. In traditionellen prozeduralen Programmen, werden die Eingaben von Maus und Tastatur in einer Schleife abgefragt, die kontinuierlich durchlaufen werden muss. Das Eventhandling-System von Java arbeitet sowohl in der ersten als auch in der neuen Variante nach einen anderen Prinzip. Zur Bearbeitung von Ereignissen stehen Event-Methoden zur Verfügung. Im AWT des JDK 1.0 ist nun zur Gewährleistung der Portabilität zwischen Java und den jeweiligen Betriebssystemen eine Zwischenebene eingeführt worden, die so genannten Peers (die gesamte Zwischenebene wird Peer-Interface genannt). Hierbei handelt es sich um spezielle Objekte, die direkt zwischen den Java-Objekten und der entsprechenden Benutzeroberfläche kommunizieren können. Diese werden nur dann aufgerufen, wenn auch tatsächlich ein Ereignis auftritt. Die Event-Methoden bekommen von den Peer-Objekten eine Event-Instanz übergeben, in der alle Daten zum Ereignis gespeichert sind, z.B. die Ursprungsinstanz, die Koordinaten, die Zeit beim Auftreten des Ereignisses, usw. Zu jedem Java-Oberflächenobjekt unter 1.0 gehört ein Peer-Objekt. Die Methoden der Peer-Objekte sind auf allen Betriebssystemen gleich, nur die Implementierung unterscheidet sich.

Der Programmierer, der das AWT lediglich verwenden will, muss über die Peers eigentlich kaum etwas wissen, da die jeweiligen plattformspezifischen Elemente vollkommen transparent sind. Wird etwa die Maus auf ein Textfeld bewegt und die linke Maustaste gedrückt, erzeugt sein zugehöriges Peer-Objekt ein Event-Objekt. Dieses wird der postEvent()-Methode des Textfelds übergeben. Direkt im Anschluss wird die Methode handleEvent() ausgeführt. Soll das Textfeld auf den Mausklick reagieren, muss die Methode handleEvent() entsprechend überschrieben werden. Hat diese Methode das Ereignis abgearbeitet, sollte sie, soweit das Ereignis nicht an weitere, übergeordnete Objekte gesendet werden soll, den Rückgabewert true liefern. Für den Fall, dass dieses Objekt nicht auf dieses Ereignis reagieren konnte, sendet postEvent() es an die postEvent()-Methode des übergeordneten Objekts, bis das Event-Objekt als abgearbeitet (konsumiert) gekennzeichnet wurde oder alle beteiligten Objekte durchlaufen sind. Dies sollten wir uns bereits im Hinterkopf behalten, denn hier wird ein wesentlicher Unterschied des 1.1-Eventhandlingmodells ansetzen.

Unter Umständen kann es vorkommen, dass Objekte auf viele verschiedene Ereignisse reagieren müssen. Damit die handleEvent()-Method nicht zu groß und unübersichtlich wird, ruft handleEvent() mehrere Hilfs-Eventmethoden (mouseEnter(), keyDown(), action(), ...) auf, die die jeweils zugeordneten Ereignisse verarbeiten. Allerdings verfügt das 1.0-Modell nur über ein einziges und allgemein einzusetzendes Event-Objekt, das sämtliche notwendigen Informationen zur Behandlung von Ereignissen enthält.

Die handleEvent()-Methode bedeutet also unter dem 1.0-Modell die allgemeinste Art, wie das AWT auf irgendwelche Ereignisse eingeht, die auf der Benutzeroberfläche stattfinden. Die Ereignisse werden innerhalb der handleEvent()-Methode interpretiert und dann gezielt passende Methoden aufgerufen. Wenn die in der Event-Klasse definierten Standardereignisse eintreten, müssen Sie die handleEvent()-Methode überschreiben. Diese sieht schematisch so aus:

public boolean handleEvent (Event evt) {
// tue etwas sinnvolles
}

Wie wir bereits gesehen haben, überprüfen Sie zum Test eines Ereignisses die ID-Instanzvariable des Event-Objekts, das an die handleEvent()-Methode übergeben wird. Die ID-Instanzvariable ist eigentlich eine Ganzzahl. Wir haben jedoch bereits davon Notiz genommen, dass für eine ganze Reihe von Ereignissen sprechende Klassenvariablen zur Verfügung stehen. Sie können jederzeit diese statt der Zahlenwerte testen.

Zum Test eignen sich besonders gut switch-Anweisungen, als auch if-Konstrukte. Natürlich sind auch andere Konstruktionen denkbar.

10.14.2 Tastaturereignisse des AWT-Event-Handlers

Hier folgt nun noch eine kleine Liste mit Tastaturereignissen, die Sie in der handleEvent()-Methode unter dem Modell 1.0 abfragen können:

Wert von evt.id Beschreibung
Event.KEY_PRESS Eine beliebige Taste wird gedrückt (entspricht der keyDown()-Methode).
Event.KEY_RELEASE Eine beliebige, bereits gedrückte Taste wird losgelassen.
Event.KEY_ACTION Eine beliebige Taste wird gedrückt oder losgelassen, d.h., es findet eine beliebige Tastenaktion statt.

Tabelle 10.5:   Tastaturereignisse im Eventmodell 1.0

10.14.3 Mausereignisse des AWT-Event-Handlers

Natürlich kann der AWT-Event-Handler auch auf Mausereignisse reagieren:

Wert von evt.id Beschreibung
Event.MOUSE_DOWN Eine Maustaste wird gedrückt (entspricht der mouseDown()-Methode).
Event.MOUSE_UP Eine Maustaste wird losgelassen (entspricht der mouseUp()-Methode).
Event.MOUSE_MOVE Die Maus wird bewegt (entspricht der mouseMove()-Methode).
Event.MOUSE_DRAG Die Maus wird mit gedrückter Maustaste bewegt (entspricht der mouseDrag()-Methode).
Event.MOUSE_ENTER Die Maus wird in den Bereich eines Applets oder einer seiner Komponenten bewegt (entspricht der mouseEnter()-Methode).
Event.MOUSE_EXIT Die Maus verlässt den Bereich eines Applets (entspricht der mouseExit()-Methode).

Tabelle 10.6:   Mausereignisse im Eventmodell 1.0

Sowohl im Zusammenhang mit den Mausereignissen, als auch mit den Tastaturereignissen können Sie auf Betätigung einer Zusatztaste überprüfen. Dazu müssen Sie nur die Methoden shiftDown(), metaDown() oder controlDown() zusätzlich abfragen.

10.14.4 Zentrale Aspekte des 1.0-Eventhandlings

Fassen wir zusammen, was das Eventhandling unter dem 1.0-Modell auszeichnet. Wenn der Benutzer in einer GUI-Schnittstelle eine Eingabe vornimmt, wird vom System ein Objekt vom Typ Event erzeugt. Das Event-Objekt hat ein Feld id, in dem vermerkt ist, welche Art von Ereignis ausgelöst wurde. Abhängig von der Art des Ereignisses enthält das Event-Objekt noch weitere Informationen.

Feld Bedeutung
Event.id Art des Ereignisses
Event.arg Information, die je nach Ereignis von unterschiedlichem Typ ist
Event.clickCount Anzahl der aufeinander folgenden Mausklicks
Event.key Tastencode der gedrückten Taste
Event.modifiers Status der Modifier-Tasten Ctrl, Shift und Meta
Event.target Instanz der Komponente, in der das Ereignis ausgelöst wurde
Event.when Zeitpunkt der Ereignisauslösung
Event.x X-Koordinate des Ereignisses
Event.y Y-Koordinate des Ereignisses

Tabelle 10.7:   Eigenschaften eines Event-Objekts

Allgemein unterstützt das AWT 1.0 folgende Arten von Events:

  • Action-Events: Betätigung eines Buttons, Auswahl aus einem Auswahlmenü oder Listenfeld, Wahl eines Menüpunktes, Eingabe in ein Textfeld
  • Keyboard-Events: Eingabe über die Tastatur
  • Maus-Events: Bewegen der Maus oder Drücken der Maustasten
  • List-Events: Selektion oder Deselektion eines Listen-Eintrages, aber nur bei Einfachklick mit der Maus
  • Scrolling-Events: Bewegen des Schiebers einer Scrollbar
  • Window-Events: Schließen eines Fensters oder Verändern der Größe.
  • Weitere Events: Beispielsweise den Eingabefokus erhalten oder verlieren usw.

Die nachfolgende Tabelle zeigt die Werte von Event.id und welche Klasse diese erzeugen kann.

Wert von Event.id Klasse
ACTION_EVENT Button, Checkbox, Choice, List, MenuItem, TextField
GOT_FOCUS, LOST_FOCUS Component
KEY_ACTION, KEY_ACTION_RELEASE, KEY_PRESS, KEY_RELEASE Component
MOUSE_ENTER, MOUSE_EXIT, MOUSE_DOWN, MOUSE_UP, MOUSE_MOVE, MOUSE_DRAG Component
LIST_SELECT, LIST_DESELECT List
SCROLL_LINE_UP, SCROLL_LINE_DOWN, SCROLL_PAGE_UP, SCROLL_PAGE_DOWN, SCROLL_ABSOLUTE Scrollbar
WINDOW_MOVED, WINDOW_DESTROY, WINDOW_ICONIFY, WINDOW_DEICONIFY Frame, Dialog

Tabelle 10.8:   Die Werte von Event.id im Eventmodell 1.0

Folgende Action-Events gibt es im Detail:

Komponente Event.arg Aktion
Button Label des Buttons Drücken des Buttons mit Maus oder Leertaste
Checkbox Status der Checkbox Anwahl der Checkbox
Choice Label des ausgewählten Eintrags Auswahl des Eintrags
List Label des ausgewählten Eintrags Return-Taste oder Doppelklick auf Listenelement
MenuItem Label des Menüpunktes Auswahl des Menüpunktes
TextField Abschluss der Eingabe mit Return

Tabelle 10.9:   Die Action-Events

Das sind die Methoden, die bei Focus-Events auftreten:

Methode Ausgelöst
gotFocus() Die Komponente erhält den Fokus.
lostFocus() Die Komponente verliert den Fokus.

Tabelle 10.10:   Die Focus-Events

Hier folgen die Maus-Events:

Methode Ausgelöst
mouseDown() Die Maustaste wird gedrückt.
mouseUp() Die Maustaste wird losgelassen.
mouseMove() Die Maus wird bewegt.
mouseDrag() Die Maus wird mit gedrückter Maustaste bewegt.
mouseEnter() Der Mauszeiger tritt in den Bereich einer Komponente ein.
mouseExit() Der Mauszeiger verlässt den Bereich einer Komponente.

Tabelle 10.11:   Die Maus-Events

And last but not least die Tatatur-Events:

Methode Ausgelöst
keyDown() Eine Taste wird gedrückt.
keyUp() Eine Taste wird losgelassen.

Tabelle 10.12:   Die Tastatus-Events

Als Parameter werden ein Event-Objekt und der Code der gedrückten Taste übergeben. Für die Sondertasten gibt es spezielle Konstanten.

Konstante Taste
Event.UP Cursor oben
Event.DOWN Cursor unten
Event.LEFT Cursor links
Event.RIGHT Cursor rechts
Event.HOME Home-Taste
Event.END Ende-Taste
Event.F1 bis Event.F12 Funktionstasten F1 bis F12

Tabelle 10.13:   Event-Konstanten

10.14.5 Das Eventhandling 1.1

Das Eventmodell 1.1 hat mit dem Vorgängermodell nur noch wenig gemein. Schauen wir uns zuerst die Neuerungen an.

Die expliziten Neuerungen des Eventmodells

Eine ganz wichtige Erweiterung des Eventmodells 1.1 ist beispielsweise, dass neben der Verwendung von Ereignissen bei grafischen Benutzereingaben diese auch für JavaBeans genutzt werden können. Die zweite auffällige Neuerung des neueren Eventmodells ist, dass es dort mehr als eine Ereignis-Klasse gibt. Unter dem alten Modell beruhte (wie wir gesehen haben) die gesamte Verarbeitung von Ereignissen auf der Klasse Event. Die Instanzvarible id der K1asse Event beinhaltet dabei die Art des Ereignisses und liefert alle notwendigen Informationen. Das Attribut target enthält die Information, welches Objekt das Ereignisses ausgelöst hat.

In dem neuen Ereignis-Modell erben alle Ereignis-Klassen von der Klasse java.util.EventObject. AWT-Events erben von der Klasse java.awt.AWTEvent. Für die letztgenannte Klasse (AWTEvent) stehen wiederum einige Subklassen zur Verfügung, die entweder einfache (Low-Level-) oder höherwertige (semantische oder High-Level-)Ereignisse abbilden.

  • Zu den Low-Level-Ereignissen zählt zum Beispiel die Klasse MouseEvent, die im neuen Modell für Mausaktivitäten zuständig ist.
  • Zu den High-Level-Ereignissen zählen ActionEvent oder ItemEvent, die bei Statusänderungen von Elementen (etwa Selektion eines Menüeintrags oder einer Listbox) ausgelöst werden.

Da wir im neuen Modell mehrere Ereignis-Klassen haben, müssen diese auch sinnvoll verwaltet werden. Die Ereignis-Klassen werden dazu in dem neuen Konzept in einer Baumhierarchie gruppiert. Die Einteilung erfolgt ausschließlich über die unterschiedlichen Ereignistypen. Der Typ des Objekts legt damit automatisch fest, welche Art von Ereignis vorliegt. Der Vorteil davon ist, dass eine Abfrage der ID-Nummer (wie bisher) nicht mehr notwendig ist. Die Ereignis-Klassen sind vollständig gekapselt und lassen damit keine unnötigen Informationen (etwa in From von Instanzvariablen) nach außen durch. Die Peer-Philosophie vom Modell 1.0 gibt es nicht mehr. Dies entspricht noch mehr dem objektorientierten Gesamtkonzept, als es in Java 1.0 realisiert war. Normalerweise werden sämtliche 1.1-Ereignisse unveränderliche Objekte sein (man nennt sie deshalb immutable).

Wenn Ereignisse entstehen, müssen diese auch irgendwie ausgewertet werden. Es sind ja reine Mitteilungs-Objekte, die erst mal nur da sind und erst nach einer Behandlung durch einen entsprechend aufgebauten Mechanismus eine Reaktion des Programms bewirken können. Das Ereignis-Objekt selbst ist noch keine Reaktion des Programms. Es muss zu einem Auswertungs-Objekt transportiert werden. Sie kennen den Begriff jedoch schon, der den neuen Mechanismus des Ereignis-Transports beschreibt - Deligierte.

Dieser funktioniert so:

1. Ein Ereignis tritt bei einem Quellobjekt (Event Source) auf.
2. Das entstandene Ereignis-Objekt wird an ein Zuhörer-Objekt (einen so genannten Event-Listener) weitergeleitet.
3. Erst von dem Event-Listener wird über die konkrete Ereignis-Behandlung entschieden und die Reaktion auch ausgelöst.

In der Auflistung der Arbeitsschritte beim Deligationsmechanismus fällt auf, dass wir dort neben den bisher bekannten Objekten einen weiteren Objekttyp benötigen, ein Listener-Objekt, das dem Ereignis-Prozess eindeutig zugeordnet ist. Dieses Objekt ist neu im 1.1-Eventmodell und von ganz zentraler Bedeutung. >

Im Source könnte das Verfahren dann folgendermaßen aussehen:

Button meinButton;
meinButton = new Button("Ende");
ButtonListener meinButtonHandler;
meinButtonHandler = new ButtonListener(this);
meinButton.addActionListener(meinButtonHandler);

Dieser kurze Sourceteil beschreibt bereits die grundsätzliche Vorgehensweise, um Ereignisse in dem neuen Modell zu behandeln:

1. Zuerst wird ein Objekt erzeugt, das ein Ereignis auswerfen kann (in unserem Fall ein Button).
2. Danach erzeugt man einen dazu passenden Event-Listener (in unserem Fall ein ButtonListener, der auf ein Ereignis der Schaltfläche reagieren kann).
3. Nach der Erzeugung wird das Listener-Objekt bei der Ereignisquelle registriert. Dazu gibt es immer eine passende Methode, die immer so aussieht:
add<Typ_des_Events>Listener
Dabei muss anstelle des in spitzen Klammern stehenden Typ_des_Events ein Listener einer bestimmten Art stehen (in unserem Fall sieht es dann so aus: addActionListener).

Ein Ereignis-auslösendes Objekt kann mehrere Listener - sogar des gleichen Typs - besitzen. Die Methode add<Typ_des_Events>Listener bewirkt eine sinnvolle Hintereinanderschaltung in einer geordneten Listenstruktur.

Für die Reihenfolge des Event-Empfangs soll nach Empfehlung von Sun keine explizite Ausnahmbehandlung erfolgen, da das AWT die Reihenfolge des Eintreffens von Ereignis- Objekten nicht garantieren kann. Wenn also ein Ereignis 1 und unmittelbar nachfolgend ein Ereignis 2 ausgelöst wird, kann es vorkommen, dass Ereignis 2 vor Ereignis 1 beim Listener ankommt.

Wenn ein Ereignis eintritt, wird es vom Quellobjekt an diejenigen Listener des entsprechenden Typs weitergeleitet, die registriert sind. Diese Technik wird von Sun als Multicast bezeichnet. Das Objekt, bei dem das Ereignis eintritt, überträgt dieses wie in einer Fernsehsendung an alle Zuschauer, die das TV-Gerät angeschaltet haben (was in unserem Zusammenhang bedeutet, die Listener sind registriert). Die von dem Ereignis betroffenen Listener können dann entsprechend reagieren.

Das Eventmodell 1.1 beinhaltet noch eine etwas veränderte Technik, mit der es möglich ist, einen einzelnen Listener gezielt anzusprechen (Sun nennt es Unicast). Sie ist jedoch nur für JavaBeans zu verwenden.

Ein Event-Listener ist stets ein Objekt der grafischen Oberfläche oder eine eigene Klasse. Um auf die interessanten Ereignisse reagieren zu können, werden die entsprechenden Java-Schnittstellen implementiert. Das neue Java stellt dazu eine Hierarchie von abstrakten Event-Listener-Klassen zur Verfügung. In Analogie zu den bisherigen Ereignis-Klassen gibt es sowohl Low-Level-Schnittstellen für einfache Ereignisse als auch High-Level-Schnittstellen (semantisch) für komplexere Ereignisse. Es gibt für jede sinnvolle Reaktion auf einen spezifischen Ereignis-Typ in einer Event-Listener-Schnittstelle eine dazu passende Schnittstellendefinition. Schauen wir uns ein paar Beispiele an.

Die Schnittstelle ContainerListener verwendet folgende Methoden:

public abstract void componentAdded(ContainerEvent e)
public abstract void componentRemoved(ContainerEvent e)

Das MouseListener-Interface unterstützt folgende Methoden:

public abstract void mouseClicked(MouseEvent e)public 
abstract void mouseEntered(MouseEvent e)
public abstract void mouseExited(MouseEvent e)
public abstract void mousePressed(MouseEvent e)
public abstract void mouseReleased(MouseEvent e)

Das TextListener-Interface unterstützt die Methode

public abstract void textValueChanged(TextEvent e)

und das ActionListener-Interface

public abstract void actionPerformed(ActionEvent e)

Neben den neuen Methoden und Klassen zur Ereignisbehandlung hat sich auch etwas in der zentralen Art der Ereignisbehandlung im neuen Eventmodell verändert. Dies betrifft die Technik der Weiterleitung eines Ereignisses an die nächsthöhere, umfassende Komponente. In der Version 1.0 musste zur Unterbindung einer Weiterleitung eines Ereignisses an die nächsthöhere, umfassende Komponente von der gerade aktuellen Komponente auf jeden Fall der Wert true zurückgegeben werden. Die aktuelle Komponente musste deshalb auf jeden Fall das Ereignis »ansehen« und verarbeiten. Das Ereignis entsteht in dem 1.0-Modell beim auslösenden Objekt und wird - falls dieses Objekt das Ereignis nicht konsumiert (was bedeutet, der Wert true wird nicht zurückgegeben) - sofort an den nächsthöheren, umfassenden GUI-Container weitergegeben. Es gibt aber diverse Situationen, wo dies nicht gewünscht wird. Das Ereignis soll nicht bei der aktuellen Komponente und auch nicht bei einer das Ereignis umfassenden Komponenten konsumiert werden, sondern das Ziel des Ereignisses ist ein vollkommen anderes Objekt. Es muss sozusagen quer weitergeleitet werden. Eine ganze Kette von GUI-Elementen bekommt unter dem 1.0-Eventmodell ein Ereignis zu sehen. Obwohl sie nicht davon betroffen sind, müssen sie bei der Weiterschleusung das Ereignis auswerten und verarbeiten, damit sie den Wert true zurückgeben können (sonst würde es auch bei einen reinem Mittlerobjekt nach oben an die umfassende Komponente weitergereicht und das wäre in der Regel die falsche Richtung). Hier haben wir einen Grund dafür, dass unter dem Eventmodell 1.0 in einigen Fällen ein beträchtlicher Performanceverlust bei der Reaktion auf Ereignisse entsteht.

Ein weiteres Problem unter 1.0 betrifft die handleEvent()-Methode, die je nach Situation verschiedene boolesche Werte zurückgeben beziehungsweise bei Bedarf durch den Aufruf von super.handleEvent(event) die Ereignisbehandlung der Superklasse aufrufen muss.

Im Modell 1.1 erreichen Eventobjekte - bis auf notwendige Ausnahmen - nur noch das Zielobjekt. Das unter Umständen zeitaufwändige und überflüssige Durchschleusen (mit der Auswertung des Ereignisses) durch die Reihe von GUI-Container (die nicht betroffen sind) entfällt. Die angedeuteten Ausnahmen sind Situationen, wo eine Auswertung des durchgeschleusten Ereignisses deshalb notwendig ist, um die weitere Ereignisverarbeitung abzubrechen. Falls etwa bestimmte Bedingungen eintreten, die eine Weiterleitung des Ereignisses an das Ziel nicht sinnvoll erscheinen lassen, muss ein Mittlerobjekt als Filter arbeiten. Sie können in jedem Objekt Eingabeereignisse mit der Methode void consume() verarbeiten und damit verbrauchen (Sie finden sie sowohl in der Klasse java.awt.AWTEvent als auch java.awt.event.InputEvent und java.awt.event.InputMethodEvent, wo sie überschrieben werden).

Diese Methode setzt einen Schalter im Ereignis-Objekt, den man durch die assoziierte Methode boolean isConsumed() abfragen kann. Das Ereignis selbst kann weiterhin an alle registrierten Listener weitergereicht werden. Diese müssen dann bei Bedarf das Flag kontrollieren und die Verarbeitung gegebenenfalls abschließen.

Wenn man das Verhalten eines AWT-Objekts durch Bildung einer Subklasse erweitert, ergeben sich hinsichtlich der Ereignis-Verarbeitung grundsätzlich drei mögliche Verfahrensweisen:

1. Integration der Event-Listener-Schnittstelle in die Subklasse über eine Befehlszeile der Art:
implements <Event>ListenerInterface
2. Implementation von separaten Event-Listener-Klassen. Dies haben wir durchgesprochen. Dieses Verfahren hat aber den Nachteil, dass es unter Umständen sehr aufwändig sein kann und die Zahl der notwendigen Klassen stark erhöht.
3. Redefinition der Event-Dispatch-Methoden (processEvent oder process<Event-Klasse>), die jedes GUI-Objekt aus dem AWT standardmäßig besitzt.

Die dritte Variante verwendet im Gegensatz zu Variante zwei kein extra Event-Listener-Interface, erkauft sich dieses Vorgehen jedoch über den Nachteil, dass es zu den gleichen Problemen wie im Eventhandling des JDK 1.0 kommen kann. Problematisch sind beispielsweise die unübersichtlichen if-else-Konstrukte bei komplexen Applikationen. Diesen Missstand versucht das neue Framework durch eine klare Trennung der Konzepte zu beheben. Das gut gegliederte, objektorientierte Design vermeidet damit solche schwer wartbaren und fehleranfälligen Konstruktionen. Durch das Zwischenschalten der Listener erstellt man nur noch dann Subklassen von Standard-GUI-Objekten, wenn sich diese tatsächlich durch ein eigenes Verhalten auszeichnen. Ansonsten versucht man, das Erzeugen von Subklassen zur Ereignisbehandlung so weit wie möglich zu unterlassen. Das Verhalten passt damit noch besser als vorher in die objektorientierte Philosophie, wo man ja ebenfalls Verhaltensweisen soweit oben wie möglich im Vererbungsbaum ansiedeln möchte. Der Preis für diese Einfachheit ist der Aufbau von zusätzlichen Listener-Klassen.

Schauen wir uns nun ein ganz einfaches Beispiel mit dem Eventhandling 1.1 unter dem Aspekt der Ereignisbehandlung an, das die gerade beschriebene Vorgehensweise demonstriert (und sicher einige der bisherigen Beispiele in dem Kapitel verständlicher macht). Eine eigenständige Java-Applikation beinhaltet einen Button. Dazu wird dann ein Action-Listener mit addActionListener(new Event1_button1_actionAdapter(this)) registriert. Die im Parameter verwendete Klasse Event1_button1_actionAdapter implementiert java.awt.event.ActionListener. Die entsprechend festgelegte Methode button1_actionPerformed(ActionEvent e) wird bei einem Klick auf den Button ausgelöst und schließt das Programm. Wir verwenden hier also einen Standard-Adapter für das Eventhandling.

import java.awt.*;
import java.awt.event.*;
public class Event1 extends Frame {
  Button button1 = new Button();
  FlowLayout flowLayout1 = new FlowLayout();
  public Event1() {
      initial();
      }
  public static void main(String[] args) {
    Event1 mF = new Event1();
    mF.setSize(200,100);
    mF.show();
  }
  private void initial() {
    button1.setLabel("button1");
    button1.addActionListener(new Event1_button1_actionAdapter(this));
    this.setLayout(flowLayout1);
    this.add(button1, null);
  }
  void button1_actionPerformed(ActionEvent e) {
   System.exit(0);
  }  }
class Event1_button1_actionAdapter implements java.awt.event.ActionListener {
  Event1 adapt;
  Event1_button1_actionAdapter(Event1 adapt) {
    this.adapt = adapt;
  }
  public void actionPerformed(ActionEvent e) {
    adapt.button1_actionPerformed(e);
  }  }

Abschließend sei der Hinweis nachgetragen, dass sich beim Eventmodell 1.1 die Verwendung von anonymen Klassen ideal anbietet. Die Umarbeitung des gerade demonstrierten Beispiels zeigt, dass der Quelltext kompakter und besser lesbar wird. Insbesondere entfällt die Implementation der Event-Listener-Schnittstelle. Im Kapitel über Swing werden wir einige weitere Beispiele mit einem Standard-Adapter durchspielen.

import java.awt.*;
import java.awt.event.*;
public class Event2 extends Frame {
  Button button1 = new Button();
  FlowLayout flowLayout1 = new FlowLayout();
  public Event2() {
      initial();
      }
  public static void main(String[] args) {
    Event2 mF = new Event2();
    mF.setSize(200,100);
    mF.show();
  }
  private void initial() {
    button1.setLabel("button1");
    button1.addActionListener(
  new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        button1_actionPerformed(e);  }  });
    this.setLayout(flowLayout1);
    this.add(button1, null);
  }
  void button1_actionPerformed(ActionEvent e) {
   System.exit(0);
  }  }

10.14.6 Der Umstieg vom 1.0-Eventmodell auf das 1.1-Modell

Obwohl seit der Einführung des Eventmodells 1.1 viel Wasser den Bach heruntergeflossen ist, gibt es - aus mehrfach erwähnten Gründen - noch zahlreiche Projekte, die auf dem alten Eventhandling beruhen. Falls diese auf das 1.1-Modell umgestellt werden sollen, steht eine Menge Arbeit bevor. Zur Erleichterung des Umstiegs vom 1.0-Eventmodell auf das 1.1-Modell folgt in diesem Abschnitt eine Event-Konvertierungstabelle. Dabei werden die 1.0-Events ihren 1.1-Partnern gegenübergestellt. Die erste Spalte listet jeden 1.0-Eventtyp mit dem Namen der Methode (falls vorhanden) auf, die dem Ereignis zugeordnet ist. Wo keine Methode angegeben ist, wird das Ereignis immer in der handleEvent()-Methode behandelt. Die zweite Spalte listet die 1.0-Komponenten auf, die den angegebenen Ereignistyp generieren können. Die dritte Spalte gibt das Listener-Interface zum Auffinden des 1.1-Äquivalents der aufgelisteten Ereignisse an. Die vierte Spalte gibt die Methoden in jedem Listener-Interface an.

Modell 1.0 Modell 1.1
Event/Methode Generiert von Listener-Interface Methoden
ACTION_EVENT/action Button,
List,
MenuItem,
TextField
ActionListener actionPerformed
(ActionEvent)
  Checkbox,
CheckboxMenuItem,
Choice
ItemListener itemStateChanged
(ItemEvent)
WINDOW_DESTROY
WINDOW_EXPOSE
WINDOW_ICONIFY
WINDOW_DEICONIFY
Dialog,
Frame
WindowListener windowClosing
(WindowEvent)
windowOpened
(WindowEvent)
windowIconified
(WindowEvent)
windowDeiconified
(WindowEvent)
windowClosed
(WindowEvent) (kein Äquvivalent in der Version 1.0)
windowActivated
(WindowEvent) (kein Äquvivalent in der Version 1.0)
windowDeactivated(WindowEvent) (kein Äquvivalent in der Version 1.0)
WINDOW_MOVED Dialog,
Frame
ComponentListener componentMoved
(ComponentEvent)
componentHidden
(ComponentEvent) (kein Äquvivalent in der Version 1.0)
componentResized
(ComponentEvent) (kein Äquvivalent in der Version 1.0)
componentShown
(ComponentEvent) (kein Äquvivalent in der Version 1.0)
SCROLL_LINE_UP
SCROLL_LINE_DOWN
SCROLL_PAGE_UP
SCROLL_PAGE_DOWN
SCROLL_ABSOLUTE
SCROLL_BEGIN
SCROLL_END
Scrollbar AdjustmentListener (alternativ kann die neue ScrollPane-Klasse verwendet werden) adjustmentValueChanged(AdjustmentEvent)
LIST_SELECT
LIST_DESELECT
Checkbox,
CheckboxMenuItem,
Choice,
List
ItemListener itemStateChanged
(ItemEvent)
MOUSE_DRAG/mouseDrag
MOUSE_MOVE/mouseMove
Canvas,
Dialog,
Frame,
Panel,
Window
MouseMotion--Listener mouseDragged
(MouseEvent)
mouseMoved(MouseEvent)
MOUSE_DOWN/mouseDown
MOUSE_UP/mouseUp
MOUSE_ENTER/mouseEnter
MOUSE_EXIT/mouseExit
Canvas,
Dialog,
Frame,
Panel,
Window
MouseListener mousePressed
(MouseEvent)
mouseReleased
(MouseEvent)
mouseEntered
(MouseEvent)
mouseExited
(MouseEvent)
mouseClicked
(MouseEvent) (kein Äquvivalent in der Version 1.0)
KEY_PRESS/keyDown
KEY_RELEASE/keyUp
KEY_ACTION/keyDown
KEY_ACTION_RELEASE/keyUp
Component KeyListener keyPressed(KeyEvent) keyReleased(KeyEvent)
keyTyped(KeyEvent) (kein Äquvivalent in der Version 1.0)
GOT_FOCUS/gotFocus
LOST_FOCUS/lostFocus
Component FocusListener focusGained
(FocusEvent)
focusLost(FocusEvent)
Kein Äquvivalent in der Version 1.0. ContainerListener componentAdded
(ContainerEvent)
componentRemoved
(ContainerEvent)
Kein Äquvivalent in der Version 1.0. TextListener textValueChanged
(TextEvent)

Tabelle 10.14:   Gegenüberstellung der 1.0- und 1.1-Modelle für den Umstieg

10.15 Zusammenfassung

Die Kommunikation mit dem Anwender wird in Java hauptsächlich über das AWT (bzw. das im nächsten Kapitel beschriebene, darauf aufbauende Swing-Konzept) realisiert. Das AWT beinhaltet zur Kommunikation mit dem Anwender im Wesentlichen ein API, über die allgemeine Komponenten der Anwenderschnittstelle wie Buttons oder Menüs plattformunabhängig genutzt werden können. Die von Anfang an vorhandenen Komponenten des AWT sind folgende:

  • Schaltflächen (Buttons)
  • Label
  • Kontrollkästchen (Checkbuttons)
  • Optionsfelder (Radiobuttons)
  • Listen
  • Auswahlfelder
  • Textfelder
  • Textbereiche
  • Menüs
  • Zeichenbereiche
  • Bildlaufleisten

Komponenten werden in Java in Containern organisiert, wo sie gemeinsam verwaltet werden. Die Komponenten werden mittels der Container in verwaltbaren Gruppen organisiert. In Java können nur dann die Komponenten im AWT verwendet werden, wenn diese dann auch in einem Container enthalten sind. Die Container im AWT sind:

  • Fenster (Windows)
  • Panels
  • Frames
  • Dialoge

Die Applet-Klasse ist eine Unterklasse der Panel-Klasse. Insbesondere stellt bei Applets der Browser eine Umgebung für das AWT (und Grafik) bereit.

Ein bedeutender Bestandteil des AWT ist die Technik der Layoutmanager, die das Layout der Oberfläche regeln. Unter Java werden Sie im Allgemeinen nicht mehr genau angeben, wo eine hinzugefügte Komponente in einem Container platziert werden soll. Die genaue Platzierung regeln die Layoutmanager. Sie weisen das AWT darüber nur noch an, wo Ihre Komponenten im Verhältnis zu den anderen Komponenten stehen sollen. Der Layoutmanager findet - angepasst an die jeweilige Situation - automatisch heraus, an welche Stelle die Komponenten am besten passen. Das AWT stellt fünf verschiedene Typen von Layoutmanagern zur Verfügung:

  • FlowLayout
  • BorderLayout
  • GridLayout
  • CardLayout
  • GridBagLayout

Das Eventhandling muss in Java zweigleisig betrachtet werden. Java und das JDK bieten zwei verschiedene Modelle, wie auf Ereignisse zu reagieren ist. Das Eventhandling hat sich im Wechsel von der Version 1.0 auf die JDK-Version 1.1 total verändert. Allerdings besitzen beide Ereignisbehandlungskonzepte noch ihre Existenzberechtigung. Sun rät explizit, die beiden Modelle nicht in einem Programm zu mischen. Zusammenfassend kann man sagen, dass das neue Eventmodell den wesentlichen Vorteil bietet, dass die gleiche Architektur sowohl in der GUI-Programmierung als auch im Rahmen des neuen Komponentenmodells, JavaBeans, anwendbar ist. Die Entwickler müssen nur ein Framework lernen, das sich allerdings von der Logik des vorher praktizierten Modells unterscheidet. Außerdem wird die gesamte Programmstruktur einfacher, übersichtlicher, stabiler und in einigen Fällen schneller.

Wo aber viel Licht ist, ist auch Schatten. Durch die neue Logik in dem Eventmodell werden die unter 1.0 entwickelten Projekte nur mit erheblichem Aufwand auf das veränderte Eventmodell zu portieren sein. Zwar verspricht Sun, dass dies einfach und ohne Probleme zu bewerkstelligen ist, aber für komplexe Anwendungen wird es - entgegen der Marktingbeteuerungen - ein kaum zu realisierender Aufwand sein. Nicht umsonst wird das alte Eventhandling auch unter Java 1.1 und 1.2 weiter unterstützt (wenn auch bei der Kompilierung die Meldungen auftauchen, man würde deprecated-Elemente verwenden). Und man sollte ebenfalls beachten, dass gerade bei Applets das neue Eventmodell erhebliche Probleme bereiten kann.

1

Natürlich benutzt damit auch der User über das fertige Produkt dann das AWT mit seinen Komponenten.


© Copyright Markt+Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH
Elektronische Fassung des Titels: Java 2 Kompendium, ISBN: 3-8272-6039-6 Kapitel: 10 Das AWT