1
votes

I have a one column JTable to which I set a custom TableCellRenderer and a custom TableCellEditor, that returns a JPanel containing JTextField, JLabel, JButtons and JProgressBar

When I try to delete a row from the table model, which is a class that extends AbstractTableModel. Here is the method for deleting rows.

public void removeRow(int row) {
 myList.remove(row);
 fireTableRowsDeleted(row, row);
}

The TableModel.fireTableRowsDeleted(int row) is not working as my table in the view keep showing the deleted row. On the other hand the method TableModel.fireTableStructureChanged(); updates the JTable properly. Which one should I use ? I checked the DefaultTableModel.removeRow(int row) method and it uses only fireTableRowsDeleted(int row);.

    public class TableTest {

    final static MyObjectTableModel model = new MyObjectTableModel();
    final static JTable table = new JTable(model);
    private static Map<Integer, Future> mapSubmittedReadProgress = new HashMap<Integer, Future>();
    final static StartProgressActionListener progressActionListener = new StartProgressActionListener();

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new TableTest().createGUI();
            }
        });
    }

    public static class MyObjectTableModel extends AbstractTableModel {

        private LinkedList<MyObject> myList;

        public MyObjectTableModel() {
            super();
            myList = new LinkedList<MyObject>();
        }

        public MyObjectTableModel(SortedSet<MyObject> myObjects) {
            super();
            this.myList = new LinkedList<MyObject>(myObjects);
        }

        public void addRow(MyObject myObject) {
            myList.add(myObject);
            fireTableRowsInserted(myList.size() - 1, myList.size() - 1);
        }

        public void removeRow(int row) {
            myList.remove(row);
            fireTableRowsDeleted(row, row);
//      fireTableStructureChanged();
        }

        @Override
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
            myList.set(rowIndex, (MyObject) aValue);
            fireTableCellUpdated(rowIndex, 0);
        }

        @Override
        public int getRowCount() {
            return myList.size();
        }

        @Override
        public int getColumnCount() {
            return 1;
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            switch (columnIndex) {
                case 0:
                    return MyObject.class;
                default:
                    throw new IllegalArgumentException("invalid column: " + columnIndex);
            }
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            switch (columnIndex) {
                case 0:
                    return myList.get(rowIndex);
                default:
                    throw new IllegalArgumentException("invalid column: " + columnIndex);
            }
        }

        public MyObject getMyObjectAt(int row) {
            return myList.get(row);
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return true;
        }

        public int getIndexOf(MyObject myObject) {
            return myList.indexOf(myObject);
        }
    }

    private static void createGUI() {
        JFrame f = new JFrame("TableTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        for (int i = 0; i < 16; i++) {
            MyObject myObject = new MyObject();
            myObject.setText1("" + i);
            model.addRow(myObject);
        }
        table.setOpaque(false);
        table.setShowGrid(false);
        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
//        table.getSelectionModel().addListSelectionListener(new SelectionListener());
        table.setDefaultRenderer(MyObject.class, new MyTableCellRenderer());
        table.setDefaultEditor(MyObject.class, new MyTableCellEditor());
        table.setFillsViewportHeight(true);
        f.add(new JScrollPane(table));

        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static class MyTableCellEditor extends AbstractCellEditor implements TableCellEditor {

        private MyObjectPanel myObjectPanel = new MyObjectPanel(model, table);
        private transient List<CellEditorListener> listeners;

        public MyTableCellEditor() {
            myObjectPanel.addStartProgressActionListener(progressActionListener);
            listeners = new ArrayList<>();
        }

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

        @Override
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
            MyObject myObject = (MyObject) value;
            myObjectPanel.setMyObject(myObject, row);
            return myObjectPanel;
        }

        @Override
        public Object getCellEditorValue() {
            MyObject myObject = myObjectPanel.getMyObject();
            return myObject;
        }

        @Override
        public void addCellEditorListener(CellEditorListener l) {
            listeners.add(l);
        }

        @Override
        public void removeCellEditorListener(CellEditorListener l) {
            listeners.remove(l);
        }

        @Override
        protected void fireEditingStopped() {
            ChangeEvent ce = new ChangeEvent(this);
            for (int i = listeners.size() - 1; i >= 0; i--) {
                ((CellEditorListener) listeners.get(i)).editingStopped(ce);
            }
        }
    }

    private static class MyTableCellRenderer implements TableCellRenderer {

        private MyObjectPanel myObjectPanel = new MyObjectPanel(model, table);

        public MyTableCellRenderer() {
            myObjectPanel.addStartProgressActionListener(progressActionListener);
//            setOpaque(false);
            int cWidth = table.getWidth();
//            setSize(new Dimension(cWidth, 1000));
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            MyObject myObject = (MyObject) value;
            myObjectPanel.setMyObject(myObject, row);
            table.setRowHeight(row, myObjectPanel.getPreferredSize().height);
            return myObjectPanel;
        }
    }

    private static class StartProgressActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            if(table.isEditing()) {
                table.getCellEditor().stopCellEditing();
            }
            final ExecutorService executor = Executors.newFixedThreadPool(1);
            Runnable progressRunnable = new ProgressRunnable(table.getSelectedRow());
            final Future<?> submit = executor.submit(progressRunnable);
            mapSubmittedReadProgress.put(table.getSelectedRow(), submit);
        }
    }

    private static class ProgressRunnable implements Runnable {

        private ExecutorService executor;
        private long beT;
        private int dur = 30; // s
        private int progress = 0;
        private int row;

        public ProgressRunnable(int row) {
            this.row = row;
            beT = System.currentTimeMillis();
        }

        @Override
        public void run() {
            boolean abort = false;
            int i = 0;
            while (i <= dur && !abort) {
                final long curT = System.currentTimeMillis();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    abort = true;
                    executor.shutdown();
                }
                if (Thread.currentThread().isInterrupted()) {
                    abort = true;
                    executor.shutdown();
                }
                progress = (int) Math.round(100 * ((double) (curT - beT) / 1000) / dur);
                MyObject myObject = new MyObject();
                myObject.setProgress(progress);
                table.setValueAt(myObject, row, 0);
                i++;
            }
        }
    }
}


MyObject :

    public class MyObject {
    private String text1;
    private String text2;
    private int progress;

    public String getText1() {
        return text1;
    }

    public void setText1(String text1) {
        this.text1 = text1;
    }

    public String getText2() {
        return text2;
    }

    public void setText2(String text2) {
        this.text2 = text2;
    }

    public int getProgress() {
        return progress;
    }

    public void setProgress(int progress) {
        this.progress = progress;
    }
}


MyObjectPanel :

public class MyObjectPanel extends javax.swing.JPanel {

private int row;
private MyObjectTableModel model;
private JTable table;

/**
 * Creates new form MyObjectPanel
 */
public MyObjectPanel() {
    initComponents();
}

MyObjectPanel(MyObjectTableModel model, JTable table) {
    this.model = model;
    this.table = table;
    initComponents();
}

/**
 * This method is called from within the constructor to initialize the form.
 * WARNING: Do NOT modify this code. The content of this method is always
 * regenerated by the Form Editor.
 */
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {

    jTextField1 = new javax.swing.JTextField();
    jTextField2 = new javax.swing.JTextField();
    jProgressBar1 = new javax.swing.JProgressBar();
    btnStart = new javax.swing.JButton();
    btnStop = new javax.swing.JButton();
    btnClose = new javax.swing.JButton();

    btnStart.setText("Start");

    btnStop.setText("Stop");

    btnClose.setText("Close");
    btnClose.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnCloseActionPerformed(evt);
        }
    });

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
    this.setLayout(layout);
    layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jTextField1)
            .addComponent(jTextField2)
            .addComponent(jProgressBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addGroup(layout.createSequentialGroup()
            .addComponent(btnStart)
            .addGap(18, 18, 18)
            .addComponent(btnStop)
            .addGap(18, 18, 18)
            .addComponent(btnClose)
            .addGap(0, 199, Short.MAX_VALUE)))
            .addContainerGap()));
    layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
            .addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addGap(18, 18, 18)
            .addComponent(jProgressBar1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addGap(18, 18, 18)
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
            .addComponent(btnStart)
            .addComponent(btnStop)
            .addComponent(btnClose))
            .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)));
}// </editor-fold>

private void btnCloseActionPerformed(java.awt.event.ActionEvent evt) {
    System.out.println("getMyObjectRow() : " + getMyObjectRow());
    table.getCellEditor().stopCellEditing();
    model.removeRow(getMyObjectRow());
}
// Variables declaration - do not modify
private javax.swing.JButton btnClose;
private javax.swing.JButton btnStart;
private javax.swing.JButton btnStop;
private javax.swing.JProgressBar jProgressBar1;
private javax.swing.JTextField jTextField1;
private javax.swing.JTextField jTextField2;
// End of variables declaration

void setMyObject(MyObject myObject, int row) {
    this.row = row;
    jTextField1.setText(myObject.getText1());
    jTextField2.setText(myObject.getText2());
    jProgressBar1.setValue(myObject.getProgress());
}

int getMyObjectRow() {
    return this.row;
}

MyObject getMyObject() {
    MyObject myObject = new MyObject();
    myObject.setText1(jTextField1.getText());
    myObject.setText2(jTextField2.getText());
    myObject.setProgress(jProgressBar1.getValue());
    return myObject;
}

void addStartProgressActionListener(ActionListener progressActionListener) {
    btnStart.addActionListener(progressActionListener);
}


Edit: Working SSCCE showing the closing issue using netbeans GUI builder for the panel. It seems to be an index problem, but couldn't find what exactly. I'll try to post complete exemple later with the progress bar working.

Edit 2 : I have problem with my progress bars not updating when the cell gain focus. The closing problem is solved.

1
TableModel.fireTableRowsDeleted(int row) is not working I doubt that. Post the complete code of your AbstractTableModel or even better, post an SSCCE. In all cases, you should not need to call fireTableStructureChangedGuillaume Polet
Are you certain you are removing the row on the Event Dispatch Thread ?Robin
How would I be certain of that ? Do I have to call the method removeRow(int row) from the model on the EDT ?jerome
several thingies are wrong (may or not be related): 1) the editor implementation is invalid - a) it must notify its observers on terminating edits b) getCellEditorValue is meant to return the edited value, not the value in the model 2) the renderer is ill-behaved in changing table state in getRendererComponent Also note that the row/column indices in the getEditor/RendererComponent are in view coordinate system: they might differ from the model after sorting/filtering or re-arranging the columnskleopatra
forgot 1c) don't let the editor change the model directly, that's the task of the listener (here: the table)kleopatra

1 Answers

1
votes

I tried your TableModel and it worked fine. You must have a problem someplace else in your code. I couldn't find MyObject, so the default renderer uses Object#toString() to display an empty one.

public class TableTest {

    private static class MyObject {
        // empty
    }

    private static class MyObjectTableModel extends AbstractTableModel {
        // no change
    }

    private static void createGUI() {
        JFrame f = new JFrame("TableTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final MyObjectTableModel model = new MyObjectTableModel();
        for (int i = 0; i < 16; i++) {
            model.addRow(new MyObject());
        }
        JTable table = new JTable(model);
        f.add(new JScrollPane(table));
        f.add(new JButton(new AbstractAction("Remove") {
            @Override
            public void actionPerformed(ActionEvent e) {
                model.removeRow(0);
            }
        }), BorderLayout.SOUTH);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

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