0

I want to provide multi-cell editing functionality to a JTable: double click will still edit the value in the selected cell (the standard behavior), while right-click should open up a popup menu with an entry "Edit selected cells".

When the user hits this menu entry, the last cell in the selected range becomes editable. The other selected cells remain selected. Then they write the new value and, when the edition is finished (usually by hitting Enter), all of the selected cells get this value.

Let's assume, for simplicity, that all cells contain the same value types, say, integers.

Here's the code that shows up the popup dialog, to get started:

table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
table.setCellSelectionEnabled(true);
table.addMouseListener(new MouseAdapter() {
    @Override
    public void mousePressed(MouseEvent e) {
        if (e.isPopupTrigger()) {
            doPop(e);
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (e.isPopupTrigger()) {
            doPop(e);
        }
    }

    private void doPop(MouseEvent e) {
        MultiEditPopUp menu = new MultiEditPopUp(tblRanges);
        menu.show(e.getComponent(), e.getX(), e.getY());
    }
});


class MultiEditPopUp extends JPopupMenu {
    JMenuItem menuItem;

    MultiEditPopUp(JTable table) {
        menuItem = new JMenuItem("Edit selected");
        menuItem.setAction(new BulkEditAction(table));
        add(menuItem);
    }
}

class BulkEditAction extends AbstractAction {
    private final JTable table;

    public BulkEditAction(JTable table) {
        this.table = table;
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {
        // TODO: let the user edit the last cell, and then apply to the others
    }
}

How can I do such a thing?

espinchi
  • 9,144
  • 6
  • 58
  • 65
  • what exactly is the problem? When receiving the edited value, propagate it to all selected cells and be happy :-) – kleopatra Oct 26 '11 at 08:09
  • 1
    a couple of comments (unrelated to the problem which I didn't get ;-) a) do not subclass any JSomething, instead use them (JPopupMenu is designed to add actions/items, there is no need to subclass just to add a particular item) b) always use the highest abstraction, available here that means setComponentPopupMenu instead of a mouseListener (which provides incomplete functionality, anyway, as it doesn't cover popups by keyboard) – kleopatra Oct 26 '11 at 08:16
  • The problem is how to now let the user edit that cell, while keeping the selection. Thanks for the suggestion about the subclassing. – espinchi Oct 26 '11 at 08:31
  • maybe http://stackoverflow.com/questions/7423533/jtable-with-jpopupmenu – mKorbel Oct 26 '11 at 08:45

2 Answers2

2

still not quite sure what the problem is. The basic approach would be

  • store the selected cells
  • let the user edit one of them
  • at the end, take the edited value and set it to all cells formerly stored

The only tricky part that I see might be detecting "at the end" (because the life-cycle of editing isn't overly well defined). Some code-snippet

public class BulkEditAction extends AbstractAction {
    JTable table;
    List selectedCells;

    public BulkEditAction(JTable table) {
        this.table = table;
    }

    @Override
    public void actionPerformed(ActionEvent actionEvent) {

        // store, here rows only, refine for cell selection
        selectedCells = Arrays.asList(table.getSelectedRows());
        final int rowToEdit =  // ...
        final int columnToEdit = // ...
        table.editCellAt(rowToEdit, columnToEdit);
        CellEditorListener l = new CellEditorListener() {

            @Override
            public void editingStopped(ChangeEvent e) {
                ((AbstractCellEditor) e.getSource()).removeCellEditorListener(this);
                propagateEditedValue(rowToEdit, columnToEdit);

            }

            @Override
            public void editingCanceled(ChangeEvent e) {
                ((AbstractCellEditor) e.getSource()).removeCellEditorListener(this);
            }
        };
        table.getCellEditor().addCellEditorListener(l);
    }

    private void propagateEditedValue(final int row, final int column) {
        // need to invoke to be sure that the table has updated itself after
        // editingStopped
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                // foreach selectedCell (with coordinates selectedRow/-column
                table.setValueAt(table.getValueAt(row, column), selectedRow, selectedColumn);
            }
        });
    }
}
kleopatra
  • 51,061
  • 28
  • 99
  • 211
  • Very good solution, this works like a charm. I'm trying to solve a user experience issue: when you edit that cell, the "select all" does not work and, what's worse, if you hit one arrow, the cursor will move, and the bulk edition will be executed. I'll post back to complement this answer if I manage. Any ideas? – espinchi Oct 26 '11 at 12:43
0

I would extend JTable to create MultiCellEditJTable

public class MultiCellEditJTable extends JTable{

    public MultiCellEditJTable(){
        setColumnSelectionAllowed(true);
        getSelectionModel().setSelectionMode(DefaultListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    }


    @Override
    public Component prepareEditor(TableCellEditor editor, int row, int column){
        Component component = super.prepareEditor(editor, row, column);

        if(component instanceof JTextField){
            JTextField textField = (JTextField)component;
            textField.selectAll();
        }

        return component;
    }


    @Override
    public void editingStopped(ChangeEvent e){
        int editingRow = getEditingRow();
        int editingColumn = getEditingColumn();

        super.editingStopped(e);

        if(1 < getSelectedRowCount() && 1 == getSelectedColumnCount() && editingColumn == getSelectedColumn()){
            Object value = getValueAt(editingRow, editingColumn);
            Arrays.stream(getSelectedRows()).filter(row->row != editingRow).forEach(row->
              setValueAt(value, row, editingColumn)
            );
        }
    }
}

When multiple rows of one column are selected and an edit completes, the all of the selected cells are set to the value resulting from the edit.

As an added bonus I made the editor selectAll to provide the function of just being able to type the desired value after selecting a range of cells. Otherwise one would have to backspace the current value first.

patrick
  • 194
  • 1
  • 3