Reputation: 516
I am trying to create an hour and minute JSpinner editor. The JSpinner is easy enough
JSpinner startSpinner = new JSpinner();
SpinnerWindowModel spinnerModel = new SpinnerWindowModel();
startSpinner.setModel(spinnerModel);
JSpinner.DateEditor editor = new JSpinner.DateEditor(startSpinner, "HH:mm");
startSpinner.setEditor(editor);
The Spinner WindowModel is:
public SpinnerWindowModel() {
super();
Calendar vCal = Calendar.getInstance();
vCal.set(Calendar.HOUR_OF_DAY, 8);
vCal.set(Calendar.MINUTE, 0);
vCal.set(Calendar.SECOND, 0);
setValue(vCal.getTime());
setCalendarField(Calendar.MINUTE);
vCal.set(Calendar.HOUR_OF_DAY, 6);
vCal.set(Calendar.MINUTE, 0);
vCal.set(Calendar.SECOND, 0);
setStart(vCal.getTime());
vCal.set(Calendar.HOUR_OF_DAY, 18);
vCal.set(Calendar.MINUTE, 45);
vCal.set(Calendar.SECOND, 0);
setEnd(vCal.getTime());
}
@Override
public Object getPreviousValue() {
Object obj = super.getPreviousValue();
int fld = getCalendarField();
System.out.println("GPV: " + fld);
return obj;
}
@Override
public Object getNextValue() {
Object obj = super.getNextValue();
int fld = getCalendarField();
System.out.println("GNV: " + fld);
return obj;
}
The two methods getNext/PreviousValue are currently only to monitor is the methods are called and on what field they are called for. With the restricted editor, these methods are not called
If I change the editor to a more complete set of fields it works like I expect. The Hour and minute fields are editable and correctly. Is there something I need to do to edit reduced editor?
editor = new JSpinner.DateEditor(startSpinner, "dd/MM/yyyy HH:mm:ss.SS");
Upvotes: 1
Views: 601
Reputation: 347234
So, when I use you model, it doesn't work, but when I use something like...
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
Date startTime = cal.getTime();
cal.set(Calendar.HOUR, 23);
cal.set(Calendar.MINUTE, 59);
Date endTime = cal.getTime();
System.out.println(startTime);
System.out.println(endTime);
// The Calendar field seems to get ignored and overriden
// by the UI delegate based on what part of the field
// the cursor is current on (hour or minute)
SpinnerDateModel model = new SpinnerDateModel(startTime, null, endTime, Calendar.MINUTE);
JSpinner spinner = new JSpinner(model);
spinner.setEditor(new JSpinner.DateEditor(spinner, "HH:mm"));
spinner.setValue(startTime);
It works fine
My "suggestion" at this time is not to use a custom model, but instead build the model, through a factory method it required, manually
Because of the complexities revolving around JSpinner
and JForamttedTextField
, I gave up on trying to get JSpinner
to handle time. Instead, I would normally fall back to using separate fields to manage the complexities of the state
I started by modifying your model...
public class SpinnerWindowModel extends SpinnerNumberModel {
private Calendar calendar;
private int calendarField;
public SpinnerWindowModel(int calendarField, Calendar calendar, int value, int min, int max, int step) {
super(value, min, max, step);
this.calendar = calendar;
}
@Override
public void setValue(Object value) {
super.setValue(value);
System.out.println(value);
}
public int getCalendarField() {
return calendarField;
}
@Override
public Object getPreviousValue() {
Object obj = super.getPreviousValue();
if (obj instanceof Integer) {
int fld = getCalendarField();
calendar.set(fld, (int)obj);
return obj;
}
return getValue();
}
@Override
public Object getNextValue() {
Object obj = super.getNextValue();
if (obj instanceof Integer) {
int fld = getCalendarField();
calendar.set(fld, (int)obj);
return obj;
}
return getValue();
}
}
And then applying that to the fields...
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
Date startTime = cal.getTime();
cal.set(Calendar.HOUR, 23);
cal.set(Calendar.MINUTE, 59);
Date endTime = cal.getTime();
System.out.println(startTime);
System.out.println(endTime);
SpinnerWindowModel hourModel = new SpinnerWindowModel(Calendar.HOUR, cal, 0, 0, 23, 1);
SpinnerWindowModel minuteModel = new SpinnerWindowModel(Calendar.HOUR, cal, 0, 0, 59, 15);
JSpinner hour = new JSpinner(hourModel);
JSpinner minute = new JSpinner(minuteModel);
add(hour);
add(new JLabel(":"));
add(minute);
Now, the draw back to this is, the minute field won't roll the hour field. I did think about using a change listener on the minute model, but the problem is, you don't really know if the field is going up or down, so there's no easy way to determine which way you should roll the hour.
I would expend the concept, using two separate JTextField
s (with appropriate DocumentFilter
s applied) and a single "roll" control. The idea been is, the roll control (ie the up and down buttons) would affect the focused field. You could then provide appropriate state information when the roll control is triggered to allow the fields to determine what they should do and how they should affect each other.
A lot of this could be rolled up into a model, which controlled a Calendar
(or preferably a LocalTime
) object, which would change the underlying date value and notify the fields that they need to change.
I believe this approach would provide you with the level of control you need to provide different step rates for the hour and minute, while providing flexibility for the user interaction (I can enter the numbers manually or via the roll control if I want to)
So, based on this answer, the underlying JFormattedTextField
is unable to validate the value, because the time format is discarding the "date" portion of the value only keeps this time, this is casing the min/max value checks to fail.
Instead, we need to define the "date" portion of the min/max values to something which we can control (and define more easily). This also means that any input MUST be constrained to this particular point in time
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(1970, Calendar.JANUARY, 0, 0, 0);
cal.set(Calendar.MILLISECOND, 0);
Date startTime = cal.getTime();
cal.set(1970, Calendar.JANUARY, 23, 59, 0);
cal.set(Calendar.MILLISECOND, 0);
Date endTime = cal.getTime();
System.out.println(startTime);
System.out.println(endTime);
Calendar value = Calendar.getInstance();
value.set(1970, Calendar.JANUARY, 0, 0, 0);
value.set(Calendar.MILLISECOND, 0);
System.out.println(value.getTime());
// The Calendar field seems to get ignored and overriden
// by the UI delegate based on what part of the field
// the cursor is current on (hour or minute)
SpinnerDateModel model = new SpinnerDateModel(value.getTime(), startTime, endTime, Calendar.MINUTE);
JSpinner spinner = new JSpinner(model);
spinner.setEditor(new JSpinner.DateEditor(spinner, "HH:mm"));
spinner.setValue(value.getTime());
add(spinner);
This then let me update your model to support multiple step states...
public class SpinnerWindowModel extends SpinnerDateModel {
public SpinnerWindowModel() {
super();
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(1970, Calendar.JANUARY, 0, 0, 0);
cal.set(Calendar.MILLISECOND, 0);
Date startTime = cal.getTime();
cal.set(1970, Calendar.JANUARY, 23, 59, 0);
cal.set(Calendar.MILLISECOND, 0);
Date endTime = cal.getTime();
System.out.println(startTime);
System.out.println(endTime);
Calendar value = Calendar.getInstance();
value.set(1970, Calendar.JANUARY, 0, 0, 0);
value.set(Calendar.MILLISECOND, 0);
setStart(startTime);
setEnd(endTime);
setValue(value.getTime());
setCalendarField(Calendar.MINUTE);
}
@Override
public Object getPreviousValue() {
int fld = getCalendarField();
Object value = super.getPreviousValue();
if (fld == Calendar.MINUTE) {
Calendar cal = Calendar.getInstance();
cal.setTime(getDate());
cal.add(Calendar.MINUTE, -15);
value = cal.getTime();
}
return value;
}
@Override
public Object getNextValue() {
int fld = getCalendarField();
Object value = super.getNextValue();
if (fld == Calendar.MINUTE) {
Calendar cal = Calendar.getInstance();
cal.setTime(getDate());
cal.add(Calendar.MINUTE, 15);
value = cal.getTime();
}
return value;
}
}
Upvotes: 2