3

I'm using a table with a cell renderer which allows me to place a button "x" in each cell with a value so that i can delete the value of the cell by pressing the button. The cell editor assigns the action listener to the button and the proper actions are called when the button is pressed.

Every column in the table model which is a DefaultTableModel is populated by an array list of values. I successfully manage to delete the right value from the array list by pressing the button and every other cell in the table refreshes after the change but the cell which's value was deleted remains unchanged. If i restart the application or add a value to the array list the table refreshes as expected and everything looks as it should.

Mo problem then is that is seems like the cell renderer won't forget the initial value of the cell and therefore still continues to print this value into the cell even though the table is redrawn. Does anyone have an idea of how i could properly refresh the table so the values are correctly shown?

Table class:

public class Table extends JTable {

public static DefaultTableModel model = new DefaultTableModel();
private Days d;
private JFrame frame;
private AddCellRenderer renderer;
private AddCellEditor editor;

public Table(Days d, JFrame frame) {
    // Assign the table model to the table
    super(model);
    this.d = d;
    this.frame = frame;

    // Define dimensions of table
    model.setRowCount(11);
    model.setColumnCount(5);

    // Block resizing and reordering of headers
    this.setColumnSelectionAllowed(false);
    this.setRowSelectionAllowed(false);
    this.setCellSelectionEnabled(true);
    this.setTableHeader(null);

    // Define first row height
    this.setRowHeight(38);
    // Define all other row's heights
    this.setRowHeight(0, 45);

    this.d.setTable(this);

    for (int i = 0; i < 5; i++) {
        editor = new AddCellEditor(new JCheckBox(), d.getVisibleDays().get(i), this);
        renderer = new AddCellRenderer(d.getVisibleDays().get(i), editor);
        this.getColumnModel().getColumn(i).setCellRenderer(renderer);
        this.getColumnModel().getColumn(i).setCellEditor(editor);
    }

    this.refresh();
}

public void refresh() {

    // Empty table
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 11; j++) {
            this.getModel().setValueAt(null, j, i);
        }
    }

    // Populate table with entries for each day
    for (int i = 0; i < 5; i++) {

        Iterator<String> iterator = d.getVisibleDays().get(i).getEntryListIterator();

        int j = 0;

        while(iterator.hasNext()){
            String s = iterator.next();
            this.getModel().setValueAt(s, (j+1), i);
            j++;
        }

        this.getModel().setValueAt(d.getVisibleDays().get(i).getDayName(), 0, i);
    }
}

public void modelClearValueAt(int row, int column) {
    this.getModel().setValueAt(null, row, column);
}
}

Cell editor class:

public class AddCellEditor extends DefaultCellEditor {

private JPanel headerPanel;
private JPanel entryPanel;
private JLabel dayName;
private JLabel entryValue;
protected JButton addButton;
private JButton removeButton;
private String label;
private int row;
private int column;
private Day day;
private String date;
private Table table;
private AddCellRenderer renderer;

public AddCellEditor(JCheckBox checkBox, Day day, Table table) {
    super(checkBox);
    this.day = day;
    this.table = table;
    date = day.getDayDate();
    headerPanel = new JPanel();
    headerPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
    entryPanel = new JPanel();
    entryPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
    dayName = new JLabel();
    entryValue = new JLabel();
    addButton = new JButton();
    removeButton = new JButton();
    headerPanel.add(dayName);
    headerPanel.add(addButton);
    entryPanel.add(entryValue);
    entryPanel.add(removeButton);
    addButton.setOpaque(true);
    addButton.setPreferredSize(new Dimension(16, 16));
    removeButton.setOpaque(true);
    removeButton.setPreferredSize(new Dimension(16, 16));
    addButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("Add pressed");
            addItem();
            fireEditingStopped();
        }
    });
    removeButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("Remove pressed");
            removeItem();
            getTable().refresh();
            fireEditingStopped();
        }
    });

}

public void setRenderer(AddCellRenderer renderer) {
    this.renderer = renderer;
}

public Component getTableCellEditorComponent(JTable table, 
        Object value, boolean isSelected, int row, int column) {

    this.row = row;
    this.column = column;

    if (row == 0) {
         return this.headerPanel;
    }
    if (row != 0 && value != null) {
        return this.entryPanel;
    }
    return null;
}

public void addItem() {
    String input = JOptionPane.showInputDialog("Add entry for " + label + ":");

    if ((input != null) && (input.length() > 0)) {
        System.out.println("Added: " + input + "Item");
        day.addEntry(input, column);
    }
}

public void removeItem() {
    table.modelClearValueAt(row, column);
    day.removeEntry(row-1);
    System.out.println("Item removed");
}

public Table getTable() {
    return this.table;
}

public boolean stopCellEditing() {
    return super.stopCellEditing();
}

protected void fireEditingStopped() {
    super.fireEditingStopped();
}
}

Thanks in advance

  • I added the Swing tag so your question can attract the Swing pros, but I had to delete one of the other tags (JTable) to do so, since a question can only have 5 tags. You may want to adjust this and swap tags back if desired, but I felt it most important that you get the Swing experts on board to maximize your chances of getting decent and timely help. – Hovercraft Full Of Eels Jan 14 '12 at 15:26
  • Sounds good to me! I appreciate your help. –  Jan 14 '12 at 15:35
  • 1
    the cell renderer is showing what's in the model, nothing else :-) Most probably your editor is an invalid implementation: it _must not_ do anything fancy, simply notify its listeners when it terminates and return an appropriate getEditorValue(). Nothing else, particularly not modifying the underlying data in _any_ way. – kleopatra Jan 14 '12 at 15:58
  • Forgot: that's on top of the analysis by @HovercraftFullOfEels - all modifications in the data layer must happen via the TableModel which in turn might update the underlying lists (assuming they are not observable themselves - in which case it's okay to change them and let the tableModel listen to the list changes). – kleopatra Jan 14 '12 at 16:02
  • unrelated to your problem: don't extend JSomething, all concrete implementations are designed to be _used_ – kleopatra Jan 14 '12 at 16:10
  • Thank you for the answers! I will look into this and hopefully i will be able to solve this :) Just one more question, how come adding entries to the table by adding a value to the underlaying list and then re-drawing the table works but not removing? It's been driving me crazy! –  Jan 14 '12 at 16:22
  • it doesn't work in any useful sense, it's purely accidental: it's _wrong_ doing so, so simply don't however it appears to doing something remotely right :-) – kleopatra Jan 14 '12 at 16:33

2 Answers2

4

If you're using a DefaultTableModel, then you would have to delete the value held by the model itself, not the value in the ArrayList that was used to build the model. You would do this by calling the setValueAt(...) method of the DefaultTableModel object.

You may wish to post an SSCCE for more specific help.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • Thank you for the answer! Do you mean using methods such as firetabledatachanged to achieve this? –  Jan 14 '12 at 15:15
  • @EdwardLandtman: no, I mean by calling the `setValueAt(...)` method of the DefaultTableModel object. An advantage of using DefaultTableModel is that you don't have to call the fire methods since these are already written to be called. A disadvantage is that you can't use your own collection as the data nucleus of the model (for that you'd need to use an AbstractTableModel, and then be sure to call the fireXXX methods when necessary). – Hovercraft Full Of Eels Jan 14 '12 at 15:20
  • Unfortunately I'm quite new to java so I'm sometimes also struggling with very basic problems. When my "remove" button is pressed i call a method which removes the item from the array list and redraws the table using the list values. I've tried to use the setValueAt(...) method to set the null value to the cell in question both before and after calling the remove method with no luck. I will post an SSCCE to give you a better picture of my problem. But thanks for the help already given! –  Jan 14 '12 at 15:38
  • @Edward: I believe that if you change the collection on which the DefaultTableModel was based you can have unpredictable results. I think that an SSCCE would be useful indeed. You're welcome of course, and best of luck with this! – Hovercraft Full Of Eels Jan 14 '12 at 15:51
  • I've now posted the code for the table and table model and also the cell editor class code. I would argue that it should work to remove the items from the collection since i always empty the table and re-draw it based on the updated collection. I might be wrong though. I hope you have some ideas for me! –  Jan 14 '12 at 16:06
  • @EdwardLandtman that's not an sscce (doesn't fulfil the "standalone") – kleopatra Jan 14 '12 at 16:13
  • @Edward: I have to agree with kleopatra on this one (not just because she knows more Java and Swing than I'll ever know), but there's no way we can run the code posted. You'll want to read the [SSCCE](http://sscce.org) specification again. – Hovercraft Full Of Eels Jan 14 '12 at 16:59
3

okay, couldn't resist (the alternative is to scrub the bathroom :-)

Below is a very raw outline (just enough to give you an idea) of how to solve your goal with well-behaved editor and custom tableModel.

  • MyTableModel is a custom implementation basically backed by a List of arbitrary entries
  • this tableModel supports removing/adding items. Privately for now, so you aren't tempted to call them from the editor :-) They are safe enough to expose with public scope, if needed, f.i. if other parts the program need to modify the entries as well
  • the model can handle different value types in setValueAt: a Modify marker, a NewEntry or a plain value
  • the custom editor is a panel with buttons to remove, add a row.
  • the actions of the button set the editorValue as appropriate before calling editingStopped/canceled as needed

The model:

public static class MyTableModel extends AbstractTableModel {

    public enum Modify {
        ADD,
        REMOVE
    }

    public static class NewItem {
        public final Object entry;

        public NewItem(Object entry) {
            this.entry = entry;
        }
    }

    private List entries;

    public MyTableModel(List entries) {
        this.entries = entries;
    }

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

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


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

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return entries.get(rowIndex);
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        if (aValue instanceof NewItem) {
            addEntry(((NewItem) aValue).entry);
        } else if (Modify.REMOVE == aValue) {
            removeEntry(rowIndex);
        } else {
            entries.set(rowIndex, aValue);
        }    
    }

    private void removeEntry(int rowIndex) {
        entries.remove(rowIndex);
        fireTableRowsDeleted(rowIndex, rowIndex);
    }

    private void addEntry(Object aValue) {
        int last = getRowCount();
        entries.add(aValue);
        fireTableRowsInserted(last, last);
    }


}

The editor:

public static class MyCellEditor extends AbstractCellEditor implements TableCellEditor {

    private Object editorValue;
    private JLabel entryView;
    JComponent editor;

    public MyCellEditor() {
        editor = new JPanel();
        entryView = new JLabel();
        editor.add(entryView);
        Action add = createAddAction();
        editor.add(new JButton(add));
        Action remove = createRemoveAction();
        editor.add(new JButton(remove));
    }

    public Action createRemoveAction() {
        Action remove = new AbstractAction("Remove Entry") {

            @Override
            public void actionPerformed(ActionEvent e) {
                editorValue = MyTableModel.Modify.REMOVE;
                fireEditingStopped();
            }

        };
        return remove;
    }

    public Action createAddAction() {
        Action add = new AbstractAction("Add Entry") {

            @Override
            public void actionPerformed(ActionEvent e) {
                String input = JOptionPane.showInputDialog("Add entry: ");

                if ((input != null) && (input.length() > 0)) {
                    editorValue = new MyTableModel.NewItem(input);
                    fireEditingStopped();
                } else {
                    fireEditingCanceled();
                }

            }

        };
        return add;
    }

    @Override
    public Object getCellEditorValue() {
        return editorValue;
    }

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


}

The usage:

    List entries = new ArrayList();
    for (int i = 0; i < 5; i++) {
        entries.add("entry " + i);
    }
    TableModel model = new MyTableModel(entries);
    JTable table = new JTable(model);
    table.getColumnModel().getColumn(0).setCellEditor(new MyCellEditor());
    table.getColumnModel().getColumn(0).setPreferredWidth(200);
    table.setRowHeight(50);
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • thank you so much! this is exactly the brilliant simplicity I've been aiming for the whole time but my skills just aren't enough :) This helped me a lot i think i will re-build my application around this logic now. I appreciate you taking this time to help me instead of scrubbing the bathroom since I've got a deadline for this application tomorrow, so this really helped me out a lot! –  Jan 14 '12 at 18:15
  • @Edward: you'll want to up-vote this answer (as I've just done), and accept it, by clicking on the check to the left of kleo's answer. – Hovercraft Full Of Eels Jan 14 '12 at 18:19
  • @Edward: You'll also want to go scrub Kleo's bathroom for her. It's the least you can do to pay her back. – Hovercraft Full Of Eels Jan 14 '12 at 18:19