Kapitel 16 Let’s Swing
Are you police officers?
No, ma’am. We’re musicians.
– Blues Brothers
16.1 Das Konzept vom Model-View-Controller  
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:
|
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. |
|
View: Die Daten vom Modell werden im View dargestellt. Er nutzt die Methoden vom Modell, um die Informationen auszulesen. |
|
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.
16.2 Der Unterschied zwischen AWT und Swing  
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.
16.2.1 Schließen eines Swing-Fensters  
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
|
|
void setDefaultCloseOperation( int operation )
Was passieren soll, wenn der Benutzer das Fenster schließt. Gültig sind die Konstanten |
|
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. |
16.3 JLabel  
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
|
|
Bevor wir zu einem Beispiel von JLabel kommen und ein Icon mit einem Text präsentieren, wollen wir zunächst die Schnittstelle Icon besprechen.
16.4 Die Klasse ImageIcon  
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.
| 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
|
|
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.
16.4.1 Die Schnittstelle Icon  
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
|
|
int getIconWidth()
Liefert die feste Breite eines Icons. |
|
int getIconHeight()
Liefert die feste Höhe eines Icons. |
|
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
|
|
16.4.2 Was Icon und Image verbindet  
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.
16.5 Die Schaltflächen von Swing  
16.5.1 JButton  
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
|
|
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.
16.5.2 AbstractButton  
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:
|
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). |
|
Automatisch eine Aktion auslösen durch doClick(). |
|
Den Zustand des Icons auf Grund des Status’ mit setDisabledIcon(), setDisabledSelectedIcon(), setPressedIcon(), setRolloverIcon(), setRolloverSelectedIcon(), setSelectedIcon() ändern. Alle Methoden haben ein Icon-Objekt als Parameter. |
|
Die Ausrichtung von Text und Icon in der Schaltfläche durch setVerticalAlignment() und setHorizontalAlignment() bestimmen. |
|
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().
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 );
}
}
16.5.3 JToggleButton  
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.
16.5.4 JCheckBox  
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
|
|
16.5.5 Radiogruppen  
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.
16.6 Tooltips  
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
|
|
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.
16.7 JScrollBar  
Ist das leichtgewichtige Pendant zu AWT Scrollbar ist JScrollbar. Sie empfängt ebenfalls AdjustmentEvents. Dem JScrollBar ist ein BoundedRangeModel zugewiesen, welches die Daten speichert.
16.8 JSlider  
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
|
|
16.9 JList  
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.
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();
}
}
|
16.10 JComboBox  
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.
16.11 Der Fortschrittsbalken JProgressBar  
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
|
|
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 ); }
} );
}
}
}
16.12 Symbolleisten alias Toolbars  
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
|
|
16.13 Texteingaben  
16.13.1 JPasswordField  
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
|
|
16.13.2 Die Editor-Klasse JEditorPane  
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 );
}
|
16.14 Rahmen (Borders)  
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. |
|