I have a JSpinner using a SpinnerDateModel which has a start at Jan 1, 2010 00:00:00.000 the end date is Jan 1, 2010 00:12:34.217. I would like my JSpinner.DateEditor to use the format HH:mm:ss.SSS but the spinner doesn't spin with this format. It only spins when "yyyy" is added to the format. How can I get around this?
import java.awt.GridLayout;
import java.util.*;
import javax.swing.*;
public class T extends JPanel {
public T() {
super(new GridLayout(2, 2));
init();
}
private void init() {
Calendar start = GregorianCalendar.getInstance();
Calendar end = GregorianCalendar.getInstance();
start.clear();
end.clear();
start.set(Calendar.YEAR, 2010);
end.set(Calendar.YEAR, 2010);
end.add(Calendar.HOUR_OF_DAY, 12);
SpinnerDateModel m1 =
new SpinnerDateModel(start.getTime(), start.getTime(),
end.getTime(), Calendar.MILLISECOND);
SpinnerDateModel m2 =
new SpinnerDateModel(start.getTime(), start.getTime(),
end.getTime(), Calendar.MILLISECOND);
JSpinner workingSpinner = new JSpinner(m1);
workingSpinner.setEditor(
new JSpinner.DateEditor(workingSpinner,
"yyyy HH:mm:ss.SSS"));
JSpinner notWorkingSpinner = new JSpinner(m2);
notWorkingSpinner.setEditor(
new JSpinner.DateEditor(notWorkingSpinner,
"HH:mm:ss.SSS"));
add(new JLabel("Working"));
add(workingSpinner);
add(new JLabel("!Working"));
add(notWorkingSpinner);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new T());
frame.pack();
frame.setVisible(true);
}
}
After a good bit of digging around in the JRE source I discovered the the spinner is backed by a text value instead of a true date. When you hit the up and down spin buttons, the value is parsed and then compared to your min and max values. Because your format does not have a year the dates are parsed with the year always being 1970 which is year offset 0 from the epoch. This causes the spinner to always return an out of range error when you try to spin it.
The quickest solution is to simply use 1970 as your year instead of 2010. However, if your initial date is at the end of 1970 the spinner won't let your users roll over into January of 1971 (instead it may jump back to the beginning of 1970).
The other solution can accomodate dates that span calendar year boundaries. However, it is not as simple (or pretty). In the JRE when the DateFormatter parses the date string, it instantiates a class dynamically using a single String parameter constructor. This string is the date from the spinner. By default, this class is either Date or some subclass of it. We can have the formatter instantiate our own Date class which fixes the year before any date comparisons are performed.
Date class that adds the year:
public static class DateThatAddsYear extends Date {
public DateThatAddsYear( String time ) {
super( time );
Calendar cal = GregorianCalendar.getInstance();
cal.setTime( this );
// Jump back to 2010, this needs to be implemented more thoroughly in order
// to support dates crossing calendar year boundaries
cal.set( Calendar.YEAR, 2010 );
setTime( cal.getTimeInMillis() );
}
}
Manually set up the spinner, using our date fix:
JSpinner notWorkingSpinner = new JSpinner(m2);
JSpinner.DateEditor dateEditor = new JSpinner.DateEditor(notWorkingSpinner);
DateFormatter formatter = new DateFormatter( format );
notWorkingSpinner.setEditor(dateEditor);
dateEditor.getTextField().setFormatterFactory( new DefaultFormatterFactory( formatter ) );
formatter.setValueClass( DateThatAddsYear.class ); // Tell it to use a different value class!
Ugly, but it works.
Also, If you want to poke around in the JRE source I suggest looking at the public method stringToValue(String text)
of InternationalFormatter (superclass of DateFormatter).
I am not sure why that is not working but if you change the declaration of m2 to:
SpinnerDateModel m2 = new SpinnerDateModel(); m2.setValue(start.getTime());
it works.
This is kind of ugly but I got it working.
Here is the code. I just take care of the range validation inside the addChangeListener for the JSpinner.
import java.awt.GridLayout;
import java.util.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class T extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
public T() {
super(new GridLayout(2, 2));
init();
}
public Calendar end;
public JSpinner notWorkingSpinner;
private void init() {
Calendar start = GregorianCalendar.getInstance();
end = GregorianCalendar.getInstance();
start.clear();
end.clear();
start.set(Calendar.YEAR, 2010);
end.set(Calendar.YEAR, 2010);
end.add(Calendar.HOUR_OF_DAY, 12);
SpinnerDateModel m1 =
new SpinnerDateModel(start.getTime(), start.getTime(),
end.getTime(), Calendar.MILLISECOND);
SpinnerDateModel m2 = new SpinnerDateModel();
m2.setValue(start.getTime());
JSpinner workingSpinner = new JSpinner(m1);
workingSpinner.setEditor(
new JSpinner.DateEditor(workingSpinner,
"yyyy HH:mm:ss.SSS"));
notWorkingSpinner = new JSpinner(m2);
notWorkingSpinner.setEditor(
new JSpinner.DateEditor(notWorkingSpinner,
"HH:mm:ss.SSS"));
notWorkingSpinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
SpinnerModel dateModel = notWorkingSpinner.getModel();
if(dateModel instanceof SpinnerDateModel){
Date check = ((SpinnerDateModel)dateModel).getDate();
Calendar checkCal = GregorianCalendar.getInstance();
checkCal.setTime(check);
checkCal.set(Calendar.YEAR, end.get(Calendar.YEAR));
checkCal.set(Calendar.MONTH, end.get(Calendar.MONTH));
checkCal.set(Calendar.DAY_OF_MONTH, end.get(Calendar.DAY_OF_MONTH));
if(checkCal.get(Calendar.HOUR_OF_DAY) == 23){
dateModel.setValue(start.getTime());
} else if(checkCal.getTime().compareTo(end.getTime()) > 0){
dateModel.setValue(end.getTime());
}
}
}
});
add(new JLabel("Working"));
add(workingSpinner);
add(new JLabel("!Working"));
add(notWorkingSpinner);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new T());
frame.pack();
frame.setVisible(true);
}
}
来源:https://stackoverflow.com/questions/3875215/jspinner-dateeditor-must-include-year-even-though-start-and-end-is-the-same-year