Java Swing Kontrollelemente
Willemers Informatik-Ecke
Layout von Kontrollelementen Java-Kurs Swing Menüs

Grafische Oberflächen stellen Kontrollelemente (oft auch Widgets genannt) für wiederkehrende Aufgaben zur Verfügung. Kontrollelemente übernehmen die Kontrolle über die Maus oder die Tastatur für ihren Bereich und zeichnen sich selbst. Widgets führen zu einer applikationsübergreifenden Einheitlichkeit (Look and Feel). Swing-Kontrollelemente erweitern JComponent und sind auf ihre Aufgabe spezialisiert.

Ereignisse

Die Kontrollelemente lösen Ereignisse aus, die eine Klasse auffängt, die das Interface ActionListener implementiert. Häufig übernimmt dies die Klasse, die sich um die Darstellung kümmert, also eine Erweiterung von JFrame oder JPanel.
import java.awt.event.*;

public class MeinPanel extends JPanel implements ActionListener {
Damit die Klasse, die ActionListener implementiert, für ein Kontrollelement aktiv wird, muss die Klasse allerdings für jedes Element mit dem Aufruf addActionListener angemeldet worden sein.
public class MeinFrame extends JFrame {
    // ...
    MeinPanel panel = new MeinPanel();
    JButton click = new JButton("Drück mich!");
    click.addActionListener(panel);

Das Interface ActionListener fordert von den Klassen, die es implementieren, dass sie die Methode actionPerformed implementieren. Diese Methode wird später aufgerufen, wenn ein Ereignis eintritt.

public void actionPerformed(ActionEvent ev) {
    if (ev.getSource()==click) { // Welcher Button?
        JOptionPane.showMessageDialog(null, "Sehr druckvoll!");
    }
}

Beim Eintreten eines Ereignisses wird die Methode actionPerformed aufgerufen. Nähere Details zum Ereignis erfährt die Methode aus dem Paramter vom Typ ActionEvent. Die Klasse stellt folgende Methoden zur Verfügung:

Buttons

Die einfachste aktive Kontrollelement ist der Button, bei Swing JButton genannt. Mit dem Aufruf von addActionListener(this) wird angemeldet, dass diese Klasse beauftragt ist, die Ereignisse des Buttons zu fangen. Dazu implementiert die Klasse das Interface ActionListener.

Wer ActionListener implementiert, muss die Methode actionPerformed definieren. Sie wird von der Laufzeitumgebung aufgerufen, wenn ein Ereignis eintrifft. Der Parameter dieser Methode ist ein ActionEvent, das Informationen liefert, die zur Verarbeitung hilfreich sind, insbesondere, welcher Button eigentlich gedrückt wurde.

import javax.swing.JFrame;
import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.event.*;

public class ButtonAction extends JFrame implements ActionListener {

    private JButton click;

    ButtonAction() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        click = new JButton("Drück mich!");
        click.addActionListener(this);
        add(click);
        pack();
        setVisible(true);
    }

    public static void main(String[] args) {
        new ButtonAction();
    }

    public void actionPerformed(ActionEvent ev) {
        if (ev.getSource()==click) {
            JOptionPane.showMessageDialog(null, "Sehr druckvoll!");
        }
    }
}
Das Hauptprogramm ist deutlich kleiner als der Dialog, der sich nach dem Drücken des Buttons davorschiebt.

Der Button hat einen Haken

Das Kontrollelement JCheckBox ist ein Button, der Wahr-Falsch-Entscheidungen grafisch umsetzt. Der Konstruktor übernimmt üblicherweise den Text, der die JCheckBox darstellt. Ansonsten verhält er sich weitgehend wie ein normaler Button.

Auch die Ereignisse der Checkbox kann durch einen ActionListener verfolgt werden. Das Vorgehen gleicht dem beim Button. Ein Ereigns wird sowohl durch das Setzen wie auch durch das Wegnehmen des Hakens ausgelöst. Allerdings ist dieses Ereignis in der Praxis selten gefragt. Vielmehr interessiert sich das Programm für den Status der Checkbox, wenn im Dialog ein OK-Button gedrückt wurde.

Anders als der normale Button hat die JCheckBox einen Status. Sie kann selektiert sein, also einen Haken tragen oder eben nicht. Dieser Haken wird vom Benutzer gesetzt werden. Aber auch das Programm kann diesen Haken durch Aufruf der Methode setSelected setzen. Als Parameter erwartet die Methode einen booleschen Wert.

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class Checkbox extends JFrame implements ActionListener {

    private JLabel label = new JLabel("k.A.");
    private JCheckBox check = new JCheckBox("richtig");

    public Checkbox() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLayout(new FlowLayout());
        JButton click = new JButton("OK");
        click.addActionListener(this);
        this.add(check);
        this.add(click);
        this.add(label);
        pack();
        setVisible(true);
    }
    public static void main(String[] args) {
        new Checkbox();
    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
        if (check.isSelected()) {
            label.setText("ja");
        } else {
            label.setText("nein");
        }        
    }
}
Das Programm erzeugt ein Fenster mit einer Checkbox, einem Button und einem Label. Wird der Button OK gedrückt, wird der Zustand der Checkbox ausgeleseen und das Label mit ja oder nein besetzt.

Damit mehrere Elemente in ein Fenster gesetzt werden können, muss ein Layout-Manager eingesetzt werden, hier ist es das FlowLayout.

RadioButtons

Während eine Checkbox nur zwei Zustände annehmen kann, erlauben Radiobuttons die Auswahl aus mehreren Zuständen. Radiobuttons haben ihren Namen von den Stationstasten am Radio. Es kann immer nur ein Sender gleichzeitig laufen. Egal wie viele Tasten also am Radio existieren, es kann immer nur eine gedrückt sein.

Radiobuttons müssen gruppiert werden

Die einzelnen Buttons sind Objekte der Klasse JRadiobutton.

JRadioButton radioMaennlich = new JRadioButton("männlich");
JRadioButton radioWeiblich = new JRadioButton("weiblich");
Sie können nun in ein Fenster eingesetzt und angeklickt werden. Allerdings kann man derzeit noch sowohl männlich als auch weiblich sein. Um die typische Radio-Funktion zu erreichen, müssen die Buttons einer ButtonGroup zugeordnet werden.
ButtonGroup grpGeschlecht = new ButtonGroup();
grpGeschlecht.add(radioMaennlich);
grpGeschlecht.add(radioWeiblich);
radioMaennlich.setSelected(true);

Ereignisse und auslesen

Auch Radiobuttons können Ereignisse auslösen, die durch einen ActionListener empfangen werden, sofern dieser angemeldet wurde. In der Regel ist das wie bei der Checkbox nicht erforderlich.

Wie bei der Checkbox kann der Zustand jedes Radiobuttons mit isSelected ausgelesen oder mit setSelected gesetzt werden.

Beispiel

Das folgende Beispielprogramm zeigt Radiobuttons für vier Farben. Wird auf den OK-Button geklickt, ändert dieser die Farbe, je nachdem, welcher Radiobutton angewählt ist. Klickt man auf einen der Radiobuttons, erscheint im Label die aktuell angewählte Farbe:
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JRadioButton;

public class RadioButton extends JFrame implements ActionListener {

    private JLabel labakt = new JLabel("aktuell");
    private JButton bt = new JButton("OK");
    private String[] strFarbe = { "gelb", "rot", "grün", "blau" };
    private Color[] col = { Color.yellow, Color.red, Color.green, Color.blue };
    private JRadioButton[] radbt = new JRadioButton[strFarbe.length];
    private ButtonGroup radgrp = new ButtonGroup();

    public RadioButton() {
        this.setLayout(new GridLayout(2, 4));
        for (int i = 0; i < strFarbe.length; i++) {
            radbt[i] = new JRadioButton(strFarbe[i]);
            radgrp.add(radbt[i]); // zur Gruppe hinzufügen
            radbt[i].setBackground(col[i]); // bunt machen
            radbt[i].addActionListener(this);
            this.add(radbt[i]); // auf das Panel setzen
        }
        radbt[0].setSelected(true); // Auswahl vorgeben
        // Setze sie auf das Panel
        this.add(bt);
        bt.addActionListener(this);
        this.add(new JLabel(" ")); // Auffüllen
        this.add(labakt);
        pack();
        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        new RadioButton();
    }

    private int getRadIndex() {
        for (int i = 0; i < strFarbe.length; i++) {
            if (radbt[i].isSelected()) {
                return i;
            }
        }
        return 0;
    }

    @Override
    public void actionPerformed(ActionEvent evt) {
        int i = getRadIndex();
        if (evt.getSource() == bt) {
            bt.setBackground(col[i]);
        } else {
            labakt.setText(strFarbe[i]);
        }
    }
}

Für die Anordnung der Kontrollelemente wurde als Layout-Manager dieses Mal das GridLayout verwendet. In einem Raster werden alle Elemente gleich groß dargestellt. Die Elemente werden auf den verfügbaren Platz aufgeblasen.

Eingabefelder

Für die Eingabe von Daten gibt es das Kontrollelement JTextField. Der Benutzer kann dort eine Zeile eintippen. Die Eingabe wird mit der Methode getText ausgelesen. Das Ergebnis steht dann in einer Variable vom Typ String. Bei Zahleneingaben muss der String in eine Zahlenvariable umgewandelt werden.
JTextField eingabeFeld = new JTextField();
Die wichtigsten Aufgaben sind das Auslesen einer Benutzereingabe mit getText. Hin und wieder möchte man einen Wert vorgeben. Dann wird die Methode setText verwendet.
eingabeFeld.setText("1.4142135");
String inhalt = eingabeFeld.getText();

Sonderfälle Passwort und mehrzeiliger Text

Abwandlungen sind das JPasswordField, mit dem man eben Passwörter eingeben lassen kann. Die Eingabe wird dann verdeckt.

Das JTextArea stellt ein mehrzeiliges Eingabefeld dar. Damit kann leicht ein kleiner Editor in die Anwendung eingebaut werden. Wird der Inhalt mit setEditable(false) als nicht editierbar gesetzt, kann man damit leicht längere Passagen wie Copyrightvermerke anzeigen.

Fokus

Wenn ein Kontrollelement die Tastenkontrolle hat, hat es den Fokus. Verlässt der Anwender das Kontrollelement, so verliert es den Fokus.

Bei Eingabefeldern ist das Ereignis, dass es den Fokus verliert von hohem Interesse, wenn man beispielsweise die Eingabe prüfen will oder Zwischenergebnisse berechnen will. Dazu muss ein FocusListener aufgesetzt werden.

Das folgende Beispiel ist ein Programm mit zwei Eingabefeldern, deren Fokus beobachtet wird. Zwischen den beiden Eingabefeldern wird ein TextArea-Feld benutzt, um die Ereignisse anzuzeigen. Bei der Gelegenheit wird noch gezeigt, wie das JTextArea-Element in ein JScrollPane gesteckt wird, das dann die Verwaltung der Schiebebalken übernimmt, wenn der Textinhalt größer wird als der Programmausschnitt anzeigt.

import javax.swing.*;
import java.awt.BorderLayout;
import java.awt.event.*;

public class TestFocus extends JFrame implements FocusListener {
    JTextArea anzeige;
    JTextField txtOben, txtUnten;

    TestFocus() {
        setDefaultCloseOperation(EXIT_ON_CLOSE); // Schließen bewirkt Ende
        setLayout(new BorderLayout());           // Layout auf Border festlegen
        anzeige = new JTextArea();               // Multi-Line-Edit als Anzeige
        anzeige.setEditable(false);
        JScrollPane scrollRahmen = new JScrollPane(anzeige); // Schiebebalken um Anzeige
        txtOben = new JTextField();              // Eingabefeld anlegen
        txtOben.addFocusListener(this);          // FocusListener anmelden
        txtUnten = new JTextField();
        txtUnten.addFocusListener(this);
        add(scrollRahmen, BorderLayout.CENTER);  // Elemente ins Layout bringen
        add(txtOben, BorderLayout.NORTH);
        add(txtUnten, BorderLayout.SOUTH);
        setSize(400, 300);                       // Rahmen groß machen
        setVisible(true);                        // Rahmen sichtbar machen
    }

    @Override public void focusGained(FocusEvent e) {  // Focus erhalten
        if (e.getSource() == txtOben) {          // wer hat das Ereignis ausgelöst?
            anzeige.append("Focus erhalten: txtOben\n");
        } else if (e.getSource() == txtUnten) {
            anzeige.append("Focus erhalten: txtUnten\n");
        }
    }

   @Override public void focusLost(FocusEvent e) {  // Focus verloren
        if (e.getSource() == txtOben) {          // wer hat das Ereignis ausgelöst?
            anzeige.append("Focus verloren: txtOben\n");
        } else if (e.getSource() == txtUnten) {
            anzeige.append("Focus verloren: txtUnten\n");
        }
    }

    public static void main(String[] args) {
        new TestFocus();                         // gib Gas!
    }
}

Das Testprgramm verwendet für die Anordnung der Textfelder ein BorderLayout als Layout-Manager. Dadurch wird der verfügbare Platz an das Element in der Mitte vergeben, während die Ränder nur das bekommen, was sie brauchen.

Interessant ist auch, dass der Fokus verloren geht, wenn ein fremdes Fenster in den Vorgrund kommt.

Liste

Die JList ist ein Kontrollelement, das Listen darstellen kann. In seiner einfachsten Art bestehen diese Listen aus Strings.
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;

public class Liste extends JFrame {
    public static void main(String[] args) {
        new Liste();
    }
    
    public Liste() {
        String[] inhalt = {"Tomatensuppe", "Kartoffelschnee",
            "Gulaschsuppe", "Puree de pommes", "Erbsensuppe"};
        JList<String> liste = new JList<>(inhalt);
        JScrollPane scroll = new JScrollPane(liste);
        add(scroll);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack(); // Gegenstück zu setSize
        setVisible(true); // sichtbar machen
    }
}
Die JList stellt ein Array von Strings dar, das im Konstruktor übergeben wird. Eine JList wird typischerweise über Generics auf String festgelegt. Die JScrollPane bildet einen Container für die JList. Sie erzeugt einen Schiebebalken, sobald der Listeninhalt nicht mehr vollständig darstellbar ist.

Listenänderungen am Modell

Wenn sich der Inhalt der Liste im Verlauf des Programms ändern soll, kommen wir mit dem String-Array nicht weiter. Denn nachträgliche Änderungen am String-Array schlagen nicht auf die JList durch, da das Array nicht weiß, dass es von der Liste verwendet wird.

In diese Lücke springt das Listenmodell. Es kann auch mit einem String-Array initialisiert werden. Das Modell wird dann der JList als Konstruktorparameter übergeben.

import javax.swing.DefaultListModel;
import javax.swing.JList;
import javax.swing.JScrollPane;
// ...
    DefaultListModel<String> model = new DefaultListModel<>();
    JList<String> liste = new JList<>(inhalt);
    JScrollPane scroll = new JScrollPane(liste);
Um den Inhalt der Liste zu ändern, werden diese am Modell ausgeführt. Dazu stellt DefaultListModel folgende Methoden zur Verfügung.

Das folgende Beispiel zeigt, wie das String-Array aus dem ersten Beispiel über eine Schleife in das Modell übernommen werden kann.

String[] inhalt = {"Tomatensuppe", "Kartoffelschnee",
    "Gulaschsuppe", "Puree de pommes de terre", "Erbsensuppe"};
DefaultListModel<String> model = new DefaultListModel<>();
for (String element : inhalt) {
    model.addElement(element);
}
JList<String> liste = new JList<String>(model);

Mit der Methode addAll kann die Liste mit einem Schlag übernommen werden. Allerdings muss dazu das String-Array mit der Funktion Arrays.asList zunächst in eine ArrayList verwandelt werden.

        List<String> arraylist = new ArrayList<String>();
        arraylist.addAll(Arrays.asList(inhalt));
        model.addAll(arraylist);

Etwas flotter geht das in einer Zeile:

model.addAll(Arrays.asList(inhalt));

Listen selektieren und auslesen

Die JList kann mehrere Listenelemente selektieren, in dem der Benutzer bei gedrückter [Strg]-Taste klickt. Entsprechend kann mit getSelectedIndex die Nummer des selektierten Elements oder mit getSelectedIndices ein Integer-Array aller selektierten Elemente ermittelt werden. Das Programm kann mit setSelectedIndex bzw. setSelectedIndices Elemente selektieren.

Um den Inhalt des Listenelements zu bekommen, muss man sich an das Modell wenden und die Methode elementAt aufrufen.

liste.setSelectedIndex(nr);
int index = liste.getSelectedIndex();
String str = model.elementAt(index);

Ereignisse

In manchen Fällen will man kontrollieren, ob ein Wechsel der Selektion stattgefunden hat. Dazu wird ein ListSelectionListener erzeugt, der die Methode valueChanged aufruft, sobald ein Listenelement angeklickt wird.
liste.addListSelectionListener(new ListSelectionListener() {
    @Override
    public void valueChanged(ListSelectionEvent arg0) {
        // TODO Auto-generated method stub
    }
});

Ein Beispiel für den Umgang mit einer JList finden Sie im Projekt CodeKnacker.

Combobox zur Auswahl aus einer Liste

Eine Combobox könnte man auch als Klappliste bezeichnen. Der ausgewählte Eintrag der Liste ist offen sichtbar, die anderen Optionen klappen heraus, wenn man den Button des Elements anklickt.

Die Combobox eignet sich als Ersatz für Radiobuttons oder als Ersatz für eine Liste, die nur eine Auswahl zulässt.

String[] fuellung = {"Tomatensuppe",
        "Kartoffelschnee", "Gulaschsuppe",
        "Puree de pommes de terre", "Erbsensuppe"};
waehler = new JComboBox(fuellung);
waehler.setEditable(true);
Eine JComboBox verhält sich ähnlich wie eine JList. Das angewählte Element ist sichtbar. Duch Anklicken fährt die Liste aus. Besonderheit: Die sichtbare Zeile kann wie ein JTextField editierbar gemacht werden.

Die Events einer JComboBox werden durch einen ActionListener gefangen. In der Methode actionPerformed wird ermittelt, ob das Feld editiert wurde.

@Override
public void actionPerformed(ActionEvent e) {
    if (e.getSource()==waehler) {
        if (e.getActionCommand().equals("comboBoxEdited")) {
            // Combobox wurde editiert
            String str = (String) waehler.getSelectedItem();
            if (str!=null && !str.isEmpty()) {
                waehler.addItem(str);
            }
        } else {
            index = waehler.getSelectedIndex();
            if (index>=0) {
                label.setText(waehler.getItemAt(index));
            }
        }
    }
}
Bei der Auswertung wird einfach abgefragt, welches Feld ausgewählt war. Der Rückgabewert ist der Index der Auswahl, beginnend bei 0.
int index = wahl.getSelectedIndex();
Man kann auch der Combobox einen ActionListener zuweisen. Dann kann das Programm auf jede Änderung in der Box reagieren.
wahl.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    JComboBox jcmbType = (JComboBox) e.getSource();
    String cmbType = (String) jcmbType.getSelectedItem();
    label.setIcon(new ImageIcon("" + cmbType.trim()) + ".jpg"));
  }
});
Die Methoden von JComboBox: Als Elemente können Arrays oder Vektoren verwendet werden. Der Konstruktor akzeptiert auch ein ComboBoxModel.

Ein naher Verwandter der Combobox ist die JList. Das ist eine Liste, die nicht ausklappt, sondern einen größeren Raum für alle Zeilen einnimmt.

Strengere Typisierung

In den neueren Java-Versionen wird es immer eine Warnung geben, wenn man JComboBox ohne Typisierung benutzt. Der Hintergrund ist, dass die Schnittstellen Object verwenden.

JSpinner

Ein Spinner ist vergleichbar mit einer Combobox, mit dem Unterschied, dass sie nicht aufklappt, sondern der Anwender mit zwei Pfeilelementen aus den Vorgaben auswählt.

JSpinner spinner = new JSpinner();
Wenn nichts angegeben wird, enthält der Spinner ganze Zahlen, die bei 0 beginnen. Um die Werte zu konfigurieren, können Modelle erstellt werden.

Zahlenspinner

Mit dem SpinnerNumberModel können die Zahlen bestimmt werden, die der Spinner annehmen kann. Dazu übergeben Sie dem Konstruktor vier Werte: der zunächst ausgewählte Startwert, der minimale und der maximale Wert, den der Spinner annehmen soll und die Schrittweite des Spinners.

double start = 7.5, min = 5, max = 9.9, step = .5;
SpinnerNumberModel spinmod = new SpinnerNumberModel(start, min, max, step);
JSpinner spinner = new JSpinner(spinmod);

Der eingestellte Wert wird über das Modell mit der Methode getValue gelesen. Dabei wird das Objekt einer Wrapperklasse zurückgegeben, die entsprechend gecastet werden muss.

Double zahl = (Double) spinmod.getValue();

Stringspinner

Anstelle von Zahlen können auch Texte verwendet werden, zwischen denen gewählt wird.

String[] strar = {"Anton", "Berta", "Caesar","Dora"};
SpinnerListModel spinmod = new SpinnerListModel(strar);
JSpinner spinner = new JSpinner(spinmod);

Auch bei dem SpinnerListModel wird die Methode getValue verwendet, um den eingestellten Wert zu liefern. Hier allerdings ist der Rückgabetyp ein String.

String text = (String) spinmod.getValue();

Datumspinner

Sie können den Spinner auch zur Datumsauswahl verwenden. Dazu wird ein SpinnerDateModel verwendet.

SpinnerDateModel spinmod = new SpinnerDateModel();
JSpinner spinner = new JSpinner(spinmod);
Ähnlich wie beim Zahlenmodell kann dem Konstruktor auch hier per Parameter übergeben werden, wie gestartet wird, welche Grenzen und Schrittweiten erlaubt sind.

Das folgende Programm enthält einen Datumsspinner und einen Button. Sobald auf den Button geklickt wird, wird das Datum des Spinners ausgelesen und hier der Einfachheit halber auf der Konsole ausgegeben werden.

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;

public class Spinner extends JFrame implements ActionListener {

    SpinnerDateModel spinmod = new SpinnerDateModel();
    JSpinner spinner;
    public Spinner() {
        this.setLayout(new FlowLayout());
        spinner = new JSpinner(spinmod);
        this.add(spinner);
        JButton button = new JButton("Klick");
        button.addActionListener(this);
        this.add(button);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack(); // Gegenstück zu setSize
        setVisible(true); // sichtbar machen
    }
    public static void main(String[] args) {
        new Spinner();
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        Date date = spinmod.getDate();
        System.out.println(date);
    }
}

Das Programm sieht wie folgt aus und gibt auf der Konsole den eingestellten Wert aus.

Ein Beispiel für den Umgang mit einem JSpinner finden Sie im Projekt CodeKnacker.


Layout von Kontrollelementen Swing Menüs