mainstringargs
mainstringargs

Reputation: 13911

JTable disable Column Selection

enter image description here

I am trying to disable selection of rows in all but 1 column in a JTable. (Layer Column in the example screenshot). In the other columns I have spinners and checkboxes that I want the user to be able to interact with, without effecting the selections in the Layer Column.

My initial attempt was to store up any selected rows as they occur, and then revert to that set of selected rows when a cell outside of column A is selected. It sort of works, but the problem is that it "flashes" when the other cell is selected, before it reverts it back. How can I prevent the "flash"?

Here is an example I set up to illustrate the problem:

public class TableTest {

    static int[] selectedRows = new int[0];

    final static String[] columns = new String[] { "Layer", "Enabled", "Read Only", "Storage" };

    final static DefaultTableModel model = new DefaultTableModel(new Vector(), new Vector(Arrays.asList(columns))) {

        @Override
        public void setValueAt(Object obj, int row, int col) {

            if (obj instanceof Boolean || obj instanceof Integer) {
                Object localObject = super.getValueAt(row, col);
                if (localObject instanceof Integer) {

                    Integer val = (Integer) localObject;

                    ((SpinnerCell) obj).getSpinner().setValue(val);
                } else if (localObject instanceof Boolean) {

                    Boolean val = (Boolean) localObject;

                    ((CheckboxCell) obj).getCheckBox().setEnabled(val);
                }

            } else {
                super.setValueAt(obj, row, col);
            }

        }

        @Override
        public boolean isCellEditable(int rowIndex, int colIndex) {

            return colIndex != 0;
        }

    };

    public static void main(String[] a) {

        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                final JTable table = new JTable(model) {

                    @Override
                    public TableCellRenderer getCellRenderer(final int rowIndex, int colIndex) {

                        int reaRowlIndex = convertRowIndexToModel(rowIndex);
                        int realColumnIndex = convertColumnIndexToModel(colIndex);

                        Object o = model.getValueAt(reaRowlIndex, realColumnIndex);

                        if (o instanceof TableCellRenderer) {
                            return (TableCellRenderer) o;
                        } else {
                            return super.getCellRenderer(reaRowlIndex, realColumnIndex);
                        }
                    }

                    //
                    @Override
                    public TableCellEditor getCellEditor(final int rowIndex, int colIndex) {

                        int reaRowlIndex = convertRowIndexToModel(rowIndex);
                        int realColumnIndex = convertColumnIndexToModel(colIndex);

                        Object o = model.getValueAt(reaRowlIndex, realColumnIndex);

                        if (o instanceof TableCellEditor) {
                            return (TableCellEditor) o;
                        } else {
                            return super.getCellEditor(reaRowlIndex, realColumnIndex);
                        }
                    }

                };

                table.getTableHeader().setReorderingAllowed(false);

                table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {

                    @Override
                    public void valueChanged(ListSelectionEvent arg0) {
                        if (table.getSelectedColumn() == 0) {
                            selectedRows = table.getSelectedRows();

                            System.out.println("Selected Rows before " + Arrays.toString(selectedRows));
                        }

                    }
                });

                final ListSelectionModel columnListSelectionModel = table.getColumnModel().getSelectionModel();
                columnListSelectionModel.addListSelectionListener(new ListSelectionListener() {
                    @Override
                    public void valueChanged(ListSelectionEvent e) {

                        if (table.getSelectedColumn() != 0) {

                            table.clearSelection();

                            System.out.println("Selected Rows during " + Arrays.toString(table.getSelectedRows()));

                            for (int i = 0; i < selectedRows.length; i++) {
                                table.getSelectionModel().addSelectionInterval(selectedRows[i], selectedRows[i]);
                            }

                            System.out.println("Selected Rows after " + Arrays.toString(table.getSelectedRows()));
                        }

                    }
                });

                model.addRow(new Object[] { "Bird", new CheckboxCell(new JCheckBox()),
                        new CheckboxCell(new JCheckBox()), new SpinnerCell(new JSpinner()) });

                model.addRow(new Object[] { "Cat", new CheckboxCell(new JCheckBox()), new CheckboxCell(new JCheckBox()),
                        new SpinnerCell(new JSpinner()) });

                model.addRow(new Object[] { "Dog", new CheckboxCell(new JCheckBox()), new CheckboxCell(new JCheckBox()),
                        new SpinnerCell(new JSpinner()) });

                model.addRow(new Object[] { "Fish", new CheckboxCell(new JCheckBox()),
                        new CheckboxCell(new JCheckBox()), new SpinnerCell(new JSpinner()) });

                model.addRow(new Object[] { "Pig", new CheckboxCell(new JCheckBox()), new CheckboxCell(new JCheckBox()),
                        new SpinnerCell(new JSpinner()) });

                frame.add(new JScrollPane(table));

                frame.setSize(300, 200);
                frame.setVisible(true);

            }

        });

    }

    static class CheckboxCell extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {

        private static final long serialVersionUID = 1L;
        private JCheckBox checkBox;

        public CheckboxCell(JCheckBox inputCheckBox) {
            checkBox = inputCheckBox;
        }

        @Override
        public Object getCellEditorValue() {
            return checkBox.isSelected();
        }

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

            return checkBox;
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
                int row, int column) {

            return checkBox;
        }

        public JCheckBox getCheckBox() {
            return checkBox;
        }

        @Override
        public boolean isCellEditable(EventObject evt) {
            return true;
        }

        public String toString() {
            return checkBox.isSelected() + "";
        }

    }

    static class SpinnerCell extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {

        private static final long serialVersionUID = 1L;
        private JSpinner editSpinner, renderSpinner;

        public SpinnerCell() {
            editSpinner = new JSpinner();
            JTextField tf = ((JSpinner.DefaultEditor) editSpinner.getEditor()).getTextField();
            tf.setForeground(Color.black);
            renderSpinner = new JSpinner();
            JTextField tf2 = ((JSpinner.DefaultEditor) renderSpinner.getEditor()).getTextField();
            tf2.setForeground(Color.black);
        }

        public SpinnerCell(JSpinner showSpinner) {
            editSpinner = showSpinner;
            JTextField tf = ((JSpinner.DefaultEditor) editSpinner.getEditor()).getTextField();
            tf.setForeground(Color.black);
            renderSpinner = showSpinner;
            JTextField tf2 = ((JSpinner.DefaultEditor) renderSpinner.getEditor()).getTextField();
            tf2.setForeground(Color.black);

        }

        @Override
        public Object getCellEditorValue() {
            return editSpinner.getValue();
        }

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

            return editSpinner;
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
                int row, int column) {
            return renderSpinner;
        }

        public String toString() {
            return editSpinner.getValue().toString();
        }

        public JSpinner getSpinner() {
            return editSpinner;
        }

        @Override
        public boolean isCellEditable(EventObject evt) {
            return true;
        }
    }

}

Upvotes: 6

Views: 4454

Answers (3)

aterai
aterai

Reputation: 9833

Here's my short example:

  • Override JTable#changeSelection(...)
  • table.setCellSelectionEnabled(true);
  • Override ListSelectionModel#isSelectedIndex(...)
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class TableTest2 {
  public JComponent makeUI() {
    String[] columnNames = {"Layer", "Enabled", "Read Only"};
    Object[][] data = {
      {"Bird", true, false}, {"Cat",  true, false},
      {"Dog",  true, false}, {"Fish", true, false}, {"Pig",  true, false}
    };
    TableModel model = new DefaultTableModel(data, columnNames) {
      @Override public Class<?> getColumnClass(int column) {
        return getValueAt(0, column).getClass();
      }
      @Override public boolean isCellEditable(int row, int col) {
        return col != 0;
      }
    };
    JTable table = new JTable(model) {
      @Override public void changeSelection(
          int rowIndex, int columnIndex, boolean toggle, boolean extend) {
        if (convertColumnIndexToModel(columnIndex) != 0) {
          return;
        }
        super.changeSelection(rowIndex, columnIndex, toggle, extend);
      }
    };
    table.setAutoCreateRowSorter(true);
    table.setCellSelectionEnabled(true);
    table.getColumnModel().setSelectionModel(new DefaultListSelectionModel() {
      @Override public boolean isSelectedIndex(int index) {
        return table.convertColumnIndexToModel(index) == 0;
      }
    });
    return new JScrollPane(table);
  }
  public static void main(String... args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new TableTest2().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

Upvotes: 6

camickr
camickr

Reputation: 324197

Haven't tried it but you might be able to use a custom ListSelectionModel.

Here is an example that lets you toggle the selection of a row on/off:

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

public class ToggleListSelectionModel extends DefaultListSelectionModel
{
    @Override
    public void setSelectionInterval(int index0, int index1)
    {
        //  Select multiple lines

        if (index0 != index1)
        {
            super.addSelectionInterval(index0, index1);
            return;
        }

        //  Toggle selection of a single line

        if  (super.isSelectedIndex(index0))
        {
            super.removeSelectionInterval(index0, index0);
        }
        else
        {
            super.addSelectionInterval(index0, index0);
        }
    }

    private static void createAndShowGUI()
    {
        String[] numbers = { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten" };
        final JList<String> list = new JList<String>( numbers );
        list.setVisibleRowCount( numbers.length );
        list.setSelectionModel(new ToggleListSelectionModel());
//      list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);

        JButton clear = new JButton("Clear");
        clear.addActionListener( new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                list.clearSelection();
            }
        });

        JFrame frame = new JFrame("SSCCE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JScrollPane(list), BorderLayout.CENTER);
        frame.add(clear, BorderLayout.PAGE_END);
        frame.setLocationByPlatform( true );
        frame.pack();
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
}

In your case I guess you would need access to the table so you can use the getSelectedColumn() method.

You would remove the logic from the example above and replace it with something like:

if (table.getSelectedColumn() == 0)
    super.setSelectionInterval(index0, index1);

Upvotes: 3

sorifiend
sorifiend

Reputation: 6307

If you want to prevent the flash you need to intercept an event before the selection is made because table.getSelectedColumn() only works after a selection has been made and you will always see that selection flash.

You could use a key listener to check for "Right Arrow", "Number Pad Right" and "End" key presses and a mouse clicked listener to check where the user is clicking and then change the event to instead select column A only.

For example see this answer showing how to check where a user clicks the mouse before a selection is made: https://stackoverflow.com/a/7351053/1270000

Then you just need to add some code to select the correct cell in column A instead, and don't forget to consume the event "e.consume()" to prevent the original event from completing with the wrong user selection.

Upvotes: 3

Related Questions