A spinner is a compound component with three
subcomponents: two small buttons and an
editor.
The editor can be any JComponent
,
but by default it is implemented
as a panel that contains a
formatted text field.
The spinner's possible and current values
are managed by its model.
Here is a picture of an application named SpinnerDemo
that has three spinners used to specify dates:
SpinnerDemo.java
.
The Month spinner displays the name
of the first month in the user's locale.
The possible values for this spinner
are specified using
an array of strings.
The Year spinner displays one value of a range of integers,
initialized to the current year.
The Another Date spinner displays one value
in a range of Date
objects
(initially the current date)
in a custom format
that shows just a month and year.
To create a spinner, first create its model and then pass the model into the JSpinner constructor. For example:
String[] monthStrings = getMonthStrings(); //get month names SpinnerListModel monthModel = new SpinnerListModel(monthStrings); JSpinner spinner = new JSpinner(monthModel);
SpinnerListModel
is a model whose values are
defined by an array of objects or a List
object.
The Month spinner in the SpinnerDemo
example
uses this model,
initialized with an array derived from the value
returned by the getMonths
method of the java.text.DateFormatSymbols
class.
See
SpinnerDemo.java
for details.
SpinnerNumberModel
supports sequences of numbers
which can be expressed as double
objects,
int
objects,
or Number
objects.
You can specify the minimum and maximum allowable values,
as well as the step size —
the amount of each increment or decrement.
The Year spinner uses this model,
created with the following code:
SpinnerModel model = new SpinnerNumberModel(currentYear, //initial value currentYear - 100, //min currentYear + 100, //max 1); //step
SpinnerDateModel
supports sequences of Date
objects.
You can specify the minimum and maximum dates,
as well as the field
(such as Calendar.YEAR
)
to increment or decrement.
Note, however, that some types of look and feel
ignore the specified field,
and instead change the field
that appears selected.
The Another Date spinner uses this model, created with the following code:
Date initDate = calendar.getTime(); calendar.add(Calendar.YEAR, -100); Date earliestDate = calendar.getTime(); calendar.add(Calendar.YEAR, 200); Date latestDate = calendar.getTime(); model = new SpinnerDateModel(initDate, earliestDate, latestDate, Calendar.YEAR);
When you set the spinner's model,
the spinner's editor is automatically set.
The Swing API provides an editor class
corresponding to each of the three model classes listed above.
These classes —
JSpinner.ListEditor,
JSpinner.NumberEditor, and
JSpinner.DateEditor —
are all subclasses of the
JSpinner.DefaultEditor class that feature editable formatted text fields.
If you use a model that does not have an editor associated with it,
the editor is by default
a JSpinner.DefaultEditor
instance
with a non-editable formatted text field.
To change the formatting used in a standard spinner editor, you can create and set the editor yourself.
The JSpinner.NumberEditor
and JSpinner.DateEditor
classes
have constructors that allow you to
create an editor that formats its data in a particular way.
For example, the following code
sets up the Another Date spinner
so that instead of using the default date format,
which is long and includes the time,
it shows just a month and year
in a compact way.
spinner.setEditor(new JSpinner.DateEditor(spinner, "MM/yyyy"));
ComboBoxDemo2
example.
Click the Launch button
to run ComboBoxDemo2 using
Java™ Web Start
(download JDK 6).
Alternatively, to compile and run the example yourself,
consult the example index.
To change the formatting
when using a default editor,
you can obtain the editor's formatted text field
and invoke methods on it.
You can call those methods using the
getTextField
method
defined in the JSpinner.DefaultEditor
class.
Note that the Swing-provided editors are not
formatted text fields.
Instead, they are the JPanel
instances
that contain
a formatted text field.
Here is an example of getting and invoking methods on
the editor's formatted text field:
//Tweak the spinner's formatted text field. ftf = getTextField(spinner); if (ftf != null ) { ftf.setColumns(8); //specify more width than we need ftf.setHorizontalAlignment(JTextField.RIGHT); } ... public JFormattedTextField getTextField(JSpinner spinner) { JComponent editor = spinner.getEditor(); if (editor instanceof JSpinner.DefaultEditor) { return ((JSpinner.DefaultEditor)editor).getTextField(); } else { System.err.println("Unexpected editor type: " + spinner.getEditor().getClass() + " isn't a descendant of DefaultEditor"); return null; } }
The easiest route to creating a custom spinner model
is to create a subclass
of an existing AbstractSpinnerModel
subclass
that already does most of what you need.
An alternative is to implement your own class
by extending
AbstractSpinnerModel
class,
which implements the event notifications
required for all spinner models.
The following subclass of SpinnerListModel
implements a spinner model
that cycles through an array of objects.
It also lets you specify a second spinner model
that will be updated whenever the cycle begins again.
For example, if the array of objects is a list of months,
the linked model could be for a spinner that displays the year.
When the month flips over from December to January the year is incremented.
Similarly, when the month flips back from January to December
the year is decremented.
public class CyclingSpinnerListModel extends SpinnerListModel { Object firstValue, lastValue; SpinnerModel linkedModel = null; public CyclingSpinnerListModel(Object[] values) { super(values); firstValue = values[0]; lastValue = values[values.length - 1]; } public void setLinkedModel(SpinnerModel linkedModel) { this.linkedModel = linkedModel; } public Object getNextValue() { Object value = super.getNextValue(); if (value == null) { value = firstValue; if (linkedModel != null) { linkedModel.setValue(linkedModel.getNextValue()); } } return value; } public Object getPreviousValue() { Object value = super.getPreviousValue(); if (value == null) { value = lastValue; if (linkedModel != null) { linkedModel.setValue(linkedModel.getPreviousValue()); } } return value; } }
The CyclingSpinnerListModel
model
is used for the Month spinner in the SpinnerDemo2
example,
an example that is almost identical to the SpinnerDemo
.
Click the Launch button
to run SpinnerDemo2 using
Java™ Web Start
(download JDK 6).
Alternatively, to compile and run the example yourself,
consult the example index.
As we mentioned before, if you implement a spinner model
that does not descend from
SpinnerListModel
,
SpinnerNumberModel
, or
SpinnerDateModel
,
then the spinner's default editor is
a non-editable instance of JSpinner.DefaultEditor
.
As you have already seen,
you can set a spinner's editor
by invoking the setEditor
method on the spinner
after the spinner's model property has been set.
An alternative to using setEditor
is to
create a subclass of the JSpinner
class
and override its createEditor
method
so that it returns a particular kind of editor
whenever the spinner model is of a certain type.
In theory at least, you can use any JComponent
instance
as an editor.
Possibilities include using a
subclass of a standard component
such as JLabel
,
or a component you have implemented from scratch,
or a subclass of
JSpinner.DefaultEditor
.
The only requirements
are that the editor must
be updated to reflect changes in the spinner's value,
and it must have a reasonable preferred size.
The editor should generally also
set its tool tip text
to whatever tool tip text has been specified
for the spinner.
An example of implementing an editor is provided in the next section.
SpinnerDemo3
,
which is based on SpinnerDemo
and uses a change listener
to change the color of some text
to match the value of the Another Date spinner.
Click the Launch button
to run SpinnerDemo3 using
Java™ Web Start
(download JDK 6).
Alternatively, to compile and run the example yourself,
consult the example index.
public class SpinnerDemo3 extends JPanel implements ChangeListener { protected Calendar calendar; protected JSpinner dateSpinner; ... public SpinnerDemo3() { ... SpinnerDateModel dateModel = ...; ... setSeasonalColor(dateModel.getDate()); //initialize color //Listen for changes on the date spinner. dateSpinner.addChangeListener(this); ... } public void stateChanged(ChangeEvent e) { SpinnerModel dateModel = dateSpinner.getModel(); if (dateModel instanceof SpinnerDateModel) { setSeasonalColor(((SpinnerDateModel)dateModel).getDate()); } } protected void setSeasonalColor(Date date) { calendar.setTime(date); int month = calendar.get(Calendar.MONTH); JFormattedTextField ftf = getTextField(dateSpinner); if (ftf == null) return; //Set the color to match northern hemisphere seasonal conventions. switch (month) { case 2: //March case 3: //April case 4: //May ftf.setForeground(SPRING_COLOR); break; ... default: //December, January, February ftf.setForeground(WINTER_COLOR); } } ... }
...//Where the components are created: JSpinner spinner = new JSpinner(new GrayModel(170)); spinner.setEditor(new GrayEditor(spinner)); class GrayModel extends SpinnerNumberModel { ... } class GrayEditor extends JLabel implements ChangeListener { public GrayEditor(JSpinner spinner) { setOpaque(true); ... //Get info from the model. GrayModel myModel = (GrayModel)(spinner.getModel()); setBackground(myModel.getColor()); spinner.addChangeListener(this); ... updateToolTipText(spinner); } protected void updateToolTipText(JSpinner spinner) { String toolTipText = spinner.getToolTipText(); if (toolTipText != null) { //JSpinner has tool tip text. Use it. if (!toolTipText.equals(getToolTipText())) { setToolTipText(toolTipText); } } else { //Define our own tool tip text. GrayModel myModel = (GrayModel)(spinner.getModel()); int rgb = myModel.getIntValue(); setToolTipText("(" + rgb + "," + rgb + "," + rgb + ")"); } } public void stateChanged(ChangeEvent e) { JSpinner mySpinner = (JSpinner)(e.getSource()); GrayModel myModel = (GrayModel)(mySpinner.getModel()); setBackground(myModel.getColor()); updateToolTipText(mySpinner); } }
The following tables list some of the commonly used API for using spinners. If you need to deal directly with the editor's formatted text field, you should also see The FormattedTextField API. Other methods you might use are listed in the API tables in The JComponent Class.
Class or Interface | Purpose |
---|---|
JSpinner | A single-line input field that allows the user to select a number or object value from an ordered sequence. |
SpinnerModel | The interface implemented by all spinner models. |
AbstractSpinnerModel | The usual superclass for spinner model implementations. |
SpinnerListModel | A subclass of AbstractSpinnerModel whose values are
defined by an array or a List .
|
SpinnerDateModel | A subclass of AbstractSpinnerModel that supports
sequences of Date instances.
|
SpinnerNumberModel | A subclass of AbstractSpinnerModel that supports
sequences of numbers.
|
JSpinner.DefaultEditor | Implements an uneditable component that displays the spinner's value. Subclasses of this class are generally more specialized (and editable). |
JSpinner.ListEditor | A subclass of JSpinner.DefaultEditor whose values are
defined by an array or a List .
|
JSpinner.DateEditor | A subclass of JSpinner.DefaultEditor that supports
sequences of Date instances.
|
JSpinner.NumberEditor | A subclass of JSpinner.DefaultEditor that supports
sequences of numbers.
|
Constructor or Method | Purpose |
---|---|
JSpinner() JSpinner(SpinnerModel) |
Creates a new JSpinner . The no-argument constructor
creates a spinner with an integer SpinnerNumberModel
with an initial value of 0 and no minimum or maximum limits. The
optional parameter on the second constructor allows you to specify
your own SpinnerModel .
|
void setValue(java.lang.Object) Object getValue() |
Sets or gets the currently displayed element of the sequence. |
Object getNextValue() Object getPreviousValue() |
Gets the object in the sequence that comes before or after the
object returned by the getValue method.
|
SpinnerModel getModel() void setModel(SpinnerModel) |
Gets or sets the spinner's model. |
JComponent getEditor() void setEditor(JComponent) |
Gets or sets the spinner's editor,
which is often an object of type JSpinner.DefaultEditor .
|
protected JComponent createEditor(SpinnerModel) | Called by the JSpinner constructors
to create the spinner's editor.
Override this method to associate an editor
with a particular type of model.
|
Constructor or Method | Purpose |
---|---|
JSpinner.NumberEditor(JSpinner, String) | Creates a JSpinner.NumberEditor instance
that displays and allows editing of
the number value of the specified spinner.
The string argument
specifies the format to use to display the number.
See the API documentation for
DecimalFormat for information about decimal format strings.
|
JSpinner.DateEditor(JSpinner, String) | Creates a JSpinner.DateEditor instance
that displays and allows editing of
the Date value of the specified spinner.
The string argument
specifies the format to use to display the date.
See the API documentation for
SimpleDateFormat for information about date format strings.
|
JFormattedTextField getTextField() (defined in JSpinner.DefaultEditor )
|
Gets the formatted text field that provides the main GUI for this editor. |
Method | Purpose |
---|---|
void setList(List) List getList() |
Sets or gets the List that defines the sequence for this model.
|
Method | Purpose |
---|---|
void setValue(Object) Date getDate() Object getValue() |
Sets or gets the current Date for this sequence.
|
void setStart(Comparable) Comparable getStart() |
Sets or gets the first Date in this sequence.
Use null to specify that
the spinner has no lower limit.
|
void setEnd(Comparable) Comparable getEnd() |
Sets or gets the last Date in this sequence.
Use null to specify that
the spinner has no upper limit.
|
void setCalendarField(int) int getCalendarField() |
Sets or gets the size of the date value increment used by
the getNextValue and getPreviousValue
methods.
This property is not used when the user explicitly
increases or decreases the value;
instead, the selected part of the formatted text field
is incremented or decremented.
The specified parameter must be one of the following
constants, defined in Calendar :
ERA , YEAR ,
MONTH , WEEK_OF_YEAR , WEEK_OF_MONTH ,
DAY_OF_MONTH , DAY_OF_YEAR , DAY_OF_WEEK ,
DAY_OF_WEEK_IN_MONTH , AM_PM , HOUR_OF_DAY ,
MINUTE , SECOND , MILLISECOND .
|
Method | Purpose |
---|---|
void setValue(Object) Number getNumber() |
Sets or gets the current value for this sequence. |
void setMaximum(Comparable) Comparable getMaximum() |
Sets or gets the upper bound for numbers in this sequence. If the
maximum is null , there is no upper bound.
|
void setMinimum(Comparable) Comparable getMinimum() |
Sets or gets the lower bound for numbers in this sequence. If the
minimum is null , there is no lower bound.
|
void setStepSize(Number) Number getStepSize() |
Sets or gets the increment used by getNextValue and
getPreviousValue methods.
|
Example | Where Described | Notes |
---|---|---|
SpinnerDemo
|
This section | Uses all three standard spinner model classes. Contains the code to use a custom spinner model, but the code is turned off by default. |
SpinnerDemo2
|
This section | A SpinnerDemo subclass
that uses the custom spinner model
for its Months spinner.
|
SpinnerDemo3
|
This section | Based on SpinnerDemo, this application shows how to listen for changes in a spinner's value. |
SpinnerDemo4
|
This section | Implements a custom model and a custom editor for a spinner that displays shades of gray. |