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.
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.
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.
zurück
zur Hauptseite |