Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 3. Auflage
 <<    <     >    >>   API  Kapitel 36 - Swing: Container und Menüs

36.3 Weitere Swing-Container



36.3.1 JComponent

Viele der Swing-Komponenten sind direkt oder indirekt aus der Klasse JComponent abgeleitet. Sie stellt eine Reihe allgemeiner Hilfsmittel zur Verfügung, die für daraus abgeleitete Komponentenklassen nützlich sind. Als Ableitung von java.awt.Container (und damit von java.awt.Component) besitzt JComponent bereits einen Großteil der Funktionalität von AWT-Komponenten. Insbesondere bietet sie als Container die Möglichkeit, andere Komponenten aufzunehmen, und sie kann einen Layout-Manager besitzen, der für die Größe und Anordnung der enthaltenen Komponenten zuständig ist.

Die mit dem AWT eingeführte grundsätzliche Unterscheidung zwischen elementaren Dialogelementen, und solchen, die Unterkomponenten aufnehmen können, wurde mit der Einführung von Swing also weitgehend fallengelassen. In der Praxis ist das jedoch nur selten bedeutsam. Ein JButton beispielsweise stellt sich im praktischen Gebrauch stets als elementare Komponente dar - obwohl er als Konkretisierung von JComponent auch Unterkomponenten enthalten könnte.

Eine der auffälligeren Konsequenzen besteht darin, daß es in Swing keine zu Canvas korrespondierende Klasse gibt (siehe Kapitel 33. Elementare Dialogelemente mit selbstdefinierter Oberfläche werden in Swing direkt aus JComponent abgeleitet.

 Hinweis 

Umrandungen

JComponent bietet die Möglichkeit, ihren Instanzen eine Umrandung zu geben. Dazu gibt es die Methode setBorder, mit der der Komponente ein Objekt des Typs Border zugewiesen werden kann:

public void setBorder(Border border)
javax.swing.JComponent

Border ist ein Interface, zu dem es verschiedene Implementierungen gibt. Die wichtigsten von ihnen zeigt folgende Tabelle:

Klassenname Beschreibung
EmptyBorder Unsichtbarer Rand mit einstellbarer Dicke
LineBorder Einfache Linie mit einstellbarer Farbe und Dicke
BevelBorder Erhabener oder vertiefter 3D-Effekt
EtchedBorder Eingelassene Linie mit 3D-Effekt
CompoundBorder Aus zwei anderen Umrandungen zusammengesetzt
TitledBorder Umrandung mit einem eingebetteten Text

Tabelle 36.4: Border-Implementierungen

Die Klassen besitzen sehr unterschiedliche Konstruktoren, mit denen ihre jeweiligen Eigenschaften festgelegt werden. Obwohl die Border-Instanzen einfach mit new erzeugt werden könnten, bietet die Klasse BorderFactory im Paket javax.swing eine bessere Möglichkeit, dies zu tun. Zu jeder Art von Umrandung steht nämlich eine Factory-Methode zur Verfügung (createEmptyBorder, createLineBorder usw.), mit der ein Border-Objekt dieses Typs erzeugt werden kann. Wann immer möglich, versucht die BorderFactory dabei, Verweise auf bereits erzeugte Instanzen zurückzugeben. Da Umrandungen in GUI-Programmen sehr häufig benötigt werden, reduziert sich dadurch bei konsequenter Verwendung die Anzahl der kurzlebigen Objekte und die Belastung des Garbage Collectors.

Abbildung 36.11 zeigt ein Beispielprogramm, das sechs Labels mit unterschiedlichen Umrandungen enthält:

Abbildung 36.11: Die wichtigsten Umrandungen

Tooltips

JComponent bietet eine einfach anzuwendende Möglichkeit, Komponenten einen Tooltip-Text zuzuweisen. Dieser wird angezeigt, wenn die Maus über das Dialogelement bewegt und dort gehalten wird. Ein Tooltip gibt dem unerfahrenen Anwender zusätzliche Informationen über die Bedeutung und Funktion des ausgewählten Dialogelements. Tooltip-Texte werden mit der Methode setToolTipText zugewiesen. Mit getToolTipText können sie abgefragt werden:

public void setToolTipText(String text)
public String getToolTipText()
javax.swing.JComponent

Tooltips können nicht nur einfache Texte enthalten, sondern prinzipiell beliebig komplex aufgebaut sein. Sie werden durch die aus JComponent abgleitete Klasse JToolTip repräsentiert. Aus ihr können anwendungsspezifische Tooltip-Klassen abgeleitet werden, die nahezu beliebige GUI-Funktionalitäten zur Verfügung stellen. Um einer Komponente einen solchen Tooltip zuzuordnen, muß die Methode createToolTip von JComponent überlagert werden und auf Anfrage ihr eigenes JToolTip-Objekt zurückgeben.

 Tip 

Bildschirmdarstellung der Komponente

Normalerweise braucht eine GUI-Anwendung sich um die konkrete Darstellung seiner Dialogelemente keine Gedanken zu machen. Die elementaren Komponenten erledigen dies selbst, und die zusammengesetzten Komponenten bedienen sich ihrer Layout-Manager und der Ausgabemethoden der elementaren Komponenten. Dies ändert sich, wenn eine eigene Komponente entwickelt werden soll. Bei AWT-Anwendungen wurde diese aus Canvas abgeleitet, und in überlagerten Varianten von paint oder update wurde die nötige Bildschirmausgabe zur Verfügung gestellt.

In elementaren Swing-Komponenten, die aus JComponent abgeleitet wurden, liegen die Dinge etwas komplizierter. Die Methode paint hat bereits in JComponent eine recht aufwendige Implementierung und wird normalerweise nicht mehr überlagert. Im Prinzip ruft sie nacheinander ihre Methoden paintComponent, paintBorder und paintChildren auf. Die letzten beiden sind für das Zeichnen der Umrandung und der enthaltenen Dialogelemente zuständig und brauchen normalerweise in eigenen Komponenten nicht überlagert zu werden. Für die Darstellung der eigenen Komponente ist dagegen paintComponent zuständig:

protected void paintComponent(Graphics g)
javax.swing.JComponent

In JComponent wird jeder Aufruf von paintComponent an das ComponentUI der Komponente delegiert. Instanzen dieser im Paket javax.swing.plaf liegenden Klasse spielen in dem von jeder Swing-Komponente implementierten Model-View-Controller-Konzept die Rolle des Views, sind also für die grafische Darstellung der Komponente zuständig. Jede Swing-Komponente besitzt ein ComponentUI, das je nach Look-and-Feel unterschiedlich sein kann. Eine selbstdefinierte Komponente muß also entweder für jedes unterstützte Look-and-Feel ein passendes ComponentUI zur Verfügung stellen oder die Bildschirmdarstellung durch Überlagern von paintComponent selbst erledigen.

Debug-Grafik

Eine interessante Hilfe zum Testen eigener Komponenten kann durch Aufruf der Methode setDebugGraphicsOptions aktiviert werden:

public void setDebugGraphicsOptions(int debugOptions)
javax.swing.JComponent

Dadurch wird die Komponente mit Hilfe eines DebugGraphics-Objekts gezeichnet. Es ist in der Lage, die verwendeten Grafikoperationen auf der Console zu protokollieren oder zur besseren Kontrolle verzögert auszugeben. Als Argument kann an setDebugGraphicsOptions eine der folgenden Konstanten aus der Klasse DebugGraphics übergeben werden:

Konstante Bedeutung
NONE_OPTION Normale Ausgabe
LOG_OPTION Die Grafikoperationen werden auf der Console protokolliert.
FLASH_OPTION Die Grafikoperationen erfolgen verzögert und werden während der Ausgabe blinkend dargestellt. Bei dieser Option muß die Doppelpufferung für die Komponente ausgeschaltet werden.
BUFFERED_OPTION Gepufferte Ausgaben werden in einem separaten Frame angezeigt (hat im Test nicht funktioniert).

Tabelle 36.5: DebugGraphics-Konstanten

Das folgende Programm zeigt eine einfache Anwendung der Debug-Grafik zur Darstellung eines Buttons. Mit Hilfe der statischen Methoden setFlashTime und setFlashCount der Klasse DebugGraphics wird die Blinkrate und -dauer angepaßt:

001 /* Listing3611.java */
002 
003 import java.awt.*;
004 import javax.swing.*;
005 
006 public class Listing3611
007 extends JFrame
008 {
009   public Listing3611()
010   {
011     super("Debug-Grafik");
012     addWindowListener(new WindowClosingAdapter(true));
013     Container cp = getContentPane();
014     DebugGraphics.setFlashTime(100);
015     DebugGraphics.setFlashCount(3);
016     JButton button = new JButton("DEBUG-Button");
017     RepaintManager repaintManager = RepaintManager.currentManager(button);
018     repaintManager.setDoubleBufferingEnabled(false);
019     button.setDebugGraphicsOptions(DebugGraphics.FLASH_OPTION);
020     cp.add(button);
021   }
022 
023   public static void main(String[] args)
024   {
025     Listing3611 frame = new Listing3611();
026     frame.setLocation(100, 100);
027     frame.setSize(300, 200);
028     frame.setVisible(true);
029   }
030 }
Listing3611.java
Listing 36.11: Debug-Grafik

Transparenter Hintergrund

Anders als im AWT, bei dem alle Dialogelemente einen undurchsichtigen Hintergrund hatten, kann dieser bei Swing-Komponenten auch transparent sein. Damit lassen sich runde Buttons, Beschriftungen mit durchscheinendem Hintergrund oder ähnliche Effekte realisieren. Um den Hintergrund einer Komponente transparent zu machen, ist die Methode setOpaque aufzurufen und false zu übergeben. Standardmäßig ist der Hintergrund undurchsichtig. Das folgende Programm besitzt zwei Buttons, von denen der eine einen transparenten und der andere einen undurchsichtigen Hintergrund hat. Um den Unterschied besser erkennen zu können, befinden sie sich auf einer Komponente, die vollständig mit Gitterlinien bedeckt ist.

001 /* Listing3612.java */
002 
003 import java.awt.*;
004 import javax.swing.*;
005 
006 public class Listing3612
007 extends JFrame
008 {
009   public Listing3612()
010   {
011     super("Transparenz");
012     addWindowListener(new WindowClosingAdapter(true));
013     Container cp = getContentPane();
014     //SimpleGridComponent erzeugen
015     SimpleGridComponent grid = new SimpleGridComponent();
016     grid.setLayout(new FlowLayout(FlowLayout.CENTER));
017     //Transparenten Button hinzufügen
018     JButton button = new JButton("Transparent");
019     button.setOpaque(false);
020     grid.add(button);
021     //Undurchsichtigen Button hinzufügen
022     button = new JButton("Opaque");
023     grid.add(button);
024     //SimpleGridComponent hinzufügen
025     cp.add(grid, BorderLayout.CENTER);
026   }
027 
028   public static void main(String[] args)
029   {
030     try {
031       String plaf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
032       UIManager.setLookAndFeel(plaf);
033       Listing3612 frame = new Listing3612();
034       frame.setLocation(100, 100);
035       frame.setSize(300, 100);
036       frame.setVisible(true);
037     } catch (Exception e) {
038       e.printStackTrace();
039       System.exit(1);
040     }
041   }
042 }
043 
044 class SimpleGridComponent
045 extends JComponent
046 {
047   protected void paintComponent(Graphics g)
048   {
049     int width = getSize().width;
050     int height = getSize().height;
051     g.setColor(Color.gray);
052     for (int i = 0; i < width; i += 10) {
053       g.drawLine(i, 0, i, height);
054     }
055     for (int i = 0; i < height; i += 10) {
056       g.drawLine(0, i, width, i);
057     }
058   }
059 }
Listing3612.java
Listing 36.12: Ein transparenter Button

Die Ausgabe des Programms sieht so aus:

Abbildung 36.12: Ein Programm mit einem transparenten Button

Doppelpufferung

Um bei animierten Komponenten das Bildschirmflackern zu vermeiden, kann die Technik des Doppelpufferns angewendet werden. Wir haben das für AWT-Komponenten in Abschnitt 34.2.4 ausführlich erklärt. Swing-Komponenten, die aus JComponent abgeleitet sind, können automatisch doppelgepuffert werden. Dazu ist lediglich ein Aufruf von setDoubleBuffered mit Übergabe von true erforderlich. Mit isDoubleBuffered kann der aktuelle Zustand dieser Eigenschaft abgefragt werden:

public void setDoubleBuffered(boolean aFlag)
public boolean isDoubleBuffered()
javax.swing.JComponent

Größenvorgaben

Bereits in der Klasse Component sind die Methoden getMinimumSize, getPreferredSize und getMaximumSize definiert. Sie werden in abgeleiteten Klassen überlagert, um dem Layoutmanager die minimale, optimale und maximale Größe der Komponenten mitzuteilen (siehe z.B. Abschnitt 33.2.2). In JComponent gibt es zusätzlich die Methoden setMinimumSize, setPreferredSize und setMaximumSize. Damit können die Größenvorgaben bestehender Komponenten verändert werden, ohne eine neue Klasse daraus ableiten zu müssen.

Invalidierung/Validierung

Sowohl in Swing- als auch in AWT-Programmen kann der Aufbau eines Dialogs geändert werden, wenn er bereits auf dem Bildschirm sichtbar ist. Dialogelemente können hinzugefügt oder entfernt oder in ihrem Aussehen geändert werden. Die dadurch implizierten Layoutänderungen werden zwar oft, aber nicht immer automatisch erkannt, und es kann sein, daß die Bildschirmdarstellung die Änderungen nicht angemessen wiedergibt.

Wird beispielsweise die Beschriftung oder der Font eines Swing-Buttons verändert, so wird seine Größe den neuen Erfordernissen angepaßt und der Dialog neu aufgebaut. Bei AWT-Buttons ist das nicht der Fall. Wenn sich der Platzbedarf für die Beschriftung ändert, ist der Button nach der Änderung entweder zu klein oder zu groß. Auch wenn neue Dialogelemente hinzugefügt werden, sind diese weder im AWT noch in Swing unmittelbar sichtbar.

Um einem Container mitzuteilen, daß das Layout seiner Komponenten komplett neu aufgebaut werden soll, ist dessen validate-Methode aufzurufen:

public void validate()
java.awt.Container

Um einen unnötigen Neuaufbau des Bildschirms zu vermeiden, wird validate allerdings nur dann wirklich aktiv, wenn der Container, auf dem der Aufruf erfolgte, zuvor mit invalidate als ungültig deklariert wurde:

public void invalidate()
java.awt.Component

Wird invalidate auf einer elementaren Komponente aufgerufen, die in einen Container eingebettet ist, wird der Aufruf an den Container weitergegeben und invalidiert auch diesen. Soll also nach der Änderung einer Komponente der zugehörige Container neu dargestellt werden, ist auf der Komponente invalidate und anschließend auf dem Container validate aufzurufen. In JComponent gibt es zusätzlich die Methode revalidate, die beide Schritte nacheinander durchführt.

Die Fokusreihenfolge

Innerhalb eines Dialogs kann immer nur eine Komponente zur Zeit den Fokus haben, also Maus- und Tastaturereignisse erhalten. Durch Anklicken mit der Maus kann dieser direkt einer bestimmten Komponente zugewiesen werden. Alternativ kann (meist mit Hilfe der Tasten [TAB] und [UMSCHALT]+[TAB]) der Fokus auch per Tastendruck verändert werden. Die Komponenten eines Dialogs durchlaufen dabei einen Zyklus, der sich an der Einfüge-Reihenfolge der Elemente orientiert. Das zuerst mit add hinzugefügte Element enthält nach dem Aufrufen des Dialogs den Fokus zuerst.

Mitunter ist es sinnvoll, lokale Fokuszyklen zu bilden, um Gruppen von Komponenten zusammenzufassen und einfacher bedienen zu können. Gruppierte Radiobuttons erledigen das beispielsweise automatisch. Mit Hilfe der Methode setNextFocusableComponent der Klasse JComponent kann dies aber auch manuell erfolgen:

public void setNextFocusableComponent(Component aComponent)
javax.swing.JComponent

Wird diese Methode auf einer beliebigen Komponente aufgerufen, erhält das als Argument übergebene Element den nächsten Fokus. Handelt es sich dabei um ein Element, das in der Fokusreihenfolge vor dem aktuellen Element liegt, wird auf diese Weise ein lokaler Zyklus gebildet, der mit der Tastatur nicht mehr verlassen werden kann (mit der Maus natürlich schon). Auch beim umgekehrten Durchlauf wird automatisch die neue Reihenfolge eingehalten; eine Methode setPreviousFocusableComponent wird dazu nicht benötigt.

Mit dem JDK 1.4 wurde das Fokus-Subsystem vollständig neu geschrieben. Hauptziel dieser Änderung war es, eine große Zahl von existierenden Fehlern zu beheben, sowie ein wohldefiniertes und plattformunabhängiges Fokus-Verhalten zu implementieren. In der JDK-Doku findet sich im »Guide To Features« im Abschnitt »Abstract Window Toolkit« eine ausführliche Beschreibung mit dem Titel Focus Model Specification.

 JDK1.1-1.4 

Registrieren von Tastaturkommandos

Als letzte Eigenschaft der Klasse JComponent wollen wir uns das Registrieren von Tastaturkommandos ansehen. Dabei wird eine bestimmte Tastenkombination bei einer Komponente angemeldet und löst bei jeder Anwendung ein Action-Event aus. Tastaturkommandos können sowohl bei elementaren Komponenten als auch bei Containern angemeldet werden, und es gibt verschiedene Möglichkeiten, sie vom Fokus der Komponente abhängig zu machen. Das Registrieren erfolgt mit der Methode registerKeyboardAction:

public void registerKeyboardAction(
  ActionListener anAction,
  String aCommand,
  KeyStroke aKeyStroke,
  int aCondition
)
javax.swing.JComponent

Deren erstes Argument ist der ActionListener, der im Falle des Tastendrucks (mit dem Kommando aCommand) aufgerufen werden soll. aKeyStroke definiert die zu registrierende Tastenkombination (wie man KeyStroke-Objekte erzeugt, wurde in Listing 36.8 gezeigt). Das letzte Argument gibt an, in welcher Relation der Fokus zur Komponente stehen muß, damit das Kommando aktiv wird. Er kann einen der folgenden Werte annehmen:

Konstante Bedeutung
WHEN_FOCUSED Das Tastaturkommando wird nur ausgelöst, wenn die Komponente den Fokus hat.
WHEN_IN_FOCUSED_WINDOW Das Tastaturkommando wird ausgelöst, wenn die Komponente den Fokus hat oder wenn sie zu einem Container gehört, der gerade den Fokus hat.
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT Das Tastaturkommando wird ausgelöst, wenn die Komponente oder eines der darin enthaltenen Elemente den Fokus hat.

Tabelle 36.6: Bedingungen zur Registrierung von Tastaturkommandos

Bei der Verwendung der zweiten und dritten Variante werden die Tastendrücke immer dann ausgeführt, wenn der Container selbst oder eine der darin enthaltenen Komponenten den Fokus hat. Der Unterschied besteht in der Sichtweise. WHEN_IN_FOCUSED_WINDOW wird auf die elementaren Komponenten angewendet, während WHEN_ANCESTOR_OF_FOCUSED_COMPONENT für den Container selbst vorgesehen ist.

Das folgende Programm zeigt eine beispielhafte Anwendung für das Registrieren von Tastaturkommandos:

001 /* Listing3613.java */
002 
003 import java.awt.*;
004 import java.awt.event.*;
005 import javax.swing.*;
006 import javax.swing.border.*;
007 
008 public class Listing3613
009 extends JFrame
010 implements ActionListener
011 {
012   public Listing3613()
013   {
014     super("Invalidierung");
015     addWindowListener(new WindowClosingAdapter(true));
016     Container cp = getContentPane();
017     ((JComponent)cp).setBorder(new EmptyBorder(5, 5, 5, 5));
018     cp.setLayout(new FlowLayout());
019     //Textfelder erzeugen
020     JTextField tf1 = new JTextField("Zeile1", 20);
021     JTextField tf2 = new JTextField("Zeile2", 20);
022     JTextField tf3 = new JTextField("Zeile3", 20);
023     //STRG+UMSCHALT+F6 auf Frame registrieren
024     ((JComponent)cp).registerKeyboardAction(
025       this,
026       "dialog",
027       ctrlShift(KeyEvent.VK_F6),
028       JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
029     );
030     //STRG+UMSCHALT+F7 auf tf1 registrieren
031     tf1.registerKeyboardAction(
032       this,
033       "tf1",
034       ctrlShift(KeyEvent.VK_F7),
035       JComponent.WHEN_IN_FOCUSED_WINDOW
036     );
037     //STRG+UMSCHALT+F8 auf tf2 registrieren
038     tf2.registerKeyboardAction(
039       this,
040       "tf2",
041       ctrlShift(KeyEvent.VK_F8),
042       JComponent.WHEN_FOCUSED
043     );
044     //Textfelder hinzufügen
045     cp.add(tf1);
046     cp.add(tf2);
047     cp.add(tf3);
048   }
049 
050   public void actionPerformed(ActionEvent event)
051   {
052     String cmd = event.getActionCommand();
053     System.out.println(cmd);
054   }
055 
056   private KeyStroke ctrlShift(int vkey)
057   {
058     return KeyStroke.getKeyStroke(
059       vkey,
060       Event.SHIFT_MASK + Event.CTRL_MASK
061     );
062   }
063 
064   public static void main(String[] args)
065   {
066     Listing3613 frame = new Listing3613();
067     frame.setLocation(100, 100);
068     frame.setSize(300, 200);
069     frame.setVisible(true);
070   }
071 }
Listing3613.java
Listing 36.13: Registrieren von Tastaturkommandos

Das Hauptfenster selbst registriert die Tastenkombination [STRG]+[UMSCHALT]+[F6]. Sie funktioniert unabhängig davon, welches der drei Textfelder den Fokus hat. Das oberste Textfeld registriert [STRG]+[UMSCHALT]+[F7] mit der Bedingung WHEN_IN_FOCUSED_WINDOW. Die Tastenkombination steht in diesem Dialog also ebenfalls immer zur Verfügung. Das nächste Textfeld registriert [STRG]+[UMSCHALT]+[F8], läßt sie allerdings nur zu, wenn es selbst den Fokus hat.

36.3.2 JPanel und JLayeredPane

JPanel

JPanel ist die Basisklasse für GUI-Container in Swing, die nicht Hauptfenster sind. Sie ist direkt aus JComponent abgeleitet und fügt dieser nur wenig hinzu. Wichtigstes Unterscheidungsmerkmal ist die Tatsache, daß einem JPanel standardmäßig ein FlowLayout als Layoutmanager zugeordnet ist. Instanzen von JComponent besitzten dagegen zunächst keinen Layoutmanager. JPanel definiert die folgenden Konstruktoren:

public JPanel()
public JPanel(boolean isDoubleBuffered)
public JPanel(LayoutManager layout)
public JPanel(LayoutManager layout, boolean isDoubleBuffered)
javax.swing.JPanel

Der parameterlose Konstruktor erzeugt ein JPanel mit Doppelpufferung und einem FlowLayout. Wahlweise können beide Eigenschaften durch Setzen der Argumente isDoubleBuffered und layout geändert werden. In der weiteren Anwendung ist JPanel mit JComponent identisch.

JLayeredPane

Eine weitere Container-Klasse, die direkt aus JComponent abgeleitet wurde, ist JLayeredPane. Sie fügt den Dialogen eine dritte Dimension hinzu und ermöglicht mit Hilfe eines Layerkonzepts die kontrollierte Anordnung von Komponenten übereinander. JLayeredPane ist Vaterklasse von JDesktopPane (siehe Abschnitt 36.1.6) und wichtiger Bestandteil der Struktur von Hauptfenstern (siehe Abschnitt 36.1.1). In der praktischen Anwendung wird sie nur selten direkt gebraucht, und wir wollen nicht weiter darauf eingehen.


 Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0.1
 <<    <     >    >>   API  © 1998-2003 Guido Krüger, http://www.javabuch.de