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. |
15.1 Peer-Klassen und Lightweight-Komponenten  
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.
15.2 Es tut sich was – Ereignisse beim AWT  
15.2.1 Was ist ein Ereignis?  
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.
15.2.2 Die Klasse AWTEvent  
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.
15.2.3 Events auf verschiedenen Ebenen  
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 |
| Eyelid1 |
Eye |
Augenzwinkern |
1
15.2.4 Ereignisquellen, -senken und Horcher (Listener)  
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.)
15.2.5 Listener implementieren  
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 )
}
|
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" );
}
}
|
15.2.6 Listener bei Ereignisauslöser anmelden  
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.
15.3 Varianten, das Fenster zu schließen  
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.
15.3.1 Eine Klasse implementiert die Schnittstelle WindowListener  
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.
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
|
|
void windowOpened( WindowEvent )
Aufgerufen, wenn Fenster geöffnet wurde. |
|
void windowClosing( WindowEvent )
Aufgerufen, wenn das Fenster geschlossen wird. |
|
void windowClosed( WindowEvent )
Aufgerufen, wenn das Fenster mit dispose() geschlossen wurde. |
|
windowIconified( WindowEvent )
Aufgerufen, wenn das Fenster verkleinert wird. |
|
windowDeiconified( WindowEvent )
Aufgerufen, wenn das Fenster wieder hochgeholt wird. |
|
windowActivated( WindowEvent )
Aufgerufen, wenn das Fenster aktiviert wird. |
|
windowDeactivated( WindowEvent )
Aufgerufen, wenn das Fenster deaktiviert wird. |
15.3.2 Adapterklassen nutzen  
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.
| 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); }
}
|
15.3.3 Innere Mitgliedsklassen und innere anonyme Klassen  
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();
}
}
15.3.4 Generic Listener  
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
|
http://java.sun.com/products/jfc/tsc/articles/generic-listener2/index.html |
|
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.
15.4 Komponenten  
15.4.1 Die Basis aller Komponenten: Die Klasse Components  
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
|
|
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;
}
|
15.4.2 Proportionales Vergrößern eines Fensters  
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.
interface java.awt.event.ComponentListener
extends EventListener
|
|
componentHidden( ComponentEvent )
Wenn die Komponente versteckt wurde. |
|
componentMoved( ComponentEvent )
Wenn die Komponente bewegt wurde. |
|
componentResized( ComponentEvent )
Wenn die Komponente in der Größe verändert wurde. |
|
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.
15.4.3 Hinzufügen von Komponenten  
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 15.2 Ein Container kann sich selbst aufnehmen
|
|
Da Container selbst wiederum ein Component ist, können auch Container wiederum Container aufnehmen.
15.5 Ein Informationstext über die Klasse Label  
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
|
|
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
|
|
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
|
|
Label()
Erzeugt ein leeres Label. |
|
Label( String )
Erzeugt ein Label mit links angeordneten Text. |
|
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. |
|
String getText()
Liefert den Text des Labels. |
|
void setText( String text )
Ändert die Aufschrift des Labels im laufenden Betrieb. |
|
void setAlignment( int alignment )
Setzt die Ausrichtung des Labels. Mögliche Werte sind Label.LEFT, Label.RIGHT und Label.CENTER. |
|
int getAlignment()
Liefert die Ausrichtung zurück. |
15.5.1 Mehrzeiliger Text  
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());
|