Was ist ein JSpinner?

Ein JSpinner ist ein einzeiliges Auswahlmenu, bevorzugt für sortierte Einträge, in dem durch Pfeilbuttons navigiert werden kann.

Die Komponente speichert eine Reihe von Auswahloptionen, von denen jedoch, im Gegensatz zu der menubasierten JComboBox, immer nur eine sichtbar ist. Die Daten, die ein JSpinner zur Auswahl anbietet, werden intern durch ein sog. Model verwaltet und im GUI durch ein JFormattedTextField präsentiert.

Rechts des angezeigten Eintrags kann mit Hilfe zweier nach oben und unten gerichteter Pfeile schrittweise durch die gespeicherten Optionen navigiert werden. Sie müssen im Standardfall natürlich textbasiert sein, können, nach Austausch des Textfeldes durch setEditor() gegen eine andere geeignete Komponente, jedoch auch anderen Objekten entsprechen. Ein Beispiel hierzu findet sich im Artikel JSpinner mit Bildern.

Die Instanzierung eines JSpinner kann entweder ohne oder mit Model erfolgen. Im ersten Fall wird ein Auswahlmenu gebildet, das intern ein vorgegebenes SpinnerNumberModel verwendet. Es stellt eine mit 0 vorausgewählte, in Einerschritten auf- und absteigend navigierbare, ganzzahlige Liste bereit.

JSpinner ohne eigenes Model

public class SpinnerNumberBsp {
            
    public SpinnerNumberBsp() {
        init();
    }

    public void init() {
        JFrame frame = new JFrame("JSpinner-Beispiel");
        JSpinner spinner = new JSpinner();
        spinner.addChangeListener(e -> System.out.println(spinner.getValue()));
        frame.add(spinner);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new SpinnerNumberBsp());
    }
}

Das Beispiel erzeugt ein auf JFrame basierendes Fenster mit einem JSpinner ohne Übergabe eines gesonderten Models. Wie erwähnt, wird in diesem Fall intern ein SpinnerNumberModel verwendet. Dem JSpinner-Objekt wurde lediglich ein ChangeListener hinzugefügt, der auf das "Blättern" in der Auswahlliste reagiert. Er gibt den im Menu gezeigten Wert auf der Konsole aus. Es muss hier beachtet werden, dass der nach Aktivieren des Navigationspfeils neu gewählte Eintrag durch getValue() zurückgegeben wird. Die Methoden getNextValue() und getPreviousValue() bieten hier weitere Auswahlmöglichkeiten.

JFrame-Fenster mit JSpinner

Die Models

Der Inhalt eines JSpinner wird durch ein Model bereitgestellt und verwaltet. Hierzu bietet die Java-Bibliothek die Klasse AbstractSpinnerModel, die das Interface SpinnerModel implementiert und bei Bedarf überschrieben werden kann. Für die häufigsten Anwendungen stehen drei Convenience-Klassen zur Verfügung, die hier kurz vorgestellt werden.

SpinnerNumberModel
Zur Auswahl numerischer Daten basierend auf den verfügbaren primitiven Typen Double, Float, Long, Integer, Short oder Byte.
SpinnerListModel
Ein Model, dem zur Initialisierung ein Object-Array oder eine List übergeben werden.
SpinnerDateModel
Verwaltet Datum-Zeit-Objekte, durch die im Standardfall tageweise navigiert wird.

SpinnerNumberModel

Ein SpinnerNumberModel kann verwendet werden, wenn eine andere als die als Standard implementierte ganzzahlige Integer-Auswahlliste oder eine abweichende Schrittweite benötigt werden. In ihm können ein Startwert, ein Minimal- und Maximalwert, sowie die Schrittweite definiert werden. Die Werte sind in den Eigenschaften value, minimum, maximum und stepSize gespeichert. Auf sie kann durch Accessor-Methoden lesend und schreibend zugegriffen werden. Entsprechend der Typ-Angabe oben können sowohl ganzzahlige, als auch gebrochene Werte verwendet werden. Minimum und Maximum können null sein und erzeugen dann eine offene, unbegrenzte Auswahlliste. Die vier Werte werden dem Konstruktor des SpinnerNumberModel als Argumente übergeben.

Das folgende Beispiel zeigt eine solche Anwendung, bei der der Auswahlbereich zwischen 2 und 8.5 liegt, 7.5 vorausgewählt ist und eine Schrittweite von 0.4 verwendet wird. Dies zeigt auch, dass der vorausgewählte Wert beliebig zwischen den Minimum- und Maximum-Grenzen liegen darf und die Schrittweite diesbezüglich nicht eingehalten werden muss.

public class SpinnerNumberModelBsp {
    
    public SpinnerNumberModelBsp() {
        init();
    }

    public void init() {
        JFrame frame = new JFrame("Number-Model-Beispiel");
        SpinnerNumberModel model = new SpinnerNumberModel(7.5, 2, 8.5, 0.4);
        JSpinner spinner = new JSpinner(model);
        spinner.addChangeListener(e -> System.out.println(spinner.getValue()));
        frame.add(spinner);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new SpinnerNumberModelBsp());
    }
}

SpinnerListModel

Soll die Auswahl des JSpinner zwischen anderen als numerischen Objekten erfolgen, so kann ein SpinnerListModel übergeben werden, dessen Inhalt durch ein Array oder eine List definiert werden kann.
Das zweite Beispiel demonstriert dies nebst der Möglichkeit, dessen Daten zu wechseln. Zunächst werden zwei String-Arrays deklariert. Sie dienen als Datenquellen des Models. Eines der beiden Arrays wird dem Konstruktor des SpinnerListModel bei dessen Erzeugung als Parameter übergeben. Das Model-Objekt wiederum dient als Argument bei der Initialisierung des JSpinner.

Mittels eines BorderLayout werden JSpinner und ein JButton nebeneinander auf dem Fenster platziert. Ein ActionListener ruft bei Betätigung des Buttons die Methode changeList() auf. Sie dient dem Wechsel der Datenbasis des Models und übergibt diesem das jeweils andere Array. Der angezeigte Inhalt des JSpinner kann so gewechselt werden.
Innerhalb der Methode wird abgefragt welcher Inhalt durch das Model aktuell geladen ist. Er wird dann gegen das jeweils andere Array getauscht. Hierbei muss beachtet werden, dass die Klasse keine direkte Methode zum Laden eines Arrays hat. Dies kann nur in Form einer List erfolgen, sodass das Array erst entsprechend umgewandelt werden muss.

public class SpinnerBsp {

    String[] deClassics = {  "Megola", "Standard", "D-Rad", "NSU", "Horex" };
    String[] itClassics = { "Moto Guzzi", "Gilera", "Moto Morini", "Aermacchi",
            "MV Agusta" };

    public SpinnerBsp() {
        init();
    }

    public void init() {
        JFrame frame = new JFrame();
        SpinnerListModel model = new SpinnerListModel(deClassics);
        JSpinner spinner = new JSpinner(model);
        spinner.setPreferredSize(new Dimension(120, 28));
        JPanel panel = new JPanel(new BorderLayout());
        JButton butt = new JButton("wechseln");
        butt.addActionListener(e -> changeList(frame, spinner));
        panel.add(spinner, BorderLayout.WEST);
        panel.add(butt, BorderLayout.EAST);
        frame.add(panel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("JSpinner-Beispiel");
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void changeList(JFrame frame, JSpinner spinner) {
        SpinnerListModel mod = (SpinnerListModel) spinner.getModel();
        if (mod.getList().equals(Arrays.asList(deClassics))) {
            mod.setList(Arrays.asList(itClassics));
        } else {
            mod.setList(Arrays.asList(deClassics));
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new SpinnerBsp().init();
            }
        });
    }
}

JFrame-Fenster mit JSpinner und JButton

Ein rotierendes ListModel

Werden der erste oder letzte Spinnereintrag angezeigt, so bleibt ein Weiter- oder Zurückblättern in der Liste wirkungslos. Um eine rotierende Anzeige zu erzeugen, also vom letzten wieder zum ersten Eintrag und umgekehrt blättern zu können, müssen die Klasse SpinnerListModel erweitert und deren Methoden getNextValue() und getPreviousValue() überschrieben werden.

public class SpinnerClassicsModel extends SpinnerListModel {
    public SpinnerClassicsModel(Object[] o) {
        super(o);
    }

    @Override
    public Object getNextValue() {
        int index = this.getList().indexOf(this.getValue());
        return (index >= (this.getList().size() - 1)) ? this.getList().get(0)
                : this.getList().get(index + 1);
    }

    @Override
    public Object getPreviousValue() {
        int index = this.getList().indexOf(this.getValue());
        return (index <= 0) ? this.getList().get(this.getList().size() - 1)
                : this.getList().get(index - 1);
    }
}

Hier werden die Indices der jeweils angezeigten Inhalte ermittelt und der letzte mit dem ersten, bzw. der erste mit dem letzten Eintrag verknüpft.

SpinnerDateModel

Ein SpinnerDateModel dient zur Auswahl von Datums- und Zeitobjekten. Als Standard werden Datum und Zeit der aktuellen Systemzeit in Form der gesetzten Gebietsvariablen verwendet.

JFrame-Fenster mit JSpinner

Wird ein eigenes SpinnerDateModel implementiert, so werden - ähnlich dem SpinnerNumberModel - vier Parameter, ein Startwert, ein Minimal- und Maximalwert, sowie die Schrittweite übergeben. Die Werte sind hier jedoch in den Eigenschaften value, start, end und calendarField gespeichert und können durch entsprechende Getter- und Setter-Methoden manipuliert werden. Minimal- und Maximalwerte können null sein, um keine Unter- und Obergrenze zu setzen. Die in calendarField abgelegte Schrittweite muss den folgenden Konstanten der Calendar-Klasse entsprechen:

Das Verhalten eines mit SpinnerDateModel belegten JSpinner weist einige Besonderheiten auf und wird aus diesem Grund zusätzlich im Artikel JSpinner zur Datumsauswahl behandelt.

Quellen

https://docs.oracle.com/javase/tutorial/uiswing/components/spinner.html