4

MY Table

I have a table like above. Initially all the cells except button column are not editable. I have created the table using custom TableModel. My isCellEditable in custom TableModel looks like this:

public boolean isCellEditable(int rowIndex, int columnIndex) { 
    //System.out.println("isCellEditable: " + rowIndex + " " + columnIndex);
    if(getColumnClass(columnIndex) == JButton.class)
        return true;
    else
        return false;
}

But when I click on the Edit button of each row a JDialog will pop up with that row (by constructing a JTable in this dialog with only one row.) I can update the cell values in this JDialog's table. and then update them in the respective cells of that row.

JDialog's Table

I successfully updated the main table's row after updating here, only if my isCellEditable return true. But this should not happen. Only when I click then I should be able to change isCellEditable of the row as Editable and update and then make it uneditable.

I have seen the following post but was unable to implement it.

Amarnath
  • 8,736
  • 10
  • 54
  • 81
  • you didn't read my answers to [your last two questions about the same issue](http://stackoverflow.com/questions/12245777/jbuttons-action-listener-not-working-in-jtable) and [here too](http://stackoverflow.com/questions/12381497/unable-to-get-column-index-with-table-getcolumn-method-using-custom-table-model) , isn't it ???, don't to reinvent the wheel ... – mKorbel Sep 13 '12 at 11:53
  • No, I read and I understood. I have implemented getColumnClass and isCellEditable methods in my custom model. But my doubt is that the rows should be editable only after clicking the button of that respective row and then make updation and make that row again uneditable. Please explain me if I am not getting. – Amarnath Sep 13 '12 at 12:26
  • `my question is ---->` is there really the reason (in your case and knowledge about `Java Essential Classes` and `Swing`) to use `AbstractTableModel`, please if isn't there really important reason, then to start with combinations JTable and DefaultTableModel, because all notifiers are implemented quite correctly, without bothering to override that – mKorbel Sep 13 '12 at 12:56
  • 2
    just to emphasize one bullet in @mKorbel's answer: Do.Not.Store.Components.In.Your.Model. Instead, implement a custom renderer/editor that paints the cell using a button. As to your question: from your description, Idon't quite understand what you are really after .. a) on clicking the button show a dialog which contains another table with a single row and allow editing in that dialog table only b) click the button and temporarily allow in-place editing in the table that contains the buttons? – kleopatra Sep 13 '12 at 15:03
  • Yeah I am using custom renderer to do this. No issues with that. But answering to your questions, the reason is that we have many columns (say around 150.) So when the user clicks he should be able to get only those columns that need to be editable. So instead of user scrolling from left to right, we are showing them only those columns that need to be updated. – Amarnath Sep 13 '12 at 15:29
  • reasons by your XxxLead could be wrong, depends of JTable and its changes on Runtime, I'm lazy, then why to wrote 1000line instead of 2-5pct of them, let it be that your job, your XxxLeader, her/his decision – mKorbel Sep 13 '12 at 15:31
  • 1
    okay, and that _showing them only those column that need to be updated_ is done in a dialog? – kleopatra Sep 13 '12 at 16:03
  • I'm insisting on that detail because if so, there's no need to tweak the editability of the full model: create another model populated by the filtered (column-wise) row of the original, make that other editable and on commit let it write back the changed values to the original. – kleopatra Sep 13 '12 at 16:13
  • @kleopatra I got what you are saying. But consider that we have 1000 rows and 150 columns. There are buttons present in between columns (say for every 30 columns there is button) like the foll. (col-1 col-2 .. Button-Column col-31 col-32 Button-Column col-61 ..) So if a user clicks button column then 30 columns associated with that button will be shown in a dialog and you can update it if you want and write it back. So instead of showing all the rows I am showing only those rows that are associated with that button. I hope this clears about the design. – Amarnath Sep 13 '12 at 16:50
  • sounds like a horrible user experience. Anyway, 30 columns and 1000 rows? Hmm ... after re-reading your question, it would be 30 columns and 1 row? – kleopatra Sep 14 '12 at 08:52

2 Answers2

6

XxxTableModel stores String.class for JButton as Renderer / Editor for JTable

and for your code (is based on) too

EDIT

DefaultTableModel

import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;

public class TableButton3 extends JFrame {

    private static final long serialVersionUID = 1L;
    private JTable table;

    public TableButton3() {
        String[] columnNames = {"Date", "String", "Decimal", "Remove"};
        Object[][] data = {
            {new Date(), "A", new Double(5.1), "Remove"}, {new Date(), "B", new Double(6.2), "Remove"},
            {new Date(), "C", new Double(7.3), "Remove"}, {new Date(), "D", new Double(8.4), "Remove"},
            {new Date(), "A", new Double(5.1), "Remove"}, {new Date(), "B", new Double(6.2), "Remove"},};
        DefaultTableModel model = new DefaultTableModel(data, columnNames){

            private static final long serialVersionUID = 1L;

            @Override//  Returning the Class of each column will allow different renderers to be used based on Class
            public Class getColumnClass(int column) {
                switch (column) {
                    case 0:
                        return Date.class;
                    case 2:
                        return Double.class;
                    default:
                        return String.class;
                }
                //return getValueAt(0, column).getClass();
            }
        };
        table = new JTable(model) ;
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane(table);
        getContentPane().add(scrollPane);
        ButtonColumn buttonColumn = new ButtonColumn(table, delete, 3);
        buttonColumn.setMnemonic(KeyEvent.VK_D);
    }

    public static void main(String[] args) {
        TableButton3 frame = new TableButton3();
        frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
    //
    private Action delete = new AbstractAction() {

        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            JTable table = (JTable) e.getSource();
            int modelRow = Integer.valueOf(e.getActionCommand());
            ((DefaultTableModel) table.getModel()).removeRow(modelRow);
            table.clearSelection();
        }
    };
}

AbstractTableModel

import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;

public class TableButton3 extends JFrame {

    private static final long serialVersionUID = 1L;
    private JTable table;
    private MyTableModel myModel = new MyTableModel();

    public TableButton3() {
        String[] columnNames = {"Date", "String", "Decimal", "Remove"};
        Object[][] data = {
            {new Date(), "A", new Double(5.1), "Remove"}, {new Date(), "B", new Double(6.2), "Remove"},
            {new Date(), "C", new Double(7.3), "Remove"}, {new Date(), "D", new Double(8.4), "Remove"},
            {new Date(), "A", new Double(5.1), "Remove"}, {new Date(), "B", new Double(6.2), "Remove"},};
        DefaultTableModel model = new DefaultTableModel(data, columnNames){

            private static final long serialVersionUID = 1L;

            @Override//  Returning the Class of each column will allow different renderers to be used based on Class
            public Class getColumnClass(int column) {
                switch (column) {
                    case 0:
                        return Date.class;
                    case 2:
                        return Double.class;
                    default:
                        return String.class;
                }
                //return getValueAt(0, column).getClass();
            }
        };
        table = new JTable(model);
        table = new JTable(myModel);
        addTableDatas();
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane(table);
        add(scrollPane);
        ButtonColumn buttonColumn = new ButtonColumn(table, delete, 3);
        buttonColumn.setMnemonic(KeyEvent.VK_D);
    }

    public static void main(String[] args) {
        TableButton3 frame = new TableButton3();
        frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    private void addTableDatas() {
        Vector<String> columnNameS = new Vector<String>();
        columnNameS.add("Date");
        columnNameS.add("String");
        columnNameS.add("Decimal");
        columnNameS.add("Remove");
        myModel.setColumnNames(columnNameS);

        Vector<Object> row1 = new Vector<Object>();
        row1.add(new Date());
        row1.add("A");
        row1.add(new Double(5.1));
        row1.add("Remove");
        myModel.addRow(row1);

        row1 = new Vector<Object>();
        row1.add(new Date());
        row1.add("B");
        row1.add(new Double(6.2));
        row1.add("Remove");
        myModel.addRow(row1);

        row1 = new Vector<Object>();
        row1.add(new Date());
        row1.add("B");
        row1.add(new Double(8.4));
        row1.add("Remove");
        myModel.addRow(row1);

        row1 = new Vector<Object>();
        row1.add(new Date());
        row1.add("B");
        row1.add(new Double(5.1));
        row1.add("Remove");
        myModel.addRow(row1);

        row1 = new Vector<Object>();
        row1.add(new Date());
        row1.add("B");
        row1.add(new Double(6.2));
        row1.add("Remove");
        myModel.addRow(row1);
    }

    private class MyTableModel extends AbstractTableModel {

        private static final long serialVersionUID = 1L;
        private Vector<Vector<Object>> data;
        private Vector<String> colNames;
        private boolean[] _columnsVisible = {true, true, true, true};

        public MyTableModel() {
            this.colNames = new Vector<String>();
            this.data = new Vector<Vector<Object>>();
        }

        public MyTableModel(Vector<String> colnames) {
            this.colNames = colnames;
            this.data = new Vector<Vector<Object>>();
        }

        public void resetTable() {
            this.colNames.removeAllElements();
            this.data.removeAllElements();
        }

        public void setColumnNames(Vector<String> colNames) {
            this.colNames = colNames;
            this.fireTableStructureChanged();
        }

        public void addRow(Vector<Object> data) {
            this.data.add(data);
            this.fireTableRowsInserted(data.size() - 1, data.size() - 1);
        }

        public void removeRowAt(int row) {
            this.data.removeElementAt(row);
            this.fireTableRowsDeleted(row - 1, data.size() - 1);
        }

        @Override
        public int getColumnCount() {
            return this.colNames.size();
        }

        @Override
        public Class<?> getColumnClass(int colNum) {
            switch (colNum) {
                case 0:
                    return Date.class;
                case 2:
                    return Double.class;
                default:
                    return String.class;
            }
        }

        @Override
        public boolean isCellEditable(int row, int colNum) {
            switch (colNum) {
                default:
                    return true;
            }
        }

        @Override
        public String getColumnName(int colNum) {
            return this.colNames.get(colNum);
        }

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

        @Override
        public Object getValueAt(int row, int col) {
            Vector<Object> value = this.data.get(row);
            return value.get(col);
        }

        @Override
        public void setValueAt(Object newVal, int row, int col) {
            Vector<Object> aRow = data.elementAt(row);
            aRow.remove(col);
            aRow.insertElementAt(newVal, col);
            fireTableCellUpdated(row, col);
        }

        public void setColumnVisible(int index, boolean visible) {
            this._columnsVisible[index] = visible;
            this.fireTableStructureChanged();
        }
    }
    //
    private Action delete = new AbstractAction() {

        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            JTable table = (JTable) e.getSource();
            int modelRow = Integer.valueOf(e.getActionCommand());
            ((DefaultTableModel) table.getModel()).removeRow(modelRow);
            table.clearSelection();
        }
    };
}

both returns the same GUI, with the same ...

enter image description here

mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • @mKorbel Yeah got it .. but what to do its part of my job .. anyways I am enjoying learning. Never mind whether it is old or new .. :) – Amarnath Sep 13 '12 at 17:00
  • @Che you are really crazy, I'm promise that I leaving to answer your futures question :-) – mKorbel Sep 13 '12 at 18:08
  • @trashgod wrote `for trying out AbstractTableModel` .... lightning from a blue sky :-) – mKorbel Sep 13 '12 at 18:11
  • I was trying for [ironic understatement](http://en.wikipedia.org/wiki/Litotes), but I like your metaphor! :-) Searching for "`extends AbstractTableModel`" proved fruitful: [yours](http://stackoverflow.com/search?q=user%3A714968+%5Bjtable%5D+%22extends+abstracttablemodel%22&submit=search), [mine](http://stackoverflow.com/search?q=user%3A230513+%5Bjtable%5D+%22extends+abstracttablemodel%22&submit=search). – trashgod Sep 13 '12 at 20:05
  • @trashgod hehehe [aaand our great lady](http://stackoverflow.com/search?q=user%3A203657+[jtable]+%22extends+abstracttablemodel%22), btw I love black black humor :-) – mKorbel Sep 13 '12 at 20:14
  • I am confused by your `isCellEditable()` in the `AbstractTableModel`: You're switching over the `colNum` ... but the switch only has the default case, which therefore always returns true. So wouldnt it be sufficient to remove the `switch-case` and only `return true;`. Or am I missing something? – hamena314 Mar 29 '16 at 09:30
  • 1
    @hamena314 this is failtrue in Java, all cells in `JTable`s view returns true from override in `isCellEditable()`, as aside `isCellEditable()` has two coordinates `row` and `column`, then `switch (colNum) {` is failtrue for all cells in the concrete column .... :-) – mKorbel Mar 29 '16 at 17:43
1

Fleshing out my comment to the question:

there's no need to tweak the editability of the full model: create another model populated by the filtered (column-wise) row of the original, make that other editable and on commit let it write back the changed values to the original

with a bit of code:

public static class ButtonDialogEditor extends AbstractCellEditor 
     implements TableCellEditor {
    // the columns to present for editing, in model coordinates
    public final int lastColumn;
    public final int firstColumn;
    // the row to present for editing, in model coordinates
    private int row;

    private DefaultTableModel model;
    private JDialog dialog;
    private boolean committed;
    private JButton editingComponent;

    public ButtonDialogEditor(int firstColumn, int lastColumn) {
       this.firstColumn = firstColumn;
       this.lastColumn = lastColumn;

       model = new DefaultTableModel(1, lastColumn - firstColumn + 1);
       JTable table = new JTable(model);
       table.putClientProperty("terminateEditOnFocusLost",true);
       dialog = new JDialog();
       dialog.setModal(true);
       dialog.add(new JScrollPane(table));
       dialog.add(new JButton(createOkAction()), BorderLayout.SOUTH);
       dialog.pack();
       editingComponent = new JButton(createShowDialogAction());
    }

    /**
     * Returns the cell value at column. Note that column
     * is in the model coordinate system of the source model.
     */
    public Object getValueAt(int column) {
        return model.getValueAt(0, column - firstColumn);
    }

    /**
     * Returns the row index of the edited row in 
     * model coordinates of the source table.
     */
    public int getModelRow() {
        return row;
    }

    /**
     * Creates and returns the action used for 
     * the editing component button.
     * 
     * Implemented to show the modal dialog and fire 
     * editingSotpped/canceled depending on the committed
     * flag
     */
    private Action createShowDialogAction() {
        Action action = new AbstractAction() {

            @Override
            public void actionPerformed(ActionEvent e) {
                // reset committed
                committed = false;
                dialog.setVisible(true);
                if (committed) {
                    fireEditingStopped();
                } else {
                    fireEditingCanceled();
                }
            }
        };
        return action;
    }

    /**
     * Creates and returns the action used for the dialog's
     * OK button.
     * 
     * Implemented to hide the dialog and set the
     * committed flag to true.
     */
    private Action createOkAction() {
        Action action = new AbstractAction("OK") {
            @Override
            public void actionPerformed(ActionEvent e) {
                dialog.setVisible(false);
                committed = true;
            }
        };
        return action;
    }

    @Override
    public Component getTableCellEditorComponent(JTable table,
            Object value, boolean isSelected, int row, int column) {
        editingComponent.setText(value != null ? value.toString() : "");
        prepareDialog(table, row);
        return editingComponent;
    }

    /**
     * Update internal state to the row to edit. 
     */
    private void prepareDialog(JTable table, int row) {
        this.row = table.convertRowIndexToModel(row);
        for (int i = firstColumn; i <= lastColumn; i++) {
            model.setValueAt(table.getModel().getValueAt(this.row, i), 0, i - firstColumn);
        }
    }

    /**
     * Implemented to return the original value as 
     * given in the 
     */
    @Override
    public Object getCellEditorValue() {
        return editingComponent.getText();
    }

}

public static class ButtonRenderer implements TableCellRenderer {

    JButton button = new JButton();
    @Override
    public Component getTableCellRendererComponent(JTable table,
            Object value, boolean isSelected, boolean hasFocus, int row,
            int column) {
        button.setText(value != null ? value.toString() : "");
        return button;
    }
}

// example usage
// source model is not editable except for the button column
final DefaultTableModel model = new DefaultTableModel(0, 3) {
    @Override
    public boolean isCellEditable(int row, int column) {
        return column == 0;
    }
};
for (int i = 0; i < 20; i++) {
    model.addRow(new Object[] {"Edit", i});
}
JTable table = new JTable(model);
table.getColumnModel().getColumn(0).setCellRenderer(new ButtonRenderer());
final ButtonDialogEditor cellEditor = new ButtonDialogEditor(1, model.getColumnCount() - 1);
// custom editor listener which writes back the edited values
// to the model on editingStopped.
CellEditorListener l = new CellEditorListener() {

    @Override
    public void editingStopped(ChangeEvent e) {
        for (int i = cellEditor.firstColumn; i <= cellEditor.lastColumn; i++) {
            model.setValueAt(cellEditor.getValueAt(i), cellEditor.getModelRow(), i);
        }
    }

    @Override
    public void editingCanceled(ChangeEvent e) {
        // nothing to do
    }
};
cellEditor.addCellEditorListener(l);
table.getColumnModel().getColumn(0).setCellEditor(
        cellEditor);
kleopatra
  • 51,061
  • 28
  • 99
  • 211