Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage
 <<    <     >    >>   API  Kapitel 39 - Swing: Komponenten II

39.1 Spezielle Panels



39.1.1 JScrollPane

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 39.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 39.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 platziert 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 platziert 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 dabei, dass 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 Einfluss auf die Größe der Ecken nehmen. Diese wird ausschließlich durch die Ausdehnung der Schieberegler und des Zeilenkopfs bestimmt. Insgesamt ist die Möglichkeit, die Ecken belegen zu können, eine eher nur in Ausnahmefällen nützliche Eigenschaft.

Abbildung 39.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 platziert und als Spaltenkopf wird eine Instanz der Klasse ColumnHeader verwendet:

001 /* Listing3901.java */
002 
003 import java.awt.*;
004 import java.awt.event.*;
005 import javax.swing.*;
006 
007 public class Listing3901
008 extends JFrame
009 {
010   public Listing3901()
011   {
012     super("JScrollPane");
013     addWindowListener(new WindowClosingAdapter(true));
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, BorderLayout.CENTER);
033   }
034 
035   public static void main(String[] args)
036   {
037     Listing3901 frame = new Listing3901();
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 }
Listing3901.java
Listing 39.1: Die Klasse JScrollPane

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 Spaltenkopfs 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 39.2: Die Klasse JScrollPane

39.1.2 JSplitPane

JSplitPane ist ein Panel, mit dem zwei Komponenten neben- oder übereinander platziert werden können. Die Komponenten werden dabei durch einen sichtbaren Separator voneinander getrennt. Der Anwender kann den Separator verschieben und so den Platz variieren, der beiden Komponenten zur Verfügung steht. 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, wie viel 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 angeordnet, bei VERTICAL_SPLIT übereinander. Hat continuousLayout den Wert true, 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.

 Tip 

Eine interessante Methode zur Verschiebung 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 ausgewählte Seite verschoben. Die auf dieser Seite liegende Komponente wird also 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 /* Listing3902.java */
002 
003 import java.awt.*;
004 import java.awt.event.*;
005 import javax.swing.*;
006 
007 public class Listing3902
008 extends JFrame
009 {
010   public Listing3902()
011   {
012     super("JSplitPane");
013     addWindowListener(new WindowClosingAdapter(true));
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, BorderLayout.CENTER);
029   }
030 
031   public static void main(String[] args)
032   {
033     Listing3902 frame = new Listing3902();
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 }
Listing3902.java
Listing 39.2: Die Klasse JSplitPane

Die Ausgabe des Programms ist:

Abbildung 39.3: Die Klasse JSplitPane

39.1.3 JTabbedPane

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 Tooltip-Text 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 kann 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 Oberflächen 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 /* Listing3903.java */
002 
003 import java.awt.*;
004 import java.awt.event.*;
005 import javax.swing.*;
006 
007 public class Listing3903
008 extends JFrame
009 {
010   JTabbedPane tp;
011 
012   public Listing3903()
013   {
014     super("JTabbedPane");
015     addWindowListener(new WindowClosingAdapter(true));
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, BorderLayout.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     Listing3903 frame = new Listing3903();
043     frame.setLocation(100, 100);
044     frame.setSize(300, 200);
045     frame.setVisible(true);
046   }
047 }
Listing3903.java
Listing 39.3: Die Klasse JTabbedPane

Die Ausgabe des Programms ist:

Abbildung 39.4: Die Klasse JTabbedPane


 Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage, Addison Wesley, Version 7.0
 <<    <     >    >>   API  © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de