Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 15 Komponenten, Ereignisse und Container
  gp 15.1 Peer-Klassen und Lightweight-Komponenten
  gp 15.2 Es tut sich was – Ereignisse beim AWT
    gp 15.2.1 Was ist ein Ereignis?
    gp 15.2.2 Die Klasse AWTEvent
    gp 15.2.3 Events auf verschiedenen Ebenen
    gp 15.2.4 Ereignisquellen, -senken und Horcher (Listener)
    gp 15.2.5 Listener implementieren
    gp 15.2.6 Listener bei Ereignisauslöser anmelden
  gp 15.3 Varianten, das Fenster zu schließen
    gp 15.3.1 Eine Klasse implementiert die Schnittstelle WindowListener
    gp 15.3.2 Adapterklassen nutzen
    gp 15.3.3 Innere Mitgliedsklassen und innere anonyme Klassen
    gp 15.3.4 Generic Listener
  gp 15.4 Komponenten
    gp 15.4.1 Die Basis aller Komponenten: Die Klasse Components
    gp 15.4.2 Proportionales Vergrößern eines Fensters
    gp 15.4.3 Hinzufügen von Komponenten
  gp 15.5 Ein Informationstext über die Klasse Label
    gp 15.5.1 Mehrzeiliger Text
  gp 15.6 Eine Schaltfläche (Button)
    gp 15.6.1 Der aufmerksame ActionListener
    gp 15.6.2 Generic Listener für Schaltflächen-Ereignisse verwenden
  gp 15.7 Horizontale und vertikale Schieberegler
    gp 15.7.1 Der AdjustmentListener, der auf Änderungen hört
  gp 15.8 Ein Auswahlmenü – Das Choice-Menü
    gp 15.8.1 ItemListener
  gp 15.9 Eines aus vielen – Kontrollfeld (Checkbox)
    gp 15.9.1 Ereignisse über ItemListener
  gp 15.10 Optionsfelder
  gp 15.11 List-Boxen
  gp 15.12 Texteingabefelder
    gp 15.12.1 Text in einer Eingabezeile
    gp 15.12.2 Passwort-Felder
    gp 15.12.3 Mehrzeilige Textfelder
  gp 15.13 Menüs
    gp 15.13.1 Die Menüleisten und die Einträge
    gp 15.13.2 Menüeinträge definieren
    gp 15.13.3 Shortcuts
    gp 15.13.4 Beispiel für ein Programm mit Menüleisten
  gp 15.14 Popup-Menüs
  gp 15.15 Selbstdefinierte Cursor
  gp 15.16 Alles Auslegungssache: Die Layout-Manager
    gp 15.16.1 FlowLayout
    gp 15.16.2 BorderLayout
    gp 15.16.3 GridLayout
    gp 15.16.4 Der GridBagLayout-Manager
  gp 15.17 Dynamisches Layout während einer Größenänderung
  gp 15.18 Dialoge
    gp 15.18.1 Der Dateiauswahl-Dialog
  gp 15.19 Die Zwischenablage (Clipboard)
  gp 15.20 Ereignisverarbeitung auf unterster Ebene
  gp 15.21 Benutzerinteraktionen automatisieren

Kapitel 15 Komponenten, Ereignisse und Container

Wenn die Reklame keinen Erfolgt hat,
muss man die Ware ändern.
– Faure

Obwohl das Abstract Window Toolkit das Problem einer einheitlichen Benutzeroberfläche lösen sollte, ist dies bei Sun nicht ganz gelungen. Das AWT war von Anfang an zusammengepfuscht. So meint auch James Gosling: »The AWT was something we put together in six weeks to run on as many platforms as we could, and its goal was really just to work. So we came out with this very simple, lowest-common-denominator thing that actually worked quite well. But we knew at the time we were doing it that it was really limited. After that was out, we started doing the Swing thing, and that involved working with Netscape and IBM and folks from all over the place.«

Doch Sun machte sich mit dem AWT noch mehr Probleme. Da es von Anfang an ungenügend durchdacht war, führten die Entwickler von AWT 1.02 auf AWT 1.1 ein anderes Ereignis-Modell ein. So kostete die Anpassung von komplexen Programmen an das neue Ereignis-Modell einen erheblichen Programmieraufwand – und nicht alle Firmen gingen darauf ein. Da Sun das AWT einfach gehalten hat, aber Entwickler von Oberflächen einen nicht zu stillenden Hunger nach Komponenten haben, konzipierte Netscape kurzerhand die Internet Foundation Classes (IFC), die das AWT in wesentlichen Punkten ergänzen. Auch Microsoft mischt durch Erweiterungen mit. Die Firma bietet die Application Foundation Class (AFC) an, die aber heute keine Rolle mehr spielt.

Tipp Lass das Design der Oberfläche einen Grafiker vornehmen – der versteht seine Arbeit.


Galileo Computing

15.1 Peer-Klassen und Lightweight-Komponenten  downtop

Die ursprüngliche AWT-Implementierung benötigt so genannte Peer-Klassen, die als native Funktionen auf der speziellen Benutzeroberfläche implementiert sind. Unter X11 sehen wir auf Motif 2.0 basierende Komponenten und auf Windows entsprechend andere. Der Vorteil ist, dass durch die nativen Peer-Klassen die Oberfläche schnell wird. Leider zeigen die Programme bisweilen unter den verschiedenen Betriebssystemen merkwürdige Seiteneffekte. So kann ein Textfeld unter Windows weniger als 64k Zeichen aufnehmen, bei anderen Oberflächen ist dies egal.

Tipp Entwickle eine Oberfläche nach den Wünschen der Benutzer, nicht nach der Schwierigkeit der Umsetzung oder Begrenzung der Hardware.

Eine Leichtgewicht-Komponente (engl. lightweight component) besitzt keine Peer, also keine direkte Repräsentation im Fenstersystem. Somit gibt es keine speziellen Implementierungen des Systems auf beispielsweise Windows, MacOS oder X11. Dieser Weg ist also plattformunabhängiger und wesentlich langsamer. Denn die vollständige Ereignisbehandlung wird von Java übernommen und die Komponenten werden mit primitiven Zeichenoperationen gemalt. Der Vorteil: Eine Leichtgewicht-Komponente kann durchsichtig sein und muss nicht mehr in einen rechteckigen Bereich passen.

Im April des Jahres 1997 haben sich Sun, Netscape und IBM auf eine GUI-Bibliothek geeinigt, die auf Netscapes IFC aufbaut und das AWT in der Java-Version 1.2 erweitert. (Für 1.1.x lässt sich die Bibliothek von den Sun-Seiten laden.) Der Name des Toolkits ist JFC (Java Foundation Classes). Ein Teil der JFC sind neue Komponenten, die so genannten Swing-Komponenten. Obwohl Swing mehr ist als die neuen grafischen Elemente, ist Swing zu einem Synonym für die JFC geworden. Als 1997 in San Francisco auf der JavaOne die neuen Komponenten vorgestellt wurden, entschied sich Georges Saab, ein Mitglied des JFC-Teams, für Musik parallel zur Präsentation. Dies war gerade Swing-Musik, denn der Entwickler glaubte, dass Swing-Musik wieder in Mode kommt. So wurden auch die neuen grafischen Elemente im Paket mit dem Namen Swing abgelegt. Obwohl der Name offiziell JFC weichen musste, war er doch so populär, dass er weiter bestehen blieb.


Galileo Computing

15.2 Es tut sich was – Ereignisse beim AWT  downtop


Galileo Computing

15.2.1 Was ist ein Ereignis?  downtop

Beim Arbeiten mit grafischen Oberflächen interagiert der Benutzer mit Komponenten, so drückt er eine Schaltfläche oder verschiebt einen Schieberegler. Das grafische System beobachtet die Aktionen der Benutzer und informiert die Applikation über die anfallenden Ereignisse. Dann kann das laufende Programm mit entsprechenden Aktionen reagieren.

Die ausgesendeten Botschaften werden in Event-Klassen kategorisiert. Da es unterschiedliche Ereignisse (engl. events) gibt, kann das System somit die Ereignisse unterteilen und eine Vorauswahl treffen. Von den vielen Ereignissen, die ausgelöst werden, sind nur manche vom Belang. Daher werden nur die Ereignisse ausgewertet, für die es passende Abnehmer gibt.


Galileo Computing

15.2.2 Die Klasse AWTEvent  downtop

Alle Ereignisse der grafischen Oberfläche sind Objekte, die aus einer Unterklasse von AWTEvent gebildet sind. Die Klasse AWTEvent ist abstrakt und selbst von EventObject aus dem util-Paket abgeleitet. Obwohl sich alle Oberflächen-Ereignisse in dem Unterpaket java.awt.event befinden, findet sich AWTEvent selbst direkt unter java.awt.

Eine wichtige Methode ist getID(). Jede Ereignisklasse definiert eine ID, durch die sich die Ereignisse neben ihrer Klassenzugehörigkeit unterschieden. Für Ereignisse von gedrückten Schaltflächen ist die ID etwa ActionEvent.ACTION_PERFORMED. Natürlich stellt sich die Frage, wieso eine ID für die Ereignisse nötig ist, die Vererbungsbeziehung klärt doch den Typ. Das ist zwar korrekt, doch gäbe es für mehr als dreißig Events zu viele Klassen. Daher fassten die Entwickler ähnliche Ereignisse zu Gruppen zusammen. So etwa bei einem WindowEvent, welches dann versandt wird, wenn etwa das Fenster geschlossen oder verkleinert wird. In diesem Fall gibt es ein Ereignis vom Typ WindowEvent, aber zwei unterschiedliche IDs. So wird eine unübersehbare Anzahl von Event-Klassen vermieden. Einige Klassen verwalten weitere Konstanten, etwa für die gedrückten Tasten. Es wäre kaum sinnvoll, für jede Taste eine eigene Klasse zu schreiben. Diese wird als eigenes Attribut im KeyEvent gespeichert.

Abbildung


Galileo Computing

15.2.3 Events auf verschiedenen Ebenen  downtop

Unter den Ereignissen werden zwei Typen unterschieden: Die Ereignisse auf niedriger und hoher Ebene. Damit ist gemeint, dass es Ereignisse gibt, die jedes GUI-Element (Komponente genannt) empfängt, wie Mausbewegung oder Fokus, und dass es auf der anderen Seite Ereignisse gibt, die nur zu ganz speziellen Objekten gehören. Sie heißen auch semantische Ereignisse. Die Trennung fällt aber nicht weiter auf. Da alle grafischen Komponenten von einer Klasse Component abgeleitet sind, liefert sie automatisch eine Reihe von nicht-semantischen Ereignissen. Wir finden die Unterklassen und die Ereignistypen in der Tabelle.

Tabelle 15.1   Ereignisklassen und ihre IDs
Klasse ID
ComponentEvent COMPONENT_MOVED
  COMPONENT_RESIZED
  COMPONENT_SHOWN
  COMPONENT_HIDDEN
FocusEvent FOCUS_GAINED
  FOCUS_LOST
KeyEvent KEY_PRESSED
  KEY_RELEASED
  KEY_TYPED
MouseEvent MOUSE_CLICKED
  MOUSE_DRAGGED
  MOUSE_ENTERED
  MOUSE_EXITED
  MOUSE_MOVED
  MOUSE_PRESSED
  MOUSE_RELEASED
HierarchyEvent ANCESTOR_MOVED, ANCESTOR_RESIZED, DISPLAYABILITY_CHANGED, HIERARCHY_CHANGED, PARENT_CHANGED SHOWING_CHANGED
InputMethodEvent CARET_POSITION_CHANGED, INPUT_METHOD_TEXT_CHANGED

Weitere niedrige Ereignisse lösen Fenster und Dialoge aus; sie senden Objekte vom Typ WindowEvent. Wir werden uns in den nachfolgenden Kapitel mit den unterschiedlichen Komponenten beschäftigen und dann auch immer gleich die zugehörigen Ereignisse untersuchen. Ein Blick auf die folgende Tabelle zeigt für ein paar Komponenten, die Ereignisse und wann sie ausgelöst werden können.

Tabelle 15.2   Ereignisauslöser
Ereignistyp Auslöser Wann das Event ausgelöst wird
Action z. B. Button Aktivierung der Schaltfläche
Adjustment Scrollbar Wertänderung
Component Component Änderung der Sichtbarkeit oder Größe
Container Container Änderung des Inhalts
Focus Component neuer Fokus (bei Tastatureingaben)
Item z. B. List Auswahl
Key Component Tastatur
Mouse Component Buttons, Betreten bzw. Verlassen einer Komponente
MouseMotion Component Bewegung
Text TextComponent Wertänderung
Window Window Zustandsänderung
Eyelid Eye Augenzwinkern


Galileo Computing

15.2.4 Ereignisquellen, -senken und Horcher (Listener)  downtop

Im Ereignismodell 1.1 gibt es eine Reihe von Ereignisauslösern (Eventquellen, engl. Event-Source), wie z. B. Schaltflächen. Diese können von der grafischen Oberfläche kommen, etwa wenn der Benutzer einen AWT-Button drückt, sie können aber auch von eigenen Auslösern kommen. Dann existieren eine Reihe von Interessenten, die gerne informiert werden wollen, wenn ein Ereignis aufgetreten ist. Da der Interessent in der Regel nicht an allen ausgelösten Oberflächen-Ereignissen interessiert ist, sagt er einfach, welche Ereignisse er empfangen möchte. Dies funktioniert so, dass er sich bei einer Ereignisquelle anmeldet, und diese informiert ihn dann, wenn sie ein Ereignis ausendet. Auf diese Weise wird die Systemeffizienz nicht minimiert, da nur diejenigen informiert werden, die auch Verwendung für das Ereignis haben. Da der Interessent an der Quelle horcht, nennt er sich auch Listener oder Horcher. Es existiert für jedes Ereignis ein eigener Listener, an den das Ereignis weitergleitet wird. Daher stammt auch der Name für das Modell: Delegation Model. (Die Entwickler hatten vorher den Namen »Command-Model« vergeben, doch das drückte die Arbeitsweise nicht richtig aus.)


Galileo Computing

15.2.5 Listener implementieren  downtop

Der Listener selbst ist eine Schnittstelle, die von den Interessenten implementiert wird. Da jede Schnittstelle Methoden vorschreibt, muss der Interessent diese Methoden implementieren. Wird im nächsten Schritt ein Horcher mit dem Ereignisauslöser verbunden, dann kann die Ereignisquelle davon ausgehen, dass der Horcher die entsprechende Methode besitzt. Diese ruft die Quelle auf.

Beispiel Ein ActionListener für gedrückte Schaltflächen
public interface ActionListener 
extends EventListener
{
void actionPerformed( ActionEvent e )
}

Abbildung

An diesem Beispiel ist abzulesen, dass jeder die Schnittstelle ActionListener implementieren muss, der Interesse an dem Ereignis hat. Dann wird diese Klasse der auslösenden Komponente, also zum Beispiel der Schaltfläche, zugefügt. Tritt ein Ereignis auf, ruft die Ereignisquelle auf allen registrierten Listenern die actionPerformed()-Methode auf.

Beispiel Eine Klasse ActionListenerImpl implementiert für den ActionListener die Methode actionPerformed().
class ActionListenerImpl implements 
ActionListener
{
public void actionPerformed( ActionEvent e )
{
System.out.println( "Ich wurde berührt" );
}
}


Galileo Computing

15.2.6 Listener bei Ereignisauslöser anmelden  downtop

Hat der Listener die Schnittstelle implementiert, dann wird er mit dem Ereignisauslöser verbunden. Dazu existieren eine Reihe von Zufügemethoden, die einer Namenskonvention folgen.

addEreignisListener( Listener-Objekt );

Das bedeutet, dass etwa ein Listener für Schaltflächen, ein ActionListener, der Action Event-Ereignisse auslöst, mit der Methode addActionListener() an die Komponenten gebunden wird. Wenn die Klasse ActionListenerImpl die Schnittstelle ActionListener implementiert und die Komponente eine Schaltfläche mit der Referenz button ist, dann ergibt sich Folgendes für die Ereignisbehandlung:

ActionListener listener = new ActionListenerImpl();
button.
addActionListener( listener );

Die letzte Zeile trägt den Listener in einer internen Liste beim Button ein.


Galileo Computing

15.3 Varianten, das Fenster zu schließen  downtop

Bei dem ersten Programm mit Fenstern haben wir das Problem, dass das Fenster nicht sauber geschlossen werden kann. Dies liegt ganz einfach daran, dass spezielle Fensterereignisse ausgelöst werden, die von uns abgefangen werden müssen, was wir aber nicht getan haben. Doch nur dann ist ein sauberes Beenden möglich, denn bei den vielen Ereignissen, die das Fenster-System sendet, ist auch die Auforderung zum Schließen des Fensters dabei.

Um ein Fenster korrekt zu schließen, müssen wir das WindowListener-Interface implementieren. Dieses Interface ist von java.util.EventListener abgeleitet, ein Interface, das keine Funktionalität besitzt, aber von allen Ereignis-Klassen erweitert wird. WindowListener definiert eine Anzahl von Funktionen, die mit addWindowListener() an ein Fenster gebunden werden. Immer dann, wenn ein Event ausgelöst wird, kümmert sich diese jeweilige Funktion um die Abarbeitung.

Wir wollen im Folgenden einige populäre Möglichkeiten zum Schließen eines Fensters aufzeigen.


Galileo Computing

15.3.1 Eine Klasse implementiert die Schnittstelle WindowListener  downtop

Die erste Möglichkeit, Ereignisbehandlung zu implementieren, besteht in der Implementierung aller vorgeschriebenen Methoden der Listener-Schnittstelle. Dazu bieten sich zwei Klassen an: Zum einen die Hauptklasse, die auch das Fenster öffnet, und zum anderen eine externe Klasse, die nichts anderes macht, als die Listener-Schnittstelle zu implementieren. Wir wollen im folgenden Beispiel unser Hauptprogramm die Schnittstelle WindowListener implementieren lassen.

Abbildung

Listing 15.1   FensterWegImplementsAll.java
import java.awt.*;
import java.awt.event.*;

class FensterWegImplementsAll extends Frame implements WindowListener
{
public FensterWegImplementsAll()
{
setSize( 400, 400 );

addWindowListener( this );

show();
}

// Implementiere WindowListener

public void windowClosing( WindowEvent event ) {
System.exit(0);
}

public void windowClosed( WindowEvent event ) {}

public void windowDeiconified( WindowEvent event ) {}

public void windowIconified( WindowEvent event ) {}

public void windowActivated( WindowEvent event ) {}

public void windowDeactivated( WindowEvent event ) {}

public void windowOpened( WindowEvent event ) {}

public static void main( String args[] )
{
new FensterWegImplementsAll();
}
}

Die anderen Funktionen sind hier nicht implementiert, aber die Funktion lässt sich leicht am Namen ablesen. Das Fenster wird geschlossen, wenn der Anwender auf das X drückt

interface java.awt.event.WindowListener
extends EventListener

gp  void windowOpened( WindowEvent )
Aufgerufen, wenn Fenster geöffnet wurde.
gp  void windowClosing( WindowEvent )
Aufgerufen, wenn das Fenster geschlossen wird.
gp  void windowClosed( WindowEvent )
Aufgerufen, wenn das Fenster mit dispose() geschlossen wurde.
gp  windowIconified( WindowEvent )
Aufgerufen, wenn das Fenster verkleinert wird.
gp  windowDeiconified( WindowEvent )
Aufgerufen, wenn das Fenster wieder hochgeholt wird.
gp  windowActivated( WindowEvent )
Aufgerufen, wenn das Fenster aktiviert wird.
gp  windowDeactivated( WindowEvent )
Aufgerufen, wenn das Fenster deaktiviert wird.

Galileo Computing

15.3.2 Adapterklassen nutzen  downtop

Der Nachteil der ersten Variante ist, dass wir immer alle Methoden implementieren müssen, auch wenn wir nur eine der Vielzahl von Funktionen brauchen. Hier helfen Adapterklassen. Sie sind Klassen, die die Schnittstellen mit leeren Rümpfen implementieren. Hat beispielweise die Schnittstelle WindowListener sieben Methoden, so steht in der Adapterklasse folgende Implementierung:

Listing 15.2   java.awt.event.WindowAdapter
public abstract class WindowAdapter
implements WindowListener, WindowStateListener,
WindowFocusListener
{
public void windowOpened(WindowEvent e) {}
public void windowClosing(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowStateChanged(WindowEvent e) {}
public void windowGainedFocus(WindowEvent e) {}
public void windowLostFocus(WindowEvent e) {}
}

Zusätzlich entdecken wir einige Methoden, die nicht direkt von unserem WindowListener kommen, sondern noch von zwei weiteren Schnittstellen, die in 1.4 hinzugekommen sind.

Abbildung

Beispiel Wenn wir jetzt einen Ereignisbehandler verwenden, erweitern wir einfach die Adapterklasse. Unser Programm zum Schließen des Fensters mit einer externen Adapterklasse sieht dann wie folgt aus:
Listing 15.3   FensterWegExternerAdapter
import java.awt.*;
import java.awt.event.*;

public class FensterWegExternerAdapter extends Frame
{
public FensterWegExternerAdapter()
{
setSize( 400, 400 );

addWindowListener( new FensterWegAdapter() );

show();
}

public static void main( String args[] )
{
new FensterWegExternerAdapter();
}
}

class FensterWegAdapter
extends WindowAdapter
{
public void windowClosing ( WindowEvent e) { System.exit(0); }
}


Galileo Computing

15.3.3 Innere Mitgliedsklassen und innere anonyme Klassen  downtop

Wir haben für die Adapterklasse eine externe Klasse benutzt, denn das Erweitern stößt wegen der Einfachvererbung schnell an seine Grenzen. Mit inneren Klassen wird das ganze allerdings elegant. Dabei lassen sich innere Klassen auf zwei Weisen verwenden. Einmal als Mitgliedsklasse, das heißt, die Klasse, die bisher als externe Klasse vorlag, wird in eine andere Klasse hinein genommen. Der zweite Weg führt über innere anonyme Klassen. Dadurch wird das Programm zwar schön kurz, doch lange Ereignishandler führen schnell zu unübersichtlichem Quellcode.

Beispiel Wir implementieren unser Programm zum Schließen des Fensters mit einer inneren anonymen Klasse.

Listing 15.4   FensterWegInnerAnonym.java
import java.awt.*;
import java.awt.event.*;

public class FensterWegInnerAnonym extends Frame
{
public FensterWeg()
{
setSize( 400, 400 );

addWindowListener( new WindowAdapter() {
public void windowClosing ( WindowEvent e ) {
System.exit(0);
}
});

setVisible( true );
}

public static void main( String args[] )
{
new FensterWegInnerAnonym();
}
}

Galileo Computing

15.3.4 Generic Listener  downtop

Eine recht neue Methode haben die Entwickler ab der Version 1.3 hinzugefügt, die Generic Listenern. Sie ist noch nicht sehr verbreitet, dennoch sollte sie erwähnt werden. Der Unterschied zu den bisher vorgestellten Möglichkeiten liegt darin, einem Verwalter die Ereignisquelle und das Ereignisziel vorzugeben. Das Ziel ist dabei eine beliebige Methode. Beim Auftreten eines Ereignisses findet das System automatisch mittels Reflection die von uns angegebene Methode und ruft sie auf. Bisher sind Generic Listener eine einfache Erweiterung der Proxy-Klassen und sind noch nicht in die Java-Standardbibliothek eingeflossen. Die Implementierung fasst jedoch mit Kommentaren nur zweihundert Zeilen. Wir werden auf Generic Listener zurückkommen, wenn wir Ereignisse von Schaltflächen kennen gelernt haben. Informationen zu den Generic Listenern gibt es auf der Webseite von Sun unter

gp  http://java.sun.com/products/jfc/tsc/articles/generic-listener2/index.html
gp  http://java.sun.com/products/jfc/tsc/articles/generic-listener/index.html

Dort lässt sich auch die allgemeine Implementierung für eine Klasse GenericListener beziehen.


Galileo Computing

15.4 Komponenten  downtop


Galileo Computing

15.4.1 Die Basis aller Komponenten: Die Klasse Components  downtop

Die Klasse Component bildet die Basisklasse der Objekte, die als grafische Elemente auf den Schirm kommen. Ein Component-Objekt verarbeitet schon eine ganze Reihe von Ereignissen, etwa ComponentEvent, FocusEvent, KeyEvent, MouseEvent und MouseMotionEvent.

Die Position der Komponente

Der Klasse Component gehört eine ganz nützliche Funktion an, um die absolute Position der Komponente auf dem Bildschirm zu ermitteln. Diese Funktion ist besonders dann praktisch, wenn die Position eines Fensters gefragt ist.

abstract class java.awt.Component
implements ImageObserver, MenuContainer, Serializable

gp  Point getLocationOnScreen()
Liefert die Position der linken oberen Ecke der Komponente als Punkt-Objekt.

Die Arbeitsweise von getLocationOnScreen() ist einfach: Gehe mit der getParent()-Methode solange zurück, bis wir zu einem Exemplar von Frame kommen.

Beispiel Wir wollen den gleichen Trick verwenden, um die absoluten Koordinaten des Cursors am Bildschirm zu bestimmen. Normalerweise bekommen wir sie ja relativ zum Fenster. Hangeln wir uns von der aktuellen Komponente bis zum aktuellen Fenster durch, so können wir die Koordinaten bezüglich des Bildschirms und nicht bezüglich des Fensters der Applikation berechnen, indem vom Frame-Objekt die Methode getLocation() benutzt wird. Nehmen wir an, wir haben bereits die relativen Koordinaten des Cursors in x und y. Dann ergibt sich Folgendes:
Component c = this;

while ( (c = c.getParent()) != null )
if ( c instanceof Frame )
break;

if ( c != null )
{
Point p = (Frame)c.getLocation();
x += p.x;
y += p.y;
}


Galileo Computing

15.4.2 Proportionales Vergrößern eines Fensters  downtop

Die Schnittstelle ComponentListener ist Basis für alle Komponenten, die Ereignisse empfangen. Sie definiert vier Methoden, die in der Klasse ComponentAdapter wieder mit leerem Programmblock gefüllt sind.

Abbildung

interface java.awt.event.ComponentListener
extends EventListener

gp  componentHidden( ComponentEvent )
Wenn die Komponente versteckt wurde.
gp  componentMoved( ComponentEvent )
Wenn die Komponente bewegt wurde.
gp  componentResized( ComponentEvent )
Wenn die Komponente in der Größe verändert wurde.
gp  componentShown( ComponentEvent )
Wenn die Komponente gezeigt wurde.

Wenn wir auf die Größenveränderung eines Fensters achten wollen, so müssen wir einen Listener an das Fenster hängen (addComponentListener()), der auf die Veränderung reagiert. Wir implementieren eine Klasse, die sich von ComponentAdapter ableitet – so müssen wir nicht die restlichen drei Methoden mit leeren Rümpfen füllen – und achten auf componentResized(). Wenn wir nur das Frame-Objekt mit einer Methode verbinden, ist klar, wer der Auslöser war. Mit getComponent() auf dem Ereignisobjekt bekommen wir genau diesen Frame. Nun ist es Sache des Algorithmus, wie mit der Größenveränderung zu verfahren ist. Eine Möglichkeit ist, die aktuelle Größe mittels getSize() auszulesen und dann das Minimum aus Höhe und Breite wieder mit setSize() zu setzen. Folgendes Programm macht genau dies:

Listing 15.5   ResizeTheFrame.java
import java.awt.*;
import java.awt.event.*;

public class ResizeTheFrame extends Frame
{
public ResizeTheFrame()
{
setSize( 400,300 );
setVisible( true );
}

public static void main( String args[] )
{
Frame f = new ResizeTheFrame();

f.addComponentListener( new ResizeEvent() );
}

static class ResizeEvent extends ComponentAdapter
{
public void componentResized( ComponentEvent event )
{
if ( event.getID() == ComponentEvent.COMPONENT_RESIZED )
{
Frame f = (Frame) event.getComponent();

Dimension d = f.getSize();

int size = Math.min( d.width, d.height );

f.setSize( size, size );
}
}
}
}

Wenn die Größenveränderung beginnt, haben wir keinen Einfluss mehr auf den Prozess. Hier unterscheiden sich die Betriebssysteme. Unter Windows wird nicht bei jeder Pixeländerung ein Ereignis ausgelöst, sondern nur am Ende einer Größenänderung. Bei Linux werden während der Bewegung Ereignisse ausgelöst, sodass eine flüssige Bewegung entsteht.


Galileo Computing

15.4.3 Hinzufügen von Komponenten  downtop

Ein Container nimmt Komponenten auf und setzt sie mit Hilfe eines Layout-Managers in die richtige Position. Die Container werden in Java über die Klasse Container verwaltet. Ein Container, den wir schon kennen gelernt haben ist Frame. Komponenten werden mithilfe der add()-Methode einem Container hinzugefügt.

Abbildung 15.1   AWT-Container
Abbildung

Abbildung 15.2   Ein Container kann sich selbst aufnehmen
Abbildung

Da Container selbst wiederum ein Component ist, können auch Container wiederum Container aufnehmen.


Galileo Computing

15.5 Ein Informationstext über die Klasse Label  downtop

Die Klasse Label ist eine der einfachsten Komponenten in Java. Es repräsentiert eine Zeichenkette, die sich an eine beliebige Position setzen lässt. Sie kann vom Benutzer nicht editiert werden. Zum Einsatz kommt das Label zum Beispiel in einer Dialogbox.

Abbildung 15.3   Ein Label unter dem AWT
Abbildung

Anstatt die Label-Klasse zu nutzen, hätten wir selbstverständlich auch mit drawString() eine Zeichenkette schreiben können. Doch Label bietet den Vorteil, dass wir von paint() befreit werden, und die paint()-Methode für andere Aufgaben frei bleibt. So übernimmt das AWT das Zeichnen und passt in der Breite des Containers den Text an. Über drawString() hätten wir dann erst die Koordinaten berechnen müssen und dies wäre besonders bei komplexeren Oberflächen umständlicher Rechenaufwand.

Wie jede andere Komponente, wird diese mit der add()-Methode auf den Bildschirm gebracht. Labels lösen keine eigenen Events aus. Da aber Label eine Unterklasse von Component ist, reagiert sie natürlich auf Component-Ereignisse wie das Erzeugen.

Beispiel Label kann Maus-Ereignisse empfangen. Wir nutzen dies, um bei einem Doppelklick die Applikation zu beenden.

Abbildung 15.4   Ein Label unter dem AWT
Abbildung

Listing 15.6   LabelDemo.java
import java.awt.*;
import java.awt.event.*;

public class LabelDemo extends Frame
{
public LabelDemo()
{
setBackground( Color.blue );

Label label = new Label( "Hit me", Label.CENTER );
label.setForeground( Color.yellow );
add( label );

label.addMouseListener( new MouseAdapter() {
public void mouseClicked( MouseEvent e ) {
if ( e.getClickCount() > 1 )
System.exit( 0 );
}
} );
}

public static void main( String args[] )
{
LabelDemo label = new LabelDemo();
label.pack();
label.setVisible( true );
}
}

Genutzter Zeichensatz des Texts

Der gesetzte Text wird im aktuellen Zeichensatz angezeigt. Um diesen zu ändern, müssen wir ein neues Font-Objekt erzeugen, und dieses dann mit der setFont()-Methode setzen. Wir können uns entscheiden, ob wir nur den Zeichensatz der Komponente zuweisen wollen oder allen folgenden Elementen des Containers. Denn so legen wir fest, wo wir setFont() nutzen: Entweder wir rufen setFont() als Methode von Frame (und somit von Component) auf und bestimmen somit alle nachfolgenden Objekte mit einem Zeichensatz oder wir rufen label.setFont() auf und setzen dann nur den Zeichensatz des konkreten Label-Objekts. Sollen verschiedene Zeichensätze verwendet werden, sind diese immer vor dem Erzeugen des Labels zu setzen. Einen speziellen Konstruktor, der ein Font-Objekt als Parameter annimmt und diesen verwendet, gibt es nicht – eine solche Methode ist aber schnell geschrieben und ganz praktisch.

Ausrichtung des Labels

Neben dem Standard-Konstruktor, der einen leeren String schreibt, existiert eine weitere Variante neben dem Konstruktor mit Textinhalt, der die Ausrichtung des Labels angibt. Diese kann LEFT – dies ist voreingestellt – CENTER oder RIGHT sein. Im Nachhinein lässt sich der Text mit setText(String) ändern. Der Text wird aber erst dann angezeigt, wenn die Komponente neu gezeichnet wird. Dies kann etwa mit repaint() erzwungen werden. Mit getText() lässt sich der aktuelle Text auch auslesen. Hier sehen wir, dass es typische Methoden für grafische Komponenten sind.

class java.awt.Label
extends Component

gp  Label()
Erzeugt ein leeres Label.
gp  Label( String )
Erzeugt ein Label mit links angeordneten Text.
gp  Label( String, int alignment )
Erzeugt ein Label mit ausgerichtetem Text. alignment ist eine Konstante der Form Label.LEFT, Label.RIGHT oder Label.CENTER. Wird die Größe der Komponente, auf dem das Label liegt, neu berechnet, so passt sich auch bei einem Alignment die Position neu an.
gp  String getText()
Liefert den Text des Labels.
gp  void setText( String text )
Ändert die Aufschrift des Labels im laufenden Betrieb.
gp  void setAlignment( int alignment )
Setzt die Ausrichtung des Labels. Mögliche Werte sind Label.LEFT, Label.RIGHT und Label.CENTER.
gp  int getAlignment()
Liefert die Ausrichtung zurück.

Galileo Computing

15.5.1 Mehrzeiliger Text  downtop

Sporadisch tritt das Problem auf, dass ein Text mit Zeilenumbruch gesetzt werden soll. Häufig ist dies eine Aufgabe beim Programmieren von Dialogen, die mehrzeilige Antworten bereithalten. Leider führt eine Anweisung wie

new Label( "erste Zeile\nzweite 
Zeile" );

sowohl bei alten Peer-basierten Komponenten und auch bei den neuen Swing-Set-Objekten nicht zum Ziel. Um dies zu erreichen, muss ein anderer Weg eingeschlagen werden, denn »\n« besitzt in einem Label keine Bedeutung. Eine Lösung besteht darin, die Text Area-Klasse zu nehmen. Eine andere Möglichkeit wäre, eine Hilfsfunktion zu bauen, die den Text auseinander nimmt und ihn in zwei Zeilen aufteilt. Aber diese Mühe können wir uns sparen, wenn wir die Klasse MultiLineLabel nutzen. Sie ist in Form eines JavaBean bei Wildcrest Associates (http://www.wildcrest.com) verfügbar. Neben dem bewussten Zeilenumbruch bricht das Bean auch automatisch um. Des Weiteren lassen sich die Texte mit Tabulatoren formatieren und Zeilen-Abstände, Fonts und Margins frei wählen. Der einzige Nachteil bei dieser Komponente ist, dass sie $70 kostet. Falls wir Swing benutzen können, haben wir das Problem sowieso nicht mehr, da mittels HTML-Tags auch mehrzeiliger Text gesetzt werden kann. Im Labeltext schreiben wir dann einfach <HTML>Huhu<P>Jetzt bin ich hier</HTML>.

Listing 15.7   MultiLineLabelTest.java
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import com.wildcrest.MultiLineLabel;

public class
MultiLineLabelTest extends Applet implements TextListener {
TextArea ta;
MultiLineLabel mLL;

public void init() {
setLayout(new FlowLayout());

ta = new TextArea("Enter desired text here...",10,30,
TextArea.SCROLLBARS_VERTICAL_ONLY);
add(ta);
mLL = new MultiLineLabel();
mLL.setLabelWidth(250);
mLL.setBackground(Color.pink);
mLL.setText("...here will be the result");
mLL.setFont(new Font("SansSerif", Font.PLAIN, 11));
add(mLL);

ta.addTextListener(this);
}
public void textValueChanged(TextEvent e) {
mLL.setText(ta.getText());
}
}

Galileo Computing

15.6 Eine Schaltfläche (Button)  downtop

Eine Schaltfläche wird verwendet, wenn der Anwender eine Aktion auslösen soll. Schaltflächen sind meistens beschriftet und stellen eine Zeichenkette dar. Erst mit der Schaltfläche unter Swing lässt sich auch eine Grafik mit auf die Schaltfläche bringen.

Die Schaltfläche kann reagieren, wenn sie gedrückt wird. Dann erzeugt sie ein ActionEvent.

Abbildung 15.5   Eine Schaltfläche unter dem AWT
Abbildung

Listing 15.8   ButtonDemo.java
import java.awt.*;
import java.awt.event.*;

public class ButtonDemo extends Frame
{
public ButtonDemo()
{
Button b = new Button( "Ende" );
add( b );

ActionListener al = new ActionListener() {
public void actionPerformed( ActionEvent e ) {
System.exit( 0 );
}
};

b.addActionListener( al );
}


public static void main( String args[] )
{
ButtonDemo button = new ButtonDemo();
button.pack();
button.setVisible( true );
}
}

Es gibt zwei Konstruktoren für Button-Objekte. Die parameterlose Variante erzeugt eine Schaltfläche ohne Text. Der Text lässt sich aber mit setLabel() nachträglich ändern. In der Regel nutzen wir den Konstruktor, dem ein String mitgegeben wird.

Einen Konstruktor, der einen neuen Zeichensatz bestimmt, gibt es genauso wenig, wie einen Konstruktor, der ein Bild anstatt des Texts anzeigt. Dazu müssen wir in Richtung JButton schauen. Dies ist die Swing-Version des Peer-Buttons.

Abbildung 15.6   Ein Button unter dem AWT
Abbildung

class java.awt.Button
extends Component

gp  Button()
Erzeugt einen neuen Button ohne Aufschrift.
gp  Button( String )
Erzeugt einen neuen Button mit Aufschrift.
gp  setLabel( String )
Ändert die Aufschrift des Buttons im laufenden Betrieb.
gp  addActionListener( ActionListener )
Fügt dem Button einen ActionListener hinzu, der die Ereignisse, die durch die Schaltfläche ausgelöst werden, abgreift.
gp  removeActionListener( ActionListener )
Entfernt den ActionListener wieder. Somit kann er keine weiteren Ereignisse mehr abgreifen.

Wie wir schon gesehen haben, lässt sich der Text eines Button-Objekts nachträglich mit setLabel(String) setzen und mit der entsprechenden Methode getLabel() auslesen. Erinnern wir uns noch einmal an die Methoden von Label. Hier heißen die Methoden zum Lesen und Ändern des Texts setText() und getText(). Dies ist für mich wieder eines der AWT-Rätsel. Warum heißt es bei einem Label-Objekt xxxText() und bei einem Button-Objekt xxxLabel()?


Galileo Computing

15.6.1 Der aufmerksame ActionListener  downtop

Drücken wir die Schaltfläche, so sollte die Aktion gemeldet werden. Diese wird in Form eines Action-Ereignisses an den Zuhörer (ein ActionLister) gesandt. Ein ActionListener wird mit der Methode addActionListener() an die Objekte angeheftet, die Aktionen auslösen können. ActionListener ist eine Schnittstelle, mit der Methode actionPerformed(). ActionListener erweitert die Schnittstelle EventListener, die von allen Listener-Interfaces implementiert werden muss.

interface java.awt.event.ActionListener
extends EventListener

gp  abstract void actionPerformed( ActionEvent e )
Wird aufgerufen, wenn eine Aktion ausgelöst wird.

Werfen wir noch einmal einen Blick auf die Implementierung von ActionListener:

ActionListener al = new ActionListener() 
{
public void actionPerformed( ActionEvent e ) {
System.exit( 0 );
}

Wird ein Event ausgelöst, dann wird die Methode actionPerformed() aufgerufen. Wir sehen, dass als Parameter ein ActionEvent übergeben wird. Die Klasse besitzt drei Methoden:

gp  getActionCommand() liefert einen String, der mit dieser Aktion verbunden ist. In einer modalen Anwendung lassen sich damit die Komponenten unterscheiden. Bei den Button-Objekten handelt es sich bei diesem String um die Aufschrift der Schaltfläche. Mit wenig Aufwand lässt sich so eine Schaltfläche erzeugen, die zwei Zustände beinhalten kann, beispielsweise mehr Details oder weniger Details. Der Handler für das Action Event bleibt also der gleiche, er kann aber über die Zeichenkette den Zustand erlesen. Leider verfangen wir uns bei dieser Programmmethode schnell in landessprachenabhängige Software.
gp  Die zweite Methode kennt die Umschalttasten, die während des Ereignisses gedrückt waren. Der Funktionsname ist getModifiers(). Die Masken sind selbstsprechend. Folgende sind als Konstanten definiert: SHIFT_MASK, CTRL_MASK, META_MASK, ALT_MASK. Diese Konstanten kommen direkt aus der Event-Klasse. So ist etwa ALT_MASK = Event.ALT_MASK. Zu diesen Masken gibt es noch zusätzliche: ACTION_FIRST, ACTION_LAST und ACTION_PERFORMED, die ein Synonym für ACTION_FIRST ist.
gp  Die letzte Methode paramString() ist von AWTEvent überschrieben und implementiert gemäß der Vorgabe eine Methode, die einen String liefert, der den Typ der Operation und getActionCommand() vereint. Der String beginnt entweder mit »ACTION_PERFORMED« oder mit »unknown type«.
class java.awt.event.ActionEvent
extends AWTEvent

gp  String getActionCommand()
Liefert den String, der mit dieser Aktion verbunden ist.
gp  int getModifiers()
Liefert den Umschaltcode für Tasten, die während des Ereignisses gedrückt waren.
gp  String paramString()
Liefert Erkennungsstring für das Event.
Abbildung

Den Erkennungsstring ändern

Wir haben gesehen, dass wir mit der getActionCommand()-Methode aus ActionEvent ein Kommandostring erfragen können, der im Fall der Schaltfläche mit der Beschriftung übereinstimmt. Dies ist allerdings etwas einschränkend und so bietet die Button-Klasse die Methode setActionCommand() an, mit der dieser String geändert werden kann. Besonders bei mehrsprachigen Anwendungen, wo die Aufschrift sich ändern kann, ist der Einsatz angebracht.

class java.awt.Button
extends Component

gp  void setActionCommand( String command )
Setzt einen neuen Kommandostring, wenn das Ereignis ausgeführt wird. So empfängt getActionCommand() dann command.
Hinweis Wörter mit einer starken emotionalen Bindung sollten vermieden werden. In englischen Programmen müssen Wörter wie »kill« oder »abort« umgangen werden.


Galileo Computing

15.6.2 Generic Listener für Schaltflächen-Ereignisse verwenden  downtop

Die Idee bei den Generic Listenern war, einen Ereignisauslöser mit einer Methode zu verbinden, die automatisch mittels Reflection aufgerufen wird. Damit das für die Ereignisbehandlung funktioniert, muss der Aufrufer natürlich die Methode finden und die Ereignisse zustellen können. Wir zeigen zunächst an einem Beispiel, wie an einer Schaltfläche button eine ActionListener gebunden wird. Dazu definieren wir uns erst einmal eine Methode, die im Falle eines Ereignisses aufgerufen werden soll. Sie trägt nicht den typischen Methodennamen actionPerformed, nimmt aber genauso als Parameter ein ActionEvent-Objekt entgegen. Der Parameter darf nicht fehlen.

public void gedrückt( ActionEvent 
e ) {
System.out.println( "Button über Proxy" );
}

Als Nächstes müssen wir ein ActionListener-Objekt konstruieren und mittels der Methode addActionListener() der Schaltfläche hinzufügen. Den ActionListener konstruieren wir uns mit der statischen Methode create() vom GenericListener.

ActionListener buttonActionListener 
=
(ActionListener)(GenericListener.create(
ActionListener.class,
"actionPerformed",
this,
"gedrückt") );
button.addActionListener( buttonActionListener );

Die Parameter von create() sind:

gp  Die Schnittstelle, von der das temporäre Event-Handler abgeleitet ist.
gp  Die Methode der Schnittstelle, die in Wirklichkeit aufgerufen wird.
gp  Das Zielobjekt (bei this sind wir das).
gp  Die Zielmethode, also die Methode, die wir selber implementieren, die im Fall des Ereignisses aufgerufen wird.
Beispiel Wir wollen ein Programm implementieren, das zwei Schaltflächen anordnet.

Die eine Schaltfläche soll über einen herkömmlichen Mechanismus die Ereignisse verwalten und das über eine innere anonyme Klasse. Die zweite Schaltfläche soll den neuen Mechanismus mit GenericListener einsetzen. Dann soll dem Frame noch eine Methode gegeben werden, damit das Fenster geschlossen wird. Interessant ist bei den Generic-Listenern, dass die Auswahl einer Methode auch dann funktioniert, wenn die Schnittstelle mehrere Methoden vorschreibt. Das gilt etwa bei einem WindowListener. Wir können uns dennoch nur für eine Methode entscheiden.

WindowListener winListener =
(WindowListener)(GenericListener.create(
WindowListener.class,
"windowClosing",
this,
"fensterWirdGeschlossen") );

Obwohl der WindowListener viele Methoden vorschreibt, leiten wir lediglich bei einem windowClosing an unsere eigene Methode fensterWirdGeschlossen weiter. Und so sieht das gesamte Programm aus:

Listing 15.9   GenericListenerDemo
import java.awt.*;
import java.awt.event.*;

public class GenericListenerDemo extends Frame
{
GenericListenerDemo()
{
Button button1 = new Button( "Normaler Listener" );
Button button2 = new Button( "Dynamischer Listener" );

setLayout( new GridLayout(2,0) );
add( button1 );
add( button2 );

pack();
show();

// Listener wie bekannt hinzufügen

button1.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
button1Action(e);
}
} );


// Die andere Variante

ActionListener button2ActionListener =
(ActionListener)(GenericListener.create(
ActionListener.class,
"actionPerformed",
this,
"button2Action"));

button2.addActionListener(button2ActionListener);

// windowClosing() ist nur eine Methoden von vielen

WindowListener winListener =
(WindowListener)(GenericListener.create(
WindowListener.class,
"windowClosing",
this,
"fensterWirdGeschlossen"));

addWindowListener( winListener );
}

public void button1Action( ActionEvent e ) {
System.out.println( "Buttton 1" );
}

public void button2Action( ActionEvent e ) {
System.out.println( "Button 2 über Proxy" );
}

public void fensterWirdGeschlossen( WindowEvent e ) {
System.exit( 0 );
}

public static void main( String args[] )
{
new GenericListenerDemo();
}
}

Galileo Computing

15.7 Horizontale und vertikale Schieberegler  downtop

Ein Schieberegler (engl. Scrollbar) ist eine grafische Benutzerschnittstelle, die es einem Benutzer auf einfache Weise ermöglicht, einen ganzzahligen Wert aus einem vorher festgelegten Wertebereich auszuwählen. Der Schieberegler kann vertikal oder horizontal angeordnet werden. Mittels eines verschiebbaren Knopfes lässt sich ein Wert auswählen. Er besitzt für Verschiebungen folgende Regel für den internen Wert: Bei einem vertikalen Schieberegler ist oben der größte Wert und bei einem horizontalen Schieberegler liegt dieser auf der linken Seite. Wird der Slider verändert, werden AdjustmentEvent-Objekte erzeugt.

Abbildung 15.7   Ein Scrollbar unter dem AWT
Abbildung

Das folgende Programm verwendet ein Scrollbar- und ein TextField-Objekt. Beide benachrichtigen sich bei Änderungen. Wird also im Textfeld eine neue Zahl gesetzt, zeigt der Schieberegler diese an. Verändern wir den Regler, so wird die passende Zahl in das Textfeld gesetzt. Sie werden mit Listener-Objekten betrieben.

Listing 15.10   ScrollbarDemo.java
import java.awt.*;
import java.awt.event.*;

public class ScrollbarDemo extends Frame
{
static TextField tf;
static Scrollbar sb;

public ScrollbarDemo()
{
setLayout( new GridLayout(2,1) );

add ( tf = new TextField( 20 ) );

add (sb = new Scrollbar(
Scrollbar.HORIZONTAL,50,10,0,100+10));

tf.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
sb.setValue( Integer.parseInt(tf.getText()) );
}
} );

sb.addAdjustmentListener( new AdjustmentListener() {
public void adjustmentValueChanged( AdjustmentEvent e ) {
tf.setText( "" + sb.getValue() );
}
} );

addWindowListener( new WindowAdapter() {
public void windowClosing ( WindowEvent e ){System.exit(0);}
} );

pack();
}

public static void main( String args[] )
{
new ScrollbarDemo().setVisible( true );
}
}

Konstruktoren

Die Klasse Scrollbar besitzt drei Konstruktoren für Schieberegler. Der mit einem int parametrisierte Konstruktor erlaubt es, die Orientierung festzulegen. Dazu sind zwei Konstanten in der Klasse Scrollbar definiert: Scrollbar.HORIZONTAL und Scrollbar.VERTICAL. Ein Schieberegler kann immer nur eines von beiden sein. Geben wir eine andere Ganzzahl an, folgt eine IllegalArgumentException("illegal scrollbar orientation"). Der parameterlose Konstruktor erzeugt automatisch einen vertikalen Schieberegler. Im dritten Konstruktor kommen noch weitere Parameter hinzu. Die Parameter sind in folgender Reihenfolge gegeben: orientation, value, visible, minimum, maximum. Der Datentyp ist int. Zunächst sind es die Eigenschaften minimum und maximum, die die Grenzen des Wertes angeben, die der Schieberegler annehmen kann. Geben wir maximum kleiner oder gleich minimum an, dann wird maximum = minimum + 1 gesetzt. Der weitere zusätzliche Parameter value bestimmt den Startwert des Schiebereglers. value darf nicht kleiner als minimum sein. Ist er es dennoch, so wird value = minimum gesetzt. Für die beiden anderen Konstruktoren gilt, dass das Minimum auf 0 und das Maximum auf 100 gestellt wird. Der value steht bei beiden auf 0, das heißt, bei einem vertikalen Schieberegler steht der Knopf unten und bei einem horizontalen Schieberegler links. Mit visible können wir die Größe des Schiebers bestimmen. Daneben hat visible aber noch eine andere Funktion, denn es bestimmt zusätzlich die Größe der Veränderung, wenn der Schieber bewegt wird – mit anderen Worten: die Schrittweite. Da unter Windows die Schieber nicht proportional zur Größe des darstellenden Inhalts sind, lässt sich hier leicht ein proportionaler Schieberegler implementieren. So können wir leicht von der Größe des Knopfs auf den Umfang schließen. Der Wert von visible muss kleiner sein, als der Wertebereich des Schiebereglers (visible <= maximumminimum). Verstößt visible gegen die einfache Gleichung, wird visible = maximumminimum gewählt. Erreicht visible einen Wert kleiner 1, wird dieser zurück auf 1 gesetzt. Für die beiden anderen Konstruktoren ist der Wert von visible auf 10 festgelegt.

class java.awt.Scrollbar
extends Component implements Adjustable

gp  Scrollbar()
Erzeugt einen vertikalen Schieberegler.
gp  Scrollbar( int orientation )
Erzeugt einen neuen Schieberegler mit der angegebenen Richtung. orientation kann entweder Scrollbar.HORIZONTAL oder Scrollbar.VERTICAL sein.
gp  Scrollbar( int orientation, int value, int visible, int minimum, int maximum )
Erzeugt einen neuen Schieberegler mit der angegebenen Richtung. orientation kann Scrollbar.HORIZONTAL oder Scrollbar.VERTICAL sein. Zudem wird der Initialwert, die Größe, Minimum und Maximum gesetzt.
gp  void addAdjustmentListener( AdjustmentListener l )
Fügt einen AdjustmentListener zu, der AdjustmentEvent auffängt.
gp  void addNotify()
Erzeugt den Peer vom Schieberegler.
gp  int getBlockIncrement()
Liefert das Inkrement vom Schieberegler.
gp  int getMaximum(), getMinimum()
Liefert das Maximum bzw. Minimum des Schiebereglers.
gp  int getOrientation()
Liefert die Orientierung des Schiebereglers.
gp  int getUnitIncrement()
Liefert die Einheit, um die der Schieberegler inkrementiert wird.
gp  int getValue()
Liefert den aktuellen Wert des Schiebereglers.
gp  int getVisibleAmount()
Liefert den sichtbaren Bereich des Schiebereglers.
gp  String paramString()
Gibt den Status des Schiebereglers als String zurück.
gp  void processAdjustmentEvent( AdjustmentEvent e )
Aufkommende Adjustment-Ereignisse werden an alle registrierten AdjustmentListener-Objekte weitergeleitet.
gp  void removeAdjustmentListener( AdjustmentListener l )
Entfernt den AdjustmentListener wieder.
gp  void setBlockIncrement( int v )
Setzt das Block-Inkrement für den Schieberegler.
gp  void setMaximum(int newMaximum), void setMinimum(int newMinimum)
Setzt den maximalen bzw. minimalen Wert des Schiebereglers.
gp  void setOrientation( int orientation )
Setzt die Orientierung neu.
gp  void setUnitIncrement( int v )
Setzt die Größe bei Veränderung.
gp  void setValue( int newValue )
Setzt einen neuen Wert.
gp  void setValues(int value, int visible, int minimum, int maximum)
Setzt die vier Eigenschaften beim Schieberegler.
gp  void setVisibleAmount( int newAmount )
Setzt den sichtbaren Bereich des Schiebereglers.

Galileo Computing

15.7.1 Der AdjustmentListener, der auf Änderungen hört  downtop

Objekte, die an Änderungen interessiert sind, müssen die Schnittstelle AdjustmentListener implementieren und sich beim jeweiligen Scrollbar registrieren.

gp  abstract void adjustmentValueChanged( AdjustmentEvent e )
Wird vom Scrollbar aufgerufen, wenn das Objekt seinen Wert verändert.

Das AdjustmentEvent verrät uns, welches Ereignis zu der Veränderung geführt hat.

class java.awt.AdjustmentEvent
extends AWTEvent

gp  Adjustable getAdjustable()
Gibt das Adjustable-Objekt zurück, bei dem das Event auftrat.
gp  int getValue()
Gibt den Wert im Adjustment-Objekt zurück.
gp  int getAdjustmentType()
Gibt eine der Konstanten UNIT_INCREMENT, UNIT_DECREMENT, BLOCK_INCREMENT, BLOCK_DECREMENT oder TRACK zurück, die den Typ des Ereignisses beschreiben.
Beispiel Wir beschreiben im folgenden Programmlisting einen AdjustmentListener, der alle möglichen Informationen über die Bewegung des Schiebereglers protokolliert.
public void adjustmentValueChanged( 
AdjustmentEvent event )
{
  Adjustable sb = event.getAdjustable();

  if ( sb.getOrientation() == Scrollbar.HORIZONTAL )
    System.out.println( "Horizontal" );
  else
    System.out.println( "Vertikal" );

  switch ( event.getAdjustmentType() )
  {
    case AdjustmentEvent.UNIT_INCREMENT:
      System.out.println( "AdjustmentEvent.UNIT_INCREMENT" );
      break;
	  
    case AdjustmentEvent.UNIT_DECREMENT:
      System.out.println( "AdjustmentEvent.UNIT_DECREMENT" );
      break;
	  
    case AdjustmentEvent.BLOCK_DECREMENT:
      System.out.println( "AdjustmentEvent.BLOCK_DECREMENT" );
      break;
 
    case AdjustmentEvent.BLOCK_INCREMENT:
      System.out.println( "AdjustmentEvent.BLOCK_INCREMENT" );
      break;

    case AdjustmentEvent.TRACK:
      System.out.println( "AdjustmentEvent.TRACK" );
      break;
  }
  System.out.println("  value: " + event.getValue());
}

Abbildung


Galileo Computing

15.8 Ein Auswahlmenü – Das Choice-Menü  downtop

Ein Auswahlmenü (engl. Choice-Box) zeigt eine Zeichenkette aus einer Liste von Möglichkeiten an. Wird die Choice-Box aufgeklappt, kann ein Element aus der List-Box gewählt werden. Ein neuer Eintrag erscheint dann im Titel des Menüs.

Abbildung 15.8   Das Auswahlmenü unter AWT
Abbildung

Beim Auswählen eines Eintrags wird ein ItemEvent ausgelöst, mit dem wir den ausgewählten String erfragen können. Das Choice-Objekt bietet Methoden, mit denen Zeichenketten eingefügt und entfernt werden können.

Listing 15.11   ChoiceDemo.java
import java.awt.*;
import java.awt.event.*;

public class ChoiceDemo extends Frame
{
public ChoiceDemo()
{
super( "Suchmaschinen-Treffer" );

Choice choice = new Choice();

choice.add( "www.northernlight.com, 16%" );
choice.add( "www.altavista.com, 15.5%" );
choice.add( "www.snap.com, 15.5%" );
choice.add( "www.hotbot.com, 11.3%" );
choice.add( "www.microsoft.com, 8.5%" );
choice.add( "www.infoseek.com, 8%" );
choice.add( "www.google.com, 7.8%" );
choice.add( "www.yahoo.com, 7.4%" );
choice.add( "www.excite.com, 5.6%" );
choice.add( "www.lycos.com, 2.5%" );
choice.add( "www.euroseek.com, 2.2%" );

choice.add( "Ende" );

add( choice );

choice.addItemListener( new ItemListener() {
public void itemStateChanged( ItemEvent e ) {
Choice selectedChoice = (Choice)e.getSource();
if ( selectedChoice.getSelectedItem().equals("Ende") )
System.exit(0);
}
});

pack();
}

public static void main( String args[] )
{
new ChoiceDemo().setVisible( true );
}
}
class java.awt.Choice
extends Component implements ItemSelectable

gp  Choice()
Erzeugt ein Choice-Objekt.
gp  void addItem( String), add( String )
Fügt einen Punkt dem Auswahlmenü hinzu. Beide Funktionen sind identisch. add() ruft addItem() auf.
gp  String getItem( int index )
Liefert den Eintrag an der Position index.
gp  int getItemCount()
Liefert die Anzahl der Einträge im Auswahlmenü.
gp  void insert( String, int index )
Fügt einen Eintrag an eine bestimmte Stelle ein.
gp  void remove( String )
Löscht den String aus der Liste.
gp  void remove( int position )
Löscht den Eintrag an der Position position.
gp  void removeAll()
Entfernt alle Einträge aus dem Auswahlmenü.
gp  String getSelectedItem()
Liefert die aktuelle Wahl als String.
gp  Object[] getSelectedObjects()
Liefert ein Array mit den selektierten Einträgen. null, wenn keine Einträge vorhanden sind.
gp  int getSelectedIndex()
Liefert den Index des aktuell selektierten Eintrags.
gp  void select( int position )
Setzt den Eintrag im Titel des Menüs auf den Eintrag mit der Nummer position.
gp  void select( String string )
Setzt den Eintrag string in die Titelleiste, wenn dieser in der Liste ist. Falls mehrere Einträge gleich dem string sind, wird der Eintrag genommen, der zuerst gefunden wurde. Dieser besitzt dann also den kleinsten Index.
gp  void addItemListener( ItemListener )
Hängt einen ItemListener an das Auswahlmenü an.
gp  void removeItemListener( ItemListener )
Löst den ItemListener vom Auswahlmenü.

Galileo Computing

15.8.1 ItemListener  downtop

Die Schnittstelle ItemListener wird von allen Objekten implementiert, die an einem Auswahlereignis interessiert sind. Wird ein Element ausgewählt, wird die Methode itemState Changed() vom Objekt auf allen registrierten ItemListenern aufgerufen. Folgende Komponenten besitzen Einträge und können dementsprechend ItemChanged-Ereignisse auslösen: Checkboxen, Checkbox-Menü-Einträge, Choice-Menüs und Listen.

Beispiel Die Methode itemStateChanged()
public void itemStateChanged( ItemEvent 
e )
{
if (e.getStateChange() == ItemEvent.SELECTED)
label.setVisible( true );
else
label.setVisible( false );
}

interface java.awt.event.ItemListener
extends EventListener

gp  void itemStateChanged( ItemEvent e )
Wird aufgerufen wenn ein Eintrag selektiert oder deselektiert wird.

Die Funktion itemStateChanged() hat einen einzigen Parameter, ein ItemEvent-Objekt.

Abbildung

class java.awt.event.ItemEvent
extends AWTEvent

gp  ItemSelectable getItemSelectable()
Gibt ein ItemSelectable-Objekt von der Komponente zurück, die das Ereignis ausgelöst hat.
gp  Object getItem()
Gibt den Eintrag der Liste zurück.
gp  int getStateChange()
Gibt den Status des Eintrags zurück. Die Klasse definiert dazu die Konstanten Item Event.SELECTED und ItemEvent.DESELECTED.

Galileo Computing

15.9 Eines aus vielen – Kontrollfeld (Checkbox)  downtop

Ein Kontrollfeld ist eine Komponente mit einem Zustand: Ein oder Aus. Der Zustand wird meistens als Rechteck oder Kreis neben einer Zeichenkette dargestellt. Kontrollfelder dienen dem Benutzer meistens als Auswahl von Optionen. Bei einer Pizza-Bestellung kann etwa ein Optionenfeld die Beläge anbieten. Hier würde ich dann immer Pilze, Paprika und Zwiebeln wählen.

Wir müssen unterscheiden, ob sich Kontrollkästchen gegenseitig ausschließen oder nicht. Falls sie sich ausschließen, kann nur ein Kontrollfeld markiert sein, wobei es bei nicht ausschließenden Feldern keine Beschränkung gibt. Sind die Kontrollkästchen in einer Gruppe (Kontrollfeldgruppe) organisiert, so werden sie auch Optionsfelder genannt. Der Name sagt es schon aus: Eine Option kann gesetzt werden. Bei einem Druckdialog etwa: Wollen wir den Ausdruck in Farbe oder Schwarzweiß.

Ob nun Kontrollfelder in einer Gruppe sind oder nicht, beide werden über die Klasse Checkbox erzeugt. So schließen sie sich standardmäßig nicht aus. Um Gruppen kümmern wir uns im nächsten Abschnitt. Der Konstruktor Checkbox() oder Checkbox(String) erzeugt ein leeres Kontrollfeld oder ein Kontrollfeld mit dem String, das nicht ausgewählt ist. Nach dem Anlegen des Objekts kann die Methode setState(boolean) den Status verändern. Das Argument true markiert die Option des Kontrollfelds. getState() liefert den aktuellen Status des Kontrollfelds. Ändert sich der Zustand des Kontrollfelds (Selektion oder Deselektion), so wird ein ItemEvent an alle registrierten ItemListener weitergeleitet.

Abbildung

Listing 15.12   CheckboxDemo.java
import java.awt.*;
import java.awt.event.*;

public class CheckboxDemo extends Frame
{
public CheckboxDemo()
{
setLayout( new FlowLayout() );
setBackground( Color.gray.brighter() );

CheckboxGroup g = new CheckboxGroup();

Checkbox dogCheckbox = new Checkbox( "Hund", g, true );
Checkbox catCheckbox = new Checkbox( "Katze", g, false );
Checkbox cowCheckbox = new Checkbox( "Kuh", g, false );

ItemListener animalListener = new ItemListener() {
public void itemStateChanged( ItemEvent e ) {
System.out.println( e );
}
};

add( dogCheckbox );
dogCheckbox.addItemListener( animalListener );

add( catCheckbox );
catCheckbox.addItemListener( animalListener );

add( cowCheckbox );
cowCheckbox.addItemListener( animalListener );

Checkbox quitCheckbox = new Checkbox( "Ende" );
add( quitCheckbox );

quitCheckbox.addItemListener( new ItemListener() {
public void itemStateChanged( ItemEvent e ) {
if ( e.getStateChange() == ItemEvent.SELECTED )
System.exit(0);
}
} );
pack();
}

public static void main( String args[] )
{
new CheckboxDemo().setVisible( true );
}
}
Abbildung 15.9   Die Checkbox unter dem AWT
Abbildung

Wählen wir nach dem Starten die Knöpfe, haben wir eine Ausgabe wie folgt:

java.awt.event.ItemEvent\
[ITEM_STATE_CHANGED,item=Katze,stateChange=SELECTED] on checkbox0
java.awt.event.ItemEvent\
[ITEM_STATE_CHANGED,item=Kuh,stateChange=SELECTED] on checkbox1
java.awt.event.ItemEvent\
[ITEM_STATE_CHANGED,item=Katze,stateChange=SELECTED] on checkbox0
java.awt.event.ItemEvent\
[ITEM_STATE_CHANGED,item=Hund,stateChange=SELECTED] on checkbox2
gp  Checkbox()
Baut eine Standard-Checkbox auf.

Galileo Computing

15.9.1 Ereignisse über ItemListener  downtop

Ein Checkbox-Objekt löst genau dann ein ItemEvent aus, wenn das Kontrollfeld ein- oder ausgeschaltet wird. Mittels addItemListener() wird ein ItemListener registriert, der bei einer Zustandsänderung über die itemStateChanged(ItemEvent)-Methode benachrichtigt wird.

Mittels der Objektmethoden getStateChange() von ItemEvent lässt sich der Status erfragen. Der Rückgabewert ist ein Integer uns sollten ihn mit den Konstanten Item Event.SELECTED und ItemEvent.DESELECTED vergleichen. Das Objekt ItemEvent stellt die Methode getItemSelectable() zur Verfügung, mit der wir herausfinden können, welche Checkbox das Ereignis ausgelöst hat. Somit ergibt sich noch eine andere Strategie, um den Zustand des Kontrollfelds zu erfragen. Wir rufen einfach die Objektmethode getState() aus dieser Checkbox auf, die getItemSelectable() lieferte. ItemEvent-Objekte werden auch bei Listen eingesetzt.

public void itemStateChanged( ItemEvent 
e )
{
Checkbox cb = (Checkbox) e.getItemSelectable();
System.out.println( cb.getLabel() + ": " + cb.getState() );
}

Die Implementierung einer Ereignisbehandlung zeigt, wie wir den Text des Kontrollfelds und den Zustand einfach ausgeben können. Hier muss eine Klasse die Schnittstelle ItemListener implementieren.


Galileo Computing

15.10 Optionsfelder  downtop

Sind Kontrollfelder in eine Gruppe eingebunden, dann lässt sich über eine CheckboxGroup erzwingen, dass nur jeweils eine Auswahl angewählt sein kann. Zunächst erstellen wir dazu ein CheckboxGroup-Objekt, das wir dann als Parameter eines Checkbox-Objekts übergeben.

Beispiel Die nachfolgenden Zeilen erzeugen eine Gruppe mit drei Optionsfeldern. Es reicht nicht aus, nur die Gruppe mit add() zum Container hinzuzufügen.
CheckboxGroup group = new CheckboxGroup();
Checkbox c1, c2, c3;
c1 = new Checkbox("Java ist toll", 
false, group);
c2 = new Checkbox("Java ist supertoll ", false, group);
c3 = new Checkbox("Java ist supersupertoll ", true, group);
add( c1 ); add( c2 ); add( c3 );

Das group-Objekt dient zur Gruppierung der drei Optionsfelder, wobei das dritte Feld ausgewählt ist. Obwohl nur maximal ein Objekt ausgewählt sein kann, könnte jedoch ein Programmierfehler etwas Falsches setzen. Dann verhält sich der Algorithmus so, dass er bei mehr als einem true das letzte Element markiert. In einer Optionsgruppe muss keine Option gesetzt sein.

Listener über Gruppen und einzelnen Kontrollfeldern

Wird ein ItemListener auf ein Auswahlmenü und Optionsfeld gleichzeitig angewendet, so ist es klug, mittels getSource() den Typ der Komponente zu erfragen:

public void itemStateChanged( ItemEvent 
e )
{
Object comp = e.getSource();

if ( comp instanceof Choice )
...

if ( comp instanceof Checkbox )
..
}

Galileo Computing

15.11 List-Boxen  downtop

Eine List-Box ist ein Exemplar der Klasse List. Ein List-Objekt verwaltet Strings, stellt einen Ausschnitt dar, und lässt einfache oder mehrfache Auswahl zu. Ein String, der angewählt wird, wird zum selektierten Element. Bei der Auswahl von Strings wird ein Item Event ausgelöst. Beim Doppel-Klick noch zusätzlich ein ActionEvent.

Abbildung 15.10   Eine List-Box unter dem AWT
Abbildung

Listing 15.13   ListDemo.java
import java.awt.*;
import java.awt.event.*;

public class ListDemo extends Frame
{
public ListDemo ()
{
super( "Hitliste der Raubkopierer" );

List l = new List( 5, true );

l.add( "Vietnam, 99%" ); l.add( "El Salvador, 97%" );
l.add( "China, 96%" ); l.add( "Slowenien, 96%" );
l.add( "Russland, 94%" ); l.add( "Deutschland, 42%" );
l.add( "Australien, 35%" ); l.add( "USA, 26%" );
l.add( "Ende" );

l.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
if ( e.getActionCommand().equals("Ende") )
System.exit(0);
}
} );

add( l );

pack();
}

public static void main( String args[] )
{
new ListDemo().setVisible( true );
}
}
class java.awt.List
extends Component implements ItemSelectable

gp  List()
Erzeugt eine neue Scrolling-Liste. Keine Einträge sind vorhanden. Die List-Box besitzt eine sichtbare Zeile. Mehrfachselektion ist nicht gestattet.
gp  List( int rows )
Erzeugt ein List-Objekt mit einer vorgegebenen Anzahl von sichtbaren Zeilen. Mehrfachselektion ist nicht gestattet.
gp  List( int rows, boolean multipleMode )
Erzeugt ein List-Objekt mit einer vorgegebenen Anzahl von sichtbaren Zeilen. Die Mehrfachselektion steuert der Parameter multipleMode.
gp  int getItemCount()
Liefert die Anzahl Einträge in der Liste.
gp  String getItem( int index )
Liefert das Item an der Position index.
gp  String[] getItems()
Liefert eine Liste mit Strings zurück.
gp  void add( String )
Fügt einen String am Ende der Liste ein.
gp  void add( String, int index )
Fügt den String an der Stelle index ein. Ist der index gleich –1 oder größer als die Anzahl der Elemente, so findet der Eintrag wieder hinten Platz.
gp  void replaceItem( String newValue, int index )
Setzt einen neuen String an die Position index.
gp  void removeAll()
Löscht alle Einträge.
gp  void remove( String )
Löscht den Eintrag. IllegalArgumentException, falls das Element nicht existiert.
gp  void remove( int )
Löscht das Element an der angegebenen Position.
gp  int getSelectedIndex()
Liefert den Index des selektierten Eintrags. Der Rückgabewert ist bei keinem angewählten Eintrag -1, ebenso bei mehreren ausgewählten Einträgen.
gp  int[] getSelectedIndexes()
Liefert die Position der ausgewählten Elemente in einem Feld.
gp  String getSelectedItem()
Gibt den selektierten String der Liste zurück.
gp  String[] getSelectedItems()
Liefert die selektierten Einträge aus der Scrolling-Liste.
gp  Object[] getSelectedObjects()
Liefert die selektierten Einträge aus der Scrolling-Liste als Objekt-Array.
gp  void select( int index )deselect( int index )
Selektiert bzw. Deselektiert den Eintrag an der Stelle index.
gp  boolean isIndexSelected( int index )
Liefert wahr, wenn der Eintrag in der Scrolling-Liste selektiert ist.
gp  int getRows()
Liefert die Anzahl der sichtbaren Zeilen in der Liste.
gp  boolean isMultipleMode()
Ist multiple Selektion erlaubt?
gp  void setMultipleMode( boolean )
Setzt die Möglichkeit, eine Mehrfachselektion zu erlauben.
gp  int getVisibleIndex()
Liefert den Index des Items, das zuletzt mit makeVisible() sichtbar gemacht wurde.
gp  void makeVisible( int index )
Macht den Eintrag an der Stelle index sichtbar.
gp  Dimension getPreferredSize()
Liefert die Wunschgröße der Scrolling-Liste.
gp  Dimension getPreferredSize( int rows )
Liefert die Wunschgröße der Scrolling-Liste mit der angegebenen Anzahl von Zeilen rows.
gp  Dimension getMinimumSize()
Liefert die minimale Größe der Scrolling-Liste.
gp  Dimension getMinimumSize( int rows )
Liefert die minimale Größe der Scrolling-Liste mit gegebener Zeilenanzahl rows.
gp  void addItemListener( ItemListener )
Fügt einen ItemListener hinzu.
gp  void removeItemListener( ItemListener )
Löst einen ItemListener von der Liste.
gp  void addActionListener( ActionListener )
Fügt einen ActionListener zum Erkennen der Doppelklicke hinzu.
gp  void removeActionListener( ActionListener )
Löst den ActionListener von der Liste.

Galileo Computing

15.12 Texteingabefelder  downtop

Das AWT bietet zwei unterschiedliche Texteingabefelder. Ein Eingabefeld mit nur einer Eingabezeile (TextField) und eine Komponente, die mehrzeiligen Text (TextArea) aufnimmt. Beide Textkomponenten sind von einer gemeinsamen Oberklasse TextComponent abgeleitet.

Abbildung


Galileo Computing

15.12.1 Text in einer Eingabezeile  downtop

Der Benutzer kann Eingaben in einem Textfeld machen, um etwa einen Namen einzugeben. Eine eigene Programmierung über ein Label, das Text-Ereignisse verarbeitet, können wir uns daher ersparen. Textfelder werden mit der Klasse TextField erstellt. Unterschiedliche Konstruktoren legen einen Start-String oder die Anzahl der Zeichen fest, die ein Textfeld anzeigen kann.

Beispiel Die folgende Zeile erzeugt ein Textfeld Namens wohnort, das den Text »Sonsbeck« enthält. Maximal zwanzig Zeichen lassen sich eingeben.
TextField wohnort = new TextField( 
"Sonsbeck", 20 );

Mit setText(String) und getText() können wir die Zeichenkette setzen und erfragen.


Galileo Computing

15.12.2 Passwort-Felder  downtop

Mit der Methode setEchoChar() lassen sich Zeichen bei der Eingabe in ein vordefiniertes Literal umwandeln. Bei allen Zeichen, die dann von uns eingetippt werden, erscheint dann dieses Zeichen. getEchoChar() liefert dieses Zeichen wieder.

Beispiel Verdeckendes Zeichen setzen
TextField passkey = new TextField();
passkey.setEchoChar( '#' );

Die Methode echoCharIsSet() liefert ein boolean, ob die Passwort-Funktion eingestellt ist. Sie lässt sich mit setEchoChar('\0') wieder rückgängig machen, sodass dann wieder die Zeichen erscheinen.


Galileo Computing

15.12.3 Mehrzeilige Textfelder  downtop

Mit der Klasse TextArea lassen sich mehrzeilige editierbare Textfelder erzeugen. Die Text Area-Klasse stellt automatisch horizontale und vertikale Bildlaufleisten ein, sodass wir durch den Text navigieren können. Dies können wir aber auch durch den Konstruktor steuern und dazu stellt TextArea die Konstanten SCROLLBARS_BOTH, SCROLLBARS_VERTICAL_ONLY, SCROLLBARS_HORIZONTAL_ONLY und SCROLLBARS_NONE zur Verfügung. Mehrere Konstruktoren erlauben das Anlegen mit unterschiedlichen Größen und optional einem voreingestellten String. Ein TextArea-Objekt stellt einen mehrzeiligen String in einer Farbe und einer Schriftart dar.

Listing 15.14   TextAreaDemo.java
import java.awt.*;
import java.awt.event.*;

class TextAreaDemo extends Frame
{
TextArea t;
String font;
int style, size;

public TextAreaDemo()
{
t = new TextArea(10, 40);
font = "SansSerif";
style = Font.PLAIN;
size = 12;

t.setFont( new Font(font, style, size) );

ActionListener al = new ActionListener()
{
public void actionPerformed( ActionEvent e ) {
t.requestFocus();
String cmd = e.getActionCommand();

if ( cmd.equals("Ende") )
System.exit( 0 );

else if ( cmd.equals("fett") )
t.setFont( new Font(font, style = Font.BOLD, size) );

else if ( cmd.equals("kursiv"))
t.setFont( new Font(font, style = Font.ITALIC, size) );
}
};

add( t );

Panel p = new Panel( new GridLayout(1,3) );

Button b;

p.add(b = new Button("fett"));
b.addActionListener( al );
b.setFont( new Font("SansSerif", Font.BOLD, 12) );

p.add( b = new Button("kursiv") );
b.addActionListener( al );
b.setFont( new Font("SansSerif", Font.ITALIC, 12) );

p.add( b = new Button("Ende") );
b.addActionListener( al );

add( "North", p );
pack();
}

public static void main( String args[] )
{
new TextAreaDemo().setVisible( true );
}
}
Abbildung 15.11   Ein TextArea unter dem AWT
Abbildung

class java.awt.TextArea
extends TextComponent

gp  TextArea()
Ein neues TextArea-Objekt wird erzeugt.
gp  TextArea( int, int )
Ein neues Objekt mit gegebener Anzahl Zeilen und Spalten.
gp  TextArea( String )
Ein TextArea-Objekt mit einem String.
gp  TextArea( String, int, int )
Kombination aus den beiden vorigen Konstruktoren.
gp  append( String )
Hängt den String an den vorhandenen Text.
gp  getColumns()
Gibt die Anzahl der Spalten an.
gp  getRows()
Gibt die Anzahl der Reihen an.
gp  insert( String, int pos )
Fügt den String an die Position pos ein.

Den Cursor an das Ende des Textfeldes setzen

Um in einem gefüllten Textfeld den Cursor an das Textende zu setzen, benutzen wir eine Kombination aus length() und setCaretPosition(). Mit textfield.getText().length() lässt sich die Länge des Strings im Textfeld ermitteln und mit textfield.setCaretPosition() die Position des Cursors setzen. Damit macht Folgendes das Gewünschte:

textfield.setCaretPosition( textfield.getText().length() 
);

Galileo Computing

15.13 Menüs  downtop

Menüs sind einteilbar in Fenstermenüs, die immer mit einem Fenster verbunden sind, oder Popup-Menüs, die an bestimmten Stellen auftauchen und nicht an eine Stelle gebunden sind. An diesem Punkt ist ein Unterschied in den Fenster-Architekturen zu verzeichnen. Während bei den meisten Windows-Systemen die Menüs ein Teil des Fensterrahmens sind, befinden sich die Menüeinträge beim Macintosh immer am oberen Bildschirmrand. Das bedeutet auch, dass beim Mac jede Anwendung die Menüleiste quasi austauscht, während unter Windows jedes Programm seine eigene Menüleiste besitzt und problemlos mehrere verschiedene Programme mit verschiedenen Menüleisten nebeneinander laufen können. Inzwischen macht sich aber eine neue Tendenz bei den Menüleisten sichtbar; der Weg von den fest verankerten Menüpunkten hin zur flexiblen Anordnung am Fensterrahmen oder als eigenes Fenster. Die Menüs sind somit beweglich geworden und die Zukunft wird zeigen, ob diese Menüart sich weiterverbreiten wird.


Galileo Computing

15.13.1 Die Menüleisten und die Einträge  downtop

Der Menübalken nimmt Menüeinträge auf, die nacheinander sichtbar sind. Der Menübalken dient damit als Container für weitere Menüs. Diese enthalten erst die Einträge (engl. Item(s)), die eine Aktion auslösen. Jeder Eintrag kann weitere Einträge enthalten. Diese werden dann »Untermenü« (engl. Submenu) genannt.

Zunächst einmal müssen wir einen Menübalken erzeugen. Dazu dient die Klasse MenuBar. Die Einträge erzeugen wir mit der Klasse Menu. Ein Menu ist eine Komponente von MenuBar. Das folgende Programmstückchen können wir direkt in den Konstruktor der Fenster-Darstellungsprogramme einbetten.

MenuBar mbar = new MenuBar();
Menu m1 = new Menu( "Datei" );
mbar.add( m1 );
mbar.add( new Menu("Bearbeiten") );
mbar.add( new Menu("Hilfe") );
setMenuBar( mbar );

Über die add()-Methode der MenuBar-Klasse fügen wir einen Eintrag hinzu. Dieser befindet sich dann direkt unter dem Titel des Fensters und ist immer sichtbar. Mit setMenuBar(), einer Methode von Frame, fügen wir den Menübalken dem Fenster hinzu. Auch Applets können Menübalken besitzen.

class java.awt.MenuBar
extends MenuComponent implements MenuContainer

gp  MenuBar()
Erzeugt einen neuen Menübalken.
gp  Menu add( Menu )
Fügt einen Menüeintrag der Menüleiste hinzu. Er wird in die vertikale Menüliste am Ende eingefügt und wird erst sichtbar, wenn wir im Menübalken einen Eintrag auswählen.
gp  void setHelpMenu( Menu )
Das Hilfemenü wird mit dieser Funktion ausgezeichnet.
class java.awt.Frame
extends Window implements MenuContainer

gp  void setMenuBar( MenuBar )
Setzt die Menüleiste des Fensters.
class java.awt.Menu
extends MenuItem implements MenuContainer

gp  Menu( String )
Erzeugt einen Menüeintrag mit einem bestimmten Text. Das Menü kann nicht abgezogen werden (kein Tear-Off-Menü).
gp  Menu()
Erzeugt einen Menüeintrag ohne Text. Kein Tear-Off-Menü.
gp  Menu( String, boolean )
Erzeugt ein Menu-Objekt mit gesetztem Namen, das durch den booleschen Parameter gesteuert abziehbar ist (Tear-Off-Menü). Diese Möglichkeit muss nicht in jeder Implementierung des AWTs gegeben sein.

Galileo Computing

15.13.2 Menüeinträge definieren  downtop

Auf jedem Menüpunkt der Klasse Menu kann eine Funktion add() aufgerufen werden, die Untermenüs hinzufügt. add() ist eine allgemeine Container-Funktion und findet sich bei vielen grafischen Container-Objekten.

Das Objekt, das add() hinzufügt, ist ein Objekt vom Typ MenuItem. Ein Trenner kann durch die Funktion addSeparator() hinzugefügt werden.

Beispiel Ein Menü aufbauen
Menu m = new Menu("Datei");
m.add( new MenuItem( "öffnen..." ) );
m.addSeparator();
m.add( new MenuItem( "Beenden" ) );

Tipp Die Ellipse »...« hinter dem Menüeintrag deutet an, dass der Benutzer einen Dialog bekommt, bevor eine Aktion ausgelöst wird.

class java.awt.Menu
extends MenuItem implements MenuContainer

gp  MenuItem add( MenuItem )
Fügt ein Menüeintrag dem Menü hinzu.
gp  void addSeparator()
Fügt eine Trennlinie oder ein Trennzeichen in das Menü ein.

Ein Objekt der Klasse MenuItem ist mit drei verschiedenen Signaturen erzeugbar.

class java.awt.MenuItem
extends MenuComponent

gp  MenuItem()
Erzeugt ein MenuItem ohne Text.
gp  MenuItem( String )
Erzeugt ein MenuItem mit vorgeschriebenen Text.
gp  MenuItem( String, MenuShortcut )
Erzeugt MenuItem mit Text und Tastatur-Shortcut.
gp  setEnabled( boolean )
Aktiviert oder deaktiviert den Menüeintrag.
gp  setLabel( String )
Setzt den Text des Eintrags.
Abbildung


Galileo Computing

15.13.3 Shortcuts  downtop

Über Tastatur-Shortcut lassen sich die Aktionen einzelner Menüs direkt ausführen. Shortcuts beschleunigen die Arbeit ungemein und sollten immer auf häufige Aktionen vergeben werden.

Tipp Die Wahl der Shortcuts: Auf keinen Fall dürfen wir vergessen, uns an die Vorgaben und Normen bei der Wahl der Shortcuts zu halten. Es ist unsinnig, sich neue Tastatur-Shortcuts zu überlegen, die entgegen aller Erwartung funktionieren. So haben sich im Laufe der Zeit verschiedene Styleguides eingebürgert: so wird beispielsweise für »Datei öffnen« der Shortcut Strg+O verwendet und nicht etwa Strg+C, der eine getätigte Selektion in den Zwischenspeicher (engl. Clipboard) kopiert.

Die Klasse MenuShortcut ist erst seit Java 1.1 im awt-Paket zu finden. Die Klasse definiert zwei Konstruktoren:

class java.awt.MenuShortcut
implements Serializable

gp  MenuShortcut( int )
Erzeugt ein MenuShortcut-Objekt mit dem definierten Zeichen.
gp  MenuShortcut( int, boolean )
Erzeugt einen Shortcut. Ist der zweite Parameter true wird die Abkürzung erst durch Drücken der Shift-Taste ausgelöst.

Es erzeugt MenuShortcut('O') eine Menüeintrag mit Strg+O. Neben den normalen Zeichen können auch andere Zeichen – zum Beispiel die Funktionstasten – Aktionen auslösen. Die Taste F1 ist oft mit einer Hilfe verbunden. Auch die Konstante Event kann über F1 als Shortcut eingebunden werden.

Beispiel Drei Menü-Einträge und ein Trenner
MenuBar mbar = new MenuBar();
Menu menuFile = new Menu( "Datei" );
mbar.add( menuFile );
Menu menuHelp = new Menu( "?" );
mbar.add( help );
MenuItem openItem = new MenuItem("Open", new MenuShortcut('O'));
menuFile.add( openItem );
menuFile.addSeparator();
MenuItem quitItem = new MenuItem("Quit");
menuFile.add( quitItem );
MenuItem helpItem = new MenuItem("Index",
new MenuShortcut(Event.F1));
menuHelp.add( helpItem );
setMenuBar( mbar );

class java.awt.MenuShortcut
implements Serializable

gp  boolean usesShiftModifier()
Gibt zurück, ob dieser MenuShortcut mit der Shift-Taste aktiviert wird.
gp  int getKey()
Gibt den Tastencode dieses Menü-Shortcuts zurück.
class java.awt.MenuItem
extends MenuComponent

gp  setShortcut( MenuShortcut )
Setzt den Shortcut vom Menüeintrag.
Tipp Alle Shortcuts sollten vom Benutzer geändert werden können – der Designer gibt lediglich Standard-Werte vor. Diese vorgegebenen Werte sollen aber nicht einfach das Alphabet runter gehen.


Galileo Computing

15.13.4 Beispiel für ein Programm mit Menüleisten  downtop

Es folgt nun ein komplettes Programm, welches ein Menü mit Menüeinträgen füllt. Um die Menüereignisse später einmal verarbeiten zu können, verwenden wir zwei unterschiedliche Implementierungen: Einmal eine bestimmte Funktion ActionProc und einmal eine innere Klasse. Alle Menüpunkte verwenden den gleichen ActionListener, denn in ActionProc geben wir die Herkunft aus. Wir implementieren keinen WindowListener, da sich das Fenster nur vom Menü aus schließen lassen soll.

Listing 15.15   MenuDemo.java, Teil 1
import java.io.*;
import java.awt.*;
import java.awt.event.*;
public class MenuDemo extends Frame
{
class ActionProc implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
String action = e.getActionCommand();
System.out.println("e.getActionCommand() = " + action );
}
}

// Fügt Eintrag ins Menü mit Shortcut ein, aber
// ohne ActionListener
private MenuItem addMenuItem( Menu m, String s, char c ) {
MenuItem menuItem;
m.add( menuItem = new MenuItem( s,new MenuShortcut((int)c)));
return menuItem;
}

// Fügt Eintrag ins Menü mit Shortcut und mit ActionListener ein
private MenuItem addMenuItem( Menu m, String s, char c,
ActionListener al ) {
MenuItem menuItem;
m.add( menuItem = new MenuItem( s,new MenuShortcut((int)c)));
menuItem.addActionListener( al );
return menuItem;
}

// Fügt Eintrag ins Menü ohne Shortcut, aber mit ActionListener ein
private MenuItem addMenuItem( Menu m, String s,
ActionListener al ) {
MenuItem menuItem;
m.add( menuItem = new MenuItem( s ) );
menuItem.addActionListener( al );
return menuItem;
}

// Fügt ein Element in die Menüleiste ein
private Menu addMenuBarItem( MenuBar menuBar, String s ) {
Menu menu = new Menu( s );
menuBar.add( menu );
return menu;
}

// gleich geht's weiter...

Nun brauchen wir nur noch ein kleines Hauptprogramm, welches eine Menüstruktur aufbaut. Unter einem Menüpunkt Datei soll sich noch ein Untermenü verbergen. Untermenüs lassen sich ebenso mit add() in ein Menu einfügen.

Abbildung 15.12   Ein komplettes Programm für Menüs
Abbildung

Tipp Hierarchische Menüs dürfen nur für wenig gebrauchte Operationen benutzt werden. Dies gilt noch mehr für Popup-Menüs.

Als Einträge finden sich dort die Dateien des aktuellen Verzeichnisses wieder. Folgt also nun der zweite Teil:

Listing 15.16   MenuDemo.java, Teil 2
//  ... Fortsetzung

public MenuDemo()
{
MenuBar mbar;
Menu menuDatei, menuBearbeiten, menuHilfe;
MenuItem item; // Konstruieren die Menüleisten

// Hauptmenü einrichten

mbar = new MenuBar();
menuDatei = addMenuBarItem( mbar, "Datei" );
menuBearbeiten = addMenuBarItem( mbar, "Bearbeiten" );
menuHilfe = addMenuBarItem( mbar, "?" );
mbar.setHelpMenu( menuHilfe );

ActionProc actionProcCmd = new ActionProc();

// Einträge für Datei

item = addMenuItem( menuDatei, "öffnen", 'O',
actionProcCmd );
item = addMenuItem( menuDatei, "Speichern", 'S',
actionProcCmd);
menuDatei.addSeparator();

Menu dateien = new Menu( "Datei(en)" );
menuDatei.add( dateien );

menuDatei.addSeparator();

item = addMenuItem( menuDatei, "Beenden", 'B' );
item.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e ) {
System.exit(0);
}
});

// Einträge für Bearbeiten

addMenuItem(menuBearbeiten,"Ausschneiden",'X',
actionProcCmd);
addMenuItem(menuBearbeiten,"Kopieren",'C',actionProcCmd);
addMenuItem(menuBearbeiten,"Einfügen",'V',actionProcCmd);

// Einträge für Hilfe

addMenuItem( menuHilfe, "Index", actionProcCmd );
addMenuItem( menuHilfe, "Über dieses Programm",
actionProcCmd);

// Menü abschließen

setMenuBar(mbar);

// fülle das Untermenü von Dateien
File userdir = new File( System.getProperty("user.dir") );
String entries[] = userdir.list();

for ( int i = 0; i < entries.length; i++ ) {
addMenuItem( dateien, entries[i], actionProcCmd );
}

setSize( 600, 400) ;
setVisible( true );
}

public static void main( String args[] )
{
MenuDemo f = new MenuDemo();
}
}

Galileo Computing

15.14 Popup-Menüs  downtop

Popup-Menüs sind nicht wie normale Menüs an eine bestimmte Position gebunden, sondern tauchen meistens dort auf, wo der Benutzer mit der rechten Maustaste geklickt hat. Ein anderer oft gebrauchter Name ist Kontextmenü, da das Menü unterschiedliche Einträge bei unterschiedlichen Kontexten besitzt.

Popup-Menüs sollen die Navigation erleichtern, deshalb sollten sie nicht zu lang sein oder zu viele Ebenen besitzen. Gleiches gilt übrigens auch für die Menüstruktur. Wenn es mehr als drei Ebenen werden, sollte über eine Neugestaltung nachgedacht werden.

Tipp Popup-Menüs müssen kurz gehalten werden. Viele Ebenen sind ebenso zu vermeiden wie lange Auswahllisten. Wenn der Benutzer am unteren Rand ist, so ist nicht abzuschätzen, wo die Auswahlliste angezeigt wird.

Die Klasse PopupMenu

Für Popup-Menüs ist die Klasse PopupMenu zuständig. Die Menüeinträge können wir, wie auch in normalen Menüs, mit der Klasse Menu erzeugen.

Beispiel Erzeuge ein Popup-Menü mit zwei Einträgen
PopupMenu popmen = new PopupMenu();
Menu menu1 = new Menu( "Eintrag 1");
popmen.add( menu1 );
popmen.add( new Menu( "Eintrag 2") );

Popup-Menüs unterscheiden sich vom Aufbau nicht von normalen Menüs, das heißt, wir können auch hier Trennlinien einfügen und Shortcuts definieren. Ein Popup-Menü wird wie ein Label mit der Methode add() zum Frame hinzugefügt.

Abbildung

Danach können wir sie mit der show()-Methode aufspringen (daher den Name »Popup«) lassen. Der Methode müssen die Komponente, auf der das Menü aufspringen soll, und die Koordinaten angegeben werden.

Beispiel Ist unsere Klasse von Frame abgeleitet, so tragen wir in den Konstruktor folgende Zeilen ein, damit das Popup-Menü hinzugefügt wird.
add( popmen );

Die Anzeige geschieht mit show().

popmen.show( this, 100,100 );
class PopupMenu extends 
Menu

gp  PopupMenu()
Erzeugt ein Popup-Menü.
gp  void show( Component origin, int x, int y)
Lässt das PopupMenu auf der Komponente origin an der Position x, y aufspringen.
Beispiel Hier ein kleines Programm mit einem Popup-Menü Listing 15.17   PopMenDemo.java
import java.awt.*;
import java.awt.event.*;

public class PopMenDemo extends Frame
{
PopupMenu popmen = new PopupMenu();

public PopMenDemo()
{
addWindowListener( new WindowAdapter() {
public void windowClosing( WindowEvent e ) {
System.exit(0);
}
});

popmen.add( new MenuItem("Eintrag 1") );
popmen.addSeparator();
popmen.add( new MenuItem("Eintrag 1") );
popmen.add( new MenuItem("Eintrag 3") );
popmen.add( new MenuItem("Eintrag 4") );
add( popmen );

popmen.addActionListener( new ActionListener() {
public void actionPerformed( ActionEvent e )
{
System.out.println( e.getActionCommand() +
" wurde gedrückt" );
}
} );

addMouseListener( new MouseAdapter() {
public void mouseClicked( MouseEvent m )
{
if ( m.getModifiers() == m.BUTTON3_MASK )
popmen.show( PopMenDemo.this, m.getX(), m.getY() );
}
} );
setSize( 300,300 );
setVisible( true );

 }
public static void main( String args[] )
{
new PopMenDemo().setVisible( true );
}
}

Tipp Popup-Menüs sollten einen ersten nicht selektierbaren Eintrag besitzen. Dies macht die Auswahl nicht wesentlich langsamer, lässt aber leichter das Abbrechen einer Operation zu.


Galileo Computing

15.15 Selbstdefinierte Cursor  downtop

Bei einem Cursor sind wir nicht nur auf die vordefinierten Muster angewiesen. Leicht lässt sich aus einer Grafik ein eigener Cursor definieren. Dazu bietet das Toolkit die Methode createCustomCursor() an. Als Parameter geben wir ein Image-Objekt, einen Hotspot und einen Namen an. Der Hotspot definiert eine Art Nullpunkt, die die Spitze angibt. Zeigt etwa der Standard-Cursor mit einem Pfeil nicht wie üblich nach oben, sondern nach unten, so gibt der untere Punkt den Nullpunkt an. Der Name ist nur nötig, wenn Java-Accessibility genutzt wird, also eine Möglichkeit, den Cursor zum Beispiel ohne Maus anzusprechen.

Beispiel Setze den Cursor mit der Grafik cursor.gif und den Hotspot auf (10,10).
Cursor c = getToolkit().createCustomCursor(
new ImageIcon( "cursor.gif" ).getImage(),
new Point(10,10), "Cursor" );

setCursor( c );

Hinweis Animierte Cursor bietet die Java-Umgebung nicht an. Wir müssen selbständig in einem Thread immer wieder setCursor() unterschiedliche Cursor setzen, um etwa eine drehende Sanduhr oder eine rotierende Festplatte zu bekommen.

Da grafische Oberflächen in der Regel keine Cursor beliebiger Auflösung und Farbanzahl zulassen, lässt sich das Toolkit auch über diese Parameter erfragen. Die Methode getBestCursorSize() liefert die mögliche Größe des Cursors zurück. Es ist sinnvoll, diese Methode vorher aufzurufen, um ein passendes Bild auszuwählen. Ähnlich wie bei den Icons in der Titelleiste werden die Grafiken sonst skaliert, und das kann ziemlich schlecht aussehen. Gleiches gilt bei den Farben. Nicht alle Systeme erlauben beliebig viele Farben für den Cursor. Die maximale Farbanzahl liefert die Funktion getMaximumCursorColors(). Notfalls wird der Cursor auf die Farbanzahl heruntergerechnet.

Tipp Unterstützt die Plattform Cursor beliebiger Größe, so lässt sich dadurch einfach eine Bubble-Help realisieren, die nicht rechteckig ist. Anstelle des Cursors wird eine Grafik mit dem Cursor zusammen mit einer Hilfe angezeigt. Das Betriebssystem verwaltet den Cursor, und wir müssen den Hintergrund nicht sichern und mit der Hilfe verknüpfen.

abstract class java.awt.Toolkit

gp  Cursor createCustomCursor( Image cursor, Point hotSpot, String name )
throws IndexOutOfBoundsException
Erzeugt ein neues Cursor-Objekt. Liegt der Hotspot außerhalb der Grenzen der Grafik, wird eine IndexOutOfBoundsException ausgelöst.
gp  Dimension getBestCursorSize( int preferredWidth, int preferredHeight )
Liefert die unterstützte Cursor-Größe, die den gewünschten Ausmaßen am nächsten liegen. Oft werden die Parameter ignoriert, wenn die Umgebung keine beliebige Cursor-Größe zulässt. Erlaubt das System überhaupt keine selbstdefinierten Cursor, erhalten wir ein Objekt der Dimension (0,0).
gp  int getMaximumCursorColors()
Liefert das Maximum an Farben, welches das Toolkit für Cursor unterstützt. Der Rückgabewert ist null, wenn selbstdefinierte Cursor nicht gestattet sind.

Galileo Computing

15.16 Alles Auslegungssache: Die Layout-Manager  downtop

Ein Layoutmanager ist dafür verantwortlich, Elemente eines Container nach einem bestimmten Verfahren anzuordnen. Ein Container wir Frame fragt bei einer Neudarstellung immer seinen Layoutmanager, wie er seine Kinder anordnen soll. Ein Layoutmanager kann mit setLayout() zugewiesen werden.

Abbildung

Tipp Fitt’s Law: beschreibt die Zeit, die benötigt wird, von einem Anfangspunkt zu einem Endpunkt zu kommen. Diese Zeit ist abhängig vom Logarithmus der Strecke zwischen dem Start- und Endpunkt und der Größe des Ziels. Daher gilt: Platziere die Elemente einer Oberfläche so, dass sie leicht zu erreichen sind. Je weiter das Ziel entfernt ist und je kleiner der Button ist, desto länger dauert die Operation.


Galileo Computing

15.16.1 FlowLayout  downtop

Dieser Layout-Manager setzte seine Elemente von links nach rechts in eine Zeile, wobei die Komponenten ihre Größe behalten. Passen nicht alle Elemente in eine Zeile, so werden sie untereinander angeordnet. Ist dies nötig, so kann durch einen zusätzlichen Parameter bestimmt werden, ob die Zeilen zentriert, rechts- oder linksbündig erscheinen sollen. Ohne Einstellung ist die Anzeige zentriert.

Abbildung

Listing 15.18   FlowChoiceWindow.java
import java.awt.*;

public class FlowChoiceWindow extends Frame
{
public FlowChoiceWindow()
{
setLayout( new FlowLayout() );

Choice choice = new Choice();
choice.addItem( "Mike: Mein Gott Walter" );
choice.addItem( "Sweet: Co Co" );

Button playButton = new Button( "Play" );

add( choice );
add( playButton );
}

public static void main( String args[] )
{
FlowChoiceWindow window = new FlowChoiceWindow();
window.pack();
window.setVisible( true );
}
}
Abbildung 15.13   FlowLayout mit Mike
Abbildung

Den Elementen kann zusätzlich mehr Freiraum (engl. gap) gegeben werden. Voreingestellt sind 5 Pixel. Das Ausrichtung (Alignment), das beim Umbruch angegeben werden kann, ist eine ganzzahlige Konstante aus FlowLayout. Es stehen drei Klassen-Konstanten zur Verfügung: FlowLayout.LEFT, FlowLayout.CENTER und FlowLayout.RIGHT.

class java.awt.FlowLayout

gp  FlowLayout()
Erzeugt ein Flow-Layout mit 5 Pixeln horizontalem und vertikalem Freiraum.
gp  FlowLayout( int )
Erzeugt ein Flow-Layout mit 5 Pixeln Freiraum und einem angegebenen Alignment.
gp  FlowLayout( int align, int hgap, int vgap )
Erzeugt ein Flow-Layout mit dem angegeben Alignment und einem horizontalen bzw. vertikalen Freiraum.
gp  int getAlignment()
Liefert das Alignment des Layout-Managers. Möglich sind FlowLayout.LEFT, Flow Layout.RIGHT oder FlowLayout.CENTER.
gp  void setAlignment( int align )
Setzt das Alignment mit Hilfe der Konstanten FlowLayout.LEFT, FlowLayout.RIGHT oder FlowLayout.CENTER.
gp  int getHgap()
Liefert den horizontalen Abstand der Komponenten.
gp  void setHgap( int hgap )
Setzt den horizontalen Abstand zwischen den Komponenten.
gp  int getVgap()
Liefert den vertikalen Abstand der Komponenten.
gp  void setVgap( int vgap )
Setzt den vertikalen Abstand zwischen den Komponenten.

Galileo Computing

15.16.2 BorderLayout  downtop

Ein BorderLayout unterteilt seine Zeichenfläche in fünf Bereiche: Norden, Osten, Süden, Westen und die Mitte. Wird das Fenster vergrößert, so bleiben die äußeren Ränder in ihrer Größe gleich, nur die Mitte (also der »Center«) vergrößert sich.

Jedem dieser Bereiche ist eine Zeichenkette zugeordnet, die beim Setzen eines Elements diesem den Bereich zuordnet. Die Zeichenketten sind »North«, »South«, »East«, »West« und »Center«. Dem Layoutmanager fügen wir mit der add()-Methode eine Komponente hinzu. Diese Methode benutzt zwei Argumente, wobei das erste Argument die Angabe der Himmelsrichtung sein muss. Wird die Funktion mit einem Parameter aufgerufen, so wird die Komponente immer in der Mitte platziert.

Listing 15.19   BorderLayoutDemo.java
import java.awt.*;

public class BorderLayoutDemo extends Frame
{
public BorderLayoutDemo()
{
setLayout( new BorderLayout() ); // Standard bei Frames

add( "North", new Button("Nie") );
add( "East", new Button("ohne") );
add( "South", new Button("Seife") );
add( "West", new Button("waschen") );
add( "Center", new Button("Center") );
}

public static void main( String args[] )
{
Frame w = new BorderLayoutDemo();
w.setSize( 300, 200 );
w.setVisible( true );
}
}
Abbildung 15.14   Der Layout-Manager BorderLayout
Abbildung

Da jedes Fenster automatisch mit einem Exemplar von BorderLayout verbunden ist, können wir uns die Zuweisung mit setLayout() eigentlich sparen – zur Übersichtlichkeit aber, falls es sich um ein Applet handelt, ist dies nötig, da dieses Programmstück dann in einem Panel abläuft.

class java.awt.BorderLayout

gp  BorderLayout()
Erzeugt eine neues BorderLayout, wobei die Komponenten aneinander liegen.
gp  BorderLayout( int hgap, int vgap )
Erzeugt ein BorderLayout, wobei zwischen den Komponenten ein Freiraum eingefügt wird. hgap spezifiziert den Freiraum in der Horizontalen und vgrap den in der Vertikalen. Die Freiräume werden in Pixeln gemessen.
gp  int getHgap()
Gibt den horizontalen Raum zwischen den Komponenten zurück.
gp  void setHgap( int hgap )
Setzt den horizontalen Zwischenraum.
gp  int getVgap()
Gibt den vertikalen Raum zwischen den Komponenten zurück.
gp  void setVgap( int vgap )
Setzt den vertikalen Zwischenraum.

Galileo Computing

15.16.3 GridLayout  downtop

Das GridLayout ordnet seine Komponenten in Zellen an, wobei die Zeichenfläche rechteckig ist. Jeder Komponente in der Zelle wird dieselbe Größe zugeordnet, also bei drei Elementen in der Breite ein Drittel des Containers. Wird dieser vergrößert, so werden die Elemente gleichmäßig vergrößert. Sie bekommen so viel Platz wie möglich.

Listing 15.20   GridLayoutWindow.java
import java.awt.*;

public class GridLayoutWindow extends Frame
{
public GridLayoutWindow()
{
setLayout( new GridLayout(3,2) );

add( new Button("Mein") );
add( new Button("Hut") );
add( new Button("der") );
add( new Button("hat") );
add( new Button("5") );
add( new Button("Ecken...") );
}

public static void main( String args[] )
{
GridLayoutWindow w = new GridLayoutWindow();
w.setSize( 400, 150 );
w.setVisible( true );
}
}
Abbildung 15.15   Beispiel für GridLayout
Abbildung

Die Klasse besitzt drei Konstruktoren. Einen Standard-Konstruktor und zwei weitere, die die Anzahl Zeilen oder Spalten erwarten. Einer der beiden parametrisierten Zeilen oder Spalten erlaubt es, so wie beim BorderLayout, Zwischenraum einzufügen. Es reicht, eine Angabe für die Anzahl der Elemente in der Zeile oder Spalte zu machen, denn der Layoutmanager nutzt sowieso nur eine Angabe und berechnet daraus die verbleibende Anzahl.

Beispiel Ein Layout mit drei Zeilen
setLayout( new GridLayout(3, 0xcafebabe) 
);

Bei nur vier Elementen können wir auf diese Anzahl von fiktiven Spalten gar nicht kommen. Bei gegebener Zeilenanzahl wird sie nicht genutzt.

GridLayout berechnet die Anzahl der passenden Spalten für die Anzahl der Komponenten. Das zeigt die Implementierung in der Methode preferredLayoutSize(), minimumLayoutSize() und layoutContainer(). (Schlechtes Design; in drei Methoden Duplizierung von Programmcode.)

if ( nrows > 0 )
ncols = (ncomponents + nrows – 1) / nrows;
else
nrows = (ncomponents + ncols – 1) / ncols;

Ist die Anzahl der Zeilen gleich 0, so berechet der Layoutmanager den Wert aus der Anzahl Spalten.

Tipp Existiert eine Anzahl Zeilen, so ist die Angabe für die Spalten völlig uninteressant. Der Wert sollte daher zur Übersichtlichkeit auf 0 gesetzt werden.

Entgegen der Dokumentation erzeugen alle Konstruktoren eine IllegalArgumentException, falls die Anzahl der Zeilen und Spalten gleich 0 ist. Die Dokumentation spricht hier nur von einer Exception beim Konstruktor mit vier Parametern, verschweigt aber, dass der Konstruktor GridLayout(int rows,int cols) auch eine Exception auslösen kann, da dieser wiederum mit this(rows, cols, 0, 0) den anderen aufruft. Von dem Standard-Konstruktor geht keine Gefahr aus, da dieser GridLayout mit this(1, 0, 0, 0) aufruft.

class java.awt.GridLayout

gp  GridLayout()
Erzeugt ein GridLayout mit einer Zelle pro Komponente in einer Zeile.
gp  GridLayout( int rows, int cols )
Erzeugt ein GridLayout mit rows Zeilen oder cols Spalten. Die zu berechnende Anzahl sollte auf 0 gesetzt werden.
gp  GridLayout( int rows, int cols, int hgap, int vgap )
Erzeugt ein GridLayout mit rows Zeilen oder cols Spalten. Horizontale Freiräume werden an die rechten und linken Ecken jeder Zeile sowie zwischen den Spalten gesetzt. Vertikale Freiräume werden an die unteren und oberen Ecken gesetzt, zudem zwischen den Reihen.

Galileo Computing

15.16.4 Der GridBagLayout-Manager  downtop

Die bisherigen Layoutmanager sind für Teilprobleme zwar einfach, lösen aber komplexe Layoutsituationen nur ungenügend; so blieb bisher nur der Weg über viele geschachtelte Panel-Objekte mit eigenen Layout-Managern. Mit dem GridBagLayout hat Sun einen sehr flexiblen, aber auch komplizierten Layoutmanager eingefügt, mit dem sich jede Oberfläche gestalten lässt. Die Idee dabei ist, wie beim GridLayout, dass die Elemente in Zeilen und Spalten eingeteilt werden. Sind bei einem GridLayout jedoch alle Elemente gleich hoch und gleich breit, lässt sich beim GridBagLayout ein Element über mehrere Zeilen und Spalten ziehen und das Verhältnis bei der Vergrößerung des Containers angeben. Dafür wird ein zusätzliches Objekt eingeführt, welches jeder Komponente die Position und Ausrichtung aufzwängt. Dies ist die Klasse GridBagConstraints. Der Name Constraint, zu Deutsch Einschränkung, sagt aus, dass der Container versucht, diese Constraints einzuhalten.

GridBagConstraints

Ein Objekt vom Typ GridBagConstraints schreibt dem Layout ganz unterschiedliche Werte vor. Um eine Komponente in einem GridBagLayout zu positionieren, muss zuerst ein Exemplar von GirdBagConstraints konstruiert werden. Anschließend wird eine Komponente mit setConstraints(Komponente, GridBagConstraints) beim GridBagLayout angemeldet. Danach muss nur noch die Komponente, wie bei jedem anderen Container auch, mit add() hinzugefügt werden.

Der prinzipielle Weg soll kurz skizziert werden:

// Zum Anfang Container und Layoutmanager 
besorgen

Container container;
...
GridBagLayout gbl = new GridBagLayout();
container.setLayout( gbl );

// Für alle Komponenten

Component component;
...
GridBagConstraints gbc = new GridBagConstraints();
gbc.XXX = YYY; // notwendigen Einstellungen machen

// Am Manager Constrains für Komponente anmelden

gbl.setConstraints( component, gbc );

// Element in den Container einfügen

container.add( component );

GridBagConstraints-Objekt aufbauen

Um ein GridBagConstraints-Objekt aufzubauen, gibt es zwei Möglichkeiten. Zuerst lässt es sich mit dem Standard-Konstruktor erzeugen oder mit einem parametrisierten Konstruktor, der jedoch gleich elf Werte annehmen möchte. Wir entscheiden uns für den Standard-Konstruktor und setzen die Werte über die Objektvariablen. Die wichtigsten Werte sind: Position der Elemente und die Ausmaße. Beim Aufbau eines eigenen Layouts ist es sinnvoll, die Elemente in Zeilen und Spalten einzutragen und dann aufzuschreiben, welche Größe sie einnehmen.

Widmen wir und nun dem Programm, das ein Layout mit 5 Zeilen und 2 Spalten realisiert. Gewünscht ist eine Realisierung der folgenden Abbildung:

Abbildung 15.16   Beispiel für ein GridBagLayout
Abbildung

Die Schaltfläche »1« nimmt Platz für 2 Spalten ein. Bei der Ausdehnung soll die Komponente den ganzen restlichen Platz einnehmen. In der dritten Zeile ist die Schaltfläche »4«, die 2 Spalten einnimmt. Für »5« und »6« gilt das umgekehrte wie für »1«, »2«, »3«.

Wichtige Attribute des GridBagConstraints: Breite, Höhe, Ausdehnung

Für das GridBagConstraint jeder Komponente sind vier Variablen besonders wichtig:

class GridBagConstraints
implements Cloneable, Serializable

gp  int gridx, int gridy
gridx
gibt die Position links vom Anzeigebereich an und gridy die Position direkt über dem Anzeigebereich der Komponente. Das Element ganz links hat den Wert 0, ebenso ist der obersten Zelle der Wert 0 zugeordnet. Wenn die Komponenten automatisch rechts bzw. unter die letzte Komponente platziert werden, wird die Konstante GridBagConstraints.RELATIVE vergeben, dass heißt, die Komponente wird direkt an der letzten Komponente in der Zeile oder Spalte positioniert. Der Standardwert ist RELATIVE mit dem Wert –1.

Stehen die Werte fest, gilt das für Komponenten, die immer die gleiche Größe von einer Zelle und einer Spalte haben. Das ist aber nicht immer der Fall und daher lassen sich die Ausdehnung in der Horizontalen und Vertikalen angeben. Dann nimmt ein Element für eine Überschrift etwa 2 Spalten ein.

gp  int gridwidth, int gridheight
Anzahl der Kästchen in einer Zeile und Spalte, die einer Komponente zur Verfügung stehen. Ist der Wert mit der Konstanten GridBagConstraints.REMAINDER belegt, so bedeutet dies, dass das Element das Letzte der Zeile oder Spalte ist. GridBagConstraints.REMAINDER trägt den Wert 0. Der Standard für beide Werte ist 1. Wurde die letzte Komponente allerdings schon mit gridwidth gleich GridBagConstraints.REMAINDER eingefügt, so wird die nächste Komponente als erste in die nächste Zeile eingesetzt.

Mit diesen Angaben lässt sich schon ein großer Teil einer grafischen Oberfläche entwerfen.

Eine weitere Variable fill bestimmt, ob überhaupt vergrößert werden darf. Wie die Größenänderung aussehen soll, das bestimmen zwei weitere Variablen.

gp  int fill
Für die Belegung von fill existieren vier Konstanten in GridBagConstraints, die angeben, ob der Bereich für die Komponente variabel ist. Das sind: NONE (vergrößern, der Standard), HORIZONTAL (nur horizontal vergrößern), VERTICAL (nur vertikal vergrößern) und BOTH (vertikal und horizontal vergrößern).
gp  double weightx, double heighx
Die Werte geben an, wie der freie horizontale und vertikale Platz verteilt wird. Der Standard ist 0. Ist in diesem Modus die Summe aller Komponenten einer Zeile bzw. Spalte 0, so wird Freiraum rechts und links bzw. oben und unten zwischen den Zeilen und dem Container eingefügt. Soll die Komponente den überschüssigen Platz verwenden, wird ein Wert größer 0 zugeteilt. Damit vergrößert oder verkleinert sie sich bei Veränderungen und behält nicht ihre bevorzugte Größe. Wenn nur ein Element einen Wert größer 0 besitzt, wird genau dieses vergrößert und die restlichen Komponenten behalten ihre Größe bei.

Programmierung vereinfachen

Mit diesen Informationen wollen wir jetzt ein Beispiel implementieren. Doch bevor wir uns einem kompletten Layout zuwenden, ist es sinnvoll, für den Umgang mit dem GridBagLayout und dem GridBagConstraints eine Hilfsfunktion zu schreiben, und zwar mit folgender Signatur:

static void addComponent( Container 
cont,
GridBagLayout gbl,
Component c,
int x, int y,
int width, int heigt,
double weightx, double weighty )

Die Funktion soll GridBagConstraints-Objekt erstellen, die Werte zuweisen und dem Container dieses Constraint-Objekt zuweisen. Damit ist mit einer Komponente eine Einschränkung verbunden. Zusätzlich soll die Methode auch noch die Komponenten in den Container legen.

Die Informationen über das Layout und unsere Abbildung wollen wir nun in einem Programm abbilden:

Listing 15.21   GridBagDemo.java
import java.awt.*;

class GridBagDemo extends Frame
{
static void addComponent(Container cont,
GridBagLayout gbl,
Component c,
int x, int y,
int width, int heigt,
double weightx, double weighty )
{
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = gbc.BOTH;
gbc.gridx = x; gbc.gridy = y;
gbc.gridwidth = width; gbc.gridheight = heigt;
gbc.weightx = weightx; gbc.weighty = weighty;
gbl.setConstraints( c, gbc );
cont.add( c );
}

public static void main( String args [])
{
Frame f = new Frame();
GridBagLayout gbl = new GridBagLayout();
f.setLayout( gbl );

// x y w h wx wy

addComponent( f, gbl, new Button("1"), 0, 0, 2, 2, 1.0, 1.0 );
addComponent( f, gbl, new Button("2"), 2, 0, 1, 1, 0 , 1.0 );
addComponent( f, gbl, new Button("3"), 2, 1, 1, 1, 0 , 0 );
addComponent( f, gbl, new Button("4"), 0, 2, 3, 1, 0 , 1.0 );
addComponent( f, gbl, new Button("5"), 0, 3, 2, 1, 0 , 0 );
addComponent( f, gbl, new Button("6"), 0, 4, 2, 1, 0 , 0 );
addComponent( f, gbl, new Button("7"), 2, 3, 1, 2, 0 , 0 );

f.setSize( 200, 200 );
f.setVisible( true );
}
}

Die restlichen Attribute

Die bisherigen Eigenschaften reichen aus, um die wichtigsten Layouts zu realisieren. Mit den Constrains lassen sich jedoch noch andere Werte einstellen:

class GridBagConstraints
implements Cloneable, Serializable

gp  int anchor
Wird die Komponente nicht auf die ganze Breite oder Höhe skaliert, so muss sie irgendwo hingesetzt werden. Die Variable anchor setzt sie nach einem bestimmten Verfahren in den Container. Folgende Konstanten sind in GridBagConstraints definiert: CENTER, NORTH, EAST, WEST, SOUTH, SOUTHEAST, NORTHEAST, SOUTHWEST, NORTHWEST. Der Standard ist CENTER.
gp  Insets insets
Ein Insets-Objekt bestimmt die minimalen Entfernungen der Komponente vom äußeren Rand in ihrem Anzeigebereich. Für ein Insets-Objekt werden vier Werte für top, left, bottom, right vergeben. Der Standard ist Insets(0,0,0,0).
Jetzt haben wie alle Informationen zusammen, um uns noch einmal mit den beiden Konstruktoren zu beschäftigen.
gp  GridBagConstraints ()
Der Standardkonstruktor. Er belegt die Werte wie die Implementierung zeigt:
public GridBagConstraints ()
{
gridx = RELATIVE;
gridy = RELATIVE;
gridwidth = 1;
gridheight = 1;

weightx = 0;
weighty = 0;
anchor = CENTER;
fill = NONE;

insets = new Insets(0, 0, 0, 0);
ipadx = 0;
ipady = 0;
}
gp  GridBagConstraints( int gridx, int gridy, int gridwidth,
int gridheight, double weightx, double weighty,
int anchor, int fill, Insets insets, int ipadx, int ipady )
Belegt das Layout mit den angegebenen Werten.

Galileo Computing

15.17 Dynamisches Layout während einer Größenänderung  downtop

Wird ein Fenster vergrößert, dann kann während der Größenänderung der Inhalt sofort neu ausgerichtet und gezeichnet werden oder auch nicht. Wird er nicht dynamisch angepasst, dann sieht der Benutzer diese Anpassung erst nach dem Loslassen der Maus, wenn die Größenänderung gemacht wurde. Dieses dynamische Vergrößern lässt sich seit 1.4 im Toolkit-Objekt einstellen. Dazu dient die Methode setDynamicLayout(boolean dynamic). Abfragen lassen sicht mit isDynamicLayoutSet() und isDynamicLayoutActive() tätigen.


Galileo Computing

15.18 Dialoge  downtop


Galileo Computing

15.18.1 Der Dateiauswahl-Dialog  downtop

Eine spezielle Klasse FileDialog lässt einen betriebssystemabhängigen Dateiauswahl-Dialog erscheinen. Damit lassen sich ohne Probleme Dateien auswählen. Der Selektor ist modal, kann also für das Speichern und Öffnen konfiguriert sein; zudem lassen sich die Pfade und ein FilenameFilter setzen. Erst nach dem Schließen und Beenden mit dem OK-Button stehen ausgewählte Dateien zur Verfügung.

Beispiel Einen Dateiauswahl-Dialog auf den Schirm bringen
FileDialog d = new FileDialog( 
this, "öffne Grafikdatei",
FileDialog.LOAD );
d.setFile( "*.jpg" );
d.show();

String f = d.getDirectory() + d.getFile();

Liefert getDirectory() oder getFile() eine Nullreferenz zurück, so wurde der Abbrechen-Button gedrückt, andernfalls stehen das Verzeichnis und der Dateiname des angewählten Objekts zur Verfügung.

class java.awt.FileDialog
extends Dialog

Zum Erzeugen eines Auswahldialoges stehen drei Konstruktoren zur Auswahl:

gp  FileDialog( Frame )
Erzeugt Datei-Dialog ohne Titel zum Öffnen einer Datei.
gp  FileDialog( Frame, String )
Erzeugt einen Dialog zum Öffnen einer Datei mit einem Titel im Fensterrahmen.
gp  FileDialog( Frame, String, int )
Erzeugt einen Dialog mit einem Titel im Fensterrahmen – da der Dialog sowohl für das Sichern als auch das Laden ausgelegt ist, sind entweder LOAD oder SAVE mit anzugeben.
gp  String getDirectory()
Liefert das Verzeichnis des Dialogs.
gp  String getFile()
Liefert die ausgewählte Datei des Dialogs.
gp  getMode()
Handelt es sich um einen Laden- oder Speichern-Dialog? (Gibt FileDialog.LOAD oder FileDialog.SAVE zurück.)
gp  setMode( int )
Setzt den Titel auf Laden oder Speichern mit den Konstanten LOAD und SAVE. Der falsche Wert wird mit einer IllegalArgumentException("illegal file dialog mode") bestraft.
gp  setDirectory( String )
Setzt das Verzeichnis, welches beim Beginn angezeigt werden soll.
gp  setFile( String )
Standarddatei, die bei Beginn angezeigt wird.
gp  FilenameFilter getFilenameFilter()
Liefert FilenameFilter-Objekt zurück.
gp  setFilenameFilter( FilenameFilter )
Setzt den FilenameFilter.

Die Vererbungskette der Klasse FileSelektor ist lang. So leitet sich FileDialog von Dialog ab, diese Klasse wiederum von Window. Window ist aber ein Container und dieser wiederum ein Component.

Abbildung

Vollständiges Programm für eine Auswahlbox

Wir können direkt aus dem Hauptprogramm ein Objekt Frame erzeugen und dem File Dialog dies mit auf den Weg geben. Das Frame-Objekt muss noch nicht einmal angezeigt sein. Ein Wert 0 funktioniert nicht, da andernfalls eine java.lang.IllegalArgumentException mit der Meldung »null owner window« ausgelöst wird.

Listing 15.22   FileSelector.java
import java.awt.*;

public class FileSelector extends Frame
{
public static void main( String args[] )
{
Frame f = new Frame();

FileDialog d = new FileDialog( f, "öffne was",
FileDialog.LOAD );
d.setFile( "*.txt" );
d.show();

String file = d.getDirectory() + d.getFile();
System.out.println( file );
}
}
Abbildung 15.17   Der Datei-Auswahldialog
Abbildung

Tipp Im Speichern-Dialog ist ein Standard-Name anzugeben. Im Idealfall richtet er sich nach dem Inhalt der Datei.


Galileo Computing

15.19 Die Zwischenablage (Clipboard)  downtop

Java ist zwar plattformunabhängig, aber der Inhalt der Zwischenablage (engl. Clipboard) ist dennoch frei zugänglich. Die Zwischenablage wird von Programmen zum Austausch von Daten und Objekten genutzt. Objekte können von beliebigen Programmen zwischengespeichert und abgerufen werden. Es ist wieder eine Eigenschaft des Toolkit-Objekts, uns an den Inhalt zu lassen. Hier zeigt sich die Funktions-Bibliothek wieder von der schönsten Seite, denn um vom Toolkit-Objekt zum tatsächlichen Inhalt zu kommen, sind eine ganze Reihe Objekte im Spiel. Zur Hilfe eilen dann noch die Klassen Clipboard, Transferable und DataFlavor.

Abbildung

Listing 15.23   PrintClip.java
import java.io.*;
import java.awt.*;
import java.awt.datatransfer.*;

class PrintClip
{
public static void main( String args[] )
{
try
{
Toolkit tk = Toolkit.getDefaultToolkit();

Clipboard systemClipboard = tk.getSystemClipboard();

Transferable transferData =
systemClipboard.getContents(null);

DataFlavor[] dataFlavor =
transferData.getTransferDataFlavors();

System.out.println( "Wir haben " + dataFlavor.length +
" Eigenschaften" );

for ( int i=0; i < dataFlavor.length; i++ )
System.out.println( " Eigenschaft " + i + " " +
dataFlavor[i].getHumanPresentableName() );

DataFlavor flavor = dataFlavor[0];

Object content = transferData.getTransferData( flavor );

System.out.println( "Der Inhalt des Clipboards:\n" +
content);
}
catch ( UnsupportedFlavorException e ) {}
catch ( IOException e ) {}
}
}

Der erste Weg führt über die Klasse Toolkit zum Clipboard-Objekt. Hier ist es die Methode getSystemClipboard(), die einen Zugriff auf die Zwischenablage erlaubt.

abstract class java.awt.Toolkit

abstract Clipboard getSystemClipboard()
Liefert ein Exemplar des Clipboard-Objekts, das Möglichkeiten zum Zugriff auf die Zwischenablage bietet.

Die Klasse Clipboard

Die Clipboard-Klasse definiert alle Methoden, die zum Transfer von Objekten in die Zwischenablage hinein (copy) und aus ihr heraus (paste) benötigt werden. Zudem lassen sich Objekte auch löschen (cut). Wir nennen sie auch Cut-, Copy- oder Paste-Operation. Nun wollen wir an den Inhalt gelangen. Dazu definiert Clipboard die Methode getContents(), das ein Transferable-Objekt liefert.

class java.awt.datatransfer.Clipboard

gp  Transferable getContents( Object requestor )
Liefert ein Transferable-Objekt, das den Inhalt der Zwischenablage verwaltet.

Dieses enthält jetzt noch nicht direkt den Inhalt, verwaltet ihn aber über ein spezielles DataFlavor-Objekt. Wir haben in unserem Programm Folgendes geschrieben:

Transferable transferData = systemClipboard.getContents(null);

Wir übergeben den Parameter null, da wir keinen spezielles Objekt haben, welches sich für den Inhalt interessiert. Als Ergebnis liefert der Aufruf ein Objekt zurück, das die Transferable-Schnittstelle implementiert. Jedes dieser Objekte bietet nun getTransferData() an, das den Inhalt als Objekt zurückliefert.

interface java.awt.datatransfer.Transferable

gp  Object getTransferData( DataFlavor flavor )
throws UnsupportedFlavorException, IOException
Gibt ein Objekt zurück, das den Inhalt der Zwischenablage enthält. Dieses ist als Objekt der Klasse DataFlavor definiert.

Der Inhalt der Zwischenablage in der Klasse DataFlavor

Die Klasse DataFlavor ist nun die letzte Klasse, die wir brauchen. Jedes Objekt dieser Klasse repräsentiert den Inhalt entweder vom Clipboard oder einer Drag&Drop-Operation. (Ganz nebenbei ist es auch ein Objekt für das Dateisystem.) Ein DataFlavor-Objekt kann den Inhalt dabei in vielfältiger Weise repräsentieren. Etwa als Text oder Unicode. Die Methode getTransferDataFlavors() liefert uns nun ein Feld dieser DataFlavor-Objekte. Dies sind in der Regel mehrere, denn der Inhalt der Zwischenablage liegt oft in verschiedenen Formaten vor. Das wichtigste Objekt liegt dabei an erster Stelle im Feld. Damit wir als Leser den Typ erkennen können, liefert die Methode getHumanPresentableName() einen String mit einer lesbaren Beschreibung zurück.

interface java.awt.datatransfer.Transferable

gp  DataFlavor[] getTransferDataFlavors()
Liefert ein Feld von DataFlavor-Objekten, die den Inhalt und den Typ umfassen. Die Reihenfolge geht von der genauesten bis zur ungenauesten Beschreibung.
Abbildung


Galileo Computing

15.20 Ereignisverarbeitung auf unterster Ebene  downtop

Der Benutzer erzeugt bei seiner Arbeit mit der Oberfläche Ereignisse. Diese werden entweder von den Peer-Objekten oder von Klassen der Applikation erzeugt. Bevor sie vom eigenen Programm bearbeitet werden, gelangen sie in eine Ereignisschlange (engl. Event Queue). Event Queue ist einzigartig für eine JVM. Diese Schlange ist für Programmierer zugänglich und in einer plattformunabhängigen Klasse EventQueue implementiert. Elemente der Klasse sind Objekte vom Typ AWTEvent.

Eigene Ereignisse in die Queue setzen

Es ist ohne großen Umweg möglich, eigene Ereignisse zu erzeugen und in der EventQueue zu platzieren. Damit lassen sich beispielsweise Eingaben des Benutzers emulieren. Da alle Ereignisse von Komponenten von AWTElement erben, lässt sich ein ActionEvent erzeugen, welches dann wiederum von einem interessierten Listener entgegengenommen wird. Jetzt fehlt uns nur noch eine Funktion, die Ereignisse in die Schlange setzt. Dazu bietet die Klasse EventQueue die Methode postEvent(). Am Beispiel sehen wir die notwendigen Aufrufe, um beginnend vom Toolkit an die SystemEventQueue zu kommen:

Abbildung

Toolkit.getDefaultToolkit().getSystemEventQueue().
postEvent(
new ActionEvent( /* Object source, int id, String command */ )
);
class java.awt.Toolkit

gp  final EventQueue getSystemEventQueue()
Liefert ein Exemplar der EventQueue für eine Applikation oder ein Applet. Eine Security Exception wird geworfen, falls der Security-Manager den Zugriff auf EventQueue verbietet.
class java.awt.EventQueue

gp  void postEvent( AWTEvent theEvent )
Legt ein Ereignis in die EventQueue. Danach werden vorhandene EventQueueListeners und notifyEventQueueListeners aufgerufen.

Galileo Computing

15.21 Benutzerinteraktionen automatisieren  toptop

Eine besondere Eigenschaft von Präsentationsprogrammen ist, dass Benutzerinteraktionen wie von Zauberhand automatisch vom System vorgenommen werden. Ein Programm kann beispielsweise die Interaktion mit der Maus oder der Tastatur aufnehmen und zu einem späteren Zeitpunkt abspielen. Genau für diese Art der Oberflächensteuerung gibt es die Klasse Robot im Paket AWT. Sie verwaltet eine mit Aktionen gefüllte Ereigniswarteschlange, die nacheinander abgearbeitet werden.

class java.awt.Robot

gp  void keyPress( int keycode )
Drückt eine Taste.
gp  void keyRelease( int keycode )
Lässt die Tasten wieder frei.
gp  void mouseMove( int x, int y )
Bewegt die Maus auf die Koordinate relativ zum aktuellen Fenster.
gp  void mousePress( int buttons )
Aktiviert eine oder mehrere Maustasten.
gp  void mouseRelease( int buttons )
Lässt die Maustaste wieder los.
gp  void delay( int ms )
Wartet Millisekunden. Mehr als 60 Sekunden sind nicht möglich und das Resultat ist ein IllegalArgumentException.

Bevor wir ein kleines Aufnahmeprogramm für Mausbewegungen schreiben, wenden wir uns den beiden interessanten Methoden keyPress(), keyRelease(), mousePress() bzw. mouseRelease() zu, da die Parameter erklärungswürdig sind. Der Parameter keycode ist der erste, der beachtet werden muss.

Beispiel Der Roboter legt Tasten in die Ereignisschlange
Robot rob = new Robot();

rob.keyPress( KeyEvent.VK_SHIFT );
rob.keyPress( KeyEvent.VK_U );
rob.keyPress( 'L' ); rob.keyPress( 'L' ); rob.keyPress( 'I' );
rob.keyPress( '0' ); rob.keyPress( '0' ); rob.keyPress( '7' );

rob.keyRelease( KeyEvent.VK_SHIFT );

rob.keyPress( KeyEvent.VK_ENTER );

Der Parameter von keyPress() und keyRelease() ist eine Konstante der Klasse KeyEvent. Neben den alphanumerischen Tasten auf der Tastatur finden sich unter anderem Konstanten für Funktionstasten, Metatasten, Cursortasten und weitere Sonderzeichen. Die Konstanten für Großbuchstaben und Ziffern decken sich mit den ASCII-Zeichen und können daher alternativ verwendet werden. Bei fast allen Sonderzeichen entspricht die Konstante dem ASCII-Code, jedoch ist dies nicht selbstverständlich. Die Kleinbuchstaben (ab 0x61) werden zum Beispiel auf den 10er-Block abgebildet (ab 0x60). Ist der übergebene Keycode undefiniert (durch die Konstante KeyEvent.VK_UNDEFINED vorgegeben), so erzeugt der Roboter eine IllegalArgumentException.

Um die Umschalttaste während der Automatisierung einzuschalten, nutzen wir key Press(KeyEvent.VK_SHIFT) und lösen die Tasten mit keyRelease(). Das Beispiel macht deutlich, dass das Freigeben nur für Operationen wie zum Beispiel Shift, VK_ALT, VK_ALT_GRAPH nötig ist, allerdings nicht für normale Buchstaben.

Wird der obere Programmtext in einer grafischen Applikation genutzt, so muss sichergestellt sein, dass die Tasten auch die passende Komponente erwischen. Liegt der Fokus zum Beispiel auf einem Schieberegler, werden die Tastendrücke natürlich ohne Wirkung bleiben. Aus diesem Grund ist es angebracht, den Fokus zu setzen. Die Methode dazu war requestFocus(). Sie ist auf Component definiert.

Jetzt fehlen uns noch die Methoden mousePress() und mouseRelease(). Beide bekommen als Parameter eine Konstante der Klasse InputEvent; entweder BUTTON1_MASK, BUTTON2_MASK, BUTTON3_MASK oder eine Kombination aus den Dreien. Folgende Zeilen aktiviert die (meist linke) Maustaste, wartet eine Sekunde und lässt dann die Maustaste wieder los.

rob.mousePress( InputEvent.BUTTON1_MASK 
);
rob.delay( 1000 );
rob.mouseRelease( InputEvent.BUTTON1_MASK );

Wenn die Mausaktion eine Komponente treffen soll, dann gilt das Gleiche wie für Tastendrücke. Nur muss hier der Fokus nicht auf der Komponente liegen, sondern die Mauskoordinaten müssen auf die Komponente zeigen. Damit die Bildschirmkoordinate einer Komponente ausgelesen werden kann, bietet Component eine Methode getLocation OnScreen() an, die ein Point-Objekt mit den Startkoordinaten liefert. Diese können mit einem kleinen Offset an mouseMove() weitergereicht werden. Dann befindet sich der Zeiger über der Komponente und mousePress() kann seine Wirkung nicht mehr verfehlen.

Methoden zur Zeitsteuerung

class java.awt.Robot

gp  void setAutoDelay( int ms )
Die Robot-Klasse sendet zum Abspielen Ereignisse an die Oberfläche. Diese Methode setzt die Verzögerung fest, die nach dem Ereignis vergehen soll. Mehr als 60 Sekunden sind auch hier nicht gültig.
gp  int getAutoDelay()
Liefert die Zeit, die nach dem Ereignisaufruf vergehen soll.
gp  void waitForIdle()
Wartet, bis alle Ereignisse in der Warteschlange bearbeitet sind.
gp  void setAutoWaitForIdle( boolean isOn )
Setzt, ob waitForIdle() nach einen Ereignis aufgerufen wird.
gp  boolean isAutoWaitForIdle()
Liefert true, falls der Robot automatisch waitForIdle() nach einem Ereignis aufruft.

Funktionsweise und Beschränkungen

Für die Steuerung auf der Rechnerseite sind insbesondere bei X11 einige Anforderungen zu erfüllen. Hier sind Rechte nötig, um auf der unteren Ebene Ereignisse platzieren zu können. Ein X-Window System benötigt hierfür die aktivierte Standarderweiterung XTEST 2.2. Verletzt die aktuelle Architektur diese Vorgaben, so wird während der Konstruktion eines Robot-Objekts eine AWTException ausgelöst.






1    In der Entwicklung.

  

Java 2




Copyright © Galileo Press GmbH 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de