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

## 38.1 Spezielle Panels

- 38.1 Spezielle Panels
  - 38.1.1 JScrollPane
  - 38.1.2 JSplitPane
  - 38.1.3 JTabbedPane

### 38.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 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. |  |
Hinweis

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(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     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

Listing 38.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 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

### 38.1.2 JSplitPane

`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:

- Bei der initialen Darstellung der Komponenten wird zunächst geprüft, ob beide in ihrer gewünschten Größe angezeigt werden können (es wird also `getPreferredSize` aufgerufen). Ist das der Fall, wird die linke (obere) Komponente in der gewünschten Größe angezeigt, und die rechte (untere) erhält den verbleibenden Platz.
- Ist das nicht der Fall, wird die linke (untere) Komponente in ihrer minimalen Größe angezeigt, und die rechte erhält den verbleibenden Platz.
- Reicht der Platz nicht einmal aus, um beide Komponenten in ihrer minimalen Ausdehnung anzuzeigen, ist das Verhalten undefiniert. Typischerweise steht der Separator danach komplett auf einer Seite, und es ist nur noch eine der beiden Komponenten sichtbar.
- Sind die Komponenten auf dem Bildschirm sichtbar, kann der Separator soweit in jede Richtung verschoben werden, bis die minimale Größe der jeweiligen Komponente erreicht ist.

|  |  |  | 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 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(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     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

Listing 38.2: Die Klasse JSplitPane

Die Ausgabe des Programms ist:

Abbildung 38.3: Die Klasse JSplitPane

### 38.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 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. |  |
Tip

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(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     Listing3803 frame = new Listing3803();
    043     frame.setLocation(100, 100);
    044     frame.setSize(300, 200);
    045     frame.setVisible(true);
    046   }
    047 }
| Listing3803.java

Listing 38.3: Die Klasse JTabbedPane

Die Ausgabe des Programms ist:

Abbildung 38.4: Die Klasse JTabbedPane

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