Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

a bug of JTable.columnMoved method

javax.swing.JTable has a bug, if we sort the table while a null valued cell is editing, and whose column class does not have a constructor with "new Object[] { new String() }" parameter, eg. Long.class, the JTable will throw an exception.

I think that because javax.swing.JTable tries to remove the cell editor twice; So I plan to override the JTable.columnMoved method as:

@Override
public void columnMoved(TableColumnModelEvent e) {
    if (isEditing() && !getCellEditor().stopCellEditing()) {
        if(getCellEditor() != null) { // In javax.swing.JTable, no this check point
            getCellEditor().cancelCellEditing();
        }
    }
    repaint();
}

I felt that's not good enough, since it's not friendly for code readers, they may know JTable well, and do not like my sub classes like this. Is there a better solution? Thanks a lot.

When running the following codes, double click one cell (do not input anything) and then click the header, the exception will show up.

public class NewJFrame extends javax.swing.JFrame {

    public NewJFrame() {
        initComponents();
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        jTable1 = new javax.swing.JTable();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jTable1.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null},
                {null, null},
                {null, null},
                {null, null}
            },
            new String [] {
                "Title 1", "Title 2"
            }
        ) {
            Class[] types = new Class [] {
                java.lang.Long.class, java.lang.Long.class
            };

            public Class getColumnClass(int columnIndex) {
                return types [columnIndex];
            }
        });
        jScrollPane1.setViewportView(jTable1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 375, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(15, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 275, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(14, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new NewJFrame().setVisible(true);
            }
        });
    }
    // Variables declaration - do not modify
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTable jTable1;
    // End of variables declaration
}
like image 379
newda Avatar asked Feb 19 '23 09:02

newda


2 Answers

I can reproduce the issue with the code you provided. I blame the JTable#stopCellEditing implementation

    public boolean stopCellEditing() {
        String s = (String)super.getCellEditorValue();
        // Here we are dealing with the case where a user
        // has deleted the string value in a cell, possibly
        // after a failed validation. Return null, so that
        // they have the option to replace the value with
        // null or use escape to restore the original.
        // For Strings, return "" for backward compatibility.
        if ("".equals(s)) {
            if (constructor.getDeclaringClass() == String.class) {
                value = s;
            }
            super.stopCellEditing();
        }

        try {
            value = constructor.newInstance(new Object[]{s});
        }
        catch (Exception e) {
            ((JComponent)getComponent()).setBorder(new LineBorder(Color.red));
            return false;
        }
        return super.stopCellEditing();
    }

You enter the first if, where super.stopCellEditing is called. The return value of this call is ignored. Then the newInstance call throws an exception, which is catched but then false is returned, no matter what the return value was of super.stopCellEditing. This sounds incorrect to me. I would log the bug with Oracle with the code you provided

like image 195
Robin Avatar answered Feb 28 '23 12:02

Robin


I can't see any issue, any bug, TableCellEditor is canceled properly on sorting and column reordering,

import java.awt.*;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;

public class TableWithTimer {

    private static final long serialVersionUID = 1L;
    private JFrame frame = new JFrame();
    private JScrollPane scroll = new JScrollPane();
    private JTable myTable;
    private String[] head = {"One", "Two", "Three", "Four", "Five", "Six"};
    private Object[][] data = {{null, null, null, null, null, null},
        {null, null, null, null, null, null},
        {null, null, null, null, null, null},
        {null, null, null, null, null, null},
        {null, null, null, null, null, null},
        {null, null, null, null, null, null},
        {null, null, null, null, null, null}};
    private DefaultTableModel model;

    public TableWithTimer() {
        model = new DefaultTableModel(data, head) {

            private static final long serialVersionUID = 1L;

            @Override
            public Class<?> getColumnClass(int colNum) {
                switch (colNum) {
                    case 0:
                        return Integer.class;
                    case 1:
                        return Double.class;
                    case 2:
                        return Long.class;
                    case 3:
                        return Boolean.class;
                    default:
                        return String.class;
                }
            }
        };
        myTable = new JTable(model);
        myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        myTable.setGridColor(Color.gray);
        myTable.setFillsViewportHeight(true);
        myTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        myTable.setAutoCreateRowSorter(true);
        scroll.setViewportView(myTable);
        frame.add(scroll, BorderLayout.CENTER);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocation(100, 100);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                TableWithTimer tableWithTimer = new TableWithTimer();
            }
        });
    }
}
like image 21
mKorbel Avatar answered Feb 28 '23 11:02

mKorbel