/*
* Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.control;
import com.sun.javafx.scene.control.FakeFocusTextField;
import javafx.beans.property.StringProperty;
import javafx.scene.control.skin.SpinnerSkin;
import javafx.beans.NamedArg;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.WritableValue;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleAttribute;
import javafx.scene.AccessibleRole;
import javafx.util.Duration;
import javafx.util.StringConverter;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.temporal.TemporalUnit;
import javafx.css.CssMetaData;
import javafx.css.converter.DurationConverter;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.css.SimpleStyleableObjectProperty;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
A single line text field that lets the user select a number or an object value from an ordered sequence. Spinners typically provide a pair of tiny arrow buttons for stepping through the elements of the sequence. The keyboard up/down arrow keys also cycle through the elements. The user may also be allowed to type a (legal) value directly into the spinner. Although combo boxes provide similar functionality, spinners are sometimes preferred because they don't require a drop down list that can obscure important data, and also because they allow for features such as wrapping
and simpler specification of 'infinite' data models (the SpinnerValueFactory
, rather than using a ObservableList
data model like many other JavaFX UI controls. A Spinner's sequence value is defined by its SpinnerValueFactory
. The value factory can be specified as a constructor argument and changed with the value factory property
. SpinnerValueFactory classes for some common types are provided with JavaFX, including:
A Spinner has a TextField child component that is responsible for displaying and potentially changing the current value
of the Spinner, which is called the editor
. By default the Spinner is non-editable, but input can be accepted if the editable property
is set to true. The Spinner editor stays in sync with the value factory by listening for changes to the value property
of the value factory. If the user has changed the value displayed in the editor it is possible for the Spinner value
to differ from that of the editor. To make sure the model has the same value as the editor, the user must commit the edit using the Enter key.
Type parameters: - <T> – The type of all values that can be iterated through in the Spinner.
Common types include Integer and String.
See Also: Since: JavaFX 8u40
/**
* A single line text field that lets the user select a number or an object
* value from an ordered sequence. Spinners typically provide a pair of tiny
* arrow buttons for stepping through the elements of the sequence. The keyboard
* up/down arrow keys also cycle through the elements. The user may also be
* allowed to type a (legal) value directly into the spinner. Although combo
* boxes provide similar functionality, spinners are sometimes preferred because
* they don't require a drop down list that can obscure important data, and also
* because they allow for features such as
* {@link SpinnerValueFactory#wrapAroundProperty() wrapping}
* and simpler specification of 'infinite' data models (the
* {@link SpinnerValueFactory SpinnerValueFactory}, rather than using a
* {@link javafx.collections.ObservableList ObservableList} data model like many
* other JavaFX UI controls.
*
* <p>A Spinner's sequence value is defined by its
* {@link SpinnerValueFactory SpinnerValueFactory}. The value factory
* can be specified as a constructor argument and changed with the
* {@link #valueFactoryProperty() value factory property}. SpinnerValueFactory
* classes for some common types are provided with JavaFX, including:
*
* <ul>
* <li>{@link SpinnerValueFactory.IntegerSpinnerValueFactory}</li>
* <li>{@link SpinnerValueFactory.DoubleSpinnerValueFactory}</li>
* <li>{@link SpinnerValueFactory.ListSpinnerValueFactory}</li>
* </ul>
*
* <p>A Spinner has a TextField child component that is responsible for displaying
* and potentially changing the current {@link #valueProperty() value} of the
* Spinner, which is called the {@link #editorProperty() editor}. By default the
* Spinner is non-editable, but input can be accepted if the
* {@link #editableProperty() editable property} is set to true. The Spinner
* editor stays in sync with the value factory by listening for changes to the
* {@link SpinnerValueFactory#valueProperty() value property} of the value factory.
* If the user has changed the value displayed in the editor it is possible for
* the Spinner {@link #valueProperty() value} to differ from that of the editor.
* To make sure the model has the same value as the editor, the user must commit
* the edit using the Enter key.
*
* @see SpinnerValueFactory
* @param <T> The type of all values that can be iterated through in the Spinner.
* Common types include Integer and String.
* @since JavaFX 8u40
*/
public class Spinner<T> extends Control {
// default style class, puts arrows on right, stacked vertically
private static final String DEFAULT_STYLE_CLASS = "spinner";
The arrows are placed on the right of the Spinner, pointing horizontally (i.e. left and right). /** The arrows are placed on the right of the Spinner, pointing horizontally (i.e. left and right). */
public static final String STYLE_CLASS_ARROWS_ON_RIGHT_HORIZONTAL = "arrows-on-right-horizontal";
The arrows are placed on the left of the Spinner, pointing vertically (i.e. up and down). /** The arrows are placed on the left of the Spinner, pointing vertically (i.e. up and down). */
public static final String STYLE_CLASS_ARROWS_ON_LEFT_VERTICAL = "arrows-on-left-vertical";
The arrows are placed on the left of the Spinner, pointing horizontally (i.e. left and right). /** The arrows are placed on the left of the Spinner, pointing horizontally (i.e. left and right). */
public static final String STYLE_CLASS_ARROWS_ON_LEFT_HORIZONTAL = "arrows-on-left-horizontal";
The arrows are placed above and beneath the spinner, stretching to take the entire width. /** The arrows are placed above and beneath the spinner, stretching to take the entire width. */
public static final String STYLE_CLASS_SPLIT_ARROWS_VERTICAL = "split-arrows-vertical";
The decrement arrow is placed on the left of the Spinner, and the increment on the right. /** The decrement arrow is placed on the left of the Spinner, and the increment on the right. */
public static final String STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL = "split-arrows-horizontal";
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
Constructs a default Spinner instance, with the default 'spinner' style
class and a non-editable editor.
/**
* Constructs a default Spinner instance, with the default 'spinner' style
* class and a non-editable editor.
*/
public Spinner() {
getStyleClass().add(DEFAULT_STYLE_CLASS);
setAccessibleRole(AccessibleRole.SPINNER);
getEditor().setOnAction(action -> {
commitValue();
});
getEditor().editableProperty().bind(editableProperty());
value.addListener((o, oldValue, newValue) -> setText(newValue));
// Fix for RT-29885
getProperties().addListener((MapChangeListener<Object, Object>) change -> {
if (change.wasAdded()) {
if (change.getKey() == "FOCUSED") {
setFocused((Boolean)change.getValueAdded());
getProperties().remove("FOCUSED");
}
}
});
// End of fix for RT-29885
focusedProperty().addListener(o -> {
if (!isFocused()) {
commitValue();
}
});
}
Creates a Spinner instance with the value factory
set to be an instance of IntegerSpinnerValueFactory
. Note that if this constructor is called, the only valid generic type for the Spinner instance is Integer, i.e. Spinner<Integer>. Params: - min – The minimum allowed integer value for the Spinner.
- max – The maximum allowed integer value for the Spinner.
- initialValue – The value of the Spinner when first instantiated, must
be within the bounds of the min and max arguments, or
else the min value will be used.
/**
* Creates a Spinner instance with the
* {@link #valueFactoryProperty() value factory} set to be an instance
* of {@link SpinnerValueFactory.IntegerSpinnerValueFactory}. Note that
* if this constructor is called, the only valid generic type for the
* Spinner instance is Integer, i.e. Spinner<Integer>.
*
* @param min The minimum allowed integer value for the Spinner.
* @param max The maximum allowed integer value for the Spinner.
* @param initialValue The value of the Spinner when first instantiated, must
* be within the bounds of the min and max arguments, or
* else the min value will be used.
*/
public Spinner(@NamedArg("min") int min,
@NamedArg("max") int max,
@NamedArg("initialValue") int initialValue) {
// This only works if the Spinner is of type Integer
this((SpinnerValueFactory<T>)new SpinnerValueFactory.IntegerSpinnerValueFactory(min, max, initialValue));
}
Creates a Spinner instance with the value factory
set to be an instance of IntegerSpinnerValueFactory
. Note that if this constructor is called, the only valid generic type for the Spinner instance is Integer, i.e. Spinner<Integer>. Params: - min – The minimum allowed integer value for the Spinner.
- max – The maximum allowed integer value for the Spinner.
- initialValue – The value of the Spinner when first instantiated, must
be within the bounds of the min and max arguments, or
else the min value will be used.
- amountToStepBy – The amount to increment or decrement by, per step.
/**
* Creates a Spinner instance with the
* {@link #valueFactoryProperty() value factory} set to be an instance
* of {@link SpinnerValueFactory.IntegerSpinnerValueFactory}. Note that
* if this constructor is called, the only valid generic type for the
* Spinner instance is Integer, i.e. Spinner<Integer>.
*
* @param min The minimum allowed integer value for the Spinner.
* @param max The maximum allowed integer value for the Spinner.
* @param initialValue The value of the Spinner when first instantiated, must
* be within the bounds of the min and max arguments, or
* else the min value will be used.
* @param amountToStepBy The amount to increment or decrement by, per step.
*/
public Spinner(@NamedArg("min") int min,
@NamedArg("max") int max,
@NamedArg("initialValue") int initialValue,
@NamedArg("amountToStepBy") int amountToStepBy) {
// This only works if the Spinner is of type Integer
this((SpinnerValueFactory<T>)new SpinnerValueFactory.IntegerSpinnerValueFactory(min, max, initialValue, amountToStepBy));
}
Creates a Spinner instance with the value factory
set to be an instance of DoubleSpinnerValueFactory
. Note that if this constructor is called, the only valid generic type for the Spinner instance is Double, i.e. Spinner<Double>. Params: - min – The minimum allowed double value for the Spinner.
- max – The maximum allowed double value for the Spinner.
- initialValue – The value of the Spinner when first instantiated, must
be within the bounds of the min and max arguments, or
else the min value will be used.
/**
* Creates a Spinner instance with the
* {@link #valueFactoryProperty() value factory} set to be an instance
* of {@link SpinnerValueFactory.DoubleSpinnerValueFactory}. Note that
* if this constructor is called, the only valid generic type for the
* Spinner instance is Double, i.e. Spinner<Double>.
*
* @param min The minimum allowed double value for the Spinner.
* @param max The maximum allowed double value for the Spinner.
* @param initialValue The value of the Spinner when first instantiated, must
* be within the bounds of the min and max arguments, or
* else the min value will be used.
*/
public Spinner(@NamedArg("min") double min,
@NamedArg("max") double max,
@NamedArg("initialValue") double initialValue) {
// This only works if the Spinner is of type Double
this((SpinnerValueFactory<T>)new SpinnerValueFactory.DoubleSpinnerValueFactory(min, max, initialValue));
}
Creates a Spinner instance with the value factory
set to be an instance of DoubleSpinnerValueFactory
. Note that if this constructor is called, the only valid generic type for the Spinner instance is Double, i.e. Spinner<Double>. Params: - min – The minimum allowed double value for the Spinner.
- max – The maximum allowed double value for the Spinner.
- initialValue – The value of the Spinner when first instantiated, must
be within the bounds of the min and max arguments, or
else the min value will be used.
- amountToStepBy – The amount to increment or decrement by, per step.
/**
* Creates a Spinner instance with the
* {@link #valueFactoryProperty() value factory} set to be an instance
* of {@link SpinnerValueFactory.DoubleSpinnerValueFactory}. Note that
* if this constructor is called, the only valid generic type for the
* Spinner instance is Double, i.e. Spinner<Double>.
*
* @param min The minimum allowed double value for the Spinner.
* @param max The maximum allowed double value for the Spinner.
* @param initialValue The value of the Spinner when first instantiated, must
* be within the bounds of the min and max arguments, or
* else the min value will be used.
* @param amountToStepBy The amount to increment or decrement by, per step.
*/
public Spinner(@NamedArg("min") double min,
@NamedArg("max") double max,
@NamedArg("initialValue") double initialValue,
@NamedArg("amountToStepBy") double amountToStepBy) {
// This only works if the Spinner is of type Double
this((SpinnerValueFactory<T>)new SpinnerValueFactory.DoubleSpinnerValueFactory(min, max, initialValue, amountToStepBy));
}
Creates a Spinner instance with the value factory
set to be an instance of LocalDateSpinnerValueFactory
. Note that if this constructor is called, the only valid generic type for the Spinner instance is LocalDate, i.e. Spinner<LocalDate>. Params: - min – The minimum allowed LocalDate value for the Spinner.
- max – The maximum allowed LocalDate value for the Spinner.
- initialValue – The value of the Spinner when first instantiated, must
be within the bounds of the min and max arguments, or
else the min value will be used.
/**
* Creates a Spinner instance with the
* {@link #valueFactoryProperty() value factory} set to be an instance
* of {@link SpinnerValueFactory.LocalDateSpinnerValueFactory}. Note that
* if this constructor is called, the only valid generic type for the
* Spinner instance is LocalDate, i.e. Spinner<LocalDate>.
*
* @param min The minimum allowed LocalDate value for the Spinner.
* @param max The maximum allowed LocalDate value for the Spinner.
* @param initialValue The value of the Spinner when first instantiated, must
* be within the bounds of the min and max arguments, or
* else the min value will be used.
*/
Spinner(@NamedArg("min") LocalDate min,
@NamedArg("max") LocalDate max,
@NamedArg("initialValue") LocalDate initialValue) {
// This only works if the Spinner is of type LocalDate
this((SpinnerValueFactory<T>)new SpinnerValueFactory.LocalDateSpinnerValueFactory(min, max, initialValue));
}
Creates a Spinner instance with the value factory
set to be an instance of LocalDateSpinnerValueFactory
. Note that if this constructor is called, the only valid generic type for the Spinner instance is LocalDate, i.e. Spinner<LocalDate>. Params: - min – The minimum allowed LocalDate value for the Spinner.
- max – The maximum allowed LocalDate value for the Spinner.
- initialValue – The value of the Spinner when first instantiated, must
be within the bounds of the min and max arguments, or
else the min value will be used.
- amountToStepBy – The amount to increment or decrement by, per step.
- temporalUnit – The size of each step (e.g. day, week, month, year, etc).
/**
* Creates a Spinner instance with the
* {@link #valueFactoryProperty() value factory} set to be an instance
* of {@link SpinnerValueFactory.LocalDateSpinnerValueFactory}. Note that
* if this constructor is called, the only valid generic type for the
* Spinner instance is LocalDate, i.e. Spinner<LocalDate>.
*
* @param min The minimum allowed LocalDate value for the Spinner.
* @param max The maximum allowed LocalDate value for the Spinner.
* @param initialValue The value of the Spinner when first instantiated, must
* be within the bounds of the min and max arguments, or
* else the min value will be used.
* @param amountToStepBy The amount to increment or decrement by, per step.
* @param temporalUnit The size of each step (e.g. day, week, month, year, etc).
*/
Spinner(@NamedArg("min") LocalDate min,
@NamedArg("max") LocalDate max,
@NamedArg("initialValue") LocalDate initialValue,
@NamedArg("amountToStepBy") long amountToStepBy,
@NamedArg("temporalUnit") TemporalUnit temporalUnit) {
// This only works if the Spinner is of type LocalDate
this((SpinnerValueFactory<T>)new SpinnerValueFactory.LocalDateSpinnerValueFactory(min, max, initialValue, amountToStepBy, temporalUnit));
}
Creates a Spinner instance with the value factory
set to be an instance of LocalTimeSpinnerValueFactory
. Note that if this constructor is called, the only valid generic type for the Spinner instance is LocalTime, i.e. Spinner<LocalTime>. Params: - min – The minimum allowed LocalTime value for the Spinner.
- max – The maximum allowed LocalTime value for the Spinner.
- initialValue – The value of the Spinner when first instantiated, must
be within the bounds of the min and max arguments, or
else the min value will be used.
/**
* Creates a Spinner instance with the
* {@link #valueFactoryProperty() value factory} set to be an instance
* of {@link SpinnerValueFactory.LocalTimeSpinnerValueFactory}. Note that
* if this constructor is called, the only valid generic type for the
* Spinner instance is LocalTime, i.e. Spinner<LocalTime>.
*
* @param min The minimum allowed LocalTime value for the Spinner.
* @param max The maximum allowed LocalTime value for the Spinner.
* @param initialValue The value of the Spinner when first instantiated, must
* be within the bounds of the min and max arguments, or
* else the min value will be used.
*/
Spinner(@NamedArg("min") LocalTime min,
@NamedArg("max") LocalTime max,
@NamedArg("initialValue") LocalTime initialValue) {
// This only works if the Spinner is of type LocalTime
this((SpinnerValueFactory<T>)new SpinnerValueFactory.LocalTimeSpinnerValueFactory(min, max, initialValue));
}
Creates a Spinner instance with the value factory
set to be an instance of LocalTimeSpinnerValueFactory
. Note that if this constructor is called, the only valid generic type for the Spinner instance is LocalTime, i.e. Spinner<LocalTime>. Params: - min – The minimum allowed LocalTime value for the Spinner.
- max – The maximum allowed LocalTime value for the Spinner.
- initialValue – The value of the Spinner when first instantiated, must
be within the bounds of the min and max arguments, or
else the min value will be used.
- amountToStepBy – The amount to increment or decrement by, per step.
- temporalUnit – The size of each step (e.g. hour, minute, second, etc).
/**
* Creates a Spinner instance with the
* {@link #valueFactoryProperty() value factory} set to be an instance
* of {@link SpinnerValueFactory.LocalTimeSpinnerValueFactory}. Note that
* if this constructor is called, the only valid generic type for the
* Spinner instance is LocalTime, i.e. Spinner<LocalTime>.
*
* @param min The minimum allowed LocalTime value for the Spinner.
* @param max The maximum allowed LocalTime value for the Spinner.
* @param initialValue The value of the Spinner when first instantiated, must
* be within the bounds of the min and max arguments, or
* else the min value will be used.
* @param amountToStepBy The amount to increment or decrement by, per step.
* @param temporalUnit The size of each step (e.g. hour, minute, second, etc).
*/
Spinner(@NamedArg("min") LocalTime min,
@NamedArg("max") LocalTime max,
@NamedArg("initialValue") LocalTime initialValue,
@NamedArg("amountToStepBy") long amountToStepBy,
@NamedArg("temporalUnit") TemporalUnit temporalUnit) {
// This only works if the Spinner is of type LocalTime
this((SpinnerValueFactory<T>)new SpinnerValueFactory.LocalTimeSpinnerValueFactory(min, max, initialValue, amountToStepBy, temporalUnit));
}
Creates a Spinner instance with the value factory
set to be an instance of ListSpinnerValueFactory
. The Spinner value property
will be set to the first element of the list, if an element exists, or null otherwise. Params: - items – A list of items that will be stepped through in the Spinner.
/**
* Creates a Spinner instance with the
* {@link #valueFactoryProperty() value factory} set to be an instance
* of {@link SpinnerValueFactory.ListSpinnerValueFactory}. The
* Spinner {@link #valueProperty() value property} will be set to the first
* element of the list, if an element exists, or null otherwise.
*
* @param items A list of items that will be stepped through in the Spinner.
*/
public Spinner(@NamedArg("items") ObservableList<T> items) {
this(new SpinnerValueFactory.ListSpinnerValueFactory<T>(items));
}
Creates a Spinner instance with the given value factory set.
Params: - valueFactory – The
value factory
to use.
/**
* Creates a Spinner instance with the given value factory set.
*
* @param valueFactory The {@link #valueFactoryProperty() value factory} to use.
*/
public Spinner(@NamedArg("valueFactory") SpinnerValueFactory<T> valueFactory) {
this();
setValueFactory(valueFactory);
}
/***************************************************************************
* *
* Public API *
* *
**************************************************************************/
Attempts to increment the value factory
by one step, by calling the SpinnerValueFactory.increment(int)
method with an argument of one. If the value factory is null, an IllegalStateException is thrown. Throws: - IllegalStateException – if the value factory returned by calling
getValueFactory()
is null.
/**
* Attempts to increment the {@link #valueFactoryProperty() value factory}
* by one step, by calling the {@link SpinnerValueFactory#increment(int)}
* method with an argument of one. If the value factory is null, an
* IllegalStateException is thrown.
*
* @throws IllegalStateException if the value factory returned by
* calling {@link #getValueFactory()} is null.
*/
public void increment() {
increment(1);
}
Attempts to increment the value factory
by the given number of steps, by calling the SpinnerValueFactory.increment(int)
method and forwarding the steps argument to it. If the value factory is null, an IllegalStateException is thrown. Params: - steps – The number of increments that should be performed on the value.
Throws: - IllegalStateException – if the value factory returned by calling
getValueFactory()
is null.
/**
* Attempts to increment the {@link #valueFactoryProperty() value factory}
* by the given number of steps, by calling the
* {@link SpinnerValueFactory#increment(int)}
* method and forwarding the steps argument to it. If the value factory is
* null, an IllegalStateException is thrown.
*
* @param steps The number of increments that should be performed on the value.
* @throws IllegalStateException if the value factory returned by
* calling {@link #getValueFactory()} is null.
*/
public void increment(int steps) {
SpinnerValueFactory<T> valueFactory = getValueFactory();
if (valueFactory == null) {
throw new IllegalStateException("Can't increment Spinner with a null SpinnerValueFactory");
}
commitValue();
valueFactory.increment(steps);
}
Attempts to decrement the value factory
by one step, by calling the SpinnerValueFactory.decrement(int)
method with an argument of one. If the value factory is null, an IllegalStateException is thrown. Throws: - IllegalStateException – if the value factory returned by calling
getValueFactory()
is null.
/**
* Attempts to decrement the {@link #valueFactoryProperty() value factory}
* by one step, by calling the {@link SpinnerValueFactory#decrement(int)}
* method with an argument of one. If the value factory is null, an
* IllegalStateException is thrown.
*
* @throws IllegalStateException if the value factory returned by
* calling {@link #getValueFactory()} is null.
*/
public void decrement() {
decrement(1);
}
Attempts to decrement the value factory
by the given number of steps, by calling the SpinnerValueFactory.decrement(int)
method and forwarding the steps argument to it. If the value factory is null, an IllegalStateException is thrown. Params: - steps – The number of decrements that should be performed on the value.
Throws: - IllegalStateException – if the value factory returned by calling
getValueFactory()
is null.
/**
* Attempts to decrement the {@link #valueFactoryProperty() value factory}
* by the given number of steps, by calling the
* {@link SpinnerValueFactory#decrement(int)}
* method and forwarding the steps argument to it. If the value factory is
* null, an IllegalStateException is thrown.
*
* @param steps The number of decrements that should be performed on the value.
* @throws IllegalStateException if the value factory returned by
* calling {@link #getValueFactory()} is null.
*/
public void decrement(int steps) {
SpinnerValueFactory<T> valueFactory = getValueFactory();
if (valueFactory == null) {
throw new IllegalStateException("Can't decrement Spinner with a null SpinnerValueFactory");
}
commitValue();
valueFactory.decrement(steps);
}
{@inheritDoc} /** {@inheritDoc} */
@Override protected Skin<?> createDefaultSkin() {
return new SpinnerSkin<>(this);
}
If the Spinner is editable
, calling this method will attempt to commit the current text and convert it to a value
. Since: 9
/**
* If the Spinner is {@link #editableProperty() editable}, calling this method will attempt to
* commit the current text and convert it to a {@link #valueProperty() value}.
* @since 9
*/
public final void commitValue() {
if (!isEditable()) return;
String text = getEditor().getText();
SpinnerValueFactory<T> valueFactory = getValueFactory();
if (valueFactory != null) {
StringConverter<T> converter = valueFactory.getConverter();
if (converter != null) {
T value = converter.fromString(text);
valueFactory.setValue(value);
}
}
}
If the Spinner is editable
, calling this method will attempt to replace the editor text with the last committed value
. Since: 9
/**
* If the Spinner is {@link #editableProperty() editable}, calling this method will attempt to
* replace the editor text with the last committed {@link #valueProperty() value}.
* @since 9
*/
public final void cancelEdit() {
if (!isEditable()) return;
final T committedValue = getValue();
SpinnerValueFactory<T> valueFactory = getValueFactory();
if (valueFactory != null) {
StringConverter<T> converter = valueFactory.getConverter();
if (converter != null) {
String valueString = converter.toString(committedValue);
getEditor().setText(valueString);
}
}
}
/***************************************************************************
* *
* Properties *
* *
**************************************************************************/
// --- value (a read only, bound property to the value factory value property)
The value property on Spinner is a read-only property, as it is bound to the SpinnerValueFactory value property
. Should the value factory
change, this value property will be unbound from the old value factory and bound to the new one. If developers wish to modify the value property, they may do so with
code in the following form:
Object newValue = ...;
spinner.getValueFactory().setValue(newValue);
/**
* The value property on Spinner is a read-only property, as it is bound to
* the SpinnerValueFactory
* {@link SpinnerValueFactory#valueProperty() value property}. Should the
* {@link #valueFactoryProperty() value factory} change, this value property
* will be unbound from the old value factory and bound to the new one.
*
* <p>If developers wish to modify the value property, they may do so with
* code in the following form:
*
* <pre>
* {@code
* Object newValue = ...;
* spinner.getValueFactory().setValue(newValue);
* }</pre>
*/
private ReadOnlyObjectWrapper<T> value = new ReadOnlyObjectWrapper<T>(this, "value");
public final T getValue() {
return value.get();
}
public final ReadOnlyObjectProperty<T> valueProperty() {
return value;
}
// --- valueFactory
The value factory is the model behind the JavaFX Spinner control - without
a value factory installed a Spinner is unusable. It is the role of the
value factory to handle almost all aspects of the Spinner, including:
- Representing the current state of the
value
,
Incrementing
and decrementing
the value, with one or more steps per call,
Converting
text input from the user (via the Spinner editor
,
- Converting
objects to user-readable strings
for display on screen
/**
* The value factory is the model behind the JavaFX Spinner control - without
* a value factory installed a Spinner is unusable. It is the role of the
* value factory to handle almost all aspects of the Spinner, including:
*
* <ul>
* <li>Representing the current state of the {@link SpinnerValueFactory#valueProperty() value},</li>
* <li>{@link SpinnerValueFactory#increment(int) Incrementing}
* and {@link SpinnerValueFactory#decrement(int) decrementing} the
* value, with one or more steps per call,</li>
* <li>{@link SpinnerValueFactory#converterProperty() Converting} text input
* from the user (via the Spinner {@link #editorProperty() editor},</li>
* <li>Converting {@link SpinnerValueFactory#converterProperty() objects to user-readable strings}
* for display on screen</li>
* </ul>
*/
private ObjectProperty<SpinnerValueFactory<T>> valueFactory =
new SimpleObjectProperty<SpinnerValueFactory<T>>(this, "valueFactory") {
@Override protected void invalidated() {
value.unbind();
SpinnerValueFactory<T> newFactory = get();
if (newFactory != null) {
// this binding is what ensures the Spinner.valueProperty()
// properly represents the value in the value factory
value.bind(newFactory.valueProperty());
}
}
};
public final void setValueFactory(SpinnerValueFactory<T> value) {
valueFactory.setValue(value);
}
public final SpinnerValueFactory<T> getValueFactory() {
return valueFactory.get();
}
public final ObjectProperty<SpinnerValueFactory<T>> valueFactoryProperty() {
return valueFactory;
}
// --- editable
The editable property is used to specify whether user input is able to be typed into the Spinner editor
. If editable is true, user input will be received once the user types and presses the Enter key. At this point the input is passed to the SpinnerValueFactory converter
StringConverter.fromString(String)
method. The returned value from this call (of type T) is then sent to the SpinnerValueFactory.setValue(Object)
method. If the value is valid, it will remain as the value. If it is invalid, the value factory will need to react accordingly and back out this change. /**
* The editable property is used to specify whether user input is able to
* be typed into the Spinner {@link #editorProperty() editor}. If editable
* is true, user input will be received once the user types and presses
* the Enter key. At this point the input is passed to the
* SpinnerValueFactory {@link SpinnerValueFactory#converterProperty() converter}
* {@link javafx.util.StringConverter#fromString(String)} method.
* The returned value from this call (of type T) is then sent to the
* {@link SpinnerValueFactory#setValue(Object)} method. If the value
* is valid, it will remain as the value. If it is invalid, the value factory
* will need to react accordingly and back out this change.
*/
private BooleanProperty editable;
public final void setEditable(boolean value) {
editableProperty().set(value);
}
public final boolean isEditable() {
return editable == null ? true : editable.get();
}
public final BooleanProperty editableProperty() {
if (editable == null) {
editable = new SimpleBooleanProperty(this, "editable", false);
}
return editable;
}
// --- editor
The editor used by the Spinner control.
Returns: the editor property
/**
* The editor used by the Spinner control.
* @return the editor property
*/
public final ReadOnlyObjectProperty<TextField> editorProperty() {
if (editor == null) {
editor = new ReadOnlyObjectWrapper<>(this, "editor");
textField = new FakeFocusTextField();
textField.tooltipProperty().bind(tooltipProperty());
editor.set(textField);
}
return editor.getReadOnlyProperty();
}
private TextField textField;
private ReadOnlyObjectWrapper<TextField> editor;
public final TextField getEditor() {
return editorProperty().get();
}
// --- prompt text
The prompt text to display in the Spinner
, or null
if no prompt text is displayed. Returns: the prompt text property Since: 9
/**
* The prompt text to display in the {@code Spinner}, or
* {@code null} if no prompt text is displayed.
* @return the prompt text property
* @since 9
*/
public final StringProperty promptTextProperty() { return getEditor().promptTextProperty(); }
public final String getPromptText() { return getEditor().getPromptText(); }
public final void setPromptText(String value) { getEditor().setPromptText(value); }
private final ObjectProperty<Duration> initialDelay =
new SimpleStyleableObjectProperty<>(INITIAL_DELAY,
this, "initialDelay", new Duration(300));
The duration that the mouse has to be pressed on an arrow button before the next value steps. Successive step duration is set using repeat delay
. Returns: inital delay property Since: 11 @defaultValue 300ms
/**
* The duration that the mouse has to be pressed on an arrow button
* before the next value steps. Successive step duration is set using
* {@link #repeatDelayProperty() repeat delay}.
*
* @return inital delay property
* @since 11
* @defaultValue 300ms
*/
public final ObjectProperty<Duration> initialDelayProperty() {
return initialDelay;
}
public final void setInitialDelay(Duration value) {
if (value != null) {
initialDelay.set(value);
}
}
public final Duration getInitialDelay() {
return initialDelay.get();
}
private final ObjectProperty<Duration> repeatDelay =
new SimpleStyleableObjectProperty<>(REPEAT_DELAY,
this, "repeatDelay", new Duration(60));
The duration that the mouse has to be pressed for each successive step after the first value steps. Initial step duration is set using initial delay
. Returns: repeat delay property Since: 11 @defaultValue 60ms
/**
* The duration that the mouse has to be pressed for each successive step
* after the first value steps. Initial step duration is set using
* {@link #initialDelayProperty() initial delay}.
*
* @return repeat delay property
* @since 11
* @defaultValue 60ms
*/
public final ObjectProperty<Duration> repeatDelayProperty() {
return repeatDelay;
}
public final void setRepeatDelay(Duration value) {
if (value != null) {
repeatDelay.set(value);
}
}
public final Duration getRepeatDelay() {
return repeatDelay.get();
}
*
Stylesheet Handling *
*
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
private static final CssMetaData<Spinner<?>,Duration> INITIAL_DELAY =
new CssMetaData<Spinner<?>,Duration>("-fx-initial-delay",
DurationConverter.getInstance(), new Duration(300)) {
@Override
public boolean isSettable(Spinner<?> spinner) {
return !spinner.initialDelayProperty().isBound();
}
@Override
public StyleableProperty<Duration> getStyleableProperty(Spinner<?> spinner) {
return (StyleableProperty<Duration>)(WritableValue<Duration>)spinner.initialDelayProperty();
}
};
private static final CssMetaData<Spinner<?>,Duration> REPEAT_DELAY =
new CssMetaData<Spinner<?>,Duration>("-fx-repeat-delay",
DurationConverter.getInstance(), new Duration(60)) {
@Override
public boolean isSettable(Spinner<?> spinner) {
return !spinner.repeatDelayProperty().isBound();
}
@Override
public StyleableProperty<Duration> getStyleableProperty(Spinner<?> spinner) {
return (StyleableProperty<Duration>)(WritableValue<Duration>)spinner.repeatDelayProperty();
}
};
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
static {
final List<CssMetaData<? extends Styleable, ?>> styleables =
new ArrayList<>(Control.getClassCssMetaData());
styleables.add(INITIAL_DELAY);
styleables.add(REPEAT_DELAY);
STYLEABLES = Collections.unmodifiableList(styleables);
}
/*
* @return The CssMetaData associated with this class, which may include the
* CssMetaData of its superclasses.
* @since 11
*/
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return STYLEABLES;
}
/*
* {@inheritDoc}
* @since 11
*/
@Override
public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
return getClassCssMetaData();
}
*
Implementation *
*
/***************************************************************************
* *
* Implementation *
* *
**************************************************************************/
/*
* Update the TextField based on the current value
*/
private void setText(T value) {
String text = null;
SpinnerValueFactory<T> valueFactory = getValueFactory();
if (valueFactory != null) {
StringConverter<T> converter = valueFactory.getConverter();
if (converter != null) {
text = converter.toString(value);
}
}
notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT);
if (text == null) {
if (value == null) {
getEditor().clear();
return;
} else {
text = value.toString();
}
}
getEditor().setText(text);
}
/*
* Convenience method to support wrapping values around their min / max
* constraints. Used by the SpinnerValueFactory implementations when
* the Spinner wrapAround property is true.
*/
static int wrapValue(int value, int min, int max) {
if (max == 0) {
throw new RuntimeException();
}
int r = value % max;
if (r > min && max < min) {
r = r + max - min;
} else if (r < min && max > min) {
r = r + max - min;
}
return r;
}
/*
* Convenience method to support wrapping values around their min / max
* constraints. Used by the SpinnerValueFactory implementations when
* the Spinner wrapAround property is true.
*/
static BigDecimal wrapValue(BigDecimal value, BigDecimal min, BigDecimal max) {
if (max.doubleValue() == 0) {
throw new RuntimeException();
}
// note that this wrap method differs from the others where we take the
// difference - in this approach we wrap to the min or max - it feels better
// to go from 1 to 0, rather than 1 to 0.05 (where max is 1 and step is 0.05).
if (value.compareTo(min) < 0) {
return max;
} else if (value.compareTo(max) > 0) {
return min;
}
return value;
}
/***************************************************************************
* *
* Accessibility handling *
* *
**************************************************************************/
{@inheritDoc} /** {@inheritDoc} */
@Override
public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
switch (attribute) {
case TEXT: {
T value = getValue();
SpinnerValueFactory<T> factory = getValueFactory();
if (factory != null) {
StringConverter<T> converter = factory.getConverter();
if (converter != null) {
return converter.toString(value);
}
}
return value != null ? value.toString() : "";
}
default: return super.queryAccessibleAttribute(attribute, parameters);
}
}
{@inheritDoc} /** {@inheritDoc} */
@Override
public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
switch (action) {
case INCREMENT:
increment();
break;
case DECREMENT:
decrement();
break;
default: super.executeAccessibleAction(action);
}
}
}