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 16 Let’s Swing
  gp 16.1 Das Konzept vom Model-View-Controller
  gp 16.2 Der Unterschied zwischen AWT und Swing
    gp 16.2.1 Schließen eines Swing-Fensters
  gp 16.3 JLabel
  gp 16.4 Die Klasse ImageIcon
    gp 16.4.1 Die Schnittstelle Icon
    gp 16.4.2 Was Icon und Image verbindet
  gp 16.5 Die Schaltflächen von Swing
    gp 16.5.1 JButton
    gp 16.5.2 AbstractButton
    gp 16.5.3 JToggleButton
    gp 16.5.4 JCheckBox
    gp 16.5.5 Radiogruppen
  gp 16.6 Tooltips
  gp 16.7 JScrollBar
  gp 16.8 JSlider
  gp 16.9 JList
  gp 16.10 JComboBox
  gp 16.11 Der Fortschrittsbalken JProgressBar
  gp 16.12 Symbolleisten alias Toolbars
  gp 16.13 Texteingaben
    gp 16.13.1 JPasswordField
    gp 16.13.2 Die Editor-Klasse JEditorPane
  gp 16.14 Rahmen (Borders)
  gp 16.15 Dialoge
    gp 16.15.1 Der Farbauswahl-Dialog JColorChooser
  gp 16.16 Mausradunterstützung
  gp 16.17 Der Inhalt einer Zeichenfläche: JPanel
  gp 16.18 JRootPane und JLayeredPane
  gp 16.19 Tabellen mit JTable
    gp 16.19.1 Ein eigenes Modell
    gp 16.19.2 AbstractTableModel
    gp 16.19.3 DefaultTableModel
    gp 16.19.4 Einen eigenen Renderer für Tabellen
  gp 16.20 AWT, Swing und die Threads
    gp 16.20.1 Warum Swing nicht threadsicher ist
    gp 16.20.2 Swing-Elemente bedienen mit invokeLater(), invokeAndWait()
  gp 16.21 Das Java Look&Feel

Kapitel 16 Let’s Swing

Are you police officers?
No, ma’am. We’re musicians.
– Blues Brothers


Galileo Computing

16.1 Das Konzept vom Model-View-Controller  downtop

Wir wollen uns noch einmal an die Idee des Observer-Pattern erinnern. Ein Beobachter beobachtet den zu Beobachtenden. Konkreter: es meldete sich ein Beobachter an und wurde bei jeder Änderung der Daten informiert. Übertragen auf grafische Benutzungsoberflächen hieße das, es könnte unterschiedlichste Visualisierungen der Daten geben. Diese Trennung heißt auch Document-View Struktur. Für grafische Oberflächen lässt sich dieses Modell zum Model-View-Controller (MVC) verfeinern. Die Idee stammt ursprünglich von Professor Trygve Reenskaug am Xerox PARC um 1978/79 und zog zuerst in Smalltalk ein.

Wie die drei Buchstaben von MVC andeuten, gibt es drei interagierende Objekte:

gp  Modell: Es repräsentiert den internen Zustand eines Objekts und speichert alle interessanten Daten. Ein Modell bietet Methoden an, mit dem sich der aktuelle Zustand erfragen und ändern lässt.
gp  View: Die Daten vom Modell werden im View dargestellt. Er nutzt die Methoden vom Modell, um die Informationen auszulesen.
gp  Controller: Nach einer Interaktion mit der grafischen Oberfläche werden die Daten im Modell aktualisiert und anschießend vom Viewer wieder neu angezeigt.

Diese Dreiteilung trennt alle Daten von der visuellen Repräsentation. Der große Vorteil dabei ist, dass sich alle drei Teile unterschiedlich entwickeln und einsetzen lassen. Die grafischen Komponenten können weiterentwickelt werden und das Modell ändert sich nicht. In Java ist dies besonders aufgrund des wechselnden Aussehens interessant. In Java wird zur Laufzeit ein neuer View zu seinem existierenden Modell gebracht.

Modifizierte MVC-Architektur für Swing

Das MVC-Konzept trennt ganz klar die Bereiche ab, führt aber bei praktischer Realisierung zu zwei Problemen. Das erste betrifft die Entwickler der Komponenten. Meistens sind View und Controller eng verbunden, sodass es zusätzlichen Schnittstellenaufwand für die Implementierung gibt. Implementieren wir etwa eine Textkomponente, müsste sich diese um alle Eingaben kümmern und diese dann an die Darstellung weiterleiten. Das zweite sich daraus ergebende Problem ist der erhöhte Kommunikationsaufwand zwischen den Objekten. Wenn sich Ergebnisse in der Darstellung oder dem Modell ergeben, führt die Benachrichtigung immer über den Controller.

Es macht demnach Sinn, VC zu einer Komponente zu verschmelzen um die komplexe Interaktion zwischen View und Controller zu vereinfachen. Genauso haben es die Entwickler der JFC daher gemacht. In Swing findet sich keine Reinform des MVC, sondern eine Verquickung von View und Controller. Durch diese Vereinfachung lassen sich die Benutzeroberflächen leichter programmieren, wobei wir nur wenig Flexibilität einbüßen. Das neue Modell wird anstatt MVC auch Model-View-Presenter (MVP-Pattern) genannt. Betrachten wir das MVP-Konzept am Beispiel einer Tabellenkalkulation. Die Daten in einem Arbeitsblatt entsprechen den Daten, die unterschiedlich visualisiert werden können; klassisch in einem Tabellenblatt und modisch in einem Diagramm. Ein Modell kann problemlos mehrere Sichten haben. Eine Änderung der Daten im Tabellenblatt führt nun zu einer Änderung in den internen Daten und umgekehrt führen diese zu einer Änderung des Diagramms.

Die Klasse ComponentUI

In Java ist der View und Controller durch ein Objekt ComponentUI repräsentiert. Da wir das Aussehen und Verhalten von Java Komponenten frei bestimmen können, gibt es demnach für alle konkreten Swingkomponenten ein ComponentUI-Objekt, welches die Darstellung und Benutzeraktionen übernimmt. Ein JList-Objekt verweist dann auf eine paint()-Methode im ComponentUI-Objekt, das die Darstellung wirklich vornehmen kann. Die Daten der Liste befinden sich im Modell.

Wenn wir uns mit einigen Modellen beschäftigen, werden wir sehen, dass für manche Komponenten sehr unterschiedliche Modelle gefordert sind. Eine Schaltfläche visualisiert meistens eine Zeichenkette. Eine Tabelle repräsentiert aber nicht immer nur einfache Texte. Hier können die Daten durchaus eine komplexe Objektstruktur darstellen. Damit diese visualisiert werden kann, muss der Viewer diese Daten auch bekommen. Daher wird häufig ein spezielles Modell implementiert, das die Daten für die Ansicht zur Verfügung stellt. Mit eigenen Modellen werden wir uns an anderer Stelle noch näher beschäftigen.


Galileo Computing

16.2 Der Unterschied zwischen AWT und Swing  downtop

Wir sollten an dieser Stelle die Unterschiede zwischen einer AWT-Applikation (beziehungsweise Applet) und einem Swing-Programm erkennen. Um beim AWT-Programm ein Objekt zu einem Fenster hinzuzufügen, schreiben wir

frame.add( component );

Da die Frame-Klasse nichts anderes ist als ein spezielles Container-Objekt, bekommt es von dort die add()-Methode, die ein Component-Objekt auf sich lässt. Bei Swing ist dies etwas anders. Zwar ist auch JFrame vom Frame abgeleitet, doch ein Fehler der folgenden Form erscheint:

Exception in thread "main" java.lang.Error:\
Do not use javax.swing.JFrame.add()
use javax.swing.JFrame.getContentPane().add() instead
at javax.swing.JFrame.createRootPaneException(Unknown Source)
at javax.swing.JFrame.addImpl(Unknown Source)
at java.awt.Container.add(Unknown Source)
at JDemo.main(JDemo.java:18)

Bei JFrame ist ein anderer Weg nötig. Hier muss das Component-Objekt erst zu der Zeichenfläche, die »ContentPane« genannt wird, zugefügt werden. Eine ContentPane ist kein spezielles Objekt, sondern nur ein Container-Objekt:

Container con = frame.getContentPane();
con.add( component );

Dies lässt sich dann zu einer Zeile abkürzen, die auch im Programm genutzt wird:

frame.getContentPane().add( component 
);

Als Component werden wir ein Objekt der Klasse PanelDemo erzeugen und dann als Parameter für die add()-Methode eines ContentPane verwenden.


Galileo Computing

16.2.1 Schließen eines Swing-Fensters  downtop

Wird eine Fenster geschlossen, verhält sich der JFrame etwas anders als ein AWT-Frame. Beim herkömmlichen Frame läuft ohne Ereignisbehandlung erst einmal gar nichts. Beim JFrame verschwindet das Fenster jedoch in den Hintergrund. Dieses Verhalten kann mit der Funktion setDefaultCloseOperation(int) geändert werden. Damit ein JFrame sich wie ein Frame verhält, geben wir Folgendes an:

setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

Jetzt lässt sich mit Hilfe eines WindowListener-Objekts auf windowClosing() reagieren, etwa schließen oder verkleinern.

Neben DO_NOTHING_ON_CLOSE existieren drei weitere Konstanten. HIDE_ON_CLOSE und DISPOSE_ON_CLOSE sind ebenfalls in WindowConstants definiert und kennzeichnen zum einen, ob das Fenster automatisch verdeckt werden soll, nachdem die WindowsListener aufgerufen wurden (dies ist der Standard), zum anderen, ob alle Listener abgearbeitet werden und das Fenster geschlossen werden soll. Die letzte Konstante ist EXIT_ON_CLOSE in JFrame. Sie ruft System.exit() auf und schließt somit die Anwendung.

Beispiel Schließe die Applikation, wenn das Fenster geschlossen wird.
frame.setDefaultCloseOperation( 
JFrame.EXIT_ON_CLOSE );

class javax.swing.JFrame
extends Frame implements WindowConstants, Accessible, RootPaneContainer

gp  void setDefaultCloseOperation( int operation )
Was passieren soll, wenn der Benutzer das Fenster schließt. Gültig sind die Konstanten
gp  WindowConstants.DO_NOTHING_ON_CLOSE, WindowConstants.HIDE_ON_CLOSE,
WindowConstants.DISPOSE_ON_CLOSE, JFrame.EXIT_ON_CLOSE.
int getDefaultCloseOperation()
Liefert die eingestellte Eigenschaft beim Schließen des Fensters.

Galileo Computing

16.3 JLabel  downtop

Das JLabel dient für kurze Textdarstellung und ist dem AWT-Label sehr ähnlich. Im Gegensatz zur Implementierung aus dem AWT können auch Bilder angezeigt werden. Dies sind jedoch keine Image-Objekte, sondern Objekte der Klasse Icon. Als Ergänzung kommt hinzu, dass sich auch Icon und Text gemeinsam verwenden lassen. Über verschiedene Möglichkeiten lassen sich horizontale und vertikale Positionen vom Text relativ zum Icon setzen. Auch die relative Position des Inhalts innerhalb der Komponente lässt sich spezifizieren. Die Voreinstellung für Labels ist eine zentrierte vertikale Darstellung im angezeigten Bereich. Enthalten die Labels nur Text, so ist dieser standardmäßig linksbündig angeordnet und Bilder sind ebenso horizontal zentriert. Ist keine relative Position des Texts zum Bild angegeben befindet sich der Text standardmäßig auf der rechten Seite des Bilds und beide sind auf der Vertikalen angeordnet. Der Abstand von Bild und Text lässt sich beliebig ändern und ist mit vier Pixeln vordefiniert.

Listing 16.1   JLabelDemo.java
import java.awt.*;
import javax.swing.*;

public class JLabelDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

JLabel l1 = new JLabel( "Ganz einfach" );
frame.getContentPane().add( l1, BorderLayout.WEST );

JLabel l2 = new JLabel( "Bin ich dick" );
l2.setFont( new Font("Serif", Font.BOLD, 30) );
frame.getContentPane().add( l2,BorderLayout.EAST );

frame.pack();
frame.show();
}
}
Abbildung 16.1   Ein JLabel mit Texten und geänderten Zeichensatzattributen
Abbildung

Bevor wir zu einem Beispiel von JLabel kommen und ein Icon mit einem Text präsentieren, wollen wir zunächst die Schnittstelle Icon besprechen.


Galileo Computing

16.4 Die Klasse ImageIcon  downtop

Da sich einem JLabel im Gegensatz zum normalen AWT-Label ein Bild hinzufügen lässt, schauen wir uns ein Beispiel an. Der Schlüssel hierzu liegt in der Klasse ImageIcon. Ein Exemplar dieser Klasse kann mit vielen Parametern erzeugt werden. Die Interessantesten sind: ImageIcon aus einem Bytefeld, welches in einem unterstützten Dateiformat wie GIF, JPG vorliegt, ImageIcon aus einem bestehenden Image-Objekt, aus einer Datei und von einer URL. ImageIcon-Objekte lassen sich auch serialisieren, ein großer Vorteil gegenüber Image-Objekten. Ein weiterer Vorteil ist, dass die Objekte synchron geladen werden, also direkt beim Erzeugen und nicht erst beim Zeichnen.

Abbildung

Hinweis Auch wenn es schön ist, dass Icon-Objekte serialisiert werden können, so sind diese als Bytefeld mit Farbwerten abgelegt und daher nicht komprimiert. Daher vergrößert sich eine serialisierte Bilddatei natürlich und sollte durch einen komprimierenden Datenstrom geschickt werden. Dieser komprimiert allerdings nur die Farbwerte und nicht speziell die Bildinformationen – die Kompression ist verlustfrei und arbeitet mit Millionen von Farben.

Icon-Objekte laden

Folgende Zeile reicht aus, um ein Icon zu laden:

ImageIcon icon = new ImageIcon( 
"vegetarian.gif" );

Zusammengebaut sieht das dann so aus:

Listing 16.2   ImageIconDemo.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ImageIconDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

ImageIcon icon1 = new ImageIcon( ImageIconDemo.class.getResource
( "vegetarian.gif" ) );
JLabel l1 = new JLabel( icon1 );
frame.getContentPane().add( l1, BorderLayout.WEST );

ImageIcon icon2 = new ImageIcon( ImageIconDemo.class.getResource
( "tweety.gif" ) );
JLabel l2 = new JLabel( icon2 );
frame.getContentPane().add( l2,BorderLayout.EAST );

frame.pack();
frame.show();
}
}

Das Beispiel ausgeführt, ergibt das nachfolgende Bild:

Abbildung 16.2   Ein JLabel mit Bildern
Abbildung

An dieser Abbildung lassen sich zwei wichtige Eigenschaften ablesen: Erstens wird die Transparenz der Bilder beachtet, und zweitens werden animierte Bilder auch wirklich bewegt dargestellt.

Wer für seine grafischen Oberflächen Icons einsetzt, der findet auf der Webseite http://www.cs.queensu.ca/~dalamb/qcis/icons eine schöne Sammlung kleiner Grafiken.


Galileo Computing

16.4.1 Die Schnittstelle Icon  downtop

Bei einer genauen Betrachtung fällt auf, dass ImageIcon eine Implementierung der Schnittstelle Icon ist und dass die JLabel-Klasse ein Icon-Objekt erwartet und nicht speziell ein Argument vom Typ ImageIcon. Dies heißt aber, wir können auch eigene Icon-Objekte zeichnen. Dazu müssen wir nun drei spezielle Methoden von Icon implementieren: die Methode paintIcon() und ferner zwei Methoden, die die Dimensionen angeben.

interface java.awt.swing.Icon

gp  int getIconWidth()
Liefert die feste Breite eines Icons.
gp  int getIconHeight()
Liefert die feste Höhe eines Icons.
gp  void paintIcon( Component c, Graphics g, int x, int y )
Zeichnet das Icon an die angegebene Position. Der Parameter Component wird häufig nicht benutzt. Er kann jedoch eingesetzt werden, wenn weitere Informationen beim Zeichnen wie etwa die Vorder- und Hintergrundfarbe oder der Zeichensatz bekannt sein müssen.
Beispiel Die nachfolgende Klasse zeigt die Verwendung der Icon-Schnittstelle.

Listing 16.3   MyIcon.java
import javax.swing.*;
import java.awt.*;

class MyIcon implements Icon
{
public void paintIcon( Component c, Graphics g, int x, int y )
{
g.setColor( Color.red );
g.fillOval( x, y, getIconWidth(), getIconHeight() );
}

public int getIconWidth()
{
return 20;
}

public int getIconHeight()
{
return 20;
}
}

Wir überschreiben die drei nötigen Methoden, sodass ein Icon-Objekt der Größe 20 mal 20 Pixel entsteht. Als Grafik erzeugen wir einen gefüllten roten Kreis. Dieser kann als Stopp-Schaltfläche verwendet werden, ohne dass wir eine spezielle Grafik verwenden müssen. Für die Grafik stehen uns demnach 400 Pixel zur Verfügung – genau getIconWidth() mal getIconheight() – und alle nicht gefüllten Punkte liegen transparent auf dem Hintergrund. Dies ist auch typisch für leichtgewichtigte Komponenten. Über das Component-Objekt können wir weitere Informationen herausholen, wie etwa den aktuellen Zeichensatz oder das darstellende Frame-Objekt.

Beispiel Das eigene Icon wird in einem JLabel eingesetzt.
JLabel label = new JLabel( new MyIcon() 
);
add( label );

Gezeichnet kann das dann so aussehen:

Abbildung 16.3   Ein JLabel mit Texten und geänderten Zeichensatzattributen
Abbildung


Galileo Computing

16.4.2 Was Icon und Image verbindet  downtop

An dieser Stelle möchte ich nun etwas vorgreifen, und zwar auf Image-Objekte und deren Verwandtschaft zu ImageIcon-Objekten. Vielleicht wird der eine oder andere sich schon überlegt haben, ob nun ImageIcon eine ganz eigene Implementierung neben der Image-Klasse ist oder ob beide miteinander verwandt sind. Das Geheimnis ist, dass ImageIcon die Icon-Schnittstelle implementiert, aber auch die Image-Klasse und deren verwandte Klassen nutzt. Schauen wir uns das einmal im Detail an. Ein ImageIcon ist serialisierbar. Also implementiert es erst einmal das Interface Serializable. Im Konstrukor kann ein URL-Objekt oder ein String mit einer URL stehen. Hier wird einfach getImage() vom Toolkit aufgerufen, um sich eine Referenz auf das Image-Objekt zu holen. Eine protected-Methode loadImage(Image) wartet nun mit Hilfe eines MediaTrackers auf das Bild. Etwas unnötig ist meines Erachtens der Image-Parameter, da wir das Image-Objekt sowieso als Objektvariable behalten. Nutznießer dieser Entscheidung sind lediglich Programmierer, die diese Klasse noch einmal erweitern wollen. Bleiben wir beim Laden in loadImage(). Nachdem der MediaTracker auf das Bild gewartet hat, setzt er die Höhe und Breite, die sich dann über die Icon-Methoden abfragen lassen. Doch ein richtiges Icon muss auch paintIcon() implementieren. Hier verbirgt sich nur die drawImage()-Methode. Kommen wir noch einmal auf die Serialisierbarkeit der ImageIcon-Objekte zurück. Die Klasse implementiert dazu die Methoden readObject() und writeObjekt(). Der Dateiaufbau ist sehr einfach. Es befinden sich Breite und Höhe im Dateistrom und anschließend ein Integer-Feld mit den Pixelwerten. Bei readObject() liest s.readObject() – wobei s das aktuelle ObjectInputStream ist – das Feld wieder ein und über die Toolkit-Funktion createImage() wird die Klasse MemoryImageSource genutzt, um das Feld wieder zu einem Image-Objekt zu konvertieren. Umgekehrt ist es genauso einfach. writeObject() schreibt die Breite und Höhe und anschließend das Integer-Feld, das es über ein PixelGrabber bekommen hat.

Die ImageIcon-Klasse ist somit eine hervorragende Klasse, Image-Objekte ohne viel Programmzeilen zu laden. Wir schreiben einfach

Image image = new ImageIcon("Bild.gif").getImage();

und dann besorgt uns die Klasse das Image inklusive Laden. Da ein Image immer wieder in ein ImageIcon umgewandelt werden kann, eignet sich die Klasse hervorragend zum Laden und Speichern von Bildern.


Galileo Computing

16.5 Die Schaltflächen von Swing  downtop


Galileo Computing

16.5.1 JButton  downtop

Die Swing Variante besitzt zum AWT-Button den Unterschied, dass sich Icon und Text auf der Schaltfläche befinden können. Zudem ist die Hintergrundfarbe beim Standard-Look&Feel eine etwas andere, denn sie besitzt die Hintergrundfarbe des Containers. Um dies zu ändern, lesen wir aus der SystemColor-Klasse den Wert des Attributes control aus.

Listing 16.4   JButtonDemo.java
import java.awt.*;
import javax.swing.*;

public class JButtonDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().setLayout( new FlowLayout() );

JButton b1 = new JButton( "Ask Dr. Bob" );
frame.getContentPane().add( b1 );

Icon icon = new ImageIcon( ImageIconDemo.class.getResource( "tipp.gif" ) );

JButton b2 = new JButton( icon );
b2.setBackground( SystemColor.control );
frame.getContentPane().add( b2 );

JButton b3 = new JButton( "Spartipp" );
b3.setIcon( icon );
frame.getContentPane().add( b3 );

frame.pack();
frame.show();
}
}
Abbildung 16.4   JButton als Schaltfläche mit Text, Icon und Text/Icon
Abbildung

Das Beispiel setzt drei Schaltflächen nebeneinander. Zwei davon tragen Bilder, wobei das erste Bild über den Konstruktor vergeben wird. Die zweite bebilderte Schaltfläche zeigt jedoch, dass sich die Bilder auch nachträglich mit setIcon() setzen lassen. Beim dritten JButton haben wir Bild und Text gemeinsam auf einer Schaltfläche.


Galileo Computing

16.5.2 AbstractButton  downtop

Die direkte Oberklasse für JButton ist AbstractButton, die auch für JMenuItem (somit für JCheckbox und JRadioButton) und JToggleButton Implementierungen vorgibt. Der AbstractButton ist, wie der Name schon sagt, eine abstrakte Klasse, die aus JComponent hervorgeht. Über die Oberklasse lassen sich folgende Schaltflächen steuern:

gp  Einen Mnemonik. Dies ist ein Zeichen, welches im Text unterstrichen dargestellt wird und schnell über Alt+Taste aufgerufen werden kann. Dies übernimmt die Methode setMnemonic(char).
gp  Automatisch eine Aktion auslösen durch doClick().
gp  Den Zustand des Icons auf Grund des Status’ mit setDisabledIcon(), setDisabledSelectedIcon(), setPressedIcon(), setRolloverIcon(), setRolloverSelectedIcon(), setSelectedIcon() ändern. Alle Methoden haben ein Icon-Objekt als Parameter.
gp  Die Ausrichtung von Text und Icon in der Schaltfläche durch setVerticalAlignment() und setHorizontalAlignment() bestimmen.
gp  Die Position von Icon und Text untereinander durch setVerticalTextPosition() und setHorizontalTextPosition() bestimmen.

Die Integration mit den Icon-Objekten liegt in der AbstractButton-Klasse. Geben wir im Konstruktor das Icon nicht an, so lässt sich dies immer noch über setIcon() nachträglich setzen und ändern. Wenn die Schaltfläche gedrückt wird, kann ein anderes Bild erscheinen. Dieses Icon setzt setPressedIcon(). Bewegen wir uns über die Schaltfläche lässt sich auch ein anderes Icon setzen. Dazu dient die Methode setRolloverIcon(). Die Fähigkeit muss aber erst mit setRolloverEnabled(true) eingeschaltet werden. Beide Eigenschaften lassen sich auch kombinieren, zu einem Icon, das erscheint, wenn die Maus über dem Bild ist und wenn eine Selektion gemacht wird. Dazu dient setRolloverSelectedIcon(). Für ToggleButton-Objekte ist eine weitere Methode wichtig, denn ein ToggleButton hat zwei Zustände: einen selektierten und einen nicht selektierten. Auch hier können zwei Icon-Objekte zugeordnet werden und das Icon der Selektion lässt sich mit setSelectedIcon() setzten. Ist die Schaltfläche ausgegraut, ist auch hier ein extra Icon möglich. Es wird mit setDisabledIcon() gesetzt. Dazu passt setDisabledSelectedIcon().

Abbildung

Flache Schaltflächen

Neuerdings werden Schaltflächen immer flach gezeichnet, also ohne Rahmen. Auch dies können wir in Java einrichten, und es wirkt bei Symbolleisten mit Grafiken noch eleganter. Folgende Implementierung bietet sich an:

Listing 16.5   JIconButton.java
public class JIconButton extends 
JButton
{
public JIconButton( String file )
{
super( new ImageIcon(file) );
setContentAreaFilled( false );
setBorderPainted( false );
setFocusPainted( false );
}
}

Galileo Computing

16.5.3 JToggleButton  downtop

Ein JToggleButton (zu Deutsch Wechselknopf) hat im Gegensatz zum JButton zwei Zustände. Der JButton kommt in diesen Zustand nur bei der Aktivierung, springt dann aber wieder in seinen ursprünglichen Zustand zurück. Der JToggleButton springt bei der ersten Aktivierung in einen festen Zustand und bleibt dort solange, bis er wieder aktiviert wird. Im alten AWT hat er keine Entsprechung und wird auch unter Swing selten verwendet. Er dient jedoch als Oberklasse für die Auswahlknöpfe JCheckBox und JRadioButton.


Galileo Computing

16.5.4 JCheckBox  downtop

Diese besondere Auswahlkomponente hat zwei Zustände: ausgewählt und nicht ausgewählt. Im Gegensatz zur Schaltfläche zeigt die Komponente nur ein Rechteck. Der JCheckBox lassen sich – gegenüber der Checkbox aus dem AWT – verschiedene Grafiken für den eingeschalteten und ausgeschalteten Zustand zuweisen. Dazu dienen die Methoden set Icon() und setSelectedIcon(). Ist der erste Parameter im Konstruktor ein Text, so lässt sich als zweiter Parameter ein Wahrheitswert angeben, der bestimmt, ob das Häkchen am Anfang gesetzt ist oder nicht.

Listing 16.6   JCheckBoxDemo.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class JCheckBoxDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

Icon unchecked =
new ImageIcon(ImageIconDemo.class.getResource("not.gif"));
Icon checked =
new ImageIcon(ImageIconDemo.class.getResource("ok.gif"));

JCheckBox cb1 = new JCheckBox( "Ich", true );
cb1.setIcon( unchecked );
cb1.setSelectedIcon( checked );
frame.getContentPane().add( cb1, BorderLayout.WEST );

JCheckBox cb2 = new JCheckBox( "Nein ich", false );
cb2.setIcon( unchecked );
cb2.setSelectedIcon( checked );
frame.getContentPane().add( cb2, BorderLayout.EAST );

frame.pack();
frame.show();
}
}

Das Beispiel ergibt folgende Grafik:

Abbildung 16.5   JCheckbox mit Icon-Objekten
Abbildung


Galileo Computing

16.5.5 Radiogruppen  downtop

Schließen sich die Auswahlknöpfe gegenseitig aus, dann ist eine Verbindung der Klassen JRadioButton und ButtonGroup zu empfehlen. Werden die JRadioButton-Objekte erzeugt, so lassen sie sich später einer Gruppe hinzufügen, sodass nur jeweils ein Element ausgewählt sein kann. Es ist nicht vergleichbar mit den Thread-Gruppen, wo beim Erzeugen eines Threads dieser schon direkt in eine Gruppe hineinpositioniert werden muss.

Um die sich gegenseitig ausschließenden Auswahlknöpfe von den JCheckBox-Objekten unterscheiden zu können, werden diese rund gezeichnet. Das Programmsegment beleuchtet den Einsatz dieser beiden Klassen. setSelected() setzt den Radioauswahlknopf, der als Erstes ausgewählt ist:

JRadioButton rb1 = new JRadioButton( 
"Simply Red" );
JRadioButton rb2 = new JRadioButton( "Gus Gus" );

rb1.setSelected();

ButtonGroup g = new ButtonGroup();

g.add( rb1 ); g.add( rb2 );

Zum Schluss müssen alle Radioschaltflächen (und nicht die Radiogruppe) zum Container hinzugefügt werden.


Galileo Computing

16.6 Tooltips  downtop

Ein Tooltip ist eine Zeichenkette, die beim längeren Verweilen auf einer JComponent auftaucht. Dazu öffnet Swing ein Popup-Fenster. Tooltips lassen sich in Swing sehr einfach mit Hilfe der Klasse Tooltip erzeugen. Es öffnet sich ein Fenster, wenn der Mauszeiger länger auf der Komponente verweilt. Das Fenster mit dem Hilfetext ist immer im Swing-Fenster und nie außerhalb. Passt die Hilfe nicht auf das Fenster wird sie gar nicht dargestellt, und die Komponente macht durch Flackern auf sie aufmerksam.

Im folgenden Programm ist der Hilfetext etwas umfangreicher, sodass das Fenster vergrößert werden muss:

Listing 16.7   TooltipDemo.java
import java.awt.*;
import javax.swing.*;

public class TooltipDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

String text = "<html>Ich brauch' Hilfe.<p>Schnell!</html>";
JButton button = new JButton( text );

String help = "<html>Hier ist sie, die <b>Hilfe:</b>"+
"<ul><li>Cool bleiben<li>Handbuch lesen</ul></html>";
button.setToolTipText( help );

frame.getContentPane().add( button );
frame.setSize( 250, 250 );
frame.show();
}
}

Dann erscheint Folgendes:

Abbildung 16.6   Die schnelle Hilfe
Abbildung

Interessant ist an den neueren Swing-Versionen, dass der Text der Komponenten auch in HTML gesetzt werden kann. So sind einfache Layouts denkbar wie die gezeigte Aufzählung. So lassen sich auch Texte untereinander stellen, was sonst im AWT etwa bei Schaltflächen nicht ohne Extra-Klassen möglich ist.


Galileo Computing

16.7 JScrollBar  downtop

Ist das leichtgewichtige Pendant zu AWT Scrollbar ist JScrollbar. Sie empfängt ebenfalls AdjustmentEvents. Dem JScrollBar ist ein BoundedRangeModel zugewiesen, welches die Daten speichert.


Galileo Computing

16.8 JSlider  downtop

Der Slider ist mit dem JScrollBar verwandt, er dient jedoch im Speziellen zur Auswahl eines Werts aus einem Zahlenbereich. Zudem fügt er noch zwei interessante Eigenschaften hinzu. Beim JSlider lassen sich unter oder neben dem eigentlichen Schieberegler noch Markierungen (engl. Tick Marks) setzen. Diese sind wiederum eingeteilt in große und kleine Markierungen. Die Ticks lassen ich mit der Methode setPaintTicks(true) setzen. Damit sich die Abstände der Unterteilungen bestimmen lassen wird setMinorTickSpacing(int) oder auch setMajorTickSpacing(int) verwendet. Die letzten Methoden sind unabhängig voneinander. Zusätzlich zu den Ticks erlaubt die Klasse auch eine automatische Nummerierung der Striche. Dann muss die Methode setPaintLabels(true) gesetzt werden. Eigene Wertbereiche werden in einer Hash-Tabelle definiert und mit setLabelTable() zugewiesen.

Listing 16.8   JSliderDemo.java
import java.awt.*;
import javax.swing.*;

public class JSliderDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().setLayout( new GridLayout(4,0) );

JSlider slider1 = new JSlider( 0, 100, 50 );
frame.getContentPane().add( slider1 );

JSlider slider2 = new JSlider( 0, 100, 50 );
slider2.setPaintTicks( true );
slider2.setMinorTickSpacing( 5 );
frame.getContentPane().add( slider2 );

JSlider slider3 = new JSlider( 0, 100, 50 );
slider3.setPaintTicks( true );
slider3.setMajorTickSpacing( 10 );
frame.getContentPane().add( slider3 );

JSlider slider4 = new JSlider ( 0, 100, 50 );
slider1.setPaintTicks(true);
slider1.setMajorTickSpacing( 10 );
slider1.setMinorTickSpacing( 2 );
frame.getContentPane().add( slider4 );

frame.pack();
frame.show();
}
}
Abbildung 16.7   Die verschiedenen JSlider ohne Nummerierung
Abbildung


Galileo Computing

16.9 JList  downtop

Die JList weist größere Unterschiede zum AWT-Original auf als andere Swing-Komponenten. Das liegt an ihrem Model, welches die Daten speichert – eine add() Methode ist der JList fremd. Das Model wird jedoch durch einen Konstruktor versteckt, der Daten in einem String-Array oder Vector entgegennimmt.

Beispiel Erzeuge mit einem Standard-Modell eine JList mit ein paar Zeichenketten:
String listData[] = {  "Shinguz", 
"Glapum'tianer", "Suffus", "Zypanon", "Tschung" };
JList jlist = new JList( listData );

Ein weiterer Unterschied zu einer AWT-Liste zeigt sich, wenn die Liste gescrollt werden soll. Dann muss sie in einem JScrollPane-Container eingebettet warden.

Abbildung

Beispiel Wir fügen einige Elemente in eine JList ein. Listing 16.9   JListDemo.java
import javax.swing.*;

public class JListDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

String data[] = {
"Williams Christ",
"Comice",
"Kaiserkrone",
"Gute Luise"
};

frame.getContentPane().add( new JList( data ) );

frame.pack();
frame.show();
}
}


Galileo Computing

16.10 JComboBox  downtop

Die JComboBox ist ein Auswahlmenü, welches ein Textfeld zur Eingabe anbietet und zudem ein Aufklappmenü enthält. In diesem können Texte in beliebigen Modellen dargestellt und ausgewählt werden; ein Tastendruck lässt die Liste zu dem Eintrag springen, dessen Buchstabe eingegeben wurde. Ob das Textfeld editiert werden kann, bestimmt setEditable(). Ist es nicht editierbar, ist es ähnlich der AWT-Komponente Choice. Befinden sich zu viele Einträge in der Liste, stellt Swing automatisch eine scrollende Liste dar. Ab welcher Anzahl von Elementen die scrollende Liste dargestellt wird, bestimmt setMaximumRowCount(). Mit addItem() lassen sich Elemente hinzufügen, mit removeItem() wieder entfernen. Mit getItemAt(index) lassen sich die Elemente erfragen und das aktuell ausgewählte Elemente erfahren wir mit getSelectedItem(); den Index mit getSelectedIndex().

Listing 16.10   JComboBoxDemo.java
import java.awt.*;
import javax.swing.*;

public class JComboBoxDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

String lang[] = { "Java", "C++", "Perl", "Cobol", "Forth",
"Lisp", "Eiffel", "Smalltalk", "Apl" };

JComboBox combo1 = new JComboBox();
JComboBox combo2 = new JComboBox();

for ( int i=0; i<lang.length; i++ ) {
combo1.addItem( lang[i] );
combo2.addItem( lang[i] );
}

combo2.setEditable( true );
combo2.setSelectedItem( "Sather" );
combo2.setMaximumRowCount( 4 );

frame.getContentPane().add( combo1, BorderLayout.WEST );
frame.getContentPane().add( combo2, BorderLayout.EAST );

frame.pack();
frame.show();
}
}

Die Methode addItem() funktioniert nur dann, wenn im Konstruktor kein spezielles Modell angegeben wurde. Mit Modellen werden wir uns zu einem späteren Zeitpunkt näher beschäftigen.

Abbildung 16.8   JComboBox
Abbildung


Galileo Computing

16.11 Der Fortschrittsbalken JProgressBar  downtop

Mit der Komponente für einen Fortschrittsbalken (auch Verlaufsbalken genannt) lassen sich Anzeigen visualisieren, die das Vorankommen (Status) einer Anwendung beschreiben. Ein Fortschrittsbalken – der unter dem AWT keine Entsprechung hat – lässt sich mit mehreren Konstruktoren erzeugen. Der Standardkonstruktor erzeugt einen horizontalen Fortschrittsbalken. Es existieren zusätzliche Konstruktoren für die Orientierung JProgressBar.HORIZONTAL und JProgressBar.VERTICAL und ein eingestelltes Maximum und Minimum. Nachträglich lassen sich diese Eigenschaften jedoch noch mit setOrientation(int), setMinimum(int) und setMaximum(int) ändern.

Abbildung 16.9   Anzeige eines Fortschrittsbalkens
Abbildung

Listing 16.11   JProgressBarDemo.java
import javax.swing.*;

public class JProgressBarDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

final int max = 10;

final JProgressBar bar = new JProgressBar( 0, max );
frame.getContentPane().add( bar );

frame.pack();
frame.show();

// Anzeige in Veränderung

for ( int i = 1; i <= max; i++ )
{
try { Thread.sleep( 1500 ); } catch ( InterruptedException e ) { }

final int j = i;
SwingUtilities.invokeLater( new Runnable() {
public void run() { bar.setValue( j ); }
} );
}
}
}

Galileo Computing

16.12 Symbolleisten alias Toolbars  downtop

Mit der Klasse JToolBar, die unter dem AWT keine Entsprechung besitzt, lassen sich Symbolleisten erstellen. Diese Symbolleisten erhalten häufig eine Menge von Schaltflächen, die horizontal oder vertikal angeordnet sein dürfen. Für die JToolBar-Klasse ist dies aber unerheblich, denn sie nimmt beliebige Swing-Komponenten an. Schöner sieht es jedoch aus, wenn alle Komponenten die gleiche Größe besitzen.

Listing 16.12   JToolBarDemo.java
import java.awt.*;
import javax.swing.*;

public class JToolBarDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

// Erste Symbolleiste

JToolBar toolbar = new JToolBar();

toolbar.add( new JButton("Compile") );
toolbar.add( new JButton("Undo") );
toolbar.addSeparator();
toolbar.add( new Checkbox("Love Bill") );
toolbar.add( new JComboBox() );

frame.getContentPane().add( toolbar, BorderLayout.NORTH );

// Zweite Symbolleiste

toolbar = new JToolBar();

Icon iconOk = new ImageIcon( (ImageIconDemo.class.getResource("ok.gif") ) );
Icon iconNo = new ImageIcon( (ImageIconDemo.class.getResource("not.gif") ) );

toolbar.add( new JButton( iconOk ) );
toolbar.add( new JButton( iconNo ) );

frame.getContentPane().add( toolbar, BorderLayout.SOUTH );

frame.pack();
frame.show();
}
}

Interessant ist auch, dass der Benutzter die Symbolleisten frei verschieben kann. Dann erscheinen die aufgenommenen Komponenten in einem eigenen Fenster mit einem Titel, der sich im Konstruktor festlegen lässt. Diese Eigenschaft lässt sich mit der Methode setFloatable(false) aber ausschalten. Das Fenster ist jedoch schwergewichtig.

Abbildung 16.10   Fenster mit horizontalen und vertikalen JToolbar-Objekten
Abbildung


Galileo Computing

16.13 Texteingaben  downtop


Galileo Computing

16.13.1 JPasswordField  downtop

Das JPasswordField ist ein spezielles JTextField, welches die Zeichen nicht auf dem Bildschirm darstellt, sondern ein alternatives Zeichen, das so genannte Echozeichen – standardmäßig ein Sternchen. So lassen sich Passwort-Felder anlegen, die eine Eingabe verbergen. Leider lässt sich jedochdarauf schließen, wie viele Zeichen die geheime Eingabe hat, was nicht immer erwünscht ist. Wird das Echozeichen auf (char)0 gesetzt, erscheint die Eingabe nicht im Klartext wie es die AWT-Komponente TextField macht:

JPasswordField pass = new JPasswordField( 
15 );
pass.setEchoChar( '#' );
add( pass );

Im Konstruktor geben wir die Länge der Textzeile an. Mit der Methode setEchoChar() lässt sich das Echozeichen festlegen.

Abbildung 16.11   Das Passwort-Feld mit eigenem Echo-Zeichen
Abbildung


Galileo Computing

16.13.2 Die Editor-Klasse JEditorPane  downtop

Die Klasse JEditorPane ist eine sehr leistungsfähige Textkomponente für verschiedene Textformate. Die Swing-Implementierung unterstützt HTML und Rich Text Format (RTF), eigene Implementierungen lassen sich ohne große Probleme beifügen. Diese werden Editor-Kits genannt. Der Editor stellt Text dar, der ihm mit setContentType() übergeben wird. Das Editor-Kit wird dann mit setEditorKit() zugewiesen. Ohne eigene Erweiterungen sind »text/html« (Standard), »text/plain« und »text/rtf« erlaubt. Soll nur Text ohne Formatierungen und ohne Attribute dargestellt werden, lässt sich auch gleich JTextField verwenden.

Meistens wird ein JEditorPane über einen Konstruktor erzeugt, dem eine URL oder ein String mit einer URL übergeben wird. Für Programme mit Dateien auf dem lokalen Dateisystem wird dann die URL mit file:// beginnen. Wird mit dem Standard-Konstruktor gearbeitet, so kann später mit setPage() ein URL-Objekt oder ein String eine Seite neu belegen. Auch setText() erlaubt ein Setzen des Inhalts. Zu guter Letzt lässt sich der Editor auch mit einem InputStream über read() mit Inhalt füllen.

Beispiel Um einfach eine HTML-Seite ohne Interaktion anzuzeigen sind nur wenige Zeilen nötig:
String url = "http://host/path";
try {
JEditorPane htmlPane = new JEditorPane( url );
htmlPane.setEditable( false );
component.add( new JScrollPane(htmlPane) );
} catch( IOException e ) {
System.err.println( "Error displaying " + url );
}


Galileo Computing

16.14 Rahmen (Bordersdowntop

Jeder Swing-Komponente kann mit der Methode setBorder() ein Rahmen zugewiesen werden. Ein Rahmen ist eine Klasse, die die Schnittstelle Border implementiert. Swing stellt einige Standard-Rahmen zur Verfügung:

Tabelle 16.1   Border in Swing
AbstractBorder Eine abstrakte Klasse, die die Schnittstelle minimal implementiert.
BevelBorder Ein 3D-Rahmen, der eingelassen sein kann.
CompoundBorder Ein Rahmen, der andere Rahmen aufnehmen kann.
EmptyBorder Rahmen, dem freier Platz zugewiesen werden kann.
EtchedBorder Noch deutlicher markierter Rahmen.
LineBorder Rahmen in einer einfachen Farbe in gewünschter Dicke.
MatteBorder Rahmen, bestehens aus Kacheln von Icons.
SoftBevelBorder Ein 3D-Rahmen mit besonderen Ecken.
TitledBorder Rahmen mit String in einer gewünschten Ecke.

Abbildung

Beispiel Damit können wir ein kleines Test-Programm für Rahmen implementieren. Listing 16.13   BorderDemo.java
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;

public class BorderDemo
{
public static void main( String args[] )
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.getContentPane().setLayout( new GridLayout(0,2,10,10) );
 JButton b1 = new JButton( "Schamlis" 
);
b1.setBorder( new BevelBorder(BevelBorder.RAISED) );
frame.getContentPane().add( b1 );

JButton b2 = new JButton( "Borfluq" );
b2.setBorder( new BevelBorder(BevelBorder.LOWERED) );
frame.getContentPane().add( b2 );

JButton b3 = new JButton( "Tüm Tüm de Lüm" );
b3.setBorder( new EtchedBorder() );
frame.getContentPane().add( b3 );

JButton b4 = new JButton( "Skromm" );
b4.setBorder( new EtchedBorder(Color.blue, Color.yellow) );
frame.getContentPane().add( b4 );

frame.setSize( 500, 200 ); frame.show();
}
}

Abbildung 16.12   BevelBorder und EtchedBorder
Abbildung


Galileo Computing

16.15 Dialoge  downtop

Vor Swing hatten Entwickler außer den Dateiauswahl-Dialog keinen weiteren Dialog. Unter Swing hat sich die Situation deutlich verbessert und eine ganze Reihe an Standard-Dialogen wird angeboten. Die Klasse JOptionPane ist dabei Basis für eine ganze Reihe von Dialogen. Sie stellt statische Methoden bereit, mit der sich Meldedialoge, Eingabedialoge, Bestätigungsdialog Optionsdialog mit nur einer Zeile implementieren lassen. Einige Beispiele:

Ein Meldedialog:

JOptionPane.showMessageDialog( null,
"Wir Kinder aus dem Möwenweg" );

Ein Eingabedialog:

JOptionPane.showInputDialog( "Bitte 
Zahl eingeben" );

Ein Bestätigungsdialog:

JOptionPane.showConfirmDialog( null,"Alles 
OK?" );

Ein Optionsdialog:

String[] optionen = { "Ja", "Nein", 
"Abbrechen" };
int n = JOptionPane.showOptionDialog( null,
"Ja oder Nein?", "Ja/Nein/Abbrechen",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE,null,
optionen,optionen[0] );
if ( n == JOptionPane.YES_OPTION )
System.out.println("Ja gewählt");

Galileo Computing

16.15.1 Der Farbauswahl-Dialog JColorChooser  downtop

Die Klasse JColorChooser ist unter Swing neu. Mit diesem Dialog lassen sich Farben in drei unterschiedlichen Dialogen auswählen. Der Benutzer hat die Auswahl unter vordefinierten Farben, HSB-Werten und RGB-Werten. Um den Farbauswahl-Dialog auf dem Bildschirm zu bekommen, genügt ein Aufruf von JColorChooser.showDialog() mit drei Argumenten: einem Component-Objekt, auf dem gezeichnet werden kann, dem Titel und eine Anfangsfarbe. Beendet der Benutzer den Dialog, so wird als Rückgabewert die ausgewählte Farbe geliefert. Wird der Dialog abgebrochen, ist der Rückgabewert null. Das Beispiel bringt eine Schaltfläche auf den Schirm, die den Auswahldialog darstellt. Eine ausgewählte Farbe wird zur Hintergrundfarbe der Schaltfläche.

Abbildung 16.13   Der Farbauswahl-Dialog JColorChooser
Abbildung

Listing 16.14   JColorChooserDemo.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class JColorChooserDemo extends JFrame implements ActionListener
{
JColorChooserDemo()
{
setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

JButton b = new JButton( "Farbe ändern" );
getContentPane().add( b );
b.addActionListener( this );

pack();
show();
}

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

public void actionPerformed( ActionEvent e )
{
Component comp = (Component)e.getSource();

Color newColor = JColorChooser.showDialog(
null, "Wähle neue Farbe", comp.getBackground() );

comp.setBackground( newColor );
}
}

Den Aufruf mit showDialog() einzuleiten ist nicht der einzige Weg. Wir können auch den Konstruktor nutzen und dann später mit JColorChooser.createDialog() dieses Exemplar übergeben und anzeigen.


Galileo Computing

16.16 Mausradunterstützung  downtop

Seit Microsoft das kleine Rädchen zum schnellen Bewegen der Schieberegler unterstützt und fördert, ist nach der Erfindung der Maus noch eine weitere Vereinfachung der Handhabung grafischer Oberflächen hinzugekommen. Eine Unterstützung wurde in Java Version 1.4 implementiert. Für alle anderen Java-Versionen bleibt nur der Weg über eine native Zusatzimplementierung.

Abbildung

Davanum Srinivas hat sich einer solchen Implementierung angenommen. So lässt sich auch bei Swing-Applikationen der Schieberegler über das Rädchen bewegen. Die DLL mit der MFC-Implementierung findet sich unter http://www.codeproject.com/java/mouse wheel.asp. Das Beispiel ist jedoch etwas größer, sodass an dieser Stelle ein kleines Programm die Funktionalität schon zeigt.

Listing 16.15   WheelHells.java
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

class WheelHells
{
public static void main( String args[] )
{
JFrameEx frame = new JFrameEx( "ScrollDemo2" );
frame.addWindowListener( new WindowAdapter() {
public void windowClosing(WindowEvent e){System.exit(0);}
} );

Vector v = new Vector();

for ( int i=0; i<20; i++ )
v.add( "" + Math.random() );

JList list = new JList( v );

frame.setContentPane( new JScrollPane(list) );

frame.pack();
frame.setVisible( true );
}
}

Soll die Radunterstützung eingeschaltet sein, so erben unsere Programme nicht mehr von JFrame, sondern von JFrameEx.

Implementierung

Die Klasse JFrameEx fängt nun für die komplette Applikation die Koordinaten ab und findet heraus, welche grafische Komponente denn gemeint sein könnte. Da jedes scrollbare Element in einer JScrollPane liegt, versucht der Ereignisbehandler den Vater (also den JScrollPane) zu bekommen. Von diesem wird dann der vertikale JScrollBar geholt und vom diesem wiederum der Wert. Dann kann durch Mausradbewegung die neue Position der Schieberegler mit setValue() gesetzt werden. Leider bietet die Klasse bisher keine Methode, um die Schrittweite anzupassen, die in der aktuellen Version etwas knapp bemessen ist.


Galileo Computing

16.17 Der Inhalt einer Zeichenfläche: JPanel  downtop

Die Arbeitsweise mit dem Swing-Toolkit schreibt uns zwingend eine Zeichenfläche vor. So gibt es unter Swing auch ein Panel-Widget, welches sich JPanel nennt. Gegenüber dem Panel aus dem AWT bietet es einige Vorteile. Zum einen ist es wie jede Swing Komponente leichtgewichtig und erlaubt Double-Buffering. Ist diese Pufferung eingeschaltet, so werden alle Zeichenoperationen von Komponenten auf dem Panel auf einem Offscreen gemacht und zu einem gewählten Zeitpunkt in den Vordergrund geschoben. Die Möglichkeit des Double-Buffering lässt sich einmal durch den Konstruktor setzen oder über die Methode setBuffered(). Als Designer von Benutzungsschnittstellen dürfen wir jedoch nicht vergessen, dass das Double-Buffering eine Menge Rechenzeit kostet und somit der Bildschirmaufbau träge wird.

Die Klasse JPanel wird von uns in vielen Beispielen eingesetzt, dort aber lediglich als Containerobjekt. Neben den geerbten Funktionen von JComponent, Container, Component und natürlich Object kommen keine nennenswerten Funktionen hinzu. Nachfolgend wollen wir die vier Konstruktoren in der Klasse beschreiben:

class java.awt.swing.JPanel
extends JComponent implements Accessible

gp  Jpanel()
Erzeugt ein neues JPanel mit Double-Buffering und einem Flow-Layout.
gp  JPanel( boolean isDoubleBuffered )
Erzeugt ein neues JPanel mit Flow-Layout und der angegebenen Puffer-Strategie.
gp  JPanel( LayoutManager layout, boolean isDoubleBuffered )
Erzeugt ein neues JPanel mit dem angegebenen Layout-Manager und Puffer-Strategie.
gp  JPanel( LayoutManager layout )
Erzeugt einen JPanel mit Double-Buffering und dem angegebenen Layout-Manager.

Galileo Computing

16.18 JRootPane und JLayeredPane  downtop

Ein JRootPane ist ein Container, der aus einer Glass-Pane und Layered-Pane besteht. Die Glass-Pane liegt über allen anderen Komponenten wie eine Glasscheibe. Die Layerd-Pane nimmt JComponent-Objekte auf und stellt sie mit in einer geschichteten Reihenfolge dar. Die Layerd-Pane besteht selbst wieder aus zwei Objekten, einer Menüzeile und der Inhaltsfläche Content-Pane. Container vom Typ JLayeredPane platzieren ihre Kinder in Ebenen (engl. Layers). Jedes Kind wird eine Ebene zugewiesen und beim Zeichnen werden die Kinder von unten nach oben gezeichnet. Damit werden die Komponenten, die unter andern Komponenten liegen unter umständen verdeckt.

Wird ein JLayeredPane-Kontainer verwendet, so ist die add()-Methode so implementiert, dass die Komponenten auf einer Standard-Ebene eingetragen werden. Diese Ebene ist ein Wert zugewiesen, JLayeredPane.DEFAULT_LAYER. Um eine Komponente auf eine eigene Ebene zu setzen, damit sie vor oder hinter anderen Komponenten liegen, wird ihnen eine eigene Ebene zugewiesen, mit einem Wert relativ zu DEFAULT_LAYER. Kleinere Werte bedeuten, dass die Komponenten unten liegen und hohe Werte bedeutet, dass sie oben liegen. Ein Beispiel:

layeredPane.add( component, new 
Integer(5000) );

Galileo Computing

16.19 Tabellen mit JTable  downtop

Mit der Klasse JTable lassen sich auf einfache Weise zweidimensionale Tabellendaten darstellen. Die Javabibliothek bietet dafür eine einfache Schnittstelle, die über ein Modell und eine eigene Visualisierung ergänzt werden kann. Die vorgefertigte Implementierung bietet schon vieles an, wie zum Beispiel Änderung der Spaltenbreite, Navigation über Tabulatortasten oder Selektion von Spalten oder Zeilen.

Für JTable gibt es einen Konstruktor, der ein zweidimensionales Feld annimmt und die Tabelle dann darstellt. Für uns fällt dabei wenig Arbeit an. Das 2D-Feld kann sich aus Object[][] oder auch aus Vektoren von Vektoren zusammensetzen. Intern wird ein Objektfeld jedoch in Vektoren kopiert.

Beispiel Gewünscht ist eine Tabelle mit zwei Spalten aus Strings
String data[][] = { {"A", "B" }, 
{ "U", "V" } };
JTable table = new JTable( data );

Tabelle in einer JScrollPane

Reicht der Platz für die Tabelle im Container nicht aus, so ist es sinnvoll, die Tabelle in eine JScrollPane zu setzen. Auch dann werden erst die Köpfe für die Tabelle angezeigt. Möchten wir die Spaltennamen extra setzen, so geben wir im Konstruktor einen zweiten Parameter mit den Namen an.

Abbildung 16.14   BevelBorder und EtchedBorder
Abbildung

Beispiel Eine Tabelle mit Überschriften in einer JScrollPane Listing 16.16   SimpleTable.java
import javax.swing.*;

public class SimpleTable
{
public static void main( String args[] )
{
String rowData[][] = {
{ "Japan", "245" }, { "USA", "240" }, { "Italien", "220" },
{ "Spanien", "217" }, {"Türkei", "215"} ,{"England", "214"},
{ "Frankreich", "190" }, {"Griechenland" ,"185" },
{ "Deutschland", "180" }, {"Portugal" ," 170" }
};

String columnNames[] = {
"Land", "Durchschnittliche Sehdauer pro Tag in Minuten"
};

JTable table = new JTable( rowData, columnNames );

JFrame frame = new JFrame();
frame.getContentPane().add( new JScrollPane(table) );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.pack();
frame.setVisible( true );
}
}


Galileo Computing

16.19.1 Ein eigenes Modell  downtop

JTable ist ein gutes Beispiel für die Trennung von Daten und Anzeige. Während View und Controller in der Klasse JTable liegen, werden die Daten im Modell durch die Schnittstelle TableModel beschrieben. Jeder Datencontainer muss daher TableModel implementieren und der Anzeige eine Möglichkeit geben, Einträge in einer Zeile und Spalte zu erfragen. Ändert sich das Modell, muss zusätzlich die Visualisierung aktualisiert werden. Daher schreibt TableModel einen TableModelListener vor, der die Beobachtung übernimmt.

interface javax.swing.table.TableModel

gp  Class getColumnClass( int columnIndex )
Liefert das allgemeinste Klassenobjekt, welches die Spalte beschreiben kann.
gp  int getColumnCount()
Liefert die Anzahl Spalten.
gp  String getColumnName( int columnIndex )
Gibt den Namen der Spalte columnIndex zurück.
gp  int getRowCount()
Liefert die Anzahl der Zeilen.
gp  Object getValueAt( int rowIndex, int columnIndex )
Gibt den Eintrag an der Stelle columnIndex und rowIndex zurück.
gp  void setValueAt( Object aValue, int rowIndex, int columnIndex )
Setzt den Wert an die gegebene Stelle.
gp  boolean isCellEditable( int rowIndex, int columnIndex )
Liefert true, wenn die Zelle an rowIndex und columnIndex editierbar ist.
gp  void addTableModelListener( TableModelListener l )
Fügt einen Ereignisbehandler zu, der immer dann informiert wird, wenn Daten geändert werden.
gp  void removeTableModelListener( TableModelListener l )
Entfernt den Horcher.

Galileo Computing

16.19.2 AbstractTableModel  downtop

Für TableModel gibt es schon eine Implementierung als abstrakte Klasse, die uns die Aufgabe abnimmt, uns um die Listener zu kümmern. Die Klasse heißt AbstractTableModel und gibt für einige Methoden eine Standard-Implementierung vor. AbstractTableModel bietet Zugriff auf die Listener über eine protected-Variable listenerList.

Um ein lauffähiges Model zusammenzubauen, muss nur noch getColumnCount(), getRowCount() und getValueAt() implementiert werden, dann ist eine Modellklasse komplett. setValueAt() ist leer implementiert und muss nur bei editierbaren Datenmodellen angepasst werden. isCellEditable() liefert false und muss so bei editierbaren Modellen ebenso überschrieben werden. getColumnName() liefert Spaltennamen nach dem Muster A, B, C, ... Z, AA, AB. getColumnClass() liefert Object.class. Um nach einer Spalte suchen zu können, gibt findColumn(String) den Index der Spalte zurück, die den eingetragenen Namen hat. Die Suche ist linear und kann für große Tabellen daher langsam sein.

Beispiel Wenn wir eine Tabelle mit Quadrat und Kubik nutzen, dann können wir eine Modell implementieren, das in der ersten Spalte die Zahl, in der zweiten das Quadrat und in der dritten das Kubik abbildet. Die Tabelle besitzt damit 3 Spalten. Sie soll 10 Zeilen groß sein.

Abbildung 16.15   Klassenhierarchie für EtchedBorder
Abbildung

Listing 16.17   QuadratTable.java, QuadratTableModelSimple
class QuadratTableModelSimple extends 
AbstractTableModel
{
public int getRowCount()
{
return 10;
}

public int getColumnCount()
{
return 3;
}

public Object getValueAt( int row, int col )
{
if ( col == 0 )
return ""+row;
else if ( col == 1 )
return ""+(row*row);
else
return ""+(row*row*row);
}
}
Abbildung 16.16   JTable mit Model
Abbildung

Ereignisse bei Änderungen

Die Events, die AbstractTableModel auslöst, sind vom Typ TableModelEvent, und werden von fireTableDataChanged(), fireTableStructureChanged(), fireTableRowsInserted(), fireTableRowsUpdated(), fireTableRowsDeleted(), fireTableCellUpdated() über die allgemeine Methode fireTableChanged(TableModelEvent) behandelt. Die Methoden zur Ereignisbehandlung sind damit komplett und müssen von Unterklassen nicht mehr überschrieben werden, es sei denn, wir wollten zusätzliche Dinge in einer fire() Methode realisieren.

Beispiel Ändern sich die Daten, muss die Visualisierung erneuert werden. Dann sollte fireTableCellUpdated() aufgerufen werden wie für die setValueAt()-Methode gezeigt wird.

public void setValueAt( Object val, 
int row, int column )
{
foo[row][column] = aValue;
fireTableCellUpdated( row, column );
}

Die Methode fireTableCellUpdated(int,int) ist nur eine Abkürzung für Folgendes:

public void fireTableCellUpdated(int 
row, int column) {
fireTableChanged(new TableModelEvent(this, row, row, column));
}

Galileo Computing

16.19.3 DefaultTableModel  downtop

Kommt ein JTable ohne eigenes Modell zum Einsatz, nutzt die Implementierung eine Unterklasse von AbstractTableModel, das sich DefaultTableModel nennt. Es ist nicht abstrakt und könnte auch von uns verwendet werden. Nützliche Ergänzungen sind Methoden, damit an beliebiger Stelle Zellen eingetragen, verschoben und gelöscht werden können. Nutzten wir JTable ohne eigenes Modell, so verwendet es standardmäßig DefaultTableModel mit einer Implementierung von Vektoren aus Vektoren. Ein Vektor ist für die Zeile und jede Spalte ist in einem eigenen Vektor implementiert. Mit den Methoden setDataVector() und getDataVector() lassen sich dann auch die Daten intern setzen und auslesen. Diese interne Abbildung der Daten ist jedoch nicht immer gewünscht, da dynamische Strukturen von der Laufzeit her ineffizient sein können. Ist das zu unflexibel, lässt sich immer noch von AbstractTableModel ableiten.

Abbildung 16.17   Klassenhierarchie für EtchedBorder
Abbildung

Das Modell nutzen

Besitzen wir eine Klasse, die ein TableModel implementiert, etwa eine Unterklasse von AbstractTableModel oder DefaultTableModel, so können wir ein JTable mit diesem Modell anlegen. Dafür gibt es zwei Möglichkeiten: im Konstruktor das Modell angeben oder es nachträglich mit setModel() zuweisen.

Beispiel Das QuadratTableModelSimple soll unserer Tabelle zugewiesen werden.
QuadratTableModelSimple model = 
new QuadratTableModel();
JTable table = new JTable();
table.setModel( model );


Galileo Computing

16.19.4 Einen eigenen Renderer für Tabellen  downtop

Damit eine Tabelle nicht nur die typischen Informationen in Zeichenketten darstellen muss, lässt sich ein TableCellRenderer einsetzen, damit sich die Tabelleneinträge beliebig visualisieren lassen. Die Schnittstelle TableCellRenderer schreibt nur eine Methode vor.

interface javax.swing.table.TableCellRenderer

gp  Component getTableCellRendererComponent( JTable table,
Object value,boolean isSelected, boolean hasFocus,
int row, int column);

Die Informationen über isSelected, hasFocus, row, column sollen der Zeichenfunktion die Möglichkeit geben, ausgewählte Tabellenteile besonders zu behandeln. Steht etwa auf einer Zelle der Fokus, ist ein Rahmen gezeichnet. Ist die Tabelle selektiert, ist die Zelle mit einer Hintergrundfarbe ausgeschmückt.

DefaultTableCellRenderer

Glücklicherweise bietet Swing wieder eine Standard-Implementierung in Form der Klasse DefaultTableCellRenderer. Diese erweitetert JLabel. Damit lässt sich schon viel anfangen. Das Ändern des Texts ist genauso einfach wie das Ändern der Farbe oder das Hinzufügen eines Bilds. Viele Aufgaben sind damit schon erledigt. Wenn es aufwändiger realisiert werden soll, dann müssen wir direkt TableCellRenderer implementieren.

Abbildung 16.18   Klassenhierarchie für EtchedBorder
Abbildung

Für unsere Zwecke soll DefaultTableCellRenderer reichen. Die wichtigste Methode zum Überschreiben ist setValue(Object). In DefaultTableCellRenderer sieht die Original-Methode wie folgt aus:

protected void setValue(Object value) 
{
setText((value == null) ? "" : value.toString());
}

Da JTable diesen Renderer als Standard nutzt, sagt es aus, dass alle Daten in der Tabelle als Stringrepräsentation eingesetzt werden.

Wenn wir eigene Visualisierungen wünschen, zum Beispiel mit einer anderen Schriftfarbe, so überschreiben wir einfach setValue() und setzen den Text mit setText() selbst.

Beispiel In einer Tabelle befinden sich nur Zahlen. Ist eine Zahl negativ, so soll sie rot erscheinen.
public void setValue( Object value 
)
{
String s = value.toString();

if ( Integer.parseInt( s ) < 0 )
setForeground( Color.red );

setText( s );
}

Die günstige Eigenschaft, dass DefaultTableCellRenderer ein erweiteter JLable ist, macht sich bei setForeground() bemerkbar. Für mehrzeiligen Text machen sich die Unterklassen von JText ganz gut.

Liegen in einer JTable nicht nur Daten einer Gattung, so lassen sie sich mit instanceof aufschlüsseln.

Beispiel In einer Tabelle sind Zahlen und Objekte vom Typ Gfx. Gfx-Objekte enthalten ein Icon-Objekt mit dem Namen icon. Es soll in die Tabelle gesetzt werden.
public void setValue( Object value 
)
{
if ( value instanceof Gfx ) {
Gfx gfx = (IconData)value;
setIcon( gfx.icon );
}
else
super.setValue(value);
}
}

Die Behandlung im else-Zweig ist dabei sehr wichtig, denn dort wird der Rest der Daten behandelt. Ist es Text, so kümmert sich die Implementierung von DefaultTableCellRenderer darum. Bei setIcon() profitieren wir wieder von der Erweitung von JLabel.

Beispiel Unserer Tabelle mit den Quadrat- und Kubikzahlen wollen wir einen Renderer mitgeben. Er soll die geraden Zahlen in Blau anzeigen und die ungeraden in Grau.
class ColoredTableCellRenderer extends 
DefaultTableCellRenderer
{
public void setValue( Object value )
{
if ( value instanceof Long )
{
if ( ((Long)value).longValue()%2 == 0 )
setForeground( Color.blue );
else
setForeground( Color.gray );

setText( ""+value );
}
else
super.setValue( value );
}
}

Renderer zuweisen

Um diesen zuzuweisen, verbinden wir einen Datentyp mit einem Renderer mit der Methode setDefaultRenderer() vom JTable.

DefaultTableCellRenderer ren = new 
ColoredTableCellRenderer();
table.
setDefaultRenderer( Long.class, ren );

Galileo Computing

16.20 AWT, Swing und die Threads  downtop

Beim AWT und bei Swing gibt es einen Thread, der für die Oberflächenelemente verantwortlich ist. Das ist der AWT-Thread. Er läuft parallel zum Hauptprogramm (ein Thread mit dem Namen main) und führt den Programmcode in den Listenern aus. Aus diesem Grund ist es auch ungünstig, in einem Event-Handler langen Programmcode zu legen, denn dann steht die grafische Applikation und kann nicht weitermachen, da der AWT-Thread blockiert ist. Wenn wir eine Aktion in einem Event-Handler machen müssen, dann sollten wir einen extra Thread starten, damit die grafische Oberfläche sofort wieder reaktionsfähig ist.

Beispiel Wenn eine Schaltfläche gedrückt wird, soll ein langer Text in den Puffer eingelesen werden. Das lässt sich schön mit zwei inneren Klassen realisieren.
ActionListener al = new ActionListener() 
{
public void actionPerformed( ActionEvent e ) {
new Thread( new ReaderThread(e.getActionCommand() )).start();
System.exit( 0 );
}
};

In einer externen Klasse lesen wir den Text:

class ReaderThread implements Runnable
{
ReaderThread( String actionCommand )
{
// ...
}

public void run() {
// ...
}
}

Unter dem AWT ist es kein Problem, wenn zwei Thread auf ein und dasselbe Oberflächenelement zugreifen. Bei Swing ist das jedoch etwas anders, wie wir im nächsten Abschnitt sehen werden.


Galileo Computing

16.20.1 Warum Swing nicht threadsicher ist  downtop

Die Tatsache, dass das Swing-Toolkit nicht threadsicher ist, erstaunt vielleicht auf den ersten Blick. Das AWT ist threadsicher, da AWT auf Plattform-Peer-Elemente vertraut. In einer Listbox unter dem AWT ist es problemlos möglich ein Element einzufügen und parallel zu löschen. Doch auf die Synchronisation bei Swing wurde aus zwei Gründen verzichtet:

gp  Untersuchungen mit anderen grafischen Bibliotheken haben ergeben, dass Operationen in Thread zu ärgerlichen Deadlock-Situationen führen können. Es ist eine zusätzliche Last, die auf dem Programmieren grafischer Oberflächen lastet, Monitore korrekt einzusetzen.
gp  Als zweiter, sicherlich in Zukunft unwichtiger werdender Punkt, ist der Gewinn von Ausführungsgeschwindigkeit. Das Swing-Toolkit kostet sowieso viel Zeit, sodass auf die zusätzliche Synchronisation gut verzichtet werden kann.
Beispiel Swing kann mit konkurrierenden Zugriffen nicht viel anfangen.

Listing 16.18   SwingNoSyncDemo.java
import javax.swing.*;

public class SwingNoSyncDemo
{
public static void main( String args[] )
{
final DefaultListModel model = new DefaultListModel();
JList list = new JList( model );

JFrame frame = new JFrame();
frame.getContentPane().add( list );
frame.setSize( 200, 100 );
frame.show();

new Thread(){ public void run() {
setPriority( Thread.MIN_PRIORITY );
while ( true )
model.addElement( "Dumm gelaufen" );
}
}.start();

new Thread(){ public void run() {
setPriority( Thread.MIN_PRIORITY );
while ( true )
model.removeElement( "Dumm gelaufen" );
}
}.start();
}
}

Werfen wir einen Blick auf die Ausgabe, die erscheint, wenn das Programm nur kurz läuft:

Exception occurred during event dispatching:
java.lang.ArrayIndexOutOfBoundsException: 4145 >= 
4145
at java.util.Vector.elementAt(Vector.java:417)
at javax.swing.DefaultListModel.getElementAt(DefaultListModel.java:70)
...
at java.awt.Component.dispatchEvent(Component.java:2499)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:319)
at java.awt.EventDispatchThread.pumpOneEvent(EventDispatchThread.java:103)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:84)
Exception occurred during event dispatching:
java.lang.ArrayIndexOutOfBoundsException: 4288 >= 4288
at java.util.Vector.elementAt(Vector.java:417)
at javax.swing.DefaultListModel.getElementAt(DefaultListModel.java:70)
...

Obwohl als unterliegende Datenstruktur der Vektor liegt, der, wie wir wissen, nur synchronisierte Methoden besitzt, ist er nicht direkt der Übeltäter. Es liegt an Swing, wie mit den Daten umgegangen wird. Wenn der erste Thread Daten in das Modell einfügt, muss die Visualisierung aktualisiert werden. Als Datenstruktur nimmt das Standard-Modell einen Vector, der die Daten aufnimmt. Das Modell informiert also das Darstellungsobjekt, dass es den Inhalt neu zeichnen muss. Merken wir uns die Stelle. Das Darstellungsobjekt wird sich nun vom Modell die Daten besorgen. Bis dahin läuft alles ganz gut. Doch der zweite Thread löscht parallel die Daten aus dem Modell. Springen wir jetzt zur Markierung zurück. Irgendwann passiert es, dass zwischen der Benachrichtigung der Darstellungskomponenten und dem wirklichen Zeichnen etwas gelöscht wurde. Die Visualisierung weiß aber davon nichts, und versucht alle Werte zu zeichnen; es fehlte aber mindestens ein Wert. Daher folgt eine ArrayIndexOutOfBoundsException in der Methode elementAt() vom Vektor:

java.lang.ArrayIndexOutOfBoundsException: 
4145 >= 4145
at java.util.Vector.elementAt(Vector.java:417)

Werfen wir einen Blick in die Implementierung, dann erkennen wie das Problem:

public synchronized Object elementAt( 
int index ) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(
index + " >= " + elementCount);
}
...
}

Die Visualisierung fragt mit dem Index 4145 im Vektor nach, doch der Vektor hat vom Lösch-Thread schon ein Element abgeben müssen. Daher ist die interne Größe elementCount auch kleiner als der Index.

Dass das Programm dennoch eine Zeit lang läuft, hängt damit zusammen, dass der Hinzufüge-Thread aufgeholt haben kann und noch viele Elemente im Vektor auf Vorrat liegen. Nehmen wie setPriority(Thread.MIN_PRIORITY) aus dem Einfüge-Thread heraus, dann gibt es kein Problem.

Lösung für Swing

Einige der Methoden, die dennoch synchronisiert sind, tragen Listener ein, so etwa bei JComponent addPropertyChangeListener(), removePropertyChangeListener() und addVeto ableChangeListener(), removeVetoableChangeListener(). Bei JCheckBoxMenuItem ist es dann die einsame Methode setState(boolean), die synchronisiert ist. Sonst findet sich intern mal hier mal da ein synchronisierter Block. Sonst ist jedoch nicht viel dabei und wir müssen unsere Teile synchronisiert ausführen.

Um Programmstücke konform ausführen zu lassen, definiert Swing einige Methoden und Klassen. Dazu gehören:

gp  invokeLater(Runnable)
gp  invokeAndWait(Runnable)
gp  JProgressBar
gp  ProgressMonitor
gp  ProgressMonitorInputStream
gp  SwingWorker

Galileo Computing

16.20.2 Swing-Elemente bedienen mit invokeLater(), invokeAndWait()  downtop

Da Swing nicht threadsicher ist, ist die einzige Möglichkeit zur Manipulation von Oberflächenelementen der AWT-Thread. Wenn wie es schaffen, dort die Aufträge einzureihen, dann wird nichts schief gehen. Genau für diese Aufgabe existieren in der Klasse EventQueue zwei Methoden: invokeLater() und invokeAndWait(). Damit lassen sich beliebige Programmstücke in die Warteschlange einzuführen. In der Warteschlange für das AWT liegen Aufträge und Ereignisse, die an die Oberflächenelemente verteilt werden. Alles spielt sich dabei neben dem Haupt-Thread ab, sodass Parallelität herrscht. Hat die Warteschlange alle Ereignisbehandler aufgerufen, kann der Programmcode von invokeLater() und invokeAndWait() durchlaufen werden. Den Methoden wird ein Runnable-Objekt übergeben. Die zwei Methoden erfüllen unterschiedliche Bedürfnisse:

gp  invokeLater() legt einen Thread in die Warteschlage und kehrt sofort zurück. Die Methode ist somit asynchron. Der Aufrufer weiß nicht, wann der Programmcode abgearbeitet wird.
gp  invokeAndWait() legt ebenfalls den Thread in die Warteschlange, verharrt aber so lange in der Methode, bis der Programmcode aufgerufen wurde. Die Methode ist also synchron.

Mit diesen Methoden lassen sich jetzt alle Manipulationen an der Oberfläche durchführen.

Beispiel Ein Fortschrittsbalken JProgressBar mit dem Namen bar soll in einer Schleife einer Berechung angepasst werden.
EventQueue.invokeLater( new Runnable()
{
public void run() {
bar.
setValue( i );
}
} );

Bei der Auswahl der beiden Funktionen haben wir uns für den Fortschrittsbalken für invokeLater() entschieden. Es macht in der Regel wenig Sinn, die Methode so lange stehen zu lassen, bis die Anzeige auch wirklich gezeichnet wurde.

Ein Problem ist leider für sehr viele Applikationen, dass das Objekt zur Manipulation immer irgendwie sichtbar sein muss. Hier soll bar einfach direkt für die innere Klasse sichtbar sein.

Die Funktionen invokeLater() und invokeAndWait() befinden sich nicht nur in der Klasse EventQueue, sondern sind noch einmal in der Klasse SwingUtilities untergebracht. Daher ist es gleichgültig, ob wir EventQueue.invokeXXX() oder SwingUtilities.invokeXXX() schreiben. SwingUtilities hat vielleicht den Vorteil, dass das Paket java.awt für die EventQueue nicht importiert werden muss, sonst gibt es aber keinen Unterschied.

Implementierung

Genehmigen wir uns abschließend noch einen kurzen Blick auf die Implementierung. Es lässt sich schon erahnen, dass invokeLater() einfacher ist:

public static void invokeLater( 
Runnable runnable )
{
Toolkit.getEventQueue().postEvent(
new InvocationEvent(Toolkit.getDefaultToolkit(), runnable));
}

Das Ereignis, was in die Event-Queue kommt, ist vom Typ InvocationEvent und damit ein AWTEvent. Wir übergeben unser Runnable-Objekt, damit der AWT-Thread später die run() Methode aufrufen kann.

Die Methode invokeAndWait() ist etwas komplizierter, und wir wollen von der Implementierung nur wenige Zeilen betrachten. Im Prinzip macht die Methode das Gleiche wie invokeLater(), sie muss ebenfalls das InvocationEvent in die Warteschlange legen. Doch hinzu kommt, dass invokeAndWait() auf das Ende des Threads warten muss:

InvocationEvent event = new InvocationEvent(
Toolkit.getDefaultToolkit(), runnable, lock, true);

synchronized (lock) {
Toolkit.getEventQueue().postEvent(event);
lock.wait();
}

Das konstruierte InvocationEvent bekommt wieder als Parameter das runnable. Jetzt bekommt es aber zusätzlich ein Lock-Objekt. Wenn der AWT-Thread durch die Ereignis-Warteschlange geht und das InvocationEvent sieht, führt er wieder die run()-Methode aus. Anschließend informiert er über notify() das wartende Objekt. Dann steigt invokeAndWait() aus dem sychronized-Block aus und es geht weiter.


Galileo Computing

16.21 Das Java Look&Feel  toptop

Das Aussehen der Komponenten lässt sich frei bestimmen und erzeugt so bei jedem Benutzer auf seiner Architektur die Illusion, als ob es eine plattformabhängige Applikation wäre. Das Programm gliedert sich mit dem Aussehen in die anderen Programme ein und fällt nicht als Fremdling auf. Standardmäßig begegnet uns das Look&Feel mit dem Namen »Metal« (Java L&F genannt). Auch in unseren Beispielen sind überall Bildschirmabzüge von diesem L&F zu sehen.

Das L&F von Applikationen lässt sich zur Laufzeit ändern. Dazu müssen wir nur eine statische Methode der Klasse UIManagers aufrufen, die sich um das Aussehen der Programme kümmert. Hier ist es die spezielle Methode setLookAndFeel(), die als Parameter eine Klasse erwartet. Verschiedene Methoden sind vordefiniert, mit denen wir das Java eigene L&F und das System-L&F einstellen können. Da Benutzer von Java Programmen im Allgemeinen eine Systemoberfläche erwarten, sollten wir das Java-L&F nach dem Erzeugen des Fensters umschalten.

Die folgenden Zeilen zeigen, wie sich das Aussehen der Oberfläche durch die Methode setLookAndFeel() ändern lässt. Der folgende Programmblock ändert das Aussehen einer Oberfläche so, wie sie das System vorgibt. Dann sehen die Programme aus wie AWT-Programme. Die Funktion ist sehr praktisch, da setLookAndFeel() einige Exceptions erzeugen kann:

static void setNativeLookAndFeel()
{
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName() );
} catch( Exception e ) { }
}

Die nächste Zeile erzeugt das plattformunabhängige Java-L&F:

UIManager.setLookAndFeel(
UIManager.getCrossPlatformLookAndFeelClassName() );

Plattformabhängiges Java-L&F muss als Klasse eingebunden werden. Etwa die Motif-Oberfläche mit folgender Zeile:

UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.motif.MotifLookAndFeel" );

Einige spezielle L&F sind nicht auf jeder Architektur erlaubt. So verbietet Apple ihr eigenes L&F auf nicht Apple-Plattformen.

 






1    Zu erreichen unter dims@geocities.com.

  

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