SpinnerModel is an interface for all spinner models. AbstractSpinnerModel is a convenient abstract class that implements SpinnerModel and provides the implementation for its registration/deregistration methods . SpinnerListModel , SpinnerNumberModel , and SpinnerDateModel are concrete implementations of SpinnerModel . The relationship among them is illustrated in Figure 30.10. Besides these models, you can create a custom spinner model that extends AbstractSpinnerModel or directly implements SpinnerModel .
SpinnerListModel (see Figure 30.11) is a simple implementation of SpinnerModel whose values are stored in a java.util.List .
You can create a SpinnerListModel using an array or a list. For example, the following code creates a model that consists of the values Freshman, Sophomore, Junior, Senior, and Graduate in an array:
// Create an array String[] grades = { "Freshman" , "Sophomore" , "Junior" , "Senior" , "Graduate" }; // Create a model from an array model = new SpinnerListModel(grades);
Alternatively, the following code creates a model using a list:
// Create an array String[] grades = { "Freshman" , "Sophomore" , "Junior" , "Senior" , "Graduate" }; // Create an array list from the array list = new ArrayList( Arrays.asList(grades) ); // Create a model from list model = new SpinnerListModel(list);
The alternative code seems unnecessary. However, it is useful if you need to add or remove elements from the model. The size of an array is fixed once an array is created. The list is a flexible data structure that enables you to add or remove elements dynamically.
SpinnerNumberModel (see Figure 30.12) is a concrete implementation of SpinnerModel that represents a sequence of numbers . It contains the properties maximum , minimum , and stepSize . The maximum and minimum properties specify the upper and lower bounds of the sequence. The stepSize specifies the size of the increase or decrease computed by the nextValue and previousValue methods defined in SpinnerModel . The minimum and maximum properties can be null to indicate that the sequence has no lower or upper limit. All of the properties in this class are defined in terms of two generic types, Number and Comparable , so that all Java numeric types may be accommodated. Internally, only the values with type Double , Float , Long , Integer , Short , or Byte are supported.
You can create a SpinnerNumberModel with integers or double. For example, the following code creates a model that represents a sequence of numbers from to 3000 with initial value 2004 and interval 1 :
// Create a spinner number model SpinnerNumberModel model = new SpinnerNumberModel( 2004 , , 3000 , 1 );
The following code creates a model that represents a sequence of numbers from to 120 with initial value 50 and interval 0.1 :
// Create a spinner number model SpinnerNumberModel model = new SpinnerNumberModel( 50 , , 120 , 0.1 );
SpinnerDateModel (see Figure 30.13) is a concrete implementation of SpinnerModel that represents a sequence of dates. The upper and lower bounds of the sequence are defined by properties called start and end , and the size of the increase or decrease computed by the nextValue and previousValue methods is defined by a property called calendarField .
The start and end properties can be null to indicate that the sequence has no lower or upper limit. The value of the calendarField 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 backward by this amount. For example, if calendarField is Calendar.DAY_OF_WEEK , then nextValue produces a date that is twenty-four hours after the current value, and previousValue produces a date that is twenty-four hours earlier.
For example, the following code creates a spinner model that represents a sequence of dates, starting from the current date without a lower or upper limit and with calendar field on month.
SpinnerDateModel model = new SpinnerDateModel( new Date(), null , null , Calendar.MONTH);
A JSpinner has a single child component, called the editor , which is responsible for displaying the current element or value of the model. Four editors are defined as static inner classes inside JSpinner .
JSpinner.DefaultEditor is a simple base class for all other specialized editors to display a read-only view of the model's current value with a JFormattedTextField . JFormattedTextField extends JTextField adding support for formatting arbitrary values, as well as retrieving a particular object once the user has edited the text.
JSpinner.NumberEditor is a specialized editor for a JSpinner whose model is a SpinnerNumberModel . The value of the editor is displayed with a JFormattedTextField whose format is defined by a NumberFormatter instance.
JSpinner.DateEditor is a specialized editor for a JSpinner whose model is a SpinnerDateModel . The value of the editor is displayed with a JFormattedTextField whose format is defined by a DateFormatter instance.
JSpinner.ListEditor is a specialized editor for a JSpinner whose model is a SpinnerListModel . The value of the editor is displayed with a JFormattedTextField .
The JSpinner 's constructor creates a NumberEditor for SpinnerNumberModel , a DateEditor for SpinnerDateModel , a ListEditor for SpinnerListModel , and a DefaultEditor for all other models. The editor can also be changed using the setEditor method. The JSpinner 's editor stays in sync with the model by listening for ChangeEvent s. The commitEdit() method should be used to commit the currently edited value to the model.
This example uses a JSpinner component to display the date and three other JSpinner components to display the day in a sequence of numbers, the month in a sequence of strings, and the year in a sequence of numbers, as shown in Figure 30.14. All four components are synchronized. For example, if you change the year in the spinner for year, the date value in the date spinner is updated accordingly . The source code of the example is given in Listing 30.6.
1 import javax.swing.*; 2 import javax.swing.event.*; 3 import java.util.*; 4 import java.text.*; 5 import java.awt.*; 6 7 public class SpinnerModelEditorDemo extends JApplet { 8 // Create four spinners for date, day, month, and year 9 private JSpinner spinnerDate = 10 new JSpinner( new SpinnerDateModel()) ; 11 private JSpinner spinnerDay = 12 new JSpinner( new SpinnerNumberModel( 1 , 1 , 31 , 1 )) ; 13 private String[] monthNames = new DateFormatSymbols().getMonths(); 14 private JSpinner spinnerMonth = new JSpinner 15 ( new SpinnerListModel(Arrays.asList(monthNames).subList( , 12 )) ); 16 private JSpinner spinnerYear = 17 new JSpinner( new SpinnerNumberModel( 2004 , 1 , 3000 , 1 )) ; 18 19 public SpinnerModelEditorDemo() { 20 // Group labels 21 JPanel panel1 = new JPanel(); 22 panel1.setLayout( new GridLayout( 4 , 1 )); 23 panel1.add( new JLabel( "Date" )); 24 panel1.add( new JLabel( "Day" )); 25 panel1.add( new JLabel( "Month" )); 26 panel1.add( new JLabel( "Year" )); 27 28 // Group spinners 29 JPanel panel2 = new JPanel(); 30 panel2.setLayout( new GridLayout( 4 , 1 )); 31 panel2.add(spinnerDate); 32 panel2.add(spinnerDay); 33 panel2.add(spinnerMonth); 34 panel2.add(spinnerYear); 35 36 // Add spinner and label to the UI 37 add(panel1, BorderLayout.WEST); 38 add(panel2, BorderLayout.CENTER); 39 40 // Set editor for date 41 JSpinner.DateEditor dateEditor = 42 new JSpinner.DateEditor(spinnerDate, "MMM dd, yyyy" ) ; 43 spinnerDate.setEditor(dateEditor); 44 45 // Set editor for year 46 JSpinner.NumberEditor yearEditor = 47 new JSpinner.NumberEditor(spinnerYear, "####" ) ; 48 spinnerYear.setEditor(yearEditor); 49 50 // Update date to synchronize with the day, month, and year 51 updateDate(); 52 53 // Register and create a listener for spinnerDay 54 spinnerDay.addChangeListener( new ChangeListener() { 55 public void stateChanged(javax.swing.event.ChangeEvent e) { 56 updateDate(); 57 } 58 }); 59 60 // Register and create a listener for spinnerMonth 61 spinnerDay.addChangeListener( new ChangeListener() { 62 public void stateChanged(javax.swing.event.ChangeEvent e) { 63 updateDate(); 64 } 65 }); 66 67 // Register and create a listener for spinnerYear 68 spinnerMonth.addChangeListener( new ChangeListener() { 69 public void stateChanged(javax.swing.event.ChangeEvent e) { 70 updateDate(); 71 } 72 }); 73 } 74 75 // Update date spinner to synchronize with the other three spinners 76 private void updateDate() { 77 // Get current month and year in int 78 int month = ((SpinnerListModel) spinnerMonth.getModel() ). 79 getList().indexOf( spinnerMonth.getValue() ); 80 int year = ((Integer) spinnerYear.getValue() ).intValue(); 81 82 // Set a new maximum number of days for the new month and year 83 SpinnerNumberModel numberModel = 84 (SpinnerNumberModel); spinnerDay.getModel() 85 numberModel.setMaximum ( new Integer(maxDaysInMonth(year, month))); 86 87 // Set a new current day if it exceeds the maximum 88 if (((Integer)( numberModel.getValue() )).intValue() > 89 maxDaysInMonth(year, month)) 90 numberModel.setValue ( new Integer(maxDaysInMonth(year, month))); 91 92 // Get the current day 93 int day = ((Integer)spinnerDay.getValue()).intValue(); 94 95 // Set a new date in the date spinner 96 spinnerDate.setValue( 97 new GregorianCalendar(year, month, day).getTime()); 98 } 99 100 /** Return the maximum number of days in a month. For example, 101 Feb 2004 has 29 days. */ 102 private int maxDaysInMonth( int year, int month) { 103 Calendar calendar = new GregorianCalendar(year, month, 1 ); 104 return calendar.getActualMaximum(Calendar.DAY_OF_MONTH); 105 } 106 } |
A JSpinner object for dates, spinnerDate , is created with a default SpinnerDateModel (lines 9 “10). The format of the date displayed in the spinner is MMM dd, yyyy (e.g., Feb 01, 2006). This format is created using the JSpinner 's inner class constructor DateEditor (lines 41 “42) and is set as spinnerDate 's editor (line 43).
A JSpinner object for days, spinnerDay , is created with a SpinnerNumberModel with a sequence of integers between 1 and 31 in which the initial value is 1 and the interval is 1 (lines 11 “12). The maximum number is reset in the updateDate() method based on the current month and year (lines 88 “90). For example, February 2004 has twenty-nine days, so the maximum in spinnerDay is set to 29 for February 2004.
A JSpinner object for months, spinnerMonth , is created with a SpinnerListModel with a list of month names (lines 14 “15). Month names are locale-specific and can be obtained using the new DateFormatSymbols().getMonths() (line 13). Some calendars can have thirteen months. Arrays.asList(monthNames) creates a list from an array of strings, and subList(0, 12) returns the first twelve elements in the list.
A JSpinner object for years , spinnerYear , is created with a SpinnerNumberModel with a sequence of integers between 1 and 3000 in which the initial value is 2004 and the interval is 1 (lines 16 “17). By default, locale-specific number separators are used. For example, 2004 would be displayed as 2,004 in the spinner. To display the number without separators, the number pattern #### is specified to construct a new NumberEditor for spinnerYear (lines 46 “47). The editor is set as spinnerYear 's editor (line 48).
The updateDate() method synchronizes the date spinner with the day, month, and year spinners. Whenever a new value is selected in the day, month, or year spinner, a new date is set in the date spinner. The maxDaysInMonth method (lines 102 “105) returns the maximum number of days in a month. For example, February 2004 has twenty nine days.
A JSpinner object can fire javax.swing.event.ChangeEvent to notify the listeners of the state change in the spinner. The anonymous event adapters are created to process spinner state changes for the day, month, and year spinners (lines 53 “72). Whenever a new value is selected in one of these three spinners, the date spinner value is updated accordingly. In Exercise 30.3, you will improve the example to synchronize the day, month, and year spinners with the date spinner. Then, when a new value is selected in the date spinner, the values in the day, month, and year spinners will be updated accordingly.
This example uses SpinnerNumberModel , SpinnerDateModel , and SpinnerListModel . They are predefined concrete spinner models in the API. You can also create custom spinner models (see Exercise 30.4).