Titel | Inhalt | Suchen | Index | API | Go To Java 2, Zweite Auflage, Handbuch der Java-Programmierung |
<< | < | > | >> | Kapitel 38 - Swing: Komponenten II |
Die Klasse JScrollPane wurde in den vorigen Abschnitten bereits mehrfach verwendet. In Verbindung mit den Klassen JTextArea und JList bestand ihre Aufgabe darin, Dialogelemente, die zu groß für den zur Verfügung stehenden Platz waren, mit Hilfe eines verschiebbaren Fensters ausschnittsweise sichtbar zu machen. Dabei war es ausreichend, das betreffende Dialogelement an den Konstruktor von JScrollPane zu übergeben und anstelle des Dialogelements selbst die JScrollPane-Instanz an den Container zu übergeben.
JScrollPane besitzt aber noch weitere Fähigkeiten, die hier kurz vorgestellt werden sollen. Zunächst wollen wir uns ihre wichtigsten Konstruktoren ansehen:
public JScrollPane(Component view) public JScrollPane(Component view, int vsbPolicy, int hsbPolicy) |
javax.swing.JScrollPane |
Die Argumente vsbPolicy und hsbPolicy geben an, wann ein horizontaler bzw. vertikaler Schieberegler eingeblendet wird. Hier können folgende Werte angegeben werden:
Konstante | Bedeutung |
VERTICAL_SCROLLBAR_NEVER | Der vertikale Schieberegler wird nie angezeigt. |
VERTICAL_SCROLLBAR_ALWAYS | Der vertikale Schieberegler wird immer angezeigt. |
VERTICAL_SCROLLBAR_AS_NEEDED | Der vertikale Schieberegler wird nur angezeigt, wenn er tatsächlich benötigt wird. |
HORIZONTAL_SCROLLBAR_NEVER | Der horizontale Schieberegler wird nie angezeigt. |
HORIZONTAL_SCROLLBAR_ALWAYS | Der horizontale Schieberegler wird immer angezeigt. |
HORIZONTAL_SCROLLBAR_AS_NEEDED | Der horizontale Schieberegler wird nur angezeigt, wenn er tatsächlich benötigt wird. |
Tabelle 38.1: Anzeige der Schieberegler bei JScrollPane
Wenn die Argumente vsbPolicy und hsbPolicy nicht angegeben werden, blendet JScrollPane die Schieberegler nur dann ein, wenn sie wirklich benötigt werden (wenn also das Dialogelement in der jeweiligen Ausdehnung größer als der verfügbare Platz ist).
Die beiden wichtigsten zusätzlichen Fähigkeiten von JScrollPane bestehen darin, Spalten- und Zeilenheader anzeigen und die Eckelemente mit beliebigen Komponenten belegen zu können (siehe Abbildung 38.1):
public void setColumnHeaderView(Component view) public void setRowHeaderView(Component view) public void setCorner(String key, Component corner) |
javax.swing.JScrollPane |
Mit setColumnHeaderView kann eine Komponente für den Spaltenkopf angegeben werden. Sie wird über dem eigentlichen Dialogelement angezeigt und bei horizontalen Bewegungen zusammen mit diesem verschoben. Bei vertikalen Schieberbewegungen bleibt sie dagegen an ihrem Platz. Analog dazu kann mit setRowHeaderView ein Zeilenkopf angegeben werden, der links neben der eigentlichen Komponente plaziert wird. Er wird bei vertikalen Bewegungen verschoben und behält bei horizontalen Bewegungen seinen Platz bei.
Mit setCorner kann in einer beliebigen der vier ungenutzten Ecken einer JScrollPane ein Dialogelement plaziert werden. Der Parameter key gibt dabei an, welche Ecke belegt werden soll. Als Argument kann eine der Konstanten LOWER_LEFT_CORNER, LOWER_RIGHT_CORNER , UPPER_LEFT_CORNER oder UPPER_RIGHT_CORNER der Klasse JScrollPane angegeben werden.
Zu beachten ist allerdings, daß die Eckflächen unter Umständen gar nicht zur Verfügung stehen. Die beiden Ecken auf der linken Seite sind beispielsweise nur dann vorhanden, wenn ein Zeilenkopf eingeblendet wurde. Die rechte obere ist nur vorhanden, wenn ein vertikaler Schieberegler eingeblendet wurde, und die rechte untere erfordert sogar die Anwesenheit beider Schieberegler. Auch kann die Anwendung praktisch keinen Einfluß auf die Größe der Ecken nehmen. Diese wird ausschließlich die Ausdehnung der Schieberegler und des Zeilenkopfes bestimmt. Summa sumarum ist die Möglichkeit, die Ecken belegen zu können, eine nur in Ausnahmefällen nützliche Eigenschaft. |
|
Abbildung 38.1: Die Anatomie einer JScrollPane
Das folgende Beispiel zeigt ein Programm, in dem eine JScrollPane ein zu großes JPanel mit hundert Checkboxen aufnimmt. In den beiden rechten Ecken wird jeweils ein JLabel plaziert, und als Spaltenkopf wird eine Instanz der Klasse ColumnHeader verwendet:
001 /* Listing3801.java */ 002 003 import java.awt.*; 004 import java.awt.event.*; 005 import javax.swing.*; 006 007 public class Listing3801 008 extends JFrame 009 { 010 public Listing3801() 011 { 012 super("JScrollPane"); 013 addWindowListener(new WindowClosingAdapter()); 014 //Dialogpanel erzeugen 015 JPanel panel = new JPanel(); 016 panel.setLayout(new GridLayout(10, 10)); 017 for (int i = 1; i <= 100; ++i) { 018 panel.add(new JCheckBox("Frage " + i)); 019 } 020 //JScrollPane erzeugen 021 JScrollPane scroll = new JScrollPane(panel); 022 scroll.setCorner( 023 JScrollPane.UPPER_RIGHT_CORNER, 024 new JLabel("1", JLabel.CENTER) 025 ); 026 scroll.setCorner( 027 JScrollPane.LOWER_RIGHT_CORNER, 028 new JLabel("2", JLabel.CENTER) 029 ); 030 scroll.setColumnHeaderView(new ColumnHeader(panel, 10)); 031 //JScrollPane zur ContentPane hinzufügen 032 getContentPane().add(scroll, "Center"); 033 } 034 035 public static void main(String[] args) 036 { 037 Listing3801 frame = new Listing3801(); 038 frame.setLocation(100, 100); 039 frame.setSize(300, 150); 040 frame.setVisible(true); 041 } 042 } 043 044 class ColumnHeader 045 extends JComponent 046 { 047 JComponent component; 048 int columns; 049 050 public ColumnHeader(JComponent component, int columns) 051 { 052 this.component = component; 053 this.columns = columns; 054 } 055 056 public void paintComponent(Graphics g) 057 { 058 int width = component.getSize().width; 059 int height = getSize().height; 060 int colwid = width / columns; 061 for (int i = 0; i < columns; ++i) { 062 g.setColor(i % 2 == 0 ? Color.yellow : Color.gray); 063 g.fillRect(i * colwid, 0, colwid, height); 064 } 065 g.setColor(Color.black); 066 g.drawLine(0, height - 1, width, height - 1); 067 } 068 069 public Dimension getPreferredSize() 070 { 071 return new Dimension(component.getSize().width, 20); 072 } 073 } |
Listing3801.java |
Die Klasse ColumnHeader ist aus JComponent abgeleitet und wird als Spaltenkopf für die JPanel-Komponente verwendet. Deren Spalten sind in diesem Sonderfall alle gleich breit. Das Panel selbst und die Spaltenzahl werden an den Konstruktor übergeben, und mit Hilfe dieser Angaben werden die nebeneinanderliegenden Spalten als abwechselnd grau und gelb gefärbte Rechtecke angezeigt. Zusätzlich wird eine schwarze Linie als untere Begrenzung des Spaltenkopfes gezeichnet. Durch Überlagern von getPreferredSize teilt ColumnHeader der JScrollPane seine Größe mit. Die Breite entspricht dabei der Breite der scrollbaren Komponente, die Höhe ist fest auf zwanzig Pixel eingestellt.
Die Ausgabe des Programms ist:
Abbildung 38.2: Die Klasse JScrollPane
JSplitPane ist ein Panel, mit dem zwei Komponenten neben- oder übereinander plaziert werden können. Die Komponenten werden dabei durch einen sichtbaren Separator voneinander getrennt. Der Anwender kann den Separator verschieben und so den Platz, der beiden Komponenten zur Verfügung steht, variieren. Ein JSplitPane ist beispielsweise nützlich, wenn Komponenten dargestellt werden sollen, deren Breite oder Höhe von den darin enthaltenen Daten abhängig ist. Das Programm braucht dann den benötigten Platz nur grob vorzugeben, und der Anwender kann durch Verschieben des Separators zur Laufzeit festlegen, wieviel Platz er jeder Komponente zur Verfügung stellen will.
Die Konstruktoren von JSplitPane sind:
public JSplitPane(int orientation) public JSplitPane( int orientation, boolean continuousLayout ) public JSplitPane( int orientation, Component leftComponent, Component rightComponent ) public JSplitPane( int orientation, boolean continuousLayout, Component leftComponent, Component rightComponent ) |
javax.swing.JSplitPane |
Der Parameter orientation gibt an, wie die Elemente zueinander angeordnet werden sollen. Hat er den Wert HORIZONTAL_SPLIT, werden sie nebeneinander, bei VERTICAL_SPLIT übereinander angeordnet. Hat continuousLayout den Wert true, so wird schon beim Verschieben des Separators der Bildschirminhalt aktualisiert. Andernfalls erfolgt das erst nach Ende des Verschiebevorgangs. In den Parametern leftComponent und rightComponent werden die beiden einzubettenden Komponenten übergeben. Alle Eigenschaften können auch nach der Instanzierung verändert bzw. abgefragt werden:
public void setOrientation(int orientation) public void setContinuousLayout(boolean continuousLayout) public void setLeftComponent(Component comp) public void setRightComponent(Component comp) public void setTopComponent(Component comp) public void setBottomComponent(Component comp) public int getOrientation() public boolean isContinuousLayout() public Component getLeftComponent() public Component getTopComponent() public Component getRightComponent() public Component getBottomComponent() |
javax.swing.JSplitPane |
Die Größe der beiden Komponenten richtet sich nach ihren minimalen und gewünschten Abmessungen. JSplitPane ermittelt diese Werte durch Aufruf von getPreferredSize und getMinimumSize auf den Komponentenobjekten. Es gilt:
Manche Komponenten liefern beim Aufruf von getMinimumSize die Größe, die sie beim ersten Sichtbarwerden auf dem Bildschirm hatten. In diesem Fall ist der Separator überhaupt nicht veränderbar, denn jede Verschiebung würde die minimale Größe einer der beiden Komponenten unterschreiten. Falls ein solches Problem auftritt, kann Abhilfe geschaffen werden, indem durch Aufruf von setMinimumSize die minimale Größe der Komponenten vermindert wird. |
|
Eine interessante Methode zur Modifikation des Separators ist setOneTouchExpandable:
public void setOneTouchExpandable(boolean newValue) |
javax.swing.JSplitPane |
Wird sie mit true als Argument aufgerufen, erhält der Separator zwei kleine Pfeile, die jeweils auf eine der beiden Komponenten zeigen. Wird einer von ihnen angeklickt, so wird der Separator komplett auf die angezeigte Seite verschoben. Die auf dieser Seite liegende Komponente wird verdeckt und die andere erhält den vollen zur Verfügung stehenden Platz (die von getMinimumSize definierten Grenzen werden dabei ignoriert). Ein weiterer Klick auf den Separator gibt der verdeckten Komponente wieder ihre Minimalgröße.
Das folgende Programm zeigt einen JFrame mit einem horizontal geteilten JSplitPane. Als Komponenten werden zwei Instanzen der weiter unten definierten Klasse GridComponent verwendet, die ein Gitternetz anzeigt, dessen Maschengröße proportional zur Ausdehnung der Komponente ist:
001 /* Listing3802.java */ 002 003 import java.awt.*; 004 import java.awt.event.*; 005 import javax.swing.*; 006 007 public class Listing3802 008 extends JFrame 009 { 010 public Listing3802() 011 { 012 super("JSplitPane"); 013 addWindowListener(new WindowClosingAdapter()); 014 //Linkes Element erzeugen 015 GridComponent grid1 = new GridComponent(); 016 grid1.setMinimumSize(new Dimension(50, 100)); 017 grid1.setPreferredSize(new Dimension(180, 100)); 018 //Rechtes Element erzeugen 019 GridComponent grid2 = new GridComponent(); 020 grid2.setMinimumSize(new Dimension(100, 100)); 021 grid2.setPreferredSize(new Dimension(80, 100)); 022 //JSplitPane erzeugen 023 JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); 024 sp.setLeftComponent(grid1); 025 sp.setRightComponent(grid2); 026 sp.setOneTouchExpandable(true); 027 sp.setContinuousLayout(true); 028 getContentPane().add(sp, "Center"); 029 } 030 031 public static void main(String[] args) 032 { 033 Listing3802 frame = new Listing3802(); 034 frame.setLocation(100, 100); 035 frame.setSize(300, 200); 036 frame.setVisible(true); 037 } 038 } 039 040 class GridComponent 041 extends JComponent 042 { 043 public void paintComponent(Graphics g) 044 { 045 g.setColor(Color.gray); 046 int width = getSize().width; 047 int height = getSize().height; 048 for (int i = 0; i < 10; ++i) { 049 g.drawLine(i * width / 10, 0, i * width / 10, height); 050 } 051 for (int i = 0; i < 10; ++i) { 052 g.drawLine(0, i * height / 10, width, i * height / 10); 053 } 054 g.setColor(Color.black); 055 g.drawString("" + width, 5, 15); 056 } 057 } |
Listing3802.java |
Die Ausgabe des Programms ist:
Abbildung 38.3: Die Klasse JSplitPane
Als letztes der speziellen Panels wollen wir uns die Klasse JTabbedPane ansehen. Mit ihr ist es möglich, Dialoge zu erstellen, die eine Reihe von Registerkarten enthalten. Das sind Unterdialoge, die über ein am Rand befindliches Register einzeln ausgewählt werden können. Derartige Registerkarten werden beispielsweise in Konfigurationsdialogen verwendet, wenn die zu bearbeitenden Optionen nicht alle auf eine Seite passen.
JTabbedPane stellt zwei Konstruktoren zur Verfügung:
public JTabbedPane() public JTabbedPane(int tabPlacement) |
javax.swing.JTabbedPane |
Der erste von beiden erzeugt ein JTabbedPane mit oben liegenden Registern. Beim zweiten kann explizit angegeben werden, auf welcher Seite die Register angeordnet werden sollen. Hier können die Konstanten TOP, BOTTOM, LEFT oder RIGHT aus dem Interface SwingConstants übergeben werden. Üblich ist es, die Register links oder oben anzuordnen. Mit den Methoden getTabPlacement und setTabPlacement kann auch nachträglich auf die Anordnung zugegriffen werden.
Nach der Instanzierung ist der Registerdialog zunächst leer. Um Registerkarten hinzuzufügen, kann eine der Methoden addTab oder insertTab aufgerufen werden:
public void addTab( String title, Icon icon, Component component, String tip ) public void insertTab( String title, Icon icon, Component component, String tip, int index ) |
javax.swing.JTabbedPane |
Der Parameter title gibt die Beschriftung des Registereintrags an. Mit icon kann zusätzlich ein Icon und mit tip ein Tooltiptext hinzugefügt werden. Der Parameter component gibt das darzustellende Dialogelement an. Meist wird hier eine Containerklasse übergeben (z.B. ein JPanel), die den entsprechenden Unterdialog enthält. Die Parameter icon und tip sind optional, d.h. es gibt die beiden Methoden auch ohne sie. addTab fügt die Registerkarte am Ende ein, bei insertTab kann die Einfügeposition mit dem Parameter index selbst angegeben werden.
Bereits definierte Registerkarten können auch wieder entfernt werden:
public void removeTabAt(int index) public void removeAll() |
javax.swing.JTabbedPane |
removeTabAt entfernt die Karte mit dem angegebenen Index, removeAll entfernt alle Karten.
Mit getTabCount kann die Anzahl der Registerkarten ermittelt werden, und mit getComponentAt kann die Komponente einer beliebigen Registerkarte ermittelt werden. Mit setComponentAt kann der Inhalt einer Registerkarte sogar nachträglich ausgetauscht werden:
public int getTabCount() public Component getComponentAt(int index) public void setComponentAt(int index, Component component) |
javax.swing.JTabbedPane |
In einem Registerdialog ist immer genau eine der Karten selektiert. Mit getSelectedIndex kann deren Index ermittelt werden, mit getSelectedComponent sogar direkt auf ihren Inhalt zugegriffen werden. Die Methode setSelectedIndex erlaubt es, programmgesteuert eine beliebige Registerkarte auszuwählen:
public int getSelectedIndex() public Component getSelectedComponent() public void setSelectedIndex(int index) |
javax.swing.JTabbedPane |
Registerkarten besitzen eine Reihe von Eigenschaften, die einzeln verändert werden können. Die wichtigste von ihnen ist die Möglichkeit, sie zu aktivieren oder zu deaktivieren. Nur aktivierte Karten können ausgewählt werden, deaktivierte dagegen nicht. Der Zugriff auf den Aktivierungsstatus erfolgt mit den Methoden setEnabledAt und isEnabledAt:
public boolean isEnabledAt(int index) public void setEnabledAt(int index, boolean enabled) |
javax.swing.JTabbedPane |
Bei jeder Änderung der Selektion versendet ein JTabbedPane ein ChangeEvent an registrierte ChangeListener. Auf diese Weise ist es möglich, Programmcode zu schreiben, der beim Anwählen oder Verlassen einzelner Registerkarten ausgeführt wird. Eine der Anwendungen hierfür besteht darin, die Dialoge auf den Registerkarten erst dann zu erzeugen, wenn sie wirklich gebraucht werden. Dazu wird an alle Registerkarten zunächst ein leeres Panel übergeben und erst in der Methode stateChanged durch den eigentlichen Dialog ersetzt. Auf diese Weise spart das Programm beim Initialisieren des Registerdialogs Rechenzeit und Speicher, was sich vor allem bei komplexen Dialogen positiv bemerkbar machen könnte. |
|
Das folgende Programm zeigt ein einfaches Beispiel eines Registerdialogs. Es erstellt ein JTabbedPane mit fünf Registerkarten, die jeweils ein JPanel mit einem Label und einem Button enthalten. Das Label zeigt den Namen der Karte an, der Button dient dazu, die jeweils nächste Karte auszuwählen. Dessen Aktivität ist in der Klasse NextTabActionListener gekapselt. Als Besonderheit wird nach jedem Seitenwechsel requestDefaultFocus auf der neuen Seite aufgerufen, um automatisch dem ersten Dialogelement den Fokus zu geben.
001 /* Listing3803.java */ 002 003 import java.awt.*; 004 import java.awt.event.*; 005 import javax.swing.*; 006 007 public class Listing3803 008 extends JFrame 009 { 010 JTabbedPane tp; 011 012 public Listing3803() 013 { 014 super("JTabbedPane"); 015 addWindowListener(new WindowClosingAdapter()); 016 tp = new JTabbedPane(); 017 for (int i = 0; i < 5; ++i) { 018 JPanel panel = new JPanel(); 019 panel.add(new JLabel("Karte " + i)); 020 JButton next = new JButton("Weiter"); 021 next.addActionListener(new NextTabActionListener()); 022 panel.add(next); 023 tp.addTab("Tab" + i, panel); 024 } 025 getContentPane().add(tp, "Center"); 026 } 027 028 class NextTabActionListener 029 implements ActionListener 030 { 031 public void actionPerformed(ActionEvent event) 032 { 033 int tab = tp.getSelectedIndex(); 034 tab = (tab >= tp.getTabCount() - 1 ? 0 : tab + 1); 035 tp.setSelectedIndex(tab); 036 ((JPanel)tp.getSelectedComponent()).requestDefaultFocus(); 037 } 038 } 039 040 public static void main(String[] args) 041 { 042 Listing3803 frame = new Listing3803(); 043 frame.setLocation(100, 100); 044 frame.setSize(300, 200); 045 frame.setVisible(true); 046 } 047 } |
Listing3803.java |
Die Ausgabe des Programms ist:
Abbildung 38.4: Die Klasse JTabbedPane
Titel | Inhalt | Suchen | Index | API | Go To Java 2, Zweite Auflage, Addison Wesley, Version 2.0 |
<< | < | > | >> | © 2000 Guido Krüger, http://www.gkrueger.com |