Proper multiple cell selection in JTable

on Friday, September 13, 2013
I find it so strange that you can't really select multiple cells in the JTable. For example, try selecting cells that are diagonal from each other. The whole "square" will be selected. This is because in cell selection mode, the selection is based on the intersection of the row model and column model. Thus, certain selections are impossible. This frustrated me to no end and I took it upon myself to find a solution.

After much research, I came up with this (untested, hackish code):

public class CellSelectionTable extends JTable {

        CellSelectionModel cellSelectionModel;

        public CellSelectionTable(TableModel tableModel) {
            super(tableModel);
            cellSelectionModel = new CellSelectionModel(getRowCount(), getColumnCount());
        }

        @Override
        public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
            cellSelectionModel.changeSelection(rowIndex, columnIndex, toggle, extend);
            super.changeSelection(rowIndex, columnIndex, toggle, extend);
        }

        private void addCellSelection(int row, int col) {
            cellSelectionModel.addSelection(row, col);
        }

        @Override
        public void selectAll() {
            cellSelectionModel.selectAll();
            super.selectAll();
        }

        @Override
        public void clearSelection() {
            if (cellSelectionModel != null) {
                cellSelectionModel.clearSelection();
            }
            super.clearSelection();
        }

        @Override
        public boolean isCellSelected(int row, int column) {
            return cellSelectionModel.isCellSelected(row, column);
        }


/**
 * 2D selection model (e.g. 2D table)
 *
 * @author RadianceOng
 */
public class CellSelectionModel {

    private boolean[][] cellSelected;
    int rows;
    int cols;
    Point anchor;
    List<ChangeListener> changeListeners;

    public CellSelectionModel(int row, int col) {
        cellSelected = new boolean[row][col];
        anchor = new Point(0, 0);
        rows = row;
        cols = col;
        changeListeners = new ArrayList<> ();

        clearSelection();
    }

    public void addSelection(int row, int col) {
        cellSelected[row][col] = true;
        setAnchor(row, col);
    }

    public void removeSelection(int row, int col) {
        cellSelected[row][col] = false;
        setAnchor(row, col);
    }

    public void toggleSelection(int row, int col) {
        cellSelected[row][col] = !cellSelected[row][col];
        setAnchor(row, col);
    }

    public void changeSelection(int row, int col, boolean toggle, boolean extend) {
        if (extend) {
            extendSelection(row, col, toggle);
        } else {
            if (toggle) {
                toggleSelection(row, col);
            } else {
                clearSelection();
                addSelection(row, col);
            }
        }
       
        stateChange();
    }

    public void extendSelection(int row, int col, boolean toggle) {
        boolean anchorState = true;
        if (toggle) {
            anchorState = isCellSelected(row, col);
        }

        Point firstPoint = new Point(Math.min(anchor.x, col), Math.min(anchor.y, row));
        Point lastPoint = new Point(Math.max(anchor.x, col), Math.max(anchor.y, row));

        for (int i = firstPoint.y; i <= lastPoint.y; i++) {
            for (int j = firstPoint.x; j <= lastPoint.x; j++) {
                cellSelected[i][j] = anchorState;
            }
        }
        //setAnchor(lastPoint.y, lastPoint.x);
    }

    public void selectAll() {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                cellSelected[i][j] = true;
            }
        }
        setAnchor(rows - 1, cols - 1);
    }

    public void clearSelection() {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                cellSelected[i][j] = false;
            }
        }
        setAnchor(rows - 1, cols - 1);
    }

    public boolean isCellSelected(int row, int col) {
        return cellSelected[row][col];
    }
   
    /**
     * Visits the entire selection, calling the visitor on every cell
     * @param v
     */
    public void visitCellSelection(CellSelectionVisitor v) {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                v.isSelected(cellSelected[i][j], i, j);
            }
        }
    }
   
    public void addChangeListener(ChangeListener l) {
        changeListeners.add(l);
    }
   
    public void removeChangeListener(ChangeListener l) {
        changeListeners.remove(l);
    }

    private void setAnchor(int row, int col) {
        anchor.x = col;
        anchor.y = row;
    }
   
    private void stateChange() {
        ChangeEvent e = new ChangeEvent(this);
        for(ChangeListener l : changeListeners) {
            l.stateChanged(e);
        }
    }
   
    public interface CellSelectionVisitor {
        /**
         * Called with state of cell
         * @param selected
         * @param row
         * @param col
         */
        void isSelected(boolean selected, int row, int col);
    }

0 comments:

Post a Comment