JSpinner
- A Simple Sequence ContainerThis document is a proposal for a new Swing component, JSpinner.
One of the most commonly requested new Swing components is a spinner - a single line input field that lets the user select an number or an object value from an ordered set. Spinners typically provide a pair of tiny arrow buttons for stepping through the possible values, the up/down arrow keys also cycle through the values. 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.
Support for a spinner has been added to the list of features for the JDK 1.4 release of Java which is covered by Java Community Process JSR 59. This particular feature is listed on the JDC Bug Parade as bug 4290529.
Spinners are common in modern GUIs. Here are some examples from some popular look and feels and from OpenWindows:
Windows | CDE/Motif | JLF (proposed) | Mac Aqua | OpenWindows |
---|
All of the spinners deliver very simple behavior: clicking on the arrow buttons changes the fields value and, when the spinner has the focus, the keyboard up and down arrow keys do the same. In some applications the spinners up or down arrow button is disabled when the upper or lower limit of the spinner has been reached, in other cases the spinner just resets its value to the opposite extreme.
JSpinner
, SpinnerModel
, SpinnerListModel
The JSpinner
class is a simple Swing container that manages three children:
two arrow buttons and a single component, called the
editor that displays the spinner's value. The value
displayed by the spinner is encapsulated by model of a sequence
of objects called a
SpinnerModel
.
public interface SpinnerModel { Object getValue(); void setValue(Object); Object getNextValue(); Object getPreviousValue(); void addChangeListener(ChangeListener x); void removeChangeListener(ChangeListener x); }
The SpinnerModel
interface is similar to
ListModel
- they both represent a sequence a values
- however there are some important differences:
size
method.
ListModels
sequence, which are read-only.
This difference reflects the different roles of
JList
and JSpinner
. The former was
designed to simplify selecting one or more items from a
list and the latter for entering a single value directly.
The relationship between the JSpinner
and its model is simple.
The editor component monitors the model with a ChangeListener
and always displays the object returned by
SpinnerModel.getValue()
. The up and down arrow buttons update
the value by calling setValue(getNextValue())
or
setValue(getPreviousValue())
respectively. The
getNextValue
and getPreviousValue
methods return null
when the end or the beginning of the
sequence has been reached, so the arrow button actions have to
check for null
before updating the models value. If the editor
is a writable field of some kind, its responsible for honoring
the constraints defined by the model or handling the
IllegalArgumentException
thrown by
setValue
for invalid values.
The
SpinnerListModel
provides support for
two common mutable sequence types: java.util.List
,
and an array of objects. For example to create a JSpinner
that
lets the user choose a day of the week in the default locale one
could write:
String[] days = new DateFormatSymbols().getWeekdays(); SpinnerModel model = new SpinnerListModel(days); JSpinner spinner = new JSpinner(model);
In addition to initializing the model
property of
the spinner, these constructors create an editor component that
displays the SpinnerModel's
value
and
can be used to change it. The protected
JSpinner.createEditor
method is used for this and,
by default, it creates a JFormattedTextField
that
has been configured to display the model.
To find or initialize a spinners current value one can either
use the models value
property or use the convenience
JSpinner
value
property which just delegates to the model. For example,
using the spinner configured in the example above, the following
two statements are equivalent:
String selectedDay = spinner.getModel().getValue().toString(); String selectedDay = spinner.getValue().toString();
Setting the spinners value is similar. Attempts to set the
SpinnerModel
value to an object that the model doesn't support
cause an IllegalArgumentException
to be thrown.
Dates and numbers are two of the most common applications for a
spinner component. To simplify spinning these types, two
additional SpinnerModel
implementation classes
are provided: SpinnerDateModel
and
SpinnerNumberModel
.
SpinnerDateModel
One of the most common uses of a spinner is to compactly present
an editable date. Here's a simple example of creating a
JSpinner
that allows the user to enter a (fully localized) date:
SpinnerDateModel model = new SpinnerDateModel(); JSpinner spinner = new JSpinner(model); Date value = model.getDate();
In this example, the JSpinner
constructor has created a
JFormattedTextField
editor that's configured for
editing dates and it has added a ChangeListener
to the SpinnerDateModel
to keep the
editor
and the model in sync.
Here's the
SpinnerDateModel
API. We've added three new read/write properties: start
,
end
, and stepSize
and a read-only date
property that returns the models value cast to a Date
.
public class SpinnerDateModel extends AbstractSpinnerModel { public SpinnerDateModel(Date value, Comparable start, Comparable end, int stepSize) public SpinnerDateModel() public void setStart(Comparable start) public Comparable getStart() public void setEnd(Comparable end) public Comparable getEnd() public void setStepSize(int stepSize) public int getStepSize() public Object getNextValue() public Object getPreviousValue() public Date getDate() public Object getValue() public void setValue(Object value) }
The startDate
and endDate
properties can be null
to indicate
that there is no lower or upper limit. The no-arguments
SpinnerDateModel
constructor initializes
both the start and end date to null
, the initial value of
the model is the current date.
The value of the stepSize
property must be one of
the java.util.Calendar
constants that specify a
field within a Calendar
. The
getNextValue
and getPreviousValue
methods change the date forward or backwards by this amount.
For example, if stepSize
is
Calendar.DAY_OF_WEEK
, then nextValue
produces a Date
that's 24 hours after the current
value
, and previousValue
produces a
Date
that's 24 hours earlier.
The legal values for stepSize
are:
Calendar.ERA
, Calendar.YEAR
Calendar.MONTH
, Calendar.WEEK_OF_YEAR
Calendar.WEEK_OF_MONTH
,
Calendar.DAY_OF_MONTH
Calendar.DAY_OF_YEAR
,
Calendar.DAY_OF_WEEK
Calendar.DAY_OF_WEEK_IN_MONTH
,
Calendar.AM_PM
Calendar.HOUR
,
Calendar.HOUR_OF_DAY
Calendar.MINUTE
,
Calendar.SECOND
, or
Calendar.MILLISECOND
.
The default SpinnerDateModel
editor
adjusts the stepSize
property based on the text
cursor position. For example if the cursor moves into the
editor's month subfield then the incrementSize
would be changed to Calendar.DAY_OF_MONTH
.
Spinners are often used to present editable integers and real
numbers that represent everything from temperature to stock
prices. The SpinnerNumberModel
provides basic support for all of the basic Java
Number
types, from Byte
to
Double
.
To create a spinner that allows the user to pick a real multiple of 1/8 between 0.0 and 1000.0, with an initial value of 500.0, one could write:
SpinnerNumberModel model = new SpinnerNumberModel(500.0, 0.0, 1000.0, 0.625); JSpinner spinner = new JSpinner(model); double value = model.getNumber().doubleValue();
In this example, the JSpinner
constructor has
created a JFormattedTextField
editor that's
configured for editing real numbers and it has added a
ChangeListener
to the
SpinnerNumberModel
to keep the editor
and the model in sync.
Here's a summary of the SpinnerNumberModel
API. We've added
three new read/write properties: minimum
,
maximum
, and stepSize
and a read-only
number
property that returns the models
value
cast to a Number
.
public class SpinnerNumberModel extends AbstractSpinnerModel { public SpinnerNumberModel(Number value, Comparable minimum, Comparable maximum, Number stepSize) public SpinnerNumberModel(int value, int minimum, int maximum, int stepSize) public SpinnerNumberModel(double value, double minimum, double maximum, double stepSize) public SpinnerNumberModel() public void setMinimum(Comparable minimum) public Comparable getMinimum() public void setMaximum(Comparable maximum) public Comparable getMaximum() public void setStepSize(Number stepSize) public Number getStepSize() public Object getNextValue() public Object getPreviousValue() public Number getNumber() public Object getValue() public void setValue(Object value) }
As with the SpinnerDateModel
, the
minimum
and maximum
properties can be
null
to indicate that there is no lower or upper limit.
The stepSize
property just specifies how much
to add or subtract from the value
to compute
the nextValue
or the previousValue
.
Support for spinners adds six classes and one interface
(SpinnerModel
) to the javax.swing
package:
Additionally we'll add SpinnerUI
to the
javax.swing.plaf
package and
BasicSpinnerUI
to the
javax.swing.plaf.basic
package. The API's for
these classes will be added to this specification when the
JSpinner
implementation is complete.