AG-Intra.net Arbeitsgemeinschaft
Intranet

Home
Was ist ein Intranet
Grundlagen
Netzwerke
Linux
Windows
Java
Sicherheit
Datenbanken
Projekte
Links
Impressum
Mitmachen ?
Diskussionsforum
Start:
25.11.2000
Letztes Update:
28.12.2000
2. Beispiel - AWT Anwendung . Java Tutorial
Copyright 2000 by Frank Gehde
<= vorherige Seite Inhaltsverzeichnis nächste Seite =>


Liebe Besucher, ein aktueller Hinweis in eigener Sache:
Es ist beabsichtigt, diese Seiten und die Domain im Januar/Februar 2004 auf einen anderen Server umzuziehen. Es ist leider nicht auszuschließen, daß es während des Umzugs zu technischen Problemen mit diesen Seiten kommen wird. Insbesondere im eMail-Bereich wird es vermutlich Probleme geben. Wenn Sie fragen haben oder mich sonstwie erreichen wollen empfehle ich an rebel@snafu.de zu posten.
Nachdem der Umzug abgeschlossen ist, wird es allerdings auch inhaltliche Änderungen während des ersten Halbjahrs 2004 geben. Keine Angst. Es werden keine Inhalte verlorengehen, aber die Struktur der Seiten wird komplett geändert. Diese Seite hat eben eine andere Entwicklung genommen seit 2000, als das Projekt gestartet wurde ;-) Ich werde mich bemühen, daß bei ihnen vorhandene alte Bookmarks wenigstens zu einem Verweis auf die Neustruktur führen, und die gesuchten Inhalte für sie trotzdem leicht und schnell auffindbar sein werden.
Die eigentlich zu dieser Seite gehörenden Domains ag-intra.com, ag-intra.org und ag-intra.de werden von mir geschlossen bzw. gelöscht und unregistriert.

2.1. Überblick
Im zweiten Beispiel des Java Tutorial geht es um die Verwendung von grafischen Oberflächen. Dabei wird es neben der Erklärung der grafischen Klassen und der Anordnung von grafischen Komponenten unter anderem auch um Klassenhierarchie, Pakete, die Sichtbarkeit von Klassen, die Erweiterung von Klassen und die Ereignisbehandlung in Java gehen. Insbesondere wird dabei das paket java.awt (Abstract Windowing Toolkit) verwendet.

Dieses Kapitel kann AWT nicht komplett behandeln. Das wäre zu komplex. Die Firma Sun empfiehlt, für Oberflächen Swing zu verwenden (siehe Beispiel 4). Ich persönlich finde Swing auch viel schöner, und folge dieser Empfehlung. Dennoch ist ein Einblick in AWT nicht obsolet. Zum einen basiert Swing teilweise auf dem AWT, zum anderen sind bestimmte Mechanismen bei beiden Paketen ähnlich zu handhaben. Auch für die Appletprogrammierung ist ein Grundeinblick in AWT sehr hilfreich.

Wie auch im ersten Beispiel, werden wir hier erst einmal Einfachstprogramme verwenden, anhand derer die Eigenheiten und Eigenschaften von Java und AWT beschrieben werden.

2.2. Das erste Fenster
Unser erstes Beispielprogramm macht nicht viel mehr, als uns tatsächlich ein echtes Fenster auf den Bildschirm zu zaubern. Wie üblich zunächst einmal den Quelltext für die Datei WinEins.java:

import java.awt.*;  //das Paket awt wird in dieser Klasse sichtbar gemacht

public class WinEins extends Frame {     //Unsere Klasse erweitert die 
                                         //Klasse Frame
  public WinEins(String myTitel) {       //Konstruktor der mit Argument
    super(myTitel);                      //myTitel aufgerufen wird
    this.setSize(300,100);               //Methode aus der Klasse Component
  }
  public static void main(String[] arg) {  //Das Hauptprogramm
    WinEins myWin = new WinEins("Das erste Fenster");
    myWin.show();
  }
}

So, daß ist doch netterweise recht wenig, was man zum Erzeugen eines Fensters benötigt. Trotzdem kann man daran sehr viel erklären. Zunächst aber einmal im Vorgriff einige Eigenschaften des Programms in der Praxis. Kompilieren Sie dazu zunächst die Klasse mit javac WinEins.java und rufen Sie das Programm anschließend mit java WinEins auf. 

screenshot1

It's magic, da kann man doch stolz sein. In C muß man wesentlich mehr Aufwand betreiben, um ein Fenster auf den Bildschirm zu nageln. Verschieben Sie das Fenster jetzt auf dem Bildschirm. Eigentlich verhält sich das Fenster ja wie man es gewohnt ist. Klicken Sie jetzt zum Schließen auf das Schließen-Symbol oben rechts in der Titelleiste. Oups. Was ist denn das? Tja, das ist ganz normal. Wir haben kürzestmöglichen Aufwand betrieben, um ein Fenster zu erzeugen. Das Schließen eines Fensters ist jedoch ein Ereignis. Und da wir keine Ereignisbehandlungsroutine geschrieben haben, können wir das Fenster auch nicht schließen. Die einzige Möglichkeit besteht darin, wie bei allen anderen Java Programmen auch, das Fenster mit der MS-DOS Eingabeaufforderung, aus der wir das Programm gestartet haben, zu aktivieren und dort die Tastenkombination[Ctrl]-[C] zu drücken. Damit wird die JVM immer beendet.
So nachdem sie jetzt eine Eigenheit des speziellen Beispielprogramms kennengelernt haben, analysieren wir den Sourcecode. Die erste Zeile ist für uns neu:

import java.awt.*;  //das Paket awt wird in dieser Klasse sichtbar gemacht

In Java gibt es eine Objekthierarchie. Ganz oben in dieser Hierarchie steht die Klasse Object. Alle, aber auch wirklich alle anderen Klassen sind von Object abgeleitet. Unsere bisher geschriebenen Klassen, sind auch direkt von Object abgeleitet, obwohl wir nirgends extends Object geschrieben haben. Wird nämlich extends weggelassen, so nimmt Java automatisch an, daß die Klasse von der SuperklasseObjectabgeleitet wird. Unsere Klassen sind somit Sub-Klassen von Object.
Eine weitere Eigenschaft von Java ist die Verwendung von Paketen. Wenn ich ein Programm schreibe, was auf andere Klassen zugreift, dann müssen sich diese Klassen in dem gleichen Paket befinden wie mein Programm. Andernfalls kann mein Programm die Klassen in anderen Paketen nicht "sehen", also adressieren. Will ich auf Klassen aus anderen Paketen zugreifen, so kann ich an der Stelle, wo ich das will, den kompletten Pfadnamen zu dem Paket in der Objekthierarchie angeben. Im Beispiel greifen wir ja auf die Klasse Frame aus dem Paket java.awt zu. Hätten wir die import-Anweisung weggelassen, dann hätten wir anstelle von Frame schreiben müssen: java.awt.Frame Auf diese Art und Weise kann ich dann auch auf Klassen und Methoden aus anderen Paketen zugreifen. 
Die hier verwendete import-Anweisung kann das ganze aber vereinfachen. Wenn ich oft auf Klassen oder Methoden aus einem bestimmten anderen Paket zugreifen will, kann ich alternativ am Anfang meiner Klassendatei das entsprechende Paket importieren. Dann kann ich Klassen- und Methodennamen direkt verwenden, ohne immer den kompletten Pfad voranstellen zu müssen. Bei dem Import wird übrigens nicht etwa der gesamte Code mit in unsere Klasse kompiliert. Die Import-Anweisung sorgt nur dafür, daß die (Klassen-)Namen aus dem entsprechenden Paket für unser Programm sichtbar, also verwendbar gemacht werden.
In unseren übrigen Programmen aus dem Beispiel 1 haben wir übrigens auch auf Klassen anderer Pakete zugegeriffen. Dabei handelt es sich um das Paket java.lang.* Dies ist aber das einzige Paket, welches wir nicht explizit importieren müssen, da Java diesen "Import" automatisch für jedes Programm im Hintergrund erledigt. Warum, wird Ihnen klar, wenn Sie in der API-Dokumentation mal gucken, was in diesem Paket alles enthalten ist.
Unsere eigenen Programme und Klassen gehören übrigens auch einem Paket an. Solange wir nicht dafür sorgen, daß wir ein bestimmtes Paket selbst erzeugen gibt es unter Java ein Default-Paket, in welches unsere Klassen einsortiert werden. Das soll uns aber noch nicht weiter interessieren.
In unserem Beispielen nutzen wir jedenfalls die import-Anweisung, weil wir noch viel mit dem Paket java.awt.* zu tun haben werden.
Als nächstes steht in unserem Programm folgendes:

public class WinEins extends Frame {   //Unsere Klasse erweitert Frame

Das Erweitern von Klassen haben wir im ersten Beispiel schon kennengelernt (das Beispiel mit den Koordinaten). Dort haben wir jedoch in der Regel nur deshalb Klassen erweitert, weil wir uns das Leben einfacher machen wollten. In diesem Fall, sind wir jedoch gezwungen mit der Erweiterung, also dem Schlüsselwort extendszu arbeiten weil wir ein Fenster erzeugen wollen. Wir nutzen die vorgefertigte Klasse Frame und erweitern diese, um die darin enthaltene Funktionalität für Fenster zu verwenden.
In Wahrheit ist auf einem Computer im Hintergrund eine Menge Arbeit zu erledigen, wenn man ein Fenster erzeugen will. Daher bringt jedes Betriebssystem bzw. jede Programmiersprache entsprechende API's mit, also Funktionsbiblitheken, die einem die Arbeit abnehmen. Jedes Windows-Programm braucht ein Fenster, warum sollte also für jedes Programm der Aufwand zur Erzeugung eines Fensters von Programmierer neu betrieben werden, obwohl das Vorgehen doch fast immer gleich ist? Dazu werden eben die API's mitgeliefert, die Funktionen für allgemeine Komponenten, Elemente oder Funktionen mitbringen.
Java bringt eben auch so eine API mit. Das JDK ist zB. nur zu einem kleinen Teil die Programmiersprache Java. Der Großteil des JDK besteht aus API's für die verschiedensten Einsatzzwecke. In unserem Beispiel eben die AWT-API für die Erstellung grafischer Oberflächen. Darin sind die verschiedensten Klassen enthalten, die man für die GUI (Graphical User Interface) Programmierung benötigt.
Um ein Fenster zu erzeugen, verwendet man mit dem AWT in der Regel die Klasse Frame. Das ist etwas lustig, weil ich es für eine Namensvertauschung halte. Es gibt nämlich im AWT auch die Klasse Window. Diese stellt aber wirklich nur einen rechteckigen Bereich ohne Titelzeile etc. dar. Frame ist von Window abgeleitet und so erweitert worden, daß eben auch Titelzeilen mit angezeigt werden. Es handelt sich dabei übrigens um Behälter, wie wir im zweiten Fenster-Programm noch erfahren werden.
Um ein eigenes Fenster darzustellen, müssen wir es von Frame ableiten, die Klasse Frame also erweitern. Das haben wir mit der oben gezeigten Signatur, mit extends getan. Unsere Klasse WinEins erbt also alle Eigenschaften und Methoden von Frame.
Sehen wir uns nun unseren Konstruktor an:

public WinEins(String myTitel) {   //Konstruktor der mit Argument myTitel
  super(myTitel);                  //aufgerufen wird
  this.setSize(300,100);           //Methode aus der Klasse Component
}

Unser Konstruktor erwartet ein Argument, nämlich den Titel für die Titelleiste des Fensters. Danach wird mit super(myTitle); der Konstruktor von Frame, der Superklasse von WinEins, aufgerufen, und ihm wird das Argument weitergereicht. Man kann den Titel übrigens auch weglassen und super mit super(); aufrufen. Dann hat das Fenster eben keine Titelbezeichnung. 
Danach wird dann noch eine Methode aufgerufen, die unser Fenster auf eine bestimmte Größe in Pixeln bringt. Diese Methode hat unserWinEins von Frame geerbt. Frame hat sie vonWindow geerbt, und Window hat sie von Container geerbt etc etc. Daran sehen wir sehr schön, daß bei der weiteren Bildung von Sub-Klassen, also Erweiterungen, die jeweiligen Methoden immer mit weitervererbt werden. Daher können wir auch die Methode .setSize(), die ursprünglich mal in der Klasse Component programmiert wurde, verwenden da sie bis zu unserer Klasse WinEins weitervererbt wurde. Mehr brauchen wir in unserem Konstruktor in diesem Beispiel eigentlich nicht (Wie gesagt, wir hätten auch nur super(); aufrufen können, und die Größendefinition weglassen können (dann hätten wir eine Default-Größe des Fensters von 100x100 Pixeln)).
Wie wir ja schon wissen, brauchen wir in einem ausführbaren Programm auch immer die Methode main. Da unser Programm ausführbar sein soll, finden wir sie hier:

public static void main(String[] arg) {           //Das Hauptprogramm
  WinEins myWin = new WinEins("Das erste Fenster");
  myWin.show();
}

Die Signatur ist klar. Die erste Zeile macht auch etwas Bekanntes. Eine Klasse, wie Frame oder auch WinEins, ist ja nur eine Beschreibung einer Funktionalität. Wenn ich mit ihr arbeiten will, so brauche ich im Programm in der Regel eine Instanz der Klasse, also ein Objekt mit dem ich arbeite. Und genau das tun wir hier. Wir legen ein Fensterobjekt mit Namen myWin an, wie wir auch im Beispiel 1 String-Objekte angelegt haben. In diesem Fall wird dann also der Speicherbereich für ein Fenster reserviert und alle anderen notwendigen Aufagaben für die Fenstererzeugung werden durchgeführt. Dem Konstruktor wird in diesem Fall ein Argument übergeben, nämlich die Bezeichnung in der Titlleiste, wie oben beschrieben.
Jetzt fehlt nur noch, daß wir das Fenster auch sichtbar machen. Dies erledigt die Methode .show() der Klasse Frame (bzw. unserer davon abgeleiteten Klasse).

So, mehr braucht es nicht um ein Fenster zu erzeugen. Sie haben dabei auch etwas mehr über die Hintergründe von Paketen, Objekthierarchien und soweiter gehört. Wer leere Fenster langweilig findet, der interessiert sich sicher für den nächste Abschnitt über Komponenten.

2.3. Komponenten
Nun steigen wir weiter ins AWT ein. Das AWT bringt eine ganze Menge an nützlichen Dingen mit: Fenster, Buttons, Radiobuttons, Zeichenflächen, leere Flächen, Menüs und so weiter. Obwohl all diese Dinge von der Klasse Component des AWT abgeleitet wurden, bezeichnen wir mal nur die Buttons, Eingabefelder, Label, Radiobuttons und Checkboxen als Komponenten. Es gibt nämlich auch noch Komponenten wie Panels und Frames. Und diese bezeichnen wir als Container. Der Sinn dieser Unterscheidung liegt darin, daß Komponenten einem Container hinzugefügt werdenmüßen. Ohne Fenster kein Button. Man kann Komponenten aber keine Komponenten oder Container hinzufügen. Man kann mit den Containern auch gewisse Raumaufteilungen in einem Fenster durchführen. Container lassen sich auch durchaus schachteln.
Zur Verteilung der Komponenten in einem Container nutzt Java die sogenannten LayoutManager. Diese LayoutManager geben eine bestimmte Anordnung für Komponenten vor. An dieser Stelle komme ich um eine kleine Grafik nicht herum. Sehen Sie hier einmal, wie drei populäre LayoutManager z.B. in einem Fenster 5 Buttons verteilen.
Grafik eines FlowLayout
Grafik eines BorderLayout Grafik eines GridLayout

Die gebräuchlichsten LayoutManager sind wohl das BorderLayout und das GridLayout. Das (hier nicht gezeigte) GridBagLayoutmacht zwar einen guten Eindruck, ist aber auch umständlich zu implementieren.
Um Komponenten in ein Fenster zu bringen, gehen Sie in der Regel in folgender Reihenfolge vor: Komponenten im Konstruktor deklarieren und entsprechende Eigenschaften einstellen, Container erzeugen (falls es nicht der Frame selbst ist), Layoutmanager einstellen, Komponenten dem Container hinzufügen und den Container schließlich dem Frame hinzufügen. Dann wird im Hauptprogramm eine Instanz des Frames erzeugt, und die Methoden .pack(), und .show(). werden aufgerufen. Das wars. Wir werden jetzt unserem oben noch leeren Fenster einfach ein Label, ein Texteingabefeld und einen Button hinzufügen.

import java.awt.*;    //das Paket awt wird in dieser Klasse sichtbar gemacht

public class WinZwei extends Frame {            //Unsere Klasse erweitert
                                                //die Klasse Frame
 public WinZwei() {                             //Konstruktor
  super("mit Komponenten");                     //Den Titel festlegen

  Label myHeader = new Label("Hier Text eingeben:"); //Labelkomponente
  TextField eingabeFeld = new TextField("hier", 20); //Textfeld-Komponente
  Button klicker = new Button("Klick Mich");         //Button-Komponente

  Panel myContainer = new Panel();                   //spezieller Container
  myContainer.setLayout(new BorderLayout(5,5));      //Layout einsetzen

  myContainer.add(myHeader, BorderLayout.NORTH);     //Die Komponenten dem
  myContainer.add(eingabeFeld, BorderLayout.CENTER); //Container hinzufügen
  myContainer.add(klicker, BorderLayout.SOUTH);

  this.add(myContainer);                             //Container dem Fenster
                                                     //hinzufügen
 }
 public static void main(String[] arg) {             //Das Hauptprogramm
  WinZwei myWin = new WinZwei();
  myWin.pack();
  myWin.show();
 }
}

Genau wie oben beschrieben, haben wir die erforderlichen Schritte durchgeführt und Komponenten zu unserem Fenster hinzugefügt. Das wir übrigens die Komponenten erst in einen Panel-Container getan haben, und nicht direkt in unser Fenster demonstriert nur, daß man das für gewöhnlich bei größeren Fenstern (mit vielen Komponenten) so tut. Wie sieht es nun aus, unser Fenster ?

Na das ist doch ganz prima. Damit können wir jetzt ein bißchen rumspielen. Text kann eingegeben werden. Wir können sogar Copy & Paste verwenden, obwohl wir dazu nichts programmiert haben. Danke AWT. Der Button kann angeklickt werden, aber es passiert nix, genausowenig, wie wir das Fenster schließen können. Nunja.
Wie wir an dem Listing sehen, erzeugen wir Instanzen der Klassen Label, TextFieldund Button. Das funktioniert ganz ähnlich, wie das Erzeugen von Variablen (bzw. Wrapper-Objekten). Beim Erzeugen wird den Konstruktoren bereits eine Eigenschaft mit auf den Weg gegeben, nämlich hier die Beschriftung. Bei der TextField Komponente haben wir zusätzlich die Breite in Spalten mit angegeben. Dann haben wir noch ein Panelerzeugt. Wie bereits gesagt, fungieren Panels als Containerobjekte. Wenn man, wie bei den meisten Programmen, sehr viele Komponenten auf der Fensterfläche unterbringen muß, verwendet man meist mehrere Panels und schachtelt diese. Sonst wären bestimmte Layouts mit den vorhandenen LayoutManagern kaum zu erzielen. Das ist auch das nächste, was wir tun. Wir weisen unserer Instanz von Panel explizit einen bestimmten LayoutManger zu, das BorderLayout. Die beiden Parameter5,5 sorgen übrigens dafür, daß zwischen allen Komponenten ein Abstand von 5 Pixeln verbleibt. Ohne diesen Abstand sah das Fensterchen sonst gar zu eng gepackt aus.
Nun werden alle Komponenten dem Container hinzugefügt. Dazu wird die Methode add() verwendet. Als Parameter wird immer der Name der Instanz der Komponente mit angegeben. Da wir hier den BorderLayout Manager verwenden, geben wir noch an, wo die Komponente genau plaziert werden soll. Dies sind bei dem BorderLayout die HimmelsrichtungenNORTH, WEST, SOUTH, EAST und zusätzlich das Zentrum CENTER. Bei den anderen LayoutManagern entscheidet meist die Reihenfolge des Hinzufügens darüber, wo die Komponenten auftauchen.
Schließlich fügen wir unser Container-Panel dem Frame selbst zu, was mit this.add(myContainer); durchgeführt wird.
Im Hauptprogramm, daß wir praktischerweise gleich in der Klasse mit drin haben, erzeugen wir eine Instanz von WinZwei. Beim Erzeugen wird ja der Konstruktor aufgerufen. Im Speicher liegt also nach dem Erzeugen der Instanz das komplette Fenster vor. Mit myWin.show() könnten wir die Instanz bereits anzeigen lassen. Die Methode .pack() tut jedoch noch etwas sinnvolles. Sie erfüllt den Zweck der LayoutManager. Intern wird nämlich jetzt festgestellt, wieviel Platz jede Komponente benötigt, um optimal dargestellt zu werden. Abhängig von der größten Komponente wird nun die Größe des Fensters berechnet und eingestellt. Daher haben wir auch auf die Zeile aus dem ersten Programm verzichtet, mit der die Fenstergröße explizit auf eine bestimmte Größe eingestellt wurde. Bei dem ersten Beispiel wäre ja die Methode .pack() auch gar nicht anwendbar gewesen, da es keine Komponenten gab, an denen sie sich hätte orientieren können.
So einfach ist es also Fenster mit funktionalen Inhalten zu erzeugen. Nur das hier noch nichts funktioniert. Funktion wird ja meist durch ein Ereignis von außen erreicht. Zum Beispiel durch Anklicken eines Button. Um also Leben in das Ganze zu bringen, müssen wir uns mit der Ereignisbehandlung beschäftigen.

2.4. Ereignisbehandlung
Ereignisbehandlung ist ein komplexes Thema und in den Versionen von Java öfter komplett geändert worden. Uns interessiert nur die Ereignisbehandlung von Java 2 die im Paket java.awt.event implementiert ist.
Ereignisbehandlung in Java und überhaupt bei grafischen Oberflächen spiegelt in der Regel das Arbeiten mit Fenstern unter Multitaskingbetriebssystemen im allgemeinen wieder. Wenn man sein Programmfenster sieht, betrachtet man erst einmal die dortigen Ausgaben und die Angebote irgendwelche Aktionen durchzuführen, die in Form von Buttons, Menüs oder anderen Komponenten angeboten werden. In dieser Zeit macht das Programm ja gar nichts. Entsprechend benötigt es auch keine Rechenzeit. Erst wenn der Nutzer durch Mausklick oder Tastendruck signalisiert, daß er vom Programm eine Aktion erwartet, muß das Programm tätig werden, und die gewünschte Funktion ausführen. Der Programmablauf ist also durch Aktion anfordern, Aktion ausführen, Warten, Aktion anfordern etc. etc. geprägt. Das Programm wartet daher in der Regel nur darauf, daß der Nutzer ein Ereignis auslöst, um darauf zu reagieren. Wo früher in Endlos-Schleifen eintretende Ereignisse abgefragt wurden, signalisiert heutzutage das Betriebssystem (oder hier im konkreten Fall die JVM (Java Virtual Machine)) ein eintretendes Ereignis. Programme sind also oft ereignisgesteuert.
Unser Programm muß also irgendwie von der JVM Ereignisse gemeldet bekommen. Das Programm muß bei Java zuerst anmelden, daß es Interesse an einem speziellen Ereignis hat. Dazu registriert es einen sogenannten Listener (Dieser horcht auf auftretende Ereignisse). Nach diesem Registrieren des Interesses, erhält das Programm auch die jeweiligen Ereignisse von der JVM mitgeteilt.
Schließlich muß das Programm auch irgendwie auf das Ereignis reagieren. Dazu gibt es zwei Varianten der Ereignisbehandlung. Soweit erstmal die Theorie.

2.4.1. Variante 1 - Überschreiben von processXXXEvent()
Wir zeigen zunächst einmal, wie wir die ärgerliche Eigenschaft unseres Programms WinZwei abstellen, daß es sich nicht über den Schließen-Button in der Titelzeile beenden ließ. Dazu verwenden wir Variante 1 der Ereignisbehandlung. Der Sourcecode vonWinDrei.java:

import java.awt.*;   //das Paket awt wird in dieser Klasse sichtbar gemacht
import java.awt.event.*;  //und das Paket für Ereignisbehandlung

public class WinDrei extends Frame {       //Unsere Klasse erweitert 
                                           //die Klasse Frame
 public WinDrei() {                        //Konstruktor
  super("process ueberschreiben");                //Den Titel festlegen

  Label myHeader = new Label("Hier Text eingeben:"); //Labelkomponente
  TextField eingabeFeld = new TextField("hier", 20); //Textfeld-Komponente
  Button klicker = new Button("Klick Mich");         //Button-Komponente

  Panel myContainer = new Panel();                   //spezieller Container
  myContainer.setLayout(new BorderLayout(5,5));      //Layout einsetzen

  myContainer.add(myHeader, BorderLayout.NORTH);     //Die Komponenten dem
  myContainer.add(eingabeFeld, BorderLayout.CENTER); //Container hinzufügen
  myContainer.add(klicker, BorderLayout.SOUTH);

  this.add(myContainer);                             //Container dem Fenster
                                                     //hinzufügen

  this.enableEvents(AWTEvent.WINDOW_EVENT_MASK);     //Ereignisse ermöglichen
 }

 protected void processWindowEvent(WindowEvent e) {    //Fenster-Ereignisse
  if (e.getID()==WindowEvent.WINDOW_CLOSING) {         //behandeln
   dispose();                        // Ressourcen des Fensters freigeben
   System.exit(0);                   // Programm beenden
  }
 }                                   // Ende Methode processWindowEvent()

 public static void main(String[] arg) {  //Das Hauptprogramm
  WinDrei myWin = new WinDrei();
  myWin.pack();
  myWin.show();
 }
}

Die Änderungen zu WinZwei (abgesehen vom Klassen und Dateinamen) habe ich jetzt einfach mal grün eingefärbt. Wir benötigen also eine zusätzliche import-Anweisung, eine weitere Zeile im Konstruktor und eine weitere Methode. Die import-Anweisung ist nötig, weil die Ereignisbehandlung komplett über das Paketjava.awt.event.* abgewickelt wird. Diese ist durch die erste import-Anweisung nicht sichtbar, da damit nur die Klassen in java.awt. sichtbar gemacht werden, nicht aber die Klassen in den Unterpaketen vonjava.awt.
Im Konstruktor erscheint nun die erste Anweisung, die für die Behandlung von Ereignissen zuständig ist.

this.enableEvents(AWTEvent.WINDOW_EVENT_MASK);

Wie wir bereits aus unseren ersten beiden Win-Programmen wissen, ist die Ereignisbehandlung erstmal nicht eingeschaltet. Bei dieser ersten Variante werden für eine Komponente (in unserem Fall das Frame, signalisiert durch das Schlüsselwort this.) Ereignisse einer bestimmten Gruppe eingeschaltet.  Die Methode enableEvents() hat unserFramevon der Klasse Component geerbt. Dieser Methode wird eben eine Konstante übergeben, die mitteilt, welche Events eigentlich eingeschaltet werden sollen. Diese Konstanten sind in der Klasse java.awt.AWTEvent definiert, und können in der API-Dokumentation nachgeschlagen werden. In unserem Fall wollen wir Fensterereignisse behandeln, und verwenden daher die Konstante AWTEvent.WINDOW_EVENT_MASK  Ein Beispiel für eine andere Maske wäre AWTEvent.MOUSE_EVENT_MASKwomit die Behandlung von Maus-Events eingeschaltet wird. Die Events werden, wie oben gesagt, durch die VirtualMachine erzeugt (oder können auch manuell durch Methoden selbst ausgelöst werden).
Nun sind also Ereignisse eingeschaltet. Es fehlt nur noch eine Methode, die auch entsprechend auf ein Ereignis reagiert. Unser Frame hat von der Klasse Component (von der ja alle AWT-Komponenten abgeleitet sind) die Methode processWindowEvent() geerbt (zur oben alternativ genannten Maske gibt es natürlich auch eine MethodeprocessMouseEvent()). In diesem Fall, wir wollen ja endlich das Fenster schließen können, konzentrieren wir uns auf die Window Events.
Um auf das Anklicken des Schließensymbols zu reagieren, überschreiben wir die Originalmethode processWindowEvent() folgendermaßen:

protected void processWindowEvent(WindowEvent e) {    //Fenster-Ereignisse
   if (e.getID()==WindowEvent.WINDOW_CLOSING) {       //behandeln
     dispose();                        // Ressourcen des Fensters freigeben
     System.exit(0);                   // Programm beenden
   }
}

Die Signatur ergibt sich aus der Originalmethode. Die VirtualMachine, die dieses Event erzeugt, übergibt ein WindowEvent als Parameter. Dieses Event-Object besitzt die Methode getID(). Der Rückgabewert von getID() kann mit den Konstanten aus den Ereignisklassen verglichen werden, um festzustellen, um welches Event es sich konkret handelt. Uns interessiert das WINDOW_CLOSING Event. Sollte dieses Auftreten, was durch Anklicken des Schließensymbols in der Titelzeile des Fensters ausgelöst wird, so werden zwei Methoden ausgeführt. Die erste lautet dispose() und gibt die Ressourcen eines Fensters wieder frei, und löscht es vom Bildschirm. Die zweite Methode beendet die Java Virtual Machine (und damit auch unser Programm, das in ihr läuft).

Wo ist die Einschränkung bei dieser Variante? Nun, diese Variante kann nur in Unterklassen von vordefinierten Komponentenklassen verwendet werden, da wir sonst die Methode processWindowEvent(WindowEvent e) ja nicht überschreiben können. In unserem Beispiel erweitern wir die Klasse Frame. Wir haben also eine Unterklasse erzeugt. Daher können wir diese Variante der Ereignisbehandlung verwenden. In manchen Fällen erzeugt man direkt Instanzen derKomponentenklassen (z.B. oft bei Labels, also einfachen Beschriftungen in einem Frame, wie in unserem Beispiel). Dann kann man diese Variante für dieses Label z.B. nicht benutzen.

2.4.2. Variante 2 - Registrieren eines XXXListeners
Wir werden jetzt wieder versuchen das Programm WinZwei so abzuändern, daß man das Fenster schließen kann, aber dabei die zweite Variante der Ereignisbehandlung benutzen. Dabei soll das Beenden nicht mehr über das Schließensymbol erfolgen, sondern über den Button, den wir zu diesem Zweck dann auch anders beschriften. Hier das Listing von WinVier.java:

import java.awt.*;    //das Paket awt wird in dieser Klasse sichtbar gemacht
import java.awt.event.*; // Ereignisbehandlung

public class WinVier extends Frame {       //Unsere Klasse erweitert 
                                           //die Klasse Frame
 public WinVier() {                        //Konstruktor
  super("ActionListener");                 //Den Titel festlegen

  Label myHeader = new Label("Hier Text eingeben:"); //Labelkomponente
  TextField eingabeFeld = new TextField("hier", 20); //Textfeld-Komponente
  Button klicker = new Button("Beenden");            //Button-Komponente

  klicker.addActionListener(new ActionListener() {   //in anonymer Klasse
   public void actionPerformed(ActionEvent e) {
    if (e.getActionCommand().equals("Beenden")) {    //Welche Komponente
     dispose();
     System.exit(0);
    }
   }
  });

  Panel myContainer = new Panel();                   //spezieller Container
  myContainer.setLayout(new BorderLayout(5,5));      //Layout einsetzen

  myContainer.add(myHeader, BorderLayout.NORTH);     //Die Komponenten dem
  myContainer.add(eingabeFeld, BorderLayout.CENTER); //Container hinzufügen
  myContainer.add(klicker, BorderLayout.SOUTH);

  this.add(myContainer);                             //Container dem Fenster
                                                     //hinzufügen
 }
 public static void main(String[] arg) {             //Das Hauptprogramm
  WinVier myWin = new WinVier();
  myWin.pack();
  myWin.show();
 }
}

Wie oben, habe ich die Änderungen zu WinZwei.java grün markiert. Ich nehme es vorweg, diese Variante nutzt Listener um auf Ereignisse zu registrieren. Fangen wir vorne an. Zuerst wird auch wieder das Paket java.awt.event.* importiert. Da wir gesagt haben, daß dieses Paket für Ereignisbehandlung benötigt wird, brauchen wir es also genauso wie bei der ersten Variante. Den Button habe ich eigentlich unverändert erzeugt, lediglich die Beschriftung habe ich dem Zweck entsprechend geändert. Interessant ist jetzt der folgende Block.

klicker.addActionListener(new ActionListener() {      //in anonymer Klasse
   public void actionPerformed(ActionEvent e) {
     if (e.getActionCommand().equals("Beenden")) {    //Welche Komponente
       dispose();
       System.exit(0);
     }
   }
});

Wir sehen, daß ich diesmal keine Events extra eingeschaltet habe. Beim Registrieren eines Listeners geschieht das nämlich automatisch. Das ganze Konstrukt beginnt mit klicker. Das ist ja die Instanz des zuvor erzeugten Buttons. Mit Komponente.addActionListener() binde ich einen ActionListener an eine Komponente. Wir sehen, daß die öffnende Klammer für den Parameter in dieser Zeile nicht mehr geschlossen wird. Wenn Sie jetzt mal Erbsen zählen, werden Sie feststellen, daß die schließende Klammer erst ganz am Ende, unten in der letzten Zeile des Blocks auftaucht und mit einem Semikolon abgeschlossen wird. Alles was also in diesem Block steht ist der Parameter. Als Parameter wird eine Instanz eines ActionListeners erwartet, die beschreibt was bei der auftretenen Aktion passieren soll. Und an dieser Stelle definieren wir eine Klasse. Diese Klasse hat keinen Namen, daher nennt man sie auch anonyme Klasse. Dieses Vorgehen mit anonymen Klassen macht zum Beispiel bei diesen Listenern Sinn, wo das Anlegen einer extra Source-Datei manchmal übertrieben ist. Nach dem Kompilieren von WinVier.java, erwarten Sie sicher wie üblich im Codeverzeichnis die Datei WinVier.class vorzufinden. Überraschung, zusätzlich steht dort jetzt auch eine Datei WinVier$1.class. Dies ist eine Class-Datei, die Java automatisch erzeugt und bezeichnet hat. Wir mußten dieser Klasse keinen Namen geben, aber Java unterstützt anonyme Klassen, und hat dies daher für uns erledigt. Es ist vielleicht alles ein wenig abstrakt, aber nach mehrmaligem Durchlesen wird es vielleicht verständlich *g*.
Nach diesem kleinen Exkurs in merkwürdige Syntax-Gefilde von Java, wissen Sie aber immer noch nicht was ein Listener ist.
Ein Listener "lauscht" auf das Auftreten eines Ereignisses. Tritt das Ereignis auf, reagiert der Listener darauf. Ein Listener muß dazu bei einer Komponente registriert werden (ab diesem Augenblick sind dann eben auch Events eingeschaltet). Dies wird in unserem Beispiel mit der Methode addActionListener() durchgeführt. Die Listener sind in Java als Schnittstellen vordefiniert. Zusätzlich zu dem hier verwendeten ActionListener gibt es z.B. auch einen KeyListener und weitere. Diese Schnittstellen geben bestimmte Methoden vor, die der Programmierer in seiner Umsetzung in der anonymen Klasse implementierenmuß. Bei unserem ActionListener ist dies die Methodepublic void actionPerformed(ActionEvent e), der als Parameter ein ActionEventübergeben wird (und wer übergibt dieses Event? Richtig, die JVM). Die von uns verwendete if-Abfrage ist im konkreten Programm eigentlich überflüssig, aber es wird gleich erklärt, was es damit auf sich hat. Wir haben jedenfalls die vorgegebene Methode in unserer (anonymen) Klasse implementiert, und damit die Vorgaben der Schnittstellen-Beschreibung (Interface) erfüllt. Stellt die Virtual Machine jetzt fest, daß auf der entsprechenden Komponente ein Ereignis auftrat (zB der Nutzer draufgeklickt hat) schickt sie allen Listenern, die bei der Komponente registriert sind eine entsprechende Nachricht, damit dieListenerreagieren können.
Unsere spezielle Reaktion dürfte klar sein, da Sie die bereits aus der ersten Variante kennen. Mit dispose() wird das Fenster zerstört, und mit System.exit(0) die virtuelle Maschine beendet. 
Der umschließende if-Block könnte aber noch etwas beschrieben werden. Der Methode actionPerformed() wird ja ein ActionEvent übergeben. Dieses ist ein Objekt und bringt eine Methode mit, die die Beschriftung der jeweiligen Komponente zurückliefert. Das ist die Methode getActionCommand(). Da der Rückgabewert ein String ist, können wir direkt die Methode.equals()zum Vergleichen verwenden. Wenn der Listener bei mehreren Komponenten angemeldet ist (kommt im nächsten Beispiel), kann er über diesen String feststellen, welche Komponente konkret von dem Ereignis betroffen ist.
Ich gebe zu, die Beschreibung zu den Listenern und der verwendeten Syntax ist etwas kompliziert. Mir persönlich war dieses ganze Konzept auch nicht von Anfang an klar. Aber nachdem man einige Male damit gearbeitet hat, sieht man dann doch "Licht am Ende des Tunnels".

2.4.3. Variante 2 - Registrieren eines XXXListeners (Variation)
Hier zeige ich nocheinmal, wie die Methode getActionCommand()  nutzbar ist und wie eine gesonderte Definition des ActionListeners ein sinnvolles Anbinden an mehrere Komponenten zuläßt. Wir nutzen einfach das Programm WinVier und machen daraus WinV.java (römisch 5?). Das Label schmeissen wir raus, und fügen dafür einen zweiten Button hinzu. Erstmal der Source:

import java.awt.*;  //das Paket awt wird in dieser Klasse sichtbar gemacht
import java.awt.event.*; // Ereignisbehandlung

public class WinV extends Frame {       //Unsere Klasse erweitert 
                                        //die Klasse Frame
 TextField eingabeFeld;                 //Wegen Zugriff, hier deklariert
 public WinV() {                        //Konstruktor
  super("mit Listenern");               //Den Titel festlegen

  eingabeFeld = new TextField("hier", 20);           //Textfeld-Komponente
  Button klicker1 = new Button("Beenden und Speichern"); //Button-Komponente
  Button klicker2 = new Button("Beenden");           //Button-Komponente

  ActionListener myListener = new ActionListener() { // in anonymer Klasse
   public void actionPerformed(ActionEvent e) {
    if (e.getActionCommand().equals("Beenden")) {    //Welche Komponente
     dispose();
     System.exit(0);
    }
    else if (e.getActionCommand().equals("Beenden und Speichern")) {
     System.out.println(eingabeFeld.getText());
     dispose();
     System.exit(0);
    }
   }
  };

  klicker1.addActionListener(myListener);
  klicker2.addActionListener(myListener);

  Panel myContainer = new Panel();                   //spezieller Container
  myContainer.setLayout(new BorderLayout(5,5));      //Layout einsetzen

  myContainer.add(eingabeFeld, BorderLayout.NORTH);  //Die Komponenten dem
  myContainer.add(klicker1, BorderLayout.CENTER);    //Container hinzufügen
  myContainer.add(klicker2, BorderLayout.SOUTH);

  this.add(myContainer);                             //Container dem Fenster
                                                     //hinzufügen
 }
 public static void main(String[] arg) {             //Das Hauptprogramm
  WinV myWin = new WinV();
  myWin.pack();
  myWin.show();
 }
}

Die erste Änderung ist die Deklaration des Textfeldes diesmal nicht im Konstruktor, sondern als Attribut der Klasse, also vor dem Konstruktor. Das hat den Grund, daß ich später in der anonymen Klasse auf den Inhalt des Textfeldes zugreifen will. Dies ist nur möglich, wenn das Textfeld als Attribut deklariert wird. Die Initialisierung des Textfeldes geschieht sonst an der bekannten Stelle. Falls Sie mal vom Compiler Fehlermeldungen bekommen, daß sie auf "non-static" Dinge zugreifen wollen, probieren Sie einfach, diese als Attribut zu deklarieren. Das hilft schon oft.
Die zweite Änderung ist auch nichts besonderes. Wir haben jetzt eben das Label nicht mehr, dafür aber zwei Buttons. Ein Button steht für "Beenden" und einer für "Beenden und Speichern".
Wesentlich ist jetzt die zweite Änderung. In WinVierhaben wir die anonyme Klasse direkt dort erzeugt, wo sie benötigt wurde, nämmlich in der addActionListener() Methode, wo der Parameter übergeben wird. Hier in WinV erzeugen wir den ActionListenerwie jede andere Komponente auch. Unsere Instanz des ActionListenersbekommt einen Namen (myListener). Die weitere Unterscheidung liegt in der Abfrage der getActionCommand() Methode. Wir prüfen hier auf zwei verschiedene Situationen. Einmal auf die Beschriftung des ersten Button, und einmal auf die Beschriftung des zweiten Button. Ansonsten ist der ActionListener der gleiche wie in WinVier.
Die letzte entscheidene Änderung ist, daß wir ja trotzdem die Methode addActionListener() nutzen müßen um den ActionListener an eine Komponente zu binden. Dies geschieht für unseren ersten Button mit klicker1.addActionListener(myListener);und für den zweiten Button analog. Wir haben jetzt also ein und denselbenActionListener an zwei verschiedene Komponenten gebunden. Deswegen ist im ActionListener auch die Abfrage der ButtonBeschriftung nötig. Klicken Sie nur auf den "Beenden" Button, wird das Fenster herkömmlich gekillt. Klicken Sie jedoch auf den "Beenden und Speichern" Button, wird zunächst der Text des Eingabefeldes abgefragt und auf der Konsole ausgegeben. Erst dann wird das Programm beendet.
Vielleicht fragen Sie sich wozu man den gleichen ActionListener an verschiedenen Komponenten registrieren soll. Denken Sie dazu einfach an herkömmliche Programme, wo sie eine Funktion oft durch Klicken im Programmfenster auf einen Button, alternativ aber auch durch Auswahl eines Menüpunktes und eine Tastenkombination erreichen können. Hier ist es dann sinnvoll den gleichen ActionListener an den Button, den Menüpunkt und die Tastenkombination zu binden.

Alle Listener Objekte von Java hier vorzustellen, sprengt den Rahmen dieses Tutorials. Auf das eine oder andere werden wir in den anderen Kapiteln sicher noch stoßen. Ansonsten erreicht man einige Funktionalitäten auch mit anderen Mitteln (siehe Beispiel 4). Auch in Java führen viele Wege nach Rom.
Die Namen, und die zu implementierenden Methoden anderer Listener finden Sie in der API-Dokumentation von Java unter java.awt.event. Sie sehen schon, Java ohne API-Dokumentation geht nicht. Sehen Sie sich dort das Interface FocusListener an, und überlegen Sie sich mal, wie Sie das nutzen würden, nach dem, was Sie hier gerade gelesen haben. (Focus ist übrigens die gerade aktive Komponente, also zB ein Textfeld, wenn der Cursor gerade drin steht, oder ein Listenfeld mit frisch ausgewähltem Eintrag. Drücken Sie dann [Tab] wandert der Focus in der Regel zur nächsten Komponente, zB dem nächsten Textfeld).

2.5. Weitere Möglichkeiten von AWT
Das Paket AWT hält noch viel mehr bereit, worauf hier aber nicht eingegangen werden kann. Dazu gehören vorgefertigte Dialoge zur Farbauswahl, Dateiauswahl, zum Drucken und selbsterstellte Dialoge. Dazu gehören Menüs aller Coleur (Fenstermenüs, PopupMenüs, Submenüs). Dazu gehören alle denkbaren Zeichenbefehle, Drag und Drop Operationen, Bildbearbeitungsmöglichkeiten, Animationen und Druckfunktionalitäten. In den anderen Kapiteln wird das eine oder andere vmtl. noch einmal auftauchen. Ansonsten können Sie das Thema auch in Sun's JavaTutorial Online vertiefen. Vielleicht spielen Sie aber auch schon an dieser Stelle mit dem Gedanken sich weiterführende Literatur zuzulegen.

2.6. Klassenhierarchie
Noch ein Wort zum Schluß: Wir haben in diesem Beispiel oft gesagt, die Klasse erbt die Methode, oder diese Eigenschaft von der und der Klasse. Gerade hier bei der AWT-Programmierung kann man sehr schön sehen, wie bei Java verschiedene Klassen immer weiter spezialisiert werden, und dabei auch die Methoden ihrer Elternklassen erben. Eine abgeleitete Klasse hat erstmal genau die Eigenschaften, die seine Elternklasse auch hat. Alles was dazu programmiert wird, spezialisiert die Klasse nur. Durch Überschreiben kann auch Funktionalität der Elternklasse eingeschränkt werden. Mit wie wenig Aufwand man sich eigene Komponenten durch dieses Prinzip selbst erstellen kann, sehen Sie im Bonus Beispiel Tic-Tac-Toe-Applet, wo die Canvas Komponente erweitert wird.

2.7. Download Quelltexte
Wie auch bei Beispiel 1, gibt es hier die Sourcecodes von WinEins.java, WinZwei.java, WinDrei.java, WinVier.java, WinV.java und Layout.java als zip-File zum herunterladen: Download Quelltexte

2.8. Übungsaufgabe
Na toll, nach diesen einfachsten Trival-Beispielen, will er von uns jetzt etwas anspruchsvolles ??? Na klar, mit den einfachen Sachen kommen wir nicht entscheidend weiter. Daher für Sie jetzt etwas richtiges (sobald ich Zeit habe programmiere ich Ihnen auch eine Musterlösung *g*):

2.8.1 Programmbeschreibung
Programmieren Sie einen Bookmark-Manager. Mit Netscape lassen sich URL's per Drag And Drop in andere Applikationen ziehen. Der Bookmark-Manager soll für jede URL die eigentliche URL, einen Titel, eine kurze Beschreibung sowie eine beliebige Anzahl von Kategorien verwalten. Einträge sollen sich als "privat" markieren lassen. Zum Speichern wird eine einfache Textdatei verwendet.
Der Manager soll den Export in Textdateien, bzw. HTML Dateien mit benutzerdefinierten Eigenschaften zulassen. Dabei kann gewählt werden, welche Kategorie exportiert wird.

2.8.2 Hinweise
Benutzen Sie die Klassen Frame, Label, List, TextField, TextArea, Button, Checkbox, ComboBox und Panel für die grafische Oberfläche. Nutzen Sie Menüs für Export-Operationen.
Benutzen Sie das Paket java.awt.dnd.* (DragAndDrop) für Drag And Drop Funktionalitäten.
Benutzen Sie das Paket java.io.* für Dateioperationen.

2.8.3 Geplantes Look And Feel
Zur Zeit sind Sie da noch frei.

2.8.4 Vorgehensweise
Versuchen Sie zunächst selbständig eine Lösung zu erarbeiten. Nutzen Sie dazu die API-Dokumentation zum JDK und die JavaTutorials von Sun. Nutzen Sie Suchmaschinen im Internet um Hilfe zu finden. Sollten Sie nicht weiterkommen, sehen Sie sich die Musterlösung (wird nachgereicht) bis zu dem Punkt an, an dem Sie nicht weiterkommen. Versuchen Sie dann wieder eigenständig weiterzuarbeiten.


<= vorherige Seite Inhaltsverzeichnis nächste Seite =>

zurück zur Hauptseite

Copyright 2000 by Frank Gehde