Руководство JavaFX Spinner

1- JavaFX Spinner

Spinner похожи на  ComboBox или  List, он позволяет пользователю выбрать из набора значений. Похоже на ComboBox можно редактировать, Spinner так же позволяет пользователю добавлять значения. В отличии от  ComboBox, Spinner не имеет выпадающий список, не отображает возможный спискок, в одно время он отображает только настоящее значение. Обычно используется вместо ComboBox или  List когда имеется большой набор возможных значений.
Структура Spinner:

2- Пример с Spinner

Простой пример ниже изображает  Spinner с числовыми значениями.
SpinnerDemo.java
package org.o7planning.javafx.spinner;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class SpinnerDemo extends Application {

    @Override
    public void start(Stage stage) {

        Label label = new Label("Select Level:");
        final Spinner<Integer> spinner = new Spinner<Integer>();

        final int initialValue = 3;

        // Value factory.
        SpinnerValueFactory<Integer> valueFactory = //
                new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 5, initialValue);

        spinner.setValueFactory(valueFactory);

        FlowPane root = new FlowPane();
        root.setHgap(10);
        root.setVgap(10);
        root.setPadding(new Insets(10));

        root.getChildren().addAll(label, spinner);

        Scene scene = new Scene(root, 400, 200);

        stage.setTitle("JavaFX Spinner (o7planning.org)");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }

}

Пример 2:

SpinnerDemo2.java
package org.o7planning.javafx.spinner;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class SpinnerDemo2 extends Application {

   @Override
   public void start(Stage stage) {

       Label label = new Label("Select Month:");

       ObservableList<String> months = FXCollections.observableArrayList(//
               "January", "February", "March", "April", //
               "May", "June", "July", "August", //
               "September", "October", "November", "December");

       final Spinner<String> spinner = new Spinner<String>();

       // Value factory.
       SpinnerValueFactory<String> valueFactory = //
               new SpinnerValueFactory.ListSpinnerValueFactory<String>(months);
     
       // Default value
       valueFactory.setValue("February");

       spinner.setValueFactory(valueFactory);

       FlowPane root = new FlowPane();
       root.setHgap(10);
       root.setVgap(10);
       root.setPadding(new Insets(10));

       root.getChildren().addAll(label, spinner);

       Scene scene = new Scene(root, 400, 200);

       stage.setTitle("JavaFX Spinner (o7planning.org)");
       stage.setScene(scene);
       stage.show();
   }

   public static void main(String[] args) {
       Application.launch(args);
   }

}

3- Spinner и Styles

JavaFX Spinner может настроить позицию и направление кнопке стрелки, если не настроено, по умолчанию будет направо и вертикальное направление.
SpinnerStyleDemo.java
package org.o7planning.javafx.spinner;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Spinner;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class SpinnerStyleDemo extends Application {

    @Override
    public void start(Stage stage) {

        String[] styleClasses = new String[] { "", // Default.
                Spinner.STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL, //
                Spinner.STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL, //
                Spinner.STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL, //
                Spinner.STYLE_CLASS_SPLIT_ARROWS_VERTICAL, //
                Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL

        };

        FlowPane root = new FlowPane();
        root.setHgap(10);
        root.setVgap(10);
        root.setPadding(new Insets(10));

        for (String styleClass : styleClasses) {
            Spinner<Integer> spinner = new Spinner<Integer>(1, 20, 10);
            spinner.getStyleClass().add(styleClass);
            root.getChildren().add(spinner);
        }

        Scene scene = new Scene(root, 400, 200);

        stage.setTitle("JavaFX Spinner (o7planning.org)");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }

}

4- Spinner и ChangeEvent

Обычно  Spinner используются для отображения числовых значений. Н вы так же можете использовать для отображения любого Object. В примере ниже  Spinner содержит набор объектов  Language, и обрабатывает событие когда значение Spinner меняется.
SpinnerChangeEventDemo.java
package org.o7planning.javafx.spinner;

import java.util.ArrayList;
import java.util.List;

import org.o7planning.javafx.model.Language;

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class SpinnerChangeEventDemo extends Application {

    @Override
    public void start(Stage stage) {

        Language vietnamese = new Language("vi", "Vietnamese", "Xin Chao");
        Language english = new Language("en", "English", "Hello");
        Language russian = new Language("ru", "Russian", "привет");

        List<Language> languages = new ArrayList<Language>();
        languages.add(vietnamese);
        languages.add(english);
        languages.add(russian);

        //
        
        Label label = new Label("Select Language:");
        final Spinner<Language> spinner = new Spinner<Language>();

        Label labelMessage = new Label("?");

        // Value factory.
        SpinnerValueFactory<Language> valueFactory = //
                new SpinnerValueFactory<Language>() {

                    @Override
                    public void decrement(int steps) {
                        Language current = this.getValue();
                        int idx = languages.indexOf(current);
                        int newIdx = (languages.size() + idx - steps) % languages.size();
                        Language newLang = languages.get(newIdx);
                        this.setValue(newLang);
                    }

                    @Override
                    public void increment(int steps) {
                        Language current = this.getValue();
                        int idx = languages.indexOf(current);
                        int newIdx = (idx + steps) % languages.size();
                        Language newLang = languages.get(newIdx);
                        this.setValue(newLang);
                    }

                };

        // Default value for Spinner
        valueFactory.setValue(vietnamese);

        spinner.setValueFactory(valueFactory);

        // When spinner change value.
        spinner.valueProperty().addListener(new ChangeListener<Language>() {

            @Override
            public void changed(ObservableValue<? extends Language> observable,//
                    Language oldValue, Language newValue) {
                
                labelMessage.setText("Greeting: "+ newValue.getGreeting());

            }
        });

        FlowPane root = new FlowPane();
        root.setHgap(10);
        root.setVgap(10);
        root.setPadding(new Insets(10));

        root.getChildren().addAll(label, spinner, labelMessage);

        Scene scene = new Scene(root, 400, 200);

        stage.setTitle("JavaFX Spinner (o7planning.org)");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }

}
Language.java
package org.o7planning.javafx.model;

public class Language {

    private String code;
    private String name;
    private String greeting;

    public Language() {

    }

    public Language(String code, String name) {
        this.code = code;
        this.name = name;
    }

    public Language(String code, String name, String greeting) {
        this.code = code;
        this.name = name;
        this.greeting = greeting;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGreeting() {
        return greeting;
    }

    public void setGreeting(String greeting) {
        this.greeting = greeting;
    }

    @Override
    public String toString() {
        return this.name;
    }

}

5- Spinner можно редактировать

Пользователь может добавить новое значение для  Spinner, в следующем примере пользователь вводит значение и нажимает Enter, значение добавится в набор готовых значений  Spinner.
SpinnerEditDemo.java
package org.o7planning.javafx.spinner;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.SpinnerValueFactory.ListSpinnerValueFactory;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;

public class SpinnerEditDemo extends Application {

   @Override
   public void start(Stage stage) {

       Label label = new Label("Select Level:");
       final Spinner<Integer> spinner = new Spinner<Integer>();

       // Editable.
       spinner.setEditable(true);

       // Item List.
       ObservableList<Integer> items = FXCollections.observableArrayList(1, 2, 3);

       // Value Factory:
       SpinnerValueFactory<Integer> valueFactory = //
               new SpinnerValueFactory.ListSpinnerValueFactory<>(items);

       // The converter to convert between text and item object.
       MyConverter converter = new MyConverter();
       valueFactory.setConverter(converter);

       spinner.setValueFactory(valueFactory);

       spinner.getEditor().setOnAction(new EventHandler<ActionEvent>() {

           @Override
           public void handle(ActionEvent event) {
               String text = spinner.getEditor().getText();
               SpinnerValueFactory.ListSpinnerValueFactory<Integer>//
               valueFactory = (ListSpinnerValueFactory<Integer>) spinner.getValueFactory();

               StringConverter<Integer> converter = valueFactory.getConverter();
               Integer enterValue = converter.fromString(text);

               // If the list does not contains 'enterValue'.
               if (!valueFactory.getItems().contains(enterValue)) {
                   // Add new item to list
                   valueFactory.getItems().add(enterValue);
                   // Set to current
                   valueFactory.setValue(enterValue);
               } else {
                   // Set to current
                   valueFactory.setValue(enterValue);
               }

           }
       });

       //
       FlowPane root = new FlowPane();
       root.setHgap(10);
       root.setVgap(10);
       root.setPadding(new Insets(10));

       root.getChildren().addAll(label, spinner);

       Scene scene = new Scene(root, 400, 200);

       stage.setTitle("JavaFX Spinner (o7planning.org)");
       stage.setScene(scene);
       stage.show();
   }

   class MyConverter extends StringConverter<Integer> {

       @Override
       public String toString(Integer object) {
           return object + "";
       }

       @Override
       public Integer fromString(String string) {
           return Integer.parseInt(string);
       }

   }

   public static void main(String[] args) {
       Application.launch(args);
   }

}