javaswingxjxtable

DatePickerCellEditor resets the time when cell editing starts and stops


I have a JXTable with two columns that hold string values and one column that holds a date time value that follows this format: yyyy-MM-dd HH:mm:ss.

Since I only care about the users changing the date, I thought I'd make use of DatePickerCellEditor from the Swingx package with a custom SimpleDateFormat("yyyy-MM-dd HH:mm:ss").

So far so good, except the problems start to occur when editing starts. The date part of the datetime is maintained, however, the time portion of it gets reset to "00:00:00" and to make matters worse, when I move to edit another cell, the value that remains in the cell that was being edited is the proper date, but the time at 00:00:00.

In other words, this Before edit becomes this enter image description here

I have tried many things to fix this, including overriding the getTableCellEditorComponent from DatePickerCellEditor and printing the date directly from the JXDatePicker. I found out that somewhere along the way, the time part of the day was being reset to "00:00:00". I couldn't find out where. I then tried, within the getTableCellEditorComponent, manually setting the date by using JXDatePicker#setDate and that also didn't work.

One workaround I found was, inside getTableCellEditorComponent, calling JXDatePicker#getEditor and manually setting the date there, however, the time part of the cell value still gets reset when editing stops.

public class SQLDatePickerCellEditor extends DatePickerCellEditor {

    public SQLDatePickerCellEditor(DateFormat dateFormat) {
        super(dateFormat);
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
        JXDatePicker picker = (JXDatePicker) super.getTableCellEditorComponent(table, value, isSelected, row, column);
        picker.getEditor().setText(value.toString());
        System.out.println(picker.getDate()); //this prints the correct date but for some reason at 00:00:00
        return picker;
    }

}

Here's the sample code to work with:

public class DateForm extends javax.swing.JFrame {

    public DateForm() {
        initComponents();
        pack();
        setLocationRelativeTo(null);
        initModel();
    }

    public void initModel() {
        Class<?>[] types = new Class<?>[]{String.class, String.class, Date.class};
        Object[][] data = new Object[][]{
            {"John", "Smith", new Date(System.currentTimeMillis())}
        };
        DefaultTableModel model = new DefaultTableModel(data, new String[]{"Name", "Surname", "Date"}) {
            @Override
            public Class<?> getColumnClass(int columnIndex) {
                return types[columnIndex];
            }
        };

        table.setModel(model);
        table.getColumn(2).setCellEditor(new DatePickerCellEditor(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));
        StringValue sv = new FormatStringValue(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        TableCellRenderer r = new DefaultTableRenderer(sv);
        table.getColumn(2).setCellRenderer(r);
    }

    @SuppressWarnings("unchecked")                      
    private void initComponents() {

        scrPane = new javax.swing.JScrollPane();
        table = new org.jdesktop.swingx.JXTable();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        setPreferredSize(new java.awt.Dimension(500, 500));
        scrPane.setViewportView(table);

        getContentPane().add(scrPane, java.awt.BorderLayout.CENTER);
    }                     

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                DateForm df = new DateForm();
                df.setVisible(true);
            }
        });
    }

    private javax.swing.JScrollPane scrPane;
    private org.jdesktop.swingx.JXTable table;               

}

Is this behaviour intended? I am aware that JXDatePicker is for dates only, but I naturally assumed that it would preserve the time as long as the user didn't change the date.


Solution

  • Oh god, I think I did it. After MUCH digging, the problem lays with the JXMonthView component that is inside the JXDatePicker. In short, it's using the wrong selection model. For it to properly work, we need to instantiate a new SingleDaySelectionModel and pass it to the JXMonthView.

    Documentation for the JXDatePicker#setDate method that gave me the answer to my problem:

    Sets the date property.

    Does nothing if the ui vetos the new date - as might happen if the code tries to set a date which is unselectable in the monthView's context. The actual value of the new Date is controlled by the JXMonthView's DateSelectionModel. The default implementation normalizes the date to the start of the day in the model's calendar's coordinates, that is all time fields are zeroed. To keep the time fields, configure the monthView with a SingleDaySelectionModel.

    Default initialisation in JXDatePicker.java - as you can see, the line we need is commented:

    private void initMonthView() {
        _monthView = new JXMonthView();
        //        _monthView.setSelectionModel(new SingleDaySelectionModel());
        _monthView.setTraversable(true);
        _monthView.addPropertyChangeListener(getMonthViewListener());
    }
    

    Since inheriting from JXDatePicker wasn't an option, what I did was create a custom CellEditor like so:

    public class SQLDatePickerCellEditor extends DatePickerCellEditor {
    
        public SQLDatePickerCellEditor() {
            super(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
            datePicker.getMonthView().setSelectionModel(new SingleDaySelectionModel());
        }
    
    }
    

    This preserves the time as needed.

    Note: I'm using the compiled .jar so I can't edit source files. If using editable source files, naturally the best solution is to simply uncomment the line.