Search code examples
javaswingdatepickerjtable

TableCellEditor that is based on JXDatePicker doesn't dissapear after the date selection was made and never gives a way to TableCellRenderer methods


I have a TableCellEditor like that:

import javax.swing.*;
import javax.swing.event.CellEditorListener;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.EventObject;

public class JTimePeriodSelectorTableCellEditor extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
  private static final long serialVersionUID = -3458207749295701928L;
  private JXDatePicker datePicker;
  private static final int prefWidthPicker = 150;
  private static final int prefHeightPicker = 22;

  public JTimePeriodSelectorTableCellEditor() {
    this("dd.MM.yyyy HH:mm:ss");
  }

  public JTimePeriodSelectorTableCellEditor(final String dateFormat) {
    super();
    initUI(dateFormat);
  }

  public void setKeyListener(KeyListener keyListener) {
    datePicker.getDateField().addKeyListener(keyListener);
  }

  private JXDatePicker createDateComponent(final String dateFormat, final Date date) {
    JXDatePicker datePicker = new JXDatePicker(date.getTime());
    datePicker.setFormats(dateFormat);
    datePicker.setMaximumSize(new Dimension(prefWidthPicker, prefHeightPicker));
    datePicker.setPreferredSize(new Dimension(prefWidthPicker, prefHeightPicker));

    datePicker.addActionListener(e -> {
      try {
        datePicker.commitEdit();
      } catch (ParseException ex) {};
      stopCellEditing();
    });

    return datePicker;
  }

  private JLabel createLabel(final String text) {
    return new JEditLabel(text);
  }

  private void initUI(final String dateFormat) {
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);

    datePicker = createDateComponent(dateFormat, calendar.getTime());

    Dimension separatorDim = new Dimension(3, 1);

  }

  public Date getDate() {
    return datePicker.getDate();
  }

  public void setDate(final Date date) {
    datePicker.setDate(date);
  }


  public void setEnabled(final boolean enabled) {
    datePicker.setEnabled(enabled);
  }

  /**
   * @param args
   */
  public static void main(final String[] args) throws InterruptedException, InvocationTargetException {
    SwingUtilities.invokeAndWait(new Runnable() {
      @Override
      public void run() {
        try {
          UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
          JFrame mainFrame = new JFrame("BasicApp");
          mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          final JTimePeriodSelectorTableCellEditor jCalendar = new JTimePeriodSelectorTableCellEditor();
          //mainFrame.add(jCalendar, BorderLayout.CENTER);
          mainFrame.pack();
          mainFrame.setVisible(true);
          mainFrame.setLocationRelativeTo(null);
        } catch (Exception e) {
          e.printStackTrace();
        }

      }
    });
  }

  @Override
  public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
    return datePicker;
  }

  @Override
  public Object getCellEditorValue() {
    return new Date(datePicker.getDate().getTime());
  }

  @Override
  public boolean isCellEditable(EventObject anEvent) {
    return true;
  }


  @Override
  public void cancelCellEditing() {

  }

  @Override
  public boolean shouldSelectCell(EventObject anEvent) {
    if (anEvent instanceof MouseEvent) {
      MouseEvent e = (MouseEvent)anEvent;
      return e.getID() != MouseEvent.MOUSE_DRAGGED;
    }
    return true;
  }
  @Override
  public boolean stopCellEditing() {
    return super.stopCellEditing();
  }

  @Override
  public void addCellEditorListener(CellEditorListener l) {
  }

  @Override
  public void removeCellEditorListener(CellEditorListener l) {
  }

  @Override
  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    JLabel label = new JLabel();
    if (value instanceof Date) {
      label.setText(value.toString()+" edit stopped");
    }
    return label;
  }
}

and a TableCellRenderer like that:

import javax.swing.table.DefaultTableCellRenderer;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateRenderer extends DefaultTableCellRenderer {
    private SimpleDateFormat dateFormat;

    public DateRenderer(String format) {
        super();
        dateFormat = new SimpleDateFormat(format);
    }

    @Override
    public void setValue(Object value) {
        if (value instanceof Date) {
            super.setText(dateFormat.format((Date) value));
        } else {
            super.setValue(value);
        }
    }
}

JEditLabel used in the first class is:

import javax.swing.*;

public class JEditLabel extends JLabel {

  private static final long serialVersionUID = -4872698588890575636L;

  public static class JEditLabelBuilder {
    private String text;
    private Icon icon;
    private int horizontalAlignment;

    public JEditLabelBuilder() {
      setText("");
      setHorizontalAlignment(SwingConstants.LEADING);
    }

    public JEditLabelBuilder setText(final String text) {
      this.text = text;
      return this;
    }

    public JEditLabelBuilder setIcon(final Icon icon) {
      this.icon = icon;
      return this;
    }

    public JEditLabelBuilder setHorizontalAlignment(final int horizontalAlignment) {
      this.horizontalAlignment = horizontalAlignment;
      return this;
    }

    public JEditLabel build() {
      return new JEditLabel(this);
    }

  }

  public JEditLabel(final String text) {
    this((new JEditLabelBuilder()).setText(text));
  }

  protected JEditLabel(final JEditLabelBuilder builder) {
    super();
    setText(builder.text);
    setIcon(builder.icon);
    setHorizontalAlignment(builder.horizontalAlignment);
  }

}

so then I upply them both the column of the JTable:

TableColumn timeColumn = changeTrafficProviderPaymentsTable.getColumnModel().getColumn(1);
JTimePeriodSelectorTableCellEditor timeColumnEditor = new JTimePeriodSelectorTableCellEditor();
timeColumn.setCellEditor(timeColumnEditor);
timeColumn.setCellRenderer(new DateRenderer("yyyy-MM-dd HH:mm:ss"));

I expect, that after date selection, the Editor will complete its work and will be switched to the Renderer. This doesn't happens on date selection in JXDatePicker. How can I trigger this change from Editor in the cell to Renderer in the cell and picj up the inserted datePicker's data into the JTable.getModel()?


Solution

  • The problem is here:

    @Override
    public void cancelCellEditing() {
    
    }
    

    And here:

    @Override
    public boolean stopCellEditing() {
    return super.stopCellEditing();
    }
    
    @Override
    public void addCellEditorListener(CellEditorListener l) {
    }
    
    @Override
    public void removeCellEditorListener(CellEditorListener l) {
    }
    

    Those methods have a purpose. CellEditorListeners are how JTables (and other components that use cell editors) know when editing is started and stopped.

    You have removed that important functionality entirely, so a JTable has no idea what your cell editor is doing.

    Remove all of the above code. Let AbstractCellEditor do its job and properly handle listeners. Your class’s job is to notify those listeners by calling fireEditingStopped and fireEditingCanceled at the appropriate times.