Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is JXTable losing input where JTable is not?

When I'm using a JXTable to render and edit my data, some input into the CellEditors gets lost. If I click on the Resizing-Divider of the JXTable-ColumnHeader or change the width of the JFrame, the CellEditor gets terminated without commiting the value. The values are saved if I use the JTable.

I want to use the JXTable because of its other features, so is there a way to fix the JXTable?

Screenrecording

Example:

package table.columnresize;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

import org.jdesktop.swingx.JXTable;

/**
 * Demo of differing behaviour of JXTable and JTable. JXTable loses input in a TableCell where JTable persists
 * it.
 * <p>
 * <table border=1>
 * <tr>
 * <th></th>
 * <th>JXTable</th>
 * <th>JTable</th>
 * </tr>
 * <tr>
 * <td>Click on TableColumnHeader</td>
 * <td>saved</td>
 * <td>saved</td>
 * </tr>
 * <tr>
 * <td>Resizing with Divider of TableColumnHeader</td>
 * <td>lost</td>
 * <td>saved</td>
 * </tr>
 * <tr>
 * <td>Changing the width of JFrame</td>
 * <td>lost</td>
 * <td>saved</td>
 * </tr>
 * 
 * </table>
 * </p>
 * 
 * @author bobndrew 2015-01-29
 */
public class JXTableAndJTableEditLossDemo
{
  private static class DataModel extends DefaultTableModel
  {
    public DataModel( Object[][] data, Object[] columnNames )
    {
      super( data, columnNames );
    }
  }

  private static void createAndShowUI()
  {
    Object[][] DATA = { { "One", 1 }, { "Two", 2 }, { "Three", 3 }, { "Four", 4 }, { "Five", 5 } };
    String[] COLUMNS = { "A", "B" };
    DataModel dataModel = new DataModel( DATA, COLUMNS );

    JFrame frame1 = new JFrame( "JXTable" );
    JXTable jXTable = new JXTable( dataModel );
    //does not change anything:    jXTable.setTerminateEditOnFocusLost( true );
    System.out.println( jXTable.isTerminateEditOnFocusLost() );
    frame1.add( new JScrollPane( jXTable ) );
    frame1.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    frame1.pack();
    frame1.setVisible( true );

    JFrame frame2 = new JFrame( "JTable" );
    JTable jTable = new JTable( dataModel );
    //does not change anything:    jTable.putClientProperty( "terminateEditOnFocusLost", Boolean.FALSE );
    System.out.println( jTable.getClientProperty( "terminateEditOnFocusLost" ) );
    frame2.add( new JScrollPane( jTable ) );
    frame2.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    frame2.pack();
    frame2.setLocation( (int) frame1.getLocation().getX() + frame1.getWidth() + 100, (int) frame1
        .getLocation().getY() );
    frame2.setVisible( true );
  }

  public static void main( String[] args )
  {
    java.awt.EventQueue.invokeLater( new Runnable()
    {
      @Override
      public void run()
      {
        createAndShowUI();
      }
    } );
  }

}
like image 237
bobndrew Avatar asked Jan 30 '15 12:01

bobndrew


2 Answers

While debugging the JXTable and the JTable I found the reason for the loss of CellEdits. The difference is in the method columnMarginChanged():

JXTable:

if (isEditing()) {
  removeEditor();
}

JTable:

if (isEditing() && !getCellEditor().stopCellEditing()) {
  getCellEditor().cancelCellEditing();
} 

At first I thought the removeEditor() method is an enhancement of the JTable... But then I found this OpenJDK changeset from September 2010 which fixes the bug "4330950: Lost newly entered data in the cell when resizing column width". It seems that changes from the JDK were not applied to the SwingX sourcecode.

I will accept my own answer because the reason for different behaviour is now clear. To fix this for me and other SwingX users I will head over to the SwingX mailing list and the bug tracker.

like image 131
bobndrew Avatar answered Nov 05 '22 03:11

bobndrew


When you take a look at the frameInit() method of JTable, you can see that it binds to all AWTEvent.WINDOW* events. In JXTable the initActionsAndBindings() method binds to specific actions (such as value changed), and only for the table.

You will need to add your own listener

    jXTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
        @Override
        public void columnMarginChanged(ChangeEvent e) {

        }
    });

and then would have to expose some functionality of the table to allow the event to trigger updating the table. Or perhaps you can trigger an TableModelEvent from there.

like image 25
kol Avatar answered Nov 05 '22 02:11

kol