1

I have a JTable in which I alter between having row or column selection enabled. This feature works well but I want to be able to determine the number of cells which are highlighted adjacent to the currently selected cell. Currently, either the entire row or column is selected. I have tried to add a ListSelectionModel to implement this functionality but it only allows for either an entire row or column to be selected. Here are example images:

enter image description here

and

enter image description here

ADJACENTSELECTION is by default set to two so I'd like to highlight the 2 cells to the right and left of the selected cell when rowSelection is enabled or 2 cells above and below the selected cell when rowSelection is false. Any help would be greatly appreciated. Here is my code:

package selectionTest;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;

public class App {

    private void display() {
        JFrame f = new JFrame("Demo");
        JPanel gui = new JPanel(new BorderLayout());
        GridTable gridTable = new GridTable(25, 25);
        gui.add(gridTable, BorderLayout.CENTER);
        f.add(gui);
        f.setBackground(Color.BLUE);
        gui.setBackground(Color.WHITE);
        f.setLocationByPlatform(true);
        f.pack();
        f.setSize(500, 600);
        f.setVisible(true);

    }

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

}

class GridTable extends JTable {

    static int ADJACENTSELECTION = 2;
    boolean rowSelection = false;
    int rows, cols;

    public GridTable(int rows, int cols) {
        this.rows = rows;
        this.cols = cols;
        this.setModel(new DefaultTableModel(rows, cols));
        this.setShowGrid(true);
        Border blackline = BorderFactory.createLineBorder(Color.black);
        this.setBorder(blackline);
        this.setGridColor(Color.black);
        ActionMap map = this.getActionMap();
        Action action = switchOrientation();
        String keyStrokeAndKey = "control O";
        KeyStroke keyStroke = KeyStroke.getKeyStroke(keyStrokeAndKey);
        this.getInputMap().put(keyStroke, keyStrokeAndKey);
        this.getActionMap().put(keyStrokeAndKey, action);
        this.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent event) {
                int row = getSelectedRow();
                int column = getSelectedColumn();
                if (rowSelection) {
                    setRowSelectionInterval(Math.max(0, row - ADJACENTSELECTION), Math.min(row, row + ADJACENTSELECTION));
                } else {
                    setColumnSelectionInterval(Math.max(0, column - ADJACENTSELECTION), Math.min(column, column + ADJACENTSELECTION));
                }
            }
        });
    }

    private AbstractAction switchOrientation() {
        AbstractAction switchOrientation = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                rowSelection = !rowSelection;
                setColumnSelectionAllowed(rowSelection);
                setRowSelectionAllowed(!rowSelection);
            }
        };
        return switchOrientation;
    }

}
219CID
  • 340
  • 5
  • 15
  • 2
    Will the cells hold displayed data or is more of just a graphic element. If the latter, then I wouldn't use a JTable for this but rather a grid of JPanels. – Hovercraft Full Of Eels Aug 11 '20 at 02:08
  • They will hold string values. The user will be frequently editing cell contents which is why I thought JTable would be a good choice – 219CID Aug 11 '20 at 02:13
  • 1
    Do you need the cells to be selected? Or could you just use a custom cell renderer to change the background colour of the cells either side of your selection? – sorifiend Aug 11 '20 at 03:59
  • Yes I’d like to apply certain operations to all selected cells. For example, if the user hit the ‘delete’ key all of the selected cells contents would be deleted. – 219CID Aug 11 '20 at 04:24
  • 1
    Don't extend JTable. You are not adding functionality to the table. Setting properties of a component is not adding functionality. You should only extend a component when you are changing/adding functionality to the component. – camickr Aug 11 '20 at 14:37

1 Answers1

2

You are almost there, just a couple of extra steps and you can update the selection listener to only highlight specific cells:

  1. Allow for selection of specific cells by adding this.setCellSelectionEnabled(true); to your GridTable method:

    public GridTable(int rows, int cols) {
        this.setCellSelectionEnabled(true);
        //...
    
  2. make sure that the SelectionInterval ranges/values are correct:

    if (rowSelection == true){
        //Correct using `getRowCount()` for selection range
        setRowSelectionInterval(Math.max(0, row - ADJACENTSELECTION), Math.min(getRowCount()-1, row + ADJACENTSELECTION));
    }
    else{
        //Correct using `getColumnCount()` for selection range
        setColumnSelectionInterval(Math.max(0, column - ADJACENTSELECTION), Math.min(getColumnCount()-1, column + ADJACENTSELECTION));
    }
    
  3. Make sure that when you update the selection that it does not trigger the selection event to happen again. You can do this by using a boolean to track the selection event:

    this.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
        //Use this to track changes
        boolean initial = true;
        //...
        public void valueChanged(ListSelectionEvent e){
            if (initial){
                int column = getSelectedColumn();
                int row = getSelectedRow();
                //...
    
  4. Finally, putting it all together, this is the full selection listener (And don't forget to add this.setCellSelectionEnabled(true); to GridTable):

    this.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
        //Use this to track changes
        boolean initial = true;
    
        public void valueChanged(ListSelectionEvent e){
            //Only change value on first select (This prevents the below from triggering itself)
            if (initial){
                int column = getSelectedColumn();
                int row = getSelectedRow();
                initial = false;
                if (rowSelection == true){
                    setRowSelectionInterval(Math.max(0, row - ADJACENTSELECTION), Math.min(getRowCount()-1, row + ADJACENTSELECTION));
                }
                else{
                    setColumnSelectionInterval(Math.max(0, column - ADJACENTSELECTION), Math.min(getColumnCount()-1, column + ADJACENTSELECTION));
                }
            }
            //Set the value back once a selection is complete
            else{
                initial = true;
            }
        }
    });
    

This will highlight the selected cell, and two cells either side of the selection

sorifiend
  • 5,927
  • 1
  • 28
  • 45