Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage |
<< | < | > | >> | API | Kapitel 36 - Swing: Container und Menüs |
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. |
|
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
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. |
|
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.
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 |
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 |
Die Ausgabe des Programms sieht so aus:
Abbildung 36.12: Ein Programm mit einem transparenten Button
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 |
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.
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.
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. |
|
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 |
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.
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.
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 |