Set step for a DateEditor in a JSpinner

巧了我就是萌 提交于 2019-12-11 10:12:39

问题


How can a step be specified for JSpinner? (for example of 10 minutes instead of 1)

There is a model that allow select step size for numbers

SpinnerNumberModel(value, min, max, step); 

but how to set it for Dates?

This code

JSpinner timeSpinner = new JSpinner( new SpinnerDateModel() );
DateEditor timeEditor = new DateEditor(timeSpinner, "HH:mm:ss");
timeSpinner.setEditor(timeEditor);
timeSpinner.setValue(new Date()); // will only show the current time

(From this answer Is there any good and free Date AND Time Picker available for Java Swing?)

Allows edit time only, but the steps are always 1 hour, 1 minute, or 1 second depending on if "HH", "HH:mm", or "HH:mm:ss" is specified.


Is there any simple way to have min, max, and step for minutes?

Thanks


回答1:


Use DateEditor.getModel() to get the SpinnerDateModel which provides setStart,setEnd and setCalendarField.




回答2:


As you can see in the code of SpinnerDateModel:

public Object getNextValue() {
    Calendar cal = Calendar.getInstance(); //Current date.
    cal.setTime(value.getTime()); //Set the date to the current value of the model.
    cal.add(calendarField, 1); //Increment the date by 1 unit of the selected calendar field (e.g. 1 month).
    Date next = cal.getTime(); //Convert back to Date Object.
    return ((end == null) || (end.compareTo(next) >= 0)) ? next : null;
}

public Object getPreviousValue() {
    Calendar cal = Calendar.getInstance(); //Current date.
    cal.setTime(value.getTime()); //Set the date to the current value of the model.
    cal.add(calendarField, -1); //Decrement the date by 1 unit of the selected calendar field (e.g. 1 month).
    Date prev = cal.getTime(); //Convert back to Date Object.
    return ((start == null) || (start.compareTo(prev) <= 0)) ? prev : null;
}

It works with adding or substracting 1 unit of the specified calendar field.
You want to customize this number 1 to something else.
So I see two options here:

  1. Reimplement AbstractSpinnerModel. Just copy-paste SpinnerDateModel (it's NOT big), then introduce your integer field "step" for example, and instead of the number 1, just put "step" in getNext and getPrevious.
  2. Implement a SpinnerDateModel, which also works internally with a SpinnerDateModel. It's going to be a lot smaller, but is a bit hackish I guess.

Follows the code of such an SpinnerDateModel (case 2):

import java.awt.GridLayout;
import java.util.Calendar;
import java.util.Date;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;

public class MultiStepDateSpinner extends JPanel {
    private static class StepperSpinnerDateModel extends SpinnerDateModel {
        private final SpinnerDateModel internal; //We let the internal SpinnerDateModel do the work for us.
        private final int step; //The number of steps to increment and decrement per click.
        private Object currentValue; //Needed to get restored each time getPreviousValue and getNextValue is called.

        private StepperSpinnerDateModel(final Date value, final Comparable start, final Comparable end, final int calendarField, final int step) {
            internal = new SpinnerDateModel(value, start, end, calendarField);
            if (step <= 0)
                throw new IllegalArgumentException("Non positive step.");
            this.step = step;
            currentValue = internal.getValue();
        }

        private StepperSpinnerDateModel(final int step) {
            this(new Date(), null, null, Calendar.DAY_OF_MONTH, step);
        }

        @Override
        public Object getValue() {
            return currentValue;
        }

        @Override
        public void setValue(final Object value) {
            internal.setValue(value);
            currentValue = value;
            fireStateChanged(); //Important step for the spinner to get updated each time the model's value changes.
        }

        @Override
        public Object getNextValue() {
            Object next = null;
            for (int i=0; i<step; ++i) { //Calculate step next values:
                next = internal.getNextValue();
                internal.setValue(next); //We have to set the next value internally, in order to recalculate the next-next value in the next loop.
            }
            internal.setValue(currentValue); //Restore current value.
            return next;
        }

        @Override
        public Object getPreviousValue() {
            Object prev = null;
            for (int i=0; i<step; ++i) { //Calculate step previous values:
                prev = internal.getPreviousValue();
                internal.setValue(prev); //We have to set the previous value internally, in order to recalculate the previous-previous value in the next loop.
            }
            internal.setValue(currentValue); //Restore current value.
            return prev;
        }
    }

    private MultiStepDateSpinner() {
        super(new GridLayout(0, 1));

        //Increment and decrement by 4 minutes each step.
        //The null values indicate there shall be no minimum nor maximum date.
        //The current value is set to the current date.
        final JSpinner spinner = new JSpinner(new StepperSpinnerDateModel(new Date(), null, null, Calendar.MINUTE, 4));

        final JButton getValueButton = new JButton("Get value");
        getValueButton.addActionListener(e -> {
            JOptionPane.showMessageDialog(null, spinner.getValue(), "Got value", JOptionPane.PLAIN_MESSAGE);
        });

        add(spinner);
        add(getValueButton);
    }

    public static void main(final String[] args) {
        final JFrame frame = new JFrame("Frame");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(new MultiStepDateSpinner());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

The example code above, is runnable, so you can see it in practice.

Why not just let it be an AbstractSpinnerModel instead of SpinnerDateModel? Because we need it to be identified as an instance of SpinnerDateModel so the JSpinner internally allocates by default a DateEditor for the editor.
Even if you extend AbstractSpinnerModel and supply the spinner with a DateEditor, you will get an exception complaining about the model not being a SpinnerDateModel.

The cleanest way though as I see it, is to reimplement AbstractSpinnerModel and copy paste the code of SpinnerDateModel if needed (which is not big) and introduce any fields you feel.



来源:https://stackoverflow.com/questions/10353087/set-step-for-a-dateeditor-in-a-jspinner

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!