Funktionale Komponenten von Benutzerschnittstellen Die Komponenten von Benutzeroberflächen, die bis zu diesem Punkt des Buches betrachtet wurden, sind Grafiken, Bilder, Animationen und Audio. Neben diesen Grundbestandteilen machen aber gerade Bausteine wie Buttons oder Texteingabefelder die eigentliche Stärke einer Benutzeroberfläche aus. Die Darstellung dieser Komponenten ist Gegenstand dieses Unterkapitels. Hierzu muss zunächst das AWT näher betrachtet werden. Bis zu diesem Zeitpunkt wurden AWT-Funktionen eher implizit verwendet, indem die zur Umsetzung der beschriebenen Aufgaben notwendigen Klassen verwendet wurden. Diese Sicht reicht allerdings für die im Folgenden zu beschreibenden Komponenten nicht mehr aus. Abstract Windowing Toolkit (AWT) Das Abstract Windowing Toolkit (AWT) ist eine Menge von Klassen, mit deren Hilfe grafische Benutzeroberflächen einschließlich der Interaktionsmöglichkeiten, die sich aus der Verwendung der Maus und der Tastatur ergeben, erstellt werden können. Da die Sprache Java plattformunabhängig ist, stellt das AWT Möglichkeiten zur Verfügung, eine Benutzerschnittstelle zu entwickeln, die dieselbe Funktionalität und dasselbe Aussehen auf allen Systemen bietet, auf der sie ausgeführt wird. Bedenkt man allerdings, dass heutzutage auf vielen Browsern Spezialeinstellungen benutzt werden können, so ist diese Forderung nur dann zu realisieren, wenn Java-Applets in Plug-Ins ablaufen. Anderenfalls müssen die Funktionalität und auch das Aussehen einer Anwendung „von Hand" auf möglichst vielen unterschiedlichen Systemen getestet werden, um tatsächlich eine Einheitlichkeit garantieren zu können. Die Klassen des AWTs sind Teil des Packages java.awt. Werden daher Benutzerschnittstellen implementiert, so darf die Importierung dieses Packages nicht vergessen werden. Die statischen Komponenten einer Benutzerschnittstelle bestehen aus den folgenden drei Komponenten: Komponenten Komponenten sind die Bausteine, die Teil einer Benutzerschnittstelle sein können, bspw. Buttons, Textfelder, Grafiken oder Bilder. Container Container werden dazu verwendet, Komponenten zu gruppieren. Ein Beispiel hierfür ist das Applet-Fenster, das bisher verwendet wurde, und das die Möglichkeit bietet, die darin enthaltenen Bestandteile als Gruppe aufzufassen. Layout-Manager Layout-Manager definieren, wie die Komponenten eines Containers auf dem Bildschirm angeordnet werden müssen. Im Gegensatz zu Komponenten oder Containern werden Layout-Manager selbst nicht sichtbar, sehr wohl aber die Effekte, die aus ihrer Verwendung resultieren. Layout-Manager werden in Kapitel 4.4. beschrieben. Die in Java möglichen Komponenten und Container werden im Folgenden beschrieben. Hierzu wird der Begriff Benutzerschnittstelle mit dem Kürzel GUI (Graphical User Interface) bezeichnet. Komponenten und Container Komponenten werden in einem GUI verwendet, indem sie in einen Container integriert werden. Container können hierbei selbst als Komponenten aufgefasst werden und in übergeordneten Containern platziert werden. Der Aufbau eines GUIs aus Containern ist mit dem Erstellen eines Layouts identisch (siehe Kapitel 4.4). Um der anschließenden Erläuterung von Layout-Strategien nicht vorzugreifen, werden die im Folgenden betrachteten Komponenten stets im Container eines Applets platziert. Eine Komponente wird in einem Container platziert, indem sie zunächst erstellt wird und anschließend mit Hilfe der add-Methode dem Container zugefügt wird. Da ein Applet gleichzeitig ein Container ist, ist die Verwendung der add-Methode innerhalb von Applets zulässig. Abb. 4.15: GUI-Komponenten Das Hinzufügen einer Komponente zu einem Container stellt diese Komponente selbst nicht notwendigerweise sofort dar. Eine Anzeige erfolgt erst dann, wenn die paint-Methode des Containers aufgerufen wird. Dies kann beschleunigt werden, indem die repaint-Methode in der Implementierung verwendet wird. Komponenten eines GUIs werden am besten in der init-Methode beschrieben. Da sich die Elemente eines GUIs im Programmablauf normalerweise nicht verändern, kann der Aufbau des GUIs bereits in der Initialisierungsphase erfolgen. Im Folgenden werden die Komponenten, die neben Grafiken, Text, Bildern, Audio und Animationen in einem GUI verwendet werden können, erläutert. Das in Abb. 4-15 angegebene Schaubild erläutert den Zusammenhang der hierbei verwendeten Klassen. Textmarken (Labels) Textmarken (Labels) werden üblicherweise dazu verwendet, den Zweck anderer Komponenten zu spezifizieren. Die Verwendung von Labels ist der bereits erläuterten Methode drawString aus zwei Gründen stets vorzuziehen. Erstens wird das Layout einer Textmarke automatisch gesetzt, während Texte, die mit drawString erzeugt werden, stets an einer festen Position erscheinen. Zweitens werden Labels nach ihrer Generierung automatisch dargestellt und müssen daher nicht explizit in der paint-Methode angegeben werden. Zur Definition eines Labels können die folgenden Konstruktoren verwendet werden: Label() erzeugt eine leere Textmarke, in der der Text linksbündig angeordnet wird, Label(String) erzeugt eine Textmarke mit dem Text, der als Parameter übergeben wurde. Der Text wird ebenfalls linksbündig dargestellt. Label(String, int) erzeugt dieselbe Ausgabe wie Label(String), der zweite Parameter legt allerdings die Ausrichtung des Textes innerhalb der Textmarke fest. Hierzu können die Konstanten Label.LEFT, Label.CENTER und Label.Right verwendet werden. Nachdem ein Label erzeugt wurde, kann der Font mit der bereits beschriebenen Methode setFont verändert werden. Erfolgt dies innerhalb eines Applets, so werden die Eigenschaften aller Textmarken verändert. Innerhalb des Labels wird nur der Font der Textmarke selbst verändert. Nachdem eine Textmarke erzeugt wurde, kann ihr Text mit der Methode setText(String) verändert werden. Dies ist vor allem nötig, wenn Inhalte dynamisch verändert werden sollen oder wenn der Konstruktor Label() verwendet wurde, da dann das Label anfangs keinen Text darstellt. Zur Abfrage des Inhalts einer Textmarke wird die Methode getText() verwendet, zur Veränderung der Textausrichtung die Methode setAlignment, die einen Parameter der Form Label.LEFT, Label.CENTER oder Label.RIGHT erwartet. Das folgende Beispiel stellt die Verwendung von Textmarken dar. import java.applet.*; import java.awt.*; public class Textmarke extends Applet{ Label links = new Label("Schiffe versenken", Label.LEFT); Label zentriert = new Label("Schiffe versenken", Label.CENTER); Label rechts = new Label("Schiffe versenken", Label.RIGHT); Font f = new Font("Times", Font.BOLD, 20); public void init () { setFont(f); add(links); add(zentriert); add(rechts); } } Die Ausgabe dieses Beispiels ist in Abb. 4-16 dargestellt. Es mag überraschend erscheinen, dass die Ausgabe nicht in drei Zeilen erscheint. Da aber kein spezielles Layout angegeben wurde, werden die Komponenten so angeordnet, dass möglichst wenig Platz verbraucht wird. In diesem Fall sind die links- bzw. rechtsbündig angeordneten Texte in einer Zeile darstellbar. Abb. 4.16: Textmarken Knöpfe (Buttons) Knöpfe (Buttons), die mit der Maus bedienbar sind, werden mit Hilfe der Klasse Button mit den folgenden Konstruktoren erzeugt: Button() erzeugt einen Knopf ohne Textmarke, die seine Funktion spezifiziert, Button(String) erzeugt entsprechend einen Button mit Textmarke. Nach der Generierung eines Button-Objekts kann der Textinhalt mit der Methode setLabel(String) verändert werden. Analog kann der bereits enthaltene Text mit der Methode getLabel() abgefragt werden. Soll ein Button aktiviert bzw. deaktivert werden, so können die Methoden setEnabled(true) bzw. setEnabled(false), die Teil der Klasse Button sind, verwendet werden. Das folgende Beispiel, dessen Ausgabe in Abb. 4-17 dargestellt ist, zeigt die Verwendung dieser Klasse. Es muss allerdings darauf hingewiesen werden, dass die Verwendung von Knöpfen dann relativ sinnlos ist, wenn mit einem Knopf keine Aktion assoziiert ist, die ausgelöst wird, wenn der Knopf gedrückt wird. Es sei aber daran erinnert, dass die Erläuterung der dynamischen Bestandteile von GUIs in Kapitel 4.5 erfolgt. import java.applet.*; import java.awt.*; public class Knoepfe extends Applet{ Button start = new Button("Beginne Spiel"); Button ende = new Button(); public void init () { ende.setLabel("Beende Spiel"); add(start); add(ende); } } Abb. 4.17: Knöpfe Checkboxen Checkboxen werden verwendet, um Optionen eines Programms auszuwählen. Die Auswahl ist hierbei nicht exklusiv, es können also auch mehrere Optionen ausgewählt sein. Soll genau eine Option auswählbar sein, so werden Radiobuttons verwendet, die im Folgenden erklärt werden. Der Name Radiobutton ist abgeleitet von alten Radios, bei denen das Drücken eines Knopfs bewirkte, dass ein anderer gedrückter Knopf wieder heraussprang. Checkboxen werden mit den folgenden Konstruktoren erzeugt: Checkbox() oder Checkbox(String). Die Verwendung dieser Konstruktoren entspricht exakt der Benutzung der Konstruktoren von Buttons. Der Anfangszustand der so erzeugten Boxen ist immer die nicht ausgefüllte Box. Dieser Zustand kann aber mit der Methode setState(boolean) verändert werden. Wird der Wert auf true gesetzt, so ist die Box mit einem Haken versehen, anderenfalls ist sie leer. Die Methode zur Abfrage des Zustands einer Checkbox lautet getState(). Das folgende Beispiel, dessen Ausgabe in Abb. 4-18 dargestellt ist, verdeutlicht die Verwendung von Checkboxen. import java.applet.*; import java.awt.*; public class Checkboxen extends Applet{ Checkbox audio = new Checkbox("Ton ausschalten"); public void init () { audio.setState(true); add(audio); } } Abb. 4.18: Checkboxen Radiobuttons Ein Radiobutton ist eine spezielle Art von Checkbox, in der immer nur eine der Optionen ausgewählt sein kann. Hierzu wird ein CheckboxGroup-Objekt mittels der Methode CheckboxGroup() angelegt. Anschließend werden die Bestandteile, die Radiobuttons, mit der folgenden Syntax hinzugefügt: Checkbox(String, boolean, CheckboxGroup) Hierdurch wird eine Checkbox mit dem durch das boolean-Argument spezifizierten Zustand erzeugt, die zur CheckboxGroup gehört, die durch den dritten Parameter angegeben ist. Das folgende Beispiel, dessen Ausgabe in Abb. 4-19 dargestellt ist, verdeutlicht diese Funktion. Weiterhin kann die Methode setCurrent(Checkbox) dazu verwendet werden, die Auswahl auf die Checkbox zu setzen, die hierbei als Parameter übergeben wird. Weiterhin kann die Methode getCurrent() dazu eingesetzt werden, die momentan selektierte Checkbox abzufragen. Abb. 4.19: Radiobuttons import java.applet.*; import java.awt.*; public class Radiobuttons extends Applet{ CheckboxGroup level = new CheckboxGroup(); Checkbox l1 = new Checkbox("Anfaenger", false, level); Checkbox l2 = new Checkbox("Fortgeschrittener", true, level); public void init () { add(l1); add(l2); } } Auswahllisten Mittels Auswahllisten kann ein Element eines aufklappbaren Menüs selektiert werden. Hierzu wird in Java die Klasse Choice verwendet. Der Aufbau einer Auswahlliste besteht aus zwei Schritten: Zuerst wird ein Choice-Objekt mittels des Konstruktors Choice() erzeugt, anschließend werden die Menüeinträge mittels der Methode add(String) hinzugefügt. Das folgende Beispiel, dessen Ausgabe in Abb. 4-20 dargestellt ist, verdeutlicht diese Funktion. import java.applet.*; import java.awt.*; public class Auswahlliste extends Applet{ Choice liste = new Choice(); public void init () { liste.add("Schiffe setzen"); liste.add("Spiel starten"); liste.add("Spiel beenden"); add(liste); } } Abb. 4.20: Auswahllisten Zur Kontrolle einer Auswahlliste stehen zusätzlich die folgenden Methoden als Teil der Klasse Choice zur Verfügung: getItem(int) gibt den Text zurück, der sich an der Stelle in der Auswahlliste befindet, die durch das Argument angegeben wird. Hierbei ist zu beachten, dass sich, wie bei anderen Listen in Java auch, das erste Element an der Position 0 befindet. getSelectedItem() gibt den Text des momentan ausgewählten Elements der Liste zurück. getSelectedIndex() gibt entsprechend den Index des momentan ausgewählten Elements zurück. getItemsCount() zählt die Elemente, die Teil der Liste sind. select(int) wählt das Element aus, das sich an der durch den Parameter spezifizierten Position befindet. select(String) wählt in ähnlicher Art und Weise das erste Element der Liste aus, das dem im Parameter angegebenen Text entspricht. Textfelder Im Gegensatz zu Textmarken sind Textfelder vom Benutzer veränderbar. Hierzu steht in Java die Klasse TextField zur Verfügung, die mit den folgenden Konstruktoren angelegt werden kann: TextField() erzeugt ein leeres Texteingabefeld ohne Größenangabe. TextField(String) erzeugt ein Texteingabefeld, das ohne Größenangabe des Feldes mit dem im Parameter angegeben Text gefüllt wird. TextField(String, int) erzeugt ein Texteingabefeld, wobei der Parameter die Breite des Feldes in Buchstaben angibt. Hierbei kann auch ein leerer String als erster Parameter übergeben werden. Oftmals ist es von Bedeutung, Felder zu verwenden, die der Benutzer verändern kann, ohne die eingetippten Buchstaben lesen zu können. Dies erfolgt typischerweise bei Passwort-Eingaben, wobei die Eingabe hierbei als Stern dargestellt wird. In der Klasse TextField wird hierzu die Methode setEchoCharacter(Char) verwendet, wobei der Parameter das in Hochkommas eingeschlossene Zeichen angibt, das die Tastatureingabe ersetzen soll. Das folgende Beispiel, dessen Ausgabe in Abb. 4-21 dargestellt ist, stellt die Eingabe eines Benutzernamens und eines Passworts dar. import java.applet.*; import java.awt.*; public class Textfeld extends Applet{ Label benutzerLabel = new Label("Benutzername"); TextField benutzerFeld = new TextField(50); Label passwortLabel = new Label("Passwort"); TextField passwortFeld= new TextField(50); public void init () { add(benutzerLabel); add(benutzerFeld); add(passwortLabel); passwortFeld.setEchoChar('#'); add(passwortFeld); } } Abb. 4.21: Textfeld Auch die Klasse TextField stellt eine Menge an Routinen zur Verfügung, mit deren Hilfe Textfelder kontrolliert werden können: getText() gibt den Text zurück, der in einem Textfeld enthalten ist, setText(String) setzt diesen Text entsprechend, isEditable() gibt einen boole'schen Wert zurück, der angibt, ob ein Feld editierbar ist (true) oder nicht (false) und setEditable(boolean) legt fest, ob ein Feld verändert werden darf (true) oder nicht (false). Der Wert true wird hierbei als Standard verwendet, wenn kein anderer Wert angegeben wird. Textbereiche Textbereiche, die mittels der Klasse TextArea erstellt werden, können größere Textmengen aufnehmen als Textfelder. Hierzu stehen horizontale und vertikale Schiebebalken zur Verfügung, die den Anzeigebereich geeignet verschieben können. Ein Textbereich wird mit den folgenden Konstruktoren angelegt: TextArea() erzeugt einen leeren Textbereich ohne Angabe der Höhe und Breite. TextArea(int, int) erzeugt analog einen Bereich mit Größenangabe. Hierbei gibt der erste Parameter die Anzahl der Zeilen und der zweite die Anzahl der Zeichen pro Zeile an. TextArea(String) erzeugt einen Textbereich mit einem festgelegten Text, aber ohne Angabe der Höhe oder Breite des Textes. TextArea(String, int, int) erzeugt denselben Bereich wie TextArea(String), aber mit zusätzlicher Größenangabe. Das folgende Beispiel, dessen Ausgabe in Abb. 4-22 dargestellt ist, zeigt die Verwendung dieser Klasse auf. Hierbei werden im String die Kontrollzeichen '\n' verwendet, um eine neue Zeile zu beginnen. import java.applet.*; import java.awt.*; public class Textbereich extends Applet{ String inhalt = String inhalt = "Textbereiche, die mittels der Klasse TextArea "+ "erstellt werden, \nkoennen groessere Textmengen aufnehmen, als"+ "Textfelder. Hierzu stehen horizontale \nund vertikale " + "Schiebebalken zur Verfuegung, die den \nAnzeigebereich geeignet" + " verschieben koennen. Ein Textbereich \nwird mit den " + "folgenden Konstruktoren angelegt:"; TextArea textFeld; public void init () { textFeld = new TextArea(inhalt, 3, 20); add(textFeld); } } Abb. 4.22: Textbereich Auch für Textbereiche stehen zusätzliche Kontrollmethoden zur Verfügung: insert(String, int) fügt den durch den ersten Parameter angegeben Text an der Indexposition in das Feld ein, die durch den zweiten Parameter angegeben wird (gemessen in Zeichen). replace(String, int, int) ersetzt den Text zwischen den Positionen, die durch den zweiten und dritten Parameter angegeben werden, durch den als erstes Argument übergebenen Text. Sowohl Textfeld als auch Textbereich erben von der Klasse TextComponent. Die Methoden setText, getText, setEditable und isEditable, die im Kontext von Textfeldern erläutert wurden, stehen daher auch für Textbereiche zur Verfügung. Frames (Fenster) Frames (Fenster) für Applications und Applets werden mit der Klasse Frame realisiert. Jede Application, die mit einem GUI arbeiten will, benötigt mindestens ein Frame-Objekt. Anstelle der Frames sollte allerdings für Applications ein Dialog-Objekt (siehe im Anschluss an diesen Abschnitt) verwendet werden, wenn eine Fensterabhängigkeit realisiert werden soll. Fenster sind bspw. dann voneinander abhängig, wenn ein Fenster unsichtbar werden soll, wenn ein anderes ikonifiziert wird. Applets können derartige Mechanismen nicht verwenden, da Dialoge aufgrund von Sicherheitsrestriktionen von Applets nur mit Einschränkungen verwendet werden können. Bei Applets sollten daher grundsätzlich Frames verwendet werden. Die Klasse Frame kann mit zwei Konstruktoren aufgerufen werden: Frame() erzeugt ein Fenster ohne Titel, Frame(String) erzeugt ein Fenster mit dem Titel, der als Parameter übergeben wird. Weiterhin stellt Frame die folgenden Hilfsmethoden zur Verfügung (zur Verwendung von Menüs siehe den folgenden Abschnitt): String getTitle() und void setTitle(String) fragt den Titel eines Fensters ab bzw. setzt diesen. Image getIconImage() und void setIconImage(Image) erfragt ein in einem Fenster angezeigtes Bild bzw. setzt dieses, wenn ein Fenster ikonifiziert ist. MenuBar getMenuBar() und void setMenuBar(MenuBar) erfragt bzw. setzt eine Menubar für ein Frame-Objekt. void remove(MenuComponent) entfernt die angegebene MenuBar aus einem Frame. boolean isResizable() und void setResizable(boolean) erfragt, ob die Größe eines Fensters variabel sein darf bzw. setzt diese Eigenschaft. int getCursorType() und void setCursor(int) erfragt die derzeitige Darstellung des Cursors bzw. setzt diese. Vordefinierte Cursor-Typen sind Frame.DEFAULT_CURSOR, Frame.CROSSHAIR_CURSOR, Frame.HAND_CURSOR, Frame.MOVE_CURSOR, Frame.TEXT_CURSOR, Frame.WAIT_CURSOR und Frame.X_RESIZE_CURSOR (X muss hierbei durch eine der Himmelsrichtungen SW, SE, NW, NE, N, S, W oder E ersetzt werden). void resize(int, int) erlaubt das Setzen einer Fenstergröße. Hierbei ist aber zu beachten, dass die beiden Parameter, die die Breite und Höhe des Fensters in Pixeln angeben, auf verschiedenen Plattformen unterschiedlich repräsentiert werden können. Deshalb sollte grundsätzlich die Methode pack() verwendet werden, um ein Fenster kleinstmöglicher Größe zu erzeugen. void pack() generiert ein Fenster kleinstmöglicher Größe, das alle darzustellenden Komponenten enthält. void show() und void hide() zeigen ein Fenster an bzw. verbergen es wieder. Der folgende Code demonstriert das Öffnen eines Fenster aus einer Application heraus. import java.awt.*; public class Fenster extends Frame { public Fenster() { } public static void main(String[] args) { Fenster fenster = new Fenster(); fenster.setSize(100, 100); fenster.pack(); fenster.setVisible(true); } } Dialogfenster Mittels der Klasse Dialog bietet das AWT die Möglichkeit, Dialogfenster zu erzeugen. Dialogfenster treten niemals selbstständig auf, sondern immer nur in Zusammenhang mit einer Anwendung. Sie sind daher auch stets von anderen Fenstern derart abhängig, dass bspw. auch Dialogfenster verschwinden, wenn die Fenster, von denen Dialogfenster abhängen, ikonifiziert werden,. Als Subklasse der Klasse Dialog kann auch die Klasse FileDialog verwendet werden, die ein Dialogfenster zum Selektieren von Dateien erzeugt. Hierbei besteht aber eine prinzipielle Schwierigkeit: Aufgrund des Sicherheitsmodells von Java kann ein Dialog im Standardbetrieb nur dann verwendet werden, wenn die Anwendung nicht in Form eines Applets implementiert ist. Dies liegt unter anderem auch daran, dass keine Möglichkeit besteht, dass Applets das Fenster, in dem sie ablaufen, identifizieren können. Eine Verknüpfung mit einem Dialogfenster ist daher unmöglich. Eine Ausnahme hierzu sind Applets, die eigene Fensterimplementierungen (Frames) realisieren. Erstellt ein derartiges Applet ein Fenster, aus dem ein Dialog aufgerufen wird, so ist dies unter Einhaltung der Sicherheitsrestriktionen zulässig. Dialoge können die Aufmerksamkeit des Anwenders erregen (exklusive Funktion), da andere Tätigkeiten solange verhindert werden können, bis ein Dialogfenster geschlossen wird. Standardmäßig ist dieses Verhalten allerdings nicht eingestellt, so dass ein Benutzer auch ohne weiteres in anderen Fenstern der Anwendung arbeiten kann, während ein Dialogfenster geöffnet ist. Die Klasse Dialog beinhaltet die folgenden Methoden: Dialog(Frame, boolean) erzeugt ein Dialogfenster ohne Titel, das nicht exklusiv funktioniert (zweiter Parameter true). Der erste Parameter gibt das Fenster an, von dem der Dialog abhängt. Dialog(Frame, String, boolean) erzeugt analog ein Dialogfenster mit Titel. boolean isModal() gibt den Wert true zurück, wenn das Fenster exklusiv funktioniert. String getTitle() und String setTitle(String) fragt den Titel eines Dialogfensters ab bzw. setzt diesen. boolean isResizable() und void setResizable(boolean) stellt fest, ob die Größe eines Fensters verändert werden darf (true) bzw. setzt diese Eigenschaft (true entspricht der Möglichkeit, die Größe zu verändern). pack() ist eine Methode, die durch die Klasse Window definiert wird und mit der ein Fenster so eingestellt werden kann, dass der Inhalt mindestens in einer minimalen Größe angezeigt wird. Die Verwendung der Methode pack() ist im Allgemeinen der Benutzung der Methode resize vorzuziehen, da hiermit keine absolute Größe angegeben wird. Anstelle dessen sorgt der verwendete Layout-Manager (siehe Erklärung im nächsten Unterkapitel) für die entsprechende plattformabhängige Darstellung des Dialogfensters. Das folgende Beispiel erzeugt ein Dialogfenster, das innerhalb einer Application aufgerufen wird. Es ist zu beachten, dass die notwendige Funktionalität der Knöpfe an dieser Stelle nicht Teil des Beispielprogramms ist, da die notwendige Erklärung erst in Kapitel 4.5 erfolgt. import java.awt.*; public class DialogFenster extends Frame { private TextArea textArea; private RealDialog dialog; public DialogFenster() { textArea = new TextArea(5, 40); add(textArea); dialog = new RealDialog(this, "Einfacher Dialog"); dialog.setVisible(true); } public static void main(String args[]) { DialogFenster fenster = new DialogFenster(); fenster.setTitle("Anwendung DialogFenster"); fenster.pack(); fenster.setVisible(true); } } class RealDialog extends Dialog { RealDialog(Frame dw, String titel) { super(dw, titel, false); Button b = new Button("Cancel"); add(b); pack(); } } Abb. 4.23: Dialogfenster Menüs Menüs sind mit Auswahllisten vergleichbar, jedoch wesentlich umfassender. Im Gegensatz zu den bisher erläuterten Komponenten erben Menüs ihre Funktionalität nicht von der Klasse Component, da einige Plattformen Menüs schwerwiegende Einschränkungen auferlegen. Menüs erben ihre Funktionalität daher von der Klasse MenuComponent. Im AWT stehen die folgenden Subklassen der Klasse MenuComponent zur Unterstützung von Menüs zur Verfügung: MenuItem zur Repräsentation eines Menüeintrags, CheckboxMenuItem zur Repräsentation von Menüeinträgen, denen eine Checkbox zugeordnet ist. CheckboxMenuItem ist eine Subklasse von MenuItem. Menu repräsentiert ein vollständiges Menüobjekt und ist als Subklasse der Klasse MenuItem implementiert, so dass Submenüs leicht erstellt werden können, indem ein Menü einem anderen hinzugefügt wird. MenuBar repräsentiert die plattformabhängige Zuweisung einer Gruppe von Menüs an ein Fenster. Um ein MenuComponent-Objekt enthalten zu dürfen, muss ein Objekt das Interface MenuContainer implementieren. Derzeit sind die Klassen Frame, Menu und MenuBar die einzigen, die innerhalb des AWTs das Interface MenuContainer implementieren. Das folgende Beispiel, das als Application implementiert ist, demonstriert die Verwendung von Menüs (siehe Abb. 4-24). Hierzu ist zunächst eine Klasse zu implementieren, die die Klasse Frame erweitert (mit Frames werden Fenster implementiert). In der main-Methode muss noch die Größe des Fensters gesetzt werden, ebenso wie die Sichtbarkeit des Fensters mittels setVisible. Zur Einrichtung eines MenuBar-Objekts wird zunächst der Konstruktor MenuBar aufgerufen, der mittels setMenuBar aktiviert wird. Anschließend wird diesem MenuBar-Objekt ein Menü zugefügt, wozu der Konstruktor Menu(String, boolean) verwendet wird. Der Parameter gibt hierbei den Namen des Menüs an. Im nächsten Schritt wird mittels add das Menü dem MenuBar-Objekt hinzugefügt. Die Menüeinträge werden anschließend unter Angabe ihres Namens erzeugt und dem Menü hinzugefügt. import java.awt.*; public class Menus extends Frame {; public MenuWindow() { MenuBar mb; Menu m1, m2; MenuItem m1_1, m1_2; mb = new MenuBar(); setMenuBar(mb); m1 = new Menu("Menu 1"); mb.add(m1); m1_1 = new MenuItem("Menueeintrag 1_1"); m1.add(m1_1); m1_2 = new MenuItem("Menueeintrag 1_2"); m1.add(m1_2); } public static void main(String[] args) { MenuWindow window = new MenuWindow(); window.setSize(300, 200); window.setVisible(true); } } Abb. 4.24: Menüs Scroll-Listen Scroll-Listen ähneln den bereits beschriebenen Auswahllisten. Im Unterschied dazu kann aber in einer Scroll-Liste mehr als ein Element gleichzeitig ausgewählt werden. Weiterhin werden Scroll-Listen nicht vollständig angezeigt. Anstelle dessen wird ein vertikaler Schiebebalken dazu verwendet, den Anzeigebereich der Listenelemente zu variieren. Zur Erzeugung einer Scroll-Liste stehen die folgenden Konstruktoren in der Klasse List zur Verfügung: List() erzeugt eine leere Scroll-Liste, bei der nur jeweils ein Element gleichzeitig selektiert werden kann. List(int, boolean) erzeugt eine leere Liste, bei der der erste Parameter angibt, wie viele Elemente gleichzeitig sichtbar sind. Der zweite Parameter spezifiziert, ob mehrere Elemente ausgewählt werden dürfen (true) oder nicht (false). Nachdem ein List-Objekt erzeugt wurde, werden mittels der Methode add(String) Elemente hinzugefügt. Das folgende Beispiel, dessen Ausgabe in Abb. 4-25 dargestellt ist, zeigt die Verwendung dieser Klasse auf. import java.applet.*; import java.awt.*; public class ScrollListe extends Applet{ List liste = new List(2, true); public void init () { liste.add("Schiffe setzen"); liste.add("Spiel starten"); liste.add("Spiel beenden"); add(liste); } } Abb. 4.25: Scroll-Liste Auch Scroll-Listen können Methoden verwenden, die bereits im Zusammenhang mit Auswahllisten definiert wurden: getItem, getItemCount, getSelectedIndex, getSelectedItem und select. Des Weiteren können die folgenden Kontrollroutinen verwendet werden: getSelectedIndexes() gibt einen Array zurück, der die Indexpositionen der ausgewählten Listenelemente enthält. getSelectedItems() gibt analog einen Array mit Strings zurück, die die Texte der ausgewählten Elemente repräsentieren. Scrollbars und Slider Scrollbars sind Komponenten, die die Auswahl eines Werts derart ermöglichen, dass ein Schiebebalken horizontal oder vertikal mit der Maus bewegt werden kann. Die Position, an der sich der Schiebebalken befindet, repräsentiert den Wert einer Variablen. Ein Beispiel für die Verwendung einer Scrollbar ist das Setzen der Geschwindigkeit der Maus. Zur Generierung einer Scrollbar werden üblicherweise der minimale und der maximale Wert angegeben, die die Auswahl begrenzen. Hierzu stehen die folgenden Konstruktoren zur Verfügung: Scrollbar() erzeugt eine vertikale Scrollbar, bei der Minimum und Maximum 0 sind. Scrollbar(int) erzeugt eine Scrollbar, bei der Minimum und Maximum 0 sind, wobei der Parameter die Orientierung des Schiebebalkens (Bar) angibt. Mögliche Werte sind hierbei Scrollbar.VERTICAL und Scrollbar.HORIZONTAL. Scrollbar(int, int, int, int, int) erzeugt eine Scrollbar mit den folgenden Parametern: Orientierung des Schiebebalkens (Scrollbar.VERTICAL oder Scrollbar.HORIZONTAL), Anfangswert des Schiebebalkens (zwischen Minimum und Maximum), Breite und Höhe der Box, die zur Veränderung der Scrollbar verwendet wird, und der minimale und maximale Wert, zwischen denen die Scrollbar selektiert. Wird für die Box der Wert 0 angegeben, so wird die Standardgröße der Box verwendet. Das folgende Beispiel, dessen Ausgabe in Abb. 4-26 dargestellt ist, zeigt die Verwendung dieser Klasse auf. import java.applet.*; import java.awt.*; public class ScrollBar extends Applet{ Scrollbar sbar = new Scrollbar(Scrollbar.HORIZONTAL, 300, 0, 0, 1000); public void init () { add(sbar); } } Abb. 4.26: Scrollbar Die Werte in Scrollbars können mit Hilfe der Methode getValue() abgefragt werden und mittels der Methode setValue(int) gesetzt werden. Canvas-Objekte Canvas-Objekte werden in einem GUI dazu eingesetzt, Bilder oder Animationen anzuzeigen. Um die Klasse Canvas verwenden zu können, muss eine Subklasse erzeugt werden, die dann bspw. Zeichenoperationen innerhalb der überschriebenen paint-Methode durchführt. Wurde eine derartige Subklasse erzeugt, so kann der Konstruktor dieser Klasse aufgerufen werden und mittels new einem Container ein neues Canvas-Objekt hinzugefügt werden. Das folgende Beispiel, dessen Ausgabe in Abb. 4-27 dargestellt ist, zeigt die Verwendung dieser Klasse auf. Hierbei wird ein Kreuz in der Mitte eines Fensters erzeugt, dessen Position verändert wird, wenn sich die Größe des Fensters ändert. Die hierzu notwendige Layout-Angabe ist Thema des folgenden Unterkapitels. import java.applet.*; import java.awt.*; public class KreuzCanvas extends Applet{ GridLayout g = new GridLayout(1,1); KreuzCanvas kc = new KreuzCanvas(); public void init () { setLayout(g); add(kc); } } class KreuzCanvas extends Canvas { public void paint(Graphics screen) { int x = getSize().width/2; int y = getSize().height/2; screen.setColor(Color.black); screen.drawLine(x-10, y-10, x+10, y+10); screen.drawLine(x+10, y-10, x-10, y+10); } } Abb. 4.27: Canvas-Beispiel Panels Die vorgestellte Klasse Canvas wird hauptsächlich dazu eingesetzt, eine Oberfläche für Bild- und Grafikelemente zu bieten. Im Gegensatz dazu dient die Klasse Panel als allgemeine Container-Subklasse. In einem Panel können daher Komponenten gruppiert werden, aber auch spezielle Funktionen definiert werden, die auf den Komponenten arbeiten, die Teil eines Panels sind. Eine häufig vorzufindende Anwendung derartiger Funktionen sind bspw. solche, die eine spezielle Event-Verarbeitung für eine Menge von Komponenten zur Verfügung stellen. Aufgrund dieser Logik ist es kaum überraschend, dass die Klasse Applet eine Subklasse von Panel ist. Die speziellen Funktionen, die mittels der Klasse Applet zur Verfügung gestellt werden, betreffen die Abarbeitung in Browsern oder im Appletviewer. Hierbei besteht auch die Möglichkeit, dass eine Anwendung sowohl als Application als auch als Applet definiert wird. In diesem speziellen Fall enthält das Programm eine Applet-Subklasse, die dann nicht verwendet wird, wenn das Programm als Application ausgeführt wird. Es ist nun auch verständlich, warum Komponenten mittels der add-Methode zu Applets hinzugefügt werden konnten, ohne explizit ein Panel zu verwenden. Da Applets ja eine Subklasse der Klasse Panel sind, wurde die letztere Klasse bisher implizit benutzt. Die Verwendung der Klasse Panel ist einfach: Es wird zunächst mittels des Konstruktors Panel() ein leeres Panel erzeugt, dem anschließend mittels der add-Methode Komponenten hinzugefügt werden. Hierzu wird der Name des Panels, ein Punkt und die add-Methode mit dem Parameter, der die hinzuzufügende Komponente angibt, verwendet. Diese Syntax entspricht vollständig der, die auch bisher zur Adressierung von Subelementen einer Klasse verwendet wurde. Das folgende Beispiel demonstriert dieses Vorgehen, indem ein Panel angelegt wird, das drei Buttons enthält. import java.applet.*; import java.awt.*; public class PanelBeispiel extends Applet{ Panel p = new Panel(); Button b1 = new Button("Spiel starten"); Button b2 = new Button("Spielstand speichern"); Button b3 = new Button("Spiel beenden"); public void init () { p.add(b1); p.add(b2); p.add(b3); add(p); } } Abb. 4.28: Canvas-Beispiel Layout-Management In der Erläuterung der Komponenten, die Teil eines GUIs sein können, wurde bereits deutlich, dass zu deren Anordnung die Angabe eines Layouts erforderlich ist. Die Hauptaufgabe eines Layouts besteht daher darin, die relative Position einer Komponente in Relation zu anderen Bestandteilen einer Benutzerschnittstelle festzulegen. In Java können das Flow-Layout, das Grid-Layout, das Border-Layout, das Card-Layout und das GridBag-Layout in Abhängigkeit vom Aussehen, das eine Anwendung haben soll, verwendet werden. Die folgenden Abschnitte erläutern die Verwendung der verschiedenen Layout-Möglichkeiten und beleuchten die Verwendung eines bestimmten Layouts in Abhängigkeit vom Typ der Anwendung, die erstellt werden soll. Zur Vergleichbarkeit der verschiedenen Möglichkeiten wird jeweils dasselbe Anwendungsbeispiel, die Benutzerschnittstelle des Spiels „Schiffe versenken", herangezogen. Flow-Layout Das Flow-Layout stellt die einfachste Layout-Möglichkeit in Java dar. Aus diesem Grund wird auch stets dieses Layout verwendet, wenn keine andere Auswahl spezifiziert wird. Allgemein wird zur Definition eines Layouts ein Layout-Manager verwendet, dessen Aufgabe die Anordnung der Komponenten eines GUIs ist. Diese Vorgehensweise ist in allen Layouts gleich. Das Flow-Layout, das mittels der Klasse FlowLayout realisiert wird, ordnet alle Komponenten eines GUIs in der Reihenfolge horizontal im GUI an, in der die Komponenten mittels der add-Methode zugefügt werden. Ist in horizontaler Richtung kein Platz mehr verfügbar, so findet ein Wechsel derart statt, dass alle folgenden Komponenten in einer neuen horizontalen Abfolge angeordnet werden. Zur Definition eines derartigen Layouts wird stets die folgende Syntax verwendet: FlowLayout fl = new FlowLayout(); Soll zusätzlich die Ausrichtung der Komponenten angegeben werden, so akzeptiert der Konstruktor FlowLayout auch einen der Parameter FlowLayout.LEFT, FlowLayout.RIGHT oder FlowLayout.CENTER, wodurch die Bestandteile des GUIs an der linken oder der rechten des jeweiligen Containers bzw. zentriert angeordnet werden. Nachdem eine Instanz eines Layouts erzeugt wurde, findet in der init-Methode eines Applets die Zuweisung des Layouts mittels der Methode setLayout(fl) statt, die als Parameter den Namen des Layouts (in diesem Fall fl) erwartet. Das folgende Beispiel erzeugt ein GUI für das Spiel „Schiffe versenken", das die Buttons „Spiel starten" und „Spiel beenden" enthält. Die Ausgabe des Beispiels ist in Abb. 4-29 angegeben. import java.applet.*; import java.awt.*; public class GUI1 extends Applet{ FlowLayout fl= new FlowLayout(); Button sb = new Button("Beginne Spiel"); Button se = new Button("Beende Spiel"); public void init () { setLayout(fl); add(sb); add(se); } } Abb. 4.29: Flow-Layout Zur Generierung eines Flow-Layouts kann auch ein Konstruktor verwendet werden, der neben dem bereits angegebenen Parameter zwei weitere enthält: Den horizontalen und den vertikalen Abstand in Pixeln zwischen den Komponenten eines GUIs. Sollen im obigen Beispiel die Abstände 20 Pixel (horizontal) bzw. 3 Pixel (vertikal) eingehalten werden, so müsste daher die folgende Zeile verwendet werden: FlowLayout fl = new FlowLayout(FlowLayout.CENTER, 20, 3); Grid-Layout Der GridLayout-Manager erzeugt ein Raster aus Zeilen und Spalten, in denen die Komponenten angeordnet werden können. Die Reihenfolge, in der Komponenten diesem Gitter zugefügt werden, beginnt stets in der obersten Reihe mit dem am weitesten links stehenden Element. Anschließend werden Elemente solange in die erste Zeile eingefügt, bis der zur Verfügung stehende Platz erschöpft ist. Weitere Komponenten werden dann nach demselben Verfahren in der nächsten Zeile eingefügt. Zur Generierung eines Grid-Layouts wird die Klasse GridLayout mit folgenden Konstruktoren verwendet: GridLayout(int, int) erzeugt ein Layout-Gitter, dessen Zeilenanzahl durch den ersten Parameter und dessen Spaltenanzahl durch den zweiten Parameter spezifiziert werden. GridLayout(int, int, int, int) erzeugt ein Gitter, dessen erste zwei Parameter dieselben sind wie beim vorhergehenden Konstruktor. Der dritte bzw. der vierte Parameter geben den horizontalen und den vertikalen Abstand der Komponenten innerhalb des Gitters an. Das folgende Beispiel erweitert das FlowLayout-Beispiel um die Spielfelder des Benutzers und des Computers. Die Ausgabe dieses Beispiels ist in Abb. 4-30 angegeben. Es ist deutlich erkennbar, dass zwar eine leichtere Anordnung der Elemente erreichbar ist, dass aber bspw. die Buttons kein gutes Erscheinungsbild haben. In Grid-Layouts wird ein Element immer so weit erweitert, dass eine Zelle eines Rasters vollständig ausgefüllt wird. Zudem ist die Anordnung der Spielfelder derart nur vertikal möglich, da Java automatisch so positioniert, dass Platz eingespart wird. Soll dieses GUI besser gestaltet werden, so muss eine Schachtelung von Containern erfolgen, die im Folgenden erklärt wird. import java.applet.*; import java.awt.*; public class GUI2 extends Applet{ Button sb = new Button("Spiel starten"); Button se = new Button("Spiel beenden"); //Spielfeld des Computers SpielCanvas sc1 = new SpielCanvas(); //Spielfeld des Benutzers SpielCanvas sc2 = new SpielCanvas(); GridLayout gl= new GridLayout(2,2,10,10); public void init () { setLayout(gl); add(sb); add(sc1); add(se); add(sc2); } } class SpielCanvas extends Canvas { public void paint(Graphics screen) { int i; screen.setColor(Color.black); // Erzeuge Quadrat als Spielfeldbegrenzung screen.drawRect(0,0,200,200); // Erzeuge horizontale Linien for (i = 1; i < 10; i++) screen.drawLine(0, 0+i*20,200, 0+i*20); // Erzeuge vertikale Linien for (i = 1; i < 10; i++) screen.drawLine(0+i*20, 0,0+i*20, 200); } } Mittels des Grid-Layouts können Elemente offensichtlich weit besser positioniert werden als mit dem Flow-Layout. Das erstere Layout wird daher nur dann verwendet, wenn ein GUI ein außerordentlich einfaches Erscheinungsbild haben soll. Abb. 4.30: Grid-Layout Border-Layout Ein anderer Ansatz wird mit dem Border-Layout verfolgt. Hierbei kann bei jeder Komponente mittels einer der Himmelsrichtungen (Norden, Süden, Osten, Westen oder zentriert) angegeben werden, wo ein Element angeordnet werden soll. Zunächst wird in den verschiedenen Himmelsrichtungen der Platz belegt, den die dort angeordneten Komponenten benötigen. Der verbleibende Platz wird der Komponente im Zentrum zugewiesen. Zur Generierung eines Border-Layouts, das in der Klasse BorderLayout realisiert ist, stehen die folgenden Konstruktoren zur Verfügung: BorderLayout() erzeugt ein Border-Layout, in dem die Komponenten unmittelbar aneinander grenzen, BorderLayout(int, int) erzeugt ein Border-Layout, in dem die Parameter den horizontalen und den vertikalen Abstand der Komponenten angeben. Offensichtlich muss die Syntax der add-Methode bei Verwendung des Border-Layouts verändert werden, da die Himmelsrichtung, an der eine Komponente angeordnet werden soll, spezifiziert werden muss. add verwendet in diesem Zusammenhang die folgende Syntax, bei der der erste Parameter einer der Strings „North", „South", „East", „West" oder „Center" ist. Der zweite Parameter gibt wie bisher auch die hinzuzufügende Komponente an. add(String, <element>); Aus der in Abb. 4-31 dargestellten Bildschirmausgabe ist ersichtlich, dass auch dieses Layout eher bescheiden aussieht. Zudem wird nur eines der Spielfelder angezeigt. Offensichtlich ist eine weitere Gruppierung der Komponenten notwendig, die es bspw. ermöglicht, beide Spielfelder im Zentrum des GUIs anzuordnen.Hierzu werden in Java sog. Panels verwendet, die im Folgenden den bisher verwendeten Begriff des Containers ersetzen. Mittels Panels, deren Funktionsweise bereits erläutert wurde, sind Schachtelungen möglich, indem Komponenten gruppiert werden. Beispielsweise können beide Buttons des GUIs und beide Spielfelder in separaten Panels angeordnet werden, die anschließend in einem übergeordneten Panel gruppiert werden. Da für jedes Panel ein eigener Layout-Manager (auch verschiedene Layouts für verschiedene Panels) angegeben werden kann, sind erheblich komplexere Darstellungsformen möglich. Zur Anwendung eines Panels, das in der Klasse Panel implementiert ist, wird dieses zuerst angelegt, anschließend ein Layout definiert und dieses dem Panel zugewiesen: Panel p = new Panel(); BorderLayout bl = new BorderLayout(); p.setLayout(bl); Abb. 4.31: Border-Layout import java.applet.*; import java.awt.*; public class GUI3 extends Applet{ Button sb = new Button("Spiel starten"); Button se = new Button("Spiel beenden"); //Spielfeld des Computers SpielCanvas sc1 = new SpielCanvas(); //Spielfeld des Benutzers SpielCanvas sc2 = new SpielCanvas(); BorderLayout bl= new BorderLayout(10,10); public void init () { setLayout(bl); add("North", sb); add("Center", sc1); add("West", se); add("South", sc2); } } class SpielCanvas extends Canvas { public void paint(Graphics screen) { screen.setColor(Color.black); // Erzeuge Quadrat als Spielfeldbegrenzung screen.drawRect(0,0,200,200); // Erzeuge horizontale Linien for (int i = 1; i < 10; i++) screen.drawLine(0, 0+i*20,200, 0+i*20); // Erzeuge vertikale Linien for (i = 1; i < 10; i++) screen.drawLine(0+i*20, 0,0+i*20, 200); } } Abb. 4.32: Grid-Layout mit Panels import java.applet.*; import java.awt.*; public class GUI4 extends Applet{ Button sb = new Button("Spiel starten"); Button se = new Button("Spiel beenden"); //Spielfeld des Computers SpielCanvas sc1 = new SpielCanvas(); //Spielfeld des Benutzers SpielCanvas sc2 = new SpielCanvas(); GridLayout knoepfe= new GridLayout(2,1, 10, 100); GridLayout gui= new GridLayout(1,3, 10, 10); Panel p = new Panel(); public void init () { p.setLayout(knoepfe); p.add(sb); p.add(se); setLayout(gui); add(p); add(sc1); add(sc2); } } class SpielCanvas extends Canvas { public void paint(Graphics screen) { int i; screen.setColor(Color.black); // Erzeuge Quadrat als Spielfeldbegrenzung screen.drawRect(0,0,200,200); // Erzeuge horizontale Linien for (i = 1; i < 10; i++) screen.drawLine(0, 0+i*20,200, 0+i*20); // Erzeuge vertikale Linien for (i = 1; i < 10; i++) screen.drawLine(0+i*20, 0,0+i*20, 200); } } Offensichtlich ist ein derartiges GUI bereits besser einsetzbar. Dennoch stehen weitaus umfassendere Mechanismen zur Verfügung, die in den nächsten beiden Abschnitten betrachtet werden. Es ist allerdings zu beachten, dass sich die bisher angesprochenen Schachtelungsmechanismen auf alle Layout-Strategien, also auch auf die im Folgenden beschriebenen Card- und GridBag-Layouts, beziehen. Card-Layout Ein Card-Layout definiert eine Gruppe von Containern oder Komponenten, von denen jeweils nur eine sichtbar ist. Jeder Container einer derartigen Gruppe wird als Card bezeichnet. Ein bekanntes Beispiel für die Anwendung dieses Layouts sind bspw. die Registerkarten, die in den Systemeigenschaften von Windows 98 der Firma Microsoft vorzufinden sind. Üblicherweise wird in einem Card-Layout ein Panel für jede Karte verwendet. Hierzu sind die folgenden Schritte zu durchlaufen: Aufruf des Konstruktors CardLayout(), der ein CardLayout-Objekt anlegt. Setzen des Layouts eines Containers (bspw. eines Applets) mittels der setLayout-Methode, die als Parameter den Namen des CardLayout-Objekts erwartet. Hinzufügen der Komponenten zu einer Karte mittels der add-Methode. add erwartet hier in der Form add(String, Container) zwei Parameter: Den Namen einer Karte als String und die Komponente, die einer Karte hinzugefügt werden soll. Anzeige einer Karte mittels der Methode show, die Teil der Klasse CardLayout ist. show erwartet als Parameter den Namen des Containers, der die Karten enthält (bspw. im Falle eines Applets der this-Zeiger), und den Namen der Karte, die angezeigt werden soll. Üblicherweise findet ein Kartenwechsel dann statt, wenn eine Benutzereingabe erfolgt ist. Das folgende Beispiel demonstriert die Anwendung des Card-Layouts, indem Felder zum Setzen von Optionen definiert werden. Die Umschaltung zwischen den Feldern kann allerdings an dieser Stelle noch nicht erläutert werden, da hierzu das Verständnis des in Kapitel 4.5 vorgestellten Event-Konzepts notwendig ist. Die Ausgabe des Beispiels ist in Abb. 4-33 dargestellt. import java.applet.*; import java.awt.*; public class CardBeispiel extends Applet{ CardLayout c = new CardLayout(); public void init () { c = new CardLayout(); setLayout(c); // Anlegen der Felder zur Aenderung von Lautstaerke und // Spielstaerke Panel p1 = new Panel(); Panel p2 = new Panel(); Label l1 = new Label("Lautstaerke setzen"); Label l2 = new Label("Spielstaerke setzen"); TextField t1 = new TextField(1); TextField t2 = new TextField(1); p1.add(l1); p1.add(t1); p2.add(l2); p2.add(t2); //Setzen von Karte 1 add("Karte1", p1); //Setzen von Karte 2 add("Karte2", p2); //Anzeigen von Karte1 c.show(this, "Karte1"); } } Abb. 4.33: Card-Layout GridBag-Layout Ein großer Nachteil der bisher erläuterten Layout-Strategien besteht darin, dass sie relativ unflexibel sind. Als Erweiterung des Grid-Layouts löst das GridBag-Layout einige dieser Probleme, indem eine Komponente mehr als eine Zelle des Gitters belegen kann, aber auch indem die Proportionen der Zeilen und Spalten des Gitters frei wählbar sind bzw. indem Komponenten innerhalb der Gitterzellen auf verschiedene Arten platziert werden können. Zur Verwendung dieses Layouts sind die folgenden Schritte zu durchlaufen: Anlegen eines GridBagLayout-Objekts mittels des Konstruktors GridBagLayout(). Setzen dieses Layouts mittels der Methode setLayout, die als Parameter den Namen des GridBagLayout-Objekts erwartet. Definition eines GridBagConstraints-Objekts mittels des Konstruktors GridBagConstraints(). In GridBagConstraints-Objekten werden die Platzierungseigenschaften der Komponenten in den Gitterzellen angegeben. Hierzu zählen bspw. die Platzierung selbst, die Dimension der Zelle oder deren Ausrichtung. Definition der Eigenschaften einer Komponente, indem Werte des GridBagConstraints-Objekts gesetzt werden. Setzen der Eigenschaften der Komponenten mittels der Methode setConstraints(Component, GridBagConstraints), die als ersten Parameter eine Komponente und als zweiten Parameter das GridBagConstraints-Objekt erwartet. Hinzufügen der Komponente zu einem Container mittels der add-Methode. Üblicherweise beginnt man den Entwurfsprozess eines GridBag-Layouts auf Papier. Hierbei ist zu beachten, dass jede Komponente in einer eigenen Gitterzelle untergebracht werden muss. Eine Komponente kann sich allerdings durchaus auch über mehrere Gitterzellen erstrecken (horizontal und vertikal). Es ist in diesem Zusammenhang hilfreich, die Komponenten mit Koordinaten zu versehen. Hierbei sollten keine Pixel- sondern Gitterkoordinaten verwendet werden. Das Element, das links oben im Gitter angebracht wäre, würde daher mit (0,0) bezeichnet werden, das Element rechts davon mit (0,1). Im nächsten Schritt setzt man die Eigenschaften der verschiedenen Elemente. Hierzu ist die Verwendung einer Hilfsmethode sinnvoll, da stets dieselbe Menge von Eigenschaften für jedes Element zu setzen ist. Die Eigenschaften, die gesetzt werden können, sind in Tab. 4-3 angegeben.

Eigenschaft | Bedeutung
gridx, gridy | Diese Eigenschaften spezifizieren die bereits angesprochenen Koordinaten innerhalb des Gitters. Die linke obere Ecke wird hierbei mit (0,0) bezeichnet, das Element zur rechten mit (0,1). Es kann aber sowohl für gridx als auch für gridy der Ausdruck GridBagConstraints.RELATIVE verwendet werden, der in horizontaler Richtung die nächste Zelle zur rechten und in vertikaler Richtung die nächste Zelle unter der Zelle bezeichnet, die zuletzt angesprochen wurde.
gridwidth, gridheight | Legt die Anzahl der Zellen in horizontaler und in vertikaler Richtung fest, die eine Komponente verwendet. GridBagConstraints.REMAINDER kann verwendet werden, um anzuzeigen, dass ein Element das letzte in einer Reihe oder Spalte ist. GridBagConstraints.RELATIVE gibt hierbei an, dass das Element in horizontaler oder in vertikaler Richtung den noch verbleibenden Platz belegen soll.
fill | Wird verwendet, um eine Komponente zu vergrößern, die kleiner ist als die Zelle, in der sie platziert wurde. Hierbei können die Werte GridBagConstraints.NONE (kein Auffüllen), GridBagConstraints.HORIZONTAL (horizontales Auffüllen), GridBagConstraints.VERTICAL und GridBagConstraints.BOTH (Auffüllen in beide Richtungen) verwendet werden.
ipadx, ipady | Spezifiziert das interne Auffüllen einer Komponente in einer Zelle, um zusätzlichen Platz um eine Komponente zu erzeugen.
insets | Gibt den Abstand eines Elements von den Grenzen der Zelle an. Hierzu muss ein neues Insets-Objekt angelegt werden, das als Parameter vier Ganzzahlen erwartet, die den Abstand des Objekts von der oberen, der unteren, der linken und der rechten Begrenzung angeben. Die bereits definierte Methode getInsets ist dazu geeignet zu überschreiben.
anchor | Wird verwendet, wenn eine Komponente kleiner als eine Zelle ist, um anzugeben, wo die Komponente platziert werden soll. Mögliche Werte sind hierbei GridBagConstraints.CENTER (zentriert), GridBagConstraints.NORTH, GridBagConstraints.NORTHEAST, GridBagConstraints.EAST, GridBagConstraints.SOUTHEAST, GridBagConstraints.SOUTH, GridBagConstraints.SOUTHWEST, GridBagConstraints.WEST und GridBagConstraints.NORTHWEST.
weightx, weighty | Hiermit wird angegeben, wie viel Gewicht eine Zeile oder Spalte innerhalb des Gitters erhalten soll. Mögliche Werte liegen im Intervall [0.0, 1.0]. Die Werte einer Zeile oder Spalte müssen addiert allerdings nicht 1.0 ergeben, Java errechnet den Anteil selbstständig, indem mit dem größten Wert einer Zeile oder Spalte normiert wird.

Tab. 4.3: Eigenschaften innerhalb eines GridBagConstraints-Objekts Eine Hilfsroutine, die die Werte geeignet setzt, ist im Folgenden angegeben. void setzeGridBagConstraints(GridBagConstraints gbc, int gx, int gy, int gw, int gh, int f, int ix, int iy, Insets in, int a, float wx, float wy) { gbc.gridx = gx; gbc.gridy = gy; gbc.gridwidth = gw; gbc.gridheight = gh; gbc.fill = f; gbc.ipadx = ix; gbc.ipady = iy; gbc.insets = in; gbc.anchor = a; gbc.weightx = wx; gbc.weighty = wy; } Im Folgenden soll die Benutzeroberfläche des Spiels „Schiffe versenken" ansprechend gestaltet werden. Zur besseren Erläuterung werden die Code-Segmente durch Erklärungen unterbrochen. Setzt man allerdings die Segmente wieder zusammen, so erhält man das vollständige Java-Applet des GUIs. Zuerst wird der Kopf folgendermaßen definiert: import java.applet.*; import java.awt.*; public class GUISVUser extends Applet{ Anschließend wird die Hilfsroutine definiert, die die Eigenschaften der Komponenten setzt: void setzeGridBagConstraints(GridBagConstraints gbc, int gx, int gy, int gw, int gh, int f, int ix, int iy, Insets in, int a, float wx, float wy) { gbc.gridx = gx; gbc.gridy = gy; gbc.gridwidth = gw; gbc.gridheight = gh; gbc.fill = f; gbc.ipadx = ix; gbc.ipady = iy; gbc.insets = in; gbc.anchor = a; gbc.weightx = wx; gbc.weighty = wy; } Im nächsten Schritt wird die Methode getInsets überschrieben, die einen 10 Pixel breiten Rand um die Komponenten anlegt. public Insets getInsets() { return new Insets(10,10,10,10); } In der folgenden init-Methode wird zuerst ein Panel angelegt, dem ein Grid-Layout zugewiesen wird. Dieses Panel enthält die im Folgenden definierten Buttons. Weiterhin werden zu Beginn der Methode das GridBagLayout und die GridBagConstraints definiert. public void init () { GridLayout g = new GridLayout(4,1); Panel p = new Panel(); p.setLayout(g); GridBagLayout gb = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); setLayout(gb); // Anlegen der Buttons Button setship = new Button("Schiffe setzen"); p.add(setship); Button shoot = new Button("Schuss abgeben"); shoot.setEnabled(false); p.add(shoot); Button save = new Button("Spielstand speichern"); save.setEnabled(false); p.add(save); Button quit= new Button("Spiel verlassen"); p.add(quit); Im nun folgenden Teil werden die Eigenschaften des Panels mit Hilfe der Methode setzeGridBagConstraints gesetzt. Das Panel befindet sich in Zelle (0,0) und hat eine horizontale Ausdehnung von 33% des GUI-Bereichs bzw. eine horizontale von 50%. Anschließend werden die zwei Spielfelder definiert. Der hierzu notwendige Code wurde bereits im Zusammenhang mit Grid-Layouts definiert. Neu ist hierbei allerdings, dass beide Felder mit Labels bezeichnet werden. Bei jedem der Objekte erfolgt ein Aufruf der Methode setzeGridBagConstraints. setzeGridBagConstraints(gbc,0,0,1,1,GridBagConstraints.HORIZ ONTAL, 0,0,getInsets(), GridBagConstraints.NORTH, 0.33f, 0.5f); gb.setConstraints(p, gbc); add(p); //Spielfeld des Computers SpielCanvas sc1 = new SpielCanvas(); setzeGridBagConstraints(gbc,1,0,1,1,GridBagConstraints.BOTH, 0,0,getInsets(), GridBagConstraints.SOUTH, 0.33f, 0.8f); gb.setConstraints(sc1, gbc); add(sc1); Label l1 = new Label("Spielfeld des Computers", Label.LEFT); setzeGridBagConstraints(gbc,1,1,1,1,GridBagConstraints.BOTH, 0,0,getInsets(), GridBagConstraints.NORTH, 0.33f, 0.2f); gb.setConstraints(l1, gbc); add(l1); //Spielfeld des Benutzers SpielCanvas sc2 = new SpielCanvas(); setzeGridBagConstraints(gbc,2,0,1,1,GridBagConstraints.BOTH, 0,0,getInsets(), GridBagConstraints.SOUTH, 0.33f, 0.8f); gb.setConstraints(sc2, gbc); add(sc2); Label l2 = new Label("Spielfeld des Benutzers", Label.LEFT); setzeGridBagConstraints(gbc,2,1,1,1,GridBagConstraints.BOTH, 0,0,getInsets(), GridBagConstraints.NORTH, 0.33f, 0.2f); gb.setConstraints(l2, gbc); add(l2); } } Mit Abschluss dieser Klassendefinition folgt noch die Definition der Klasse, die für die Erzeugung der Spielfelder notwendig ist. Diese wurde bereits erläutert. Die Ausgabe der Oberfläche ist in Abb. 4-34 angegeben. class SpielCanvas extends Canvas { public void paint(Graphics screen) { int i; screen.setColor(Color.black); // Erzeuge Quadrat als Spielfeldbegrenzung screen.drawRect(0,0,200,200); // Erzeuge horizontale Linien for (i = 1; i < 10; i++) screen.drawLine(0, 0+i*20,200, 0+i*20); // Erzeuge vertikale Linien for (i = 1; i < 10; i++) screen.drawLine(0+i*20, 0,0+i*20, 200); } } Abb. 4.34: Oberfläche des Spiels „Schiffe versenken"