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