Multiple row selection in JTable
Asked Answered
B

4

7

I have a JTable, that has one column that is text which is not editable and the second column is a check box that displays boolean values.... Now what i want is, when the user selects multiple rows and unchecks any one of the selected check boxes, then all the check boxes under selection should get unchecked and vice versa.

Botha answered 24/12, 2010 at 14:14 Comment(0)
I
15

Using @Hovercraft's example and @camickr's advice, the example below shows a suitable user interface. Although it uses buttons, the SelectionAction would also be suitable for a menu or popup.

Check A Bunch

import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.DefaultListSelectionModel;
import javax.swing.table.DefaultTableModel;

/** @see http://stackoverflow.com/questions/4526779 */
public class CheckABunch extends JPanel {

    private static final int CHECK_COL = 1;
    private static final Object[][] DATA = {
        {"One", Boolean.TRUE}, {"Two", Boolean.FALSE},
        {"Three", Boolean.TRUE}, {"Four", Boolean.FALSE},
        {"Five", Boolean.TRUE}, {"Six", Boolean.FALSE},
        {"Seven", Boolean.TRUE}, {"Eight", Boolean.FALSE},
        {"Nine", Boolean.TRUE}, {"Ten", Boolean.FALSE}};
    private static final String[] COLUMNS = {"Number", "CheckBox"};
    private DataModel dataModel = new DataModel(DATA, COLUMNS);
    private JTable table = new JTable(dataModel);
    private DefaultListSelectionModel selectionModel;

    public CheckABunch() {
        super(new BorderLayout());
        this.add(new JScrollPane(table));
        this.add(new ControlPanel(), BorderLayout.SOUTH);
        table.setPreferredScrollableViewportSize(new Dimension(250, 175));
        selectionModel = (DefaultListSelectionModel) table.getSelectionModel();
    }

    private class DataModel extends DefaultTableModel {

        public DataModel(Object[][] data, Object[] columnNames) {
            super(data, columnNames);
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            if (columnIndex == CHECK_COL) {
                return getValueAt(0, CHECK_COL).getClass();
            }
            return super.getColumnClass(columnIndex);
        }

        @Override
        public boolean isCellEditable(int row, int column) {
            return column == CHECK_COL;
        }
    }

    private class ControlPanel extends JPanel {

        public ControlPanel() {
            this.add(new JLabel("Selection:"));
            this.add(new JButton(new SelectionAction("Clear", false)));
            this.add(new JButton(new SelectionAction("Check", true)));
        }
    }

    private class SelectionAction extends AbstractAction {

        boolean value;

        public SelectionAction(String name, boolean value) {
            super(name);
            this.value = value;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            for (int i = 0; i < dataModel.getRowCount(); i++) {
                if (selectionModel.isSelectedIndex(i)) {
                    dataModel.setValueAt(value, i, CHECK_COL);
                }
            }
        }
    }

    private static void createAndShowUI() {
        JFrame frame = new JFrame("CheckABunch");
        frame.add(new CheckABunch());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowUI();
            }
        });
    }
}
Infancy answered 24/12, 2010 at 22:45 Comment(3)
See comments here if sorting is enabled.Infancy
Hi, I'm sorry for replying but I just found this a little while ago and would like some input on adding a listener for changes in the list. Right now I'm using a mouseListener, but it is firing before the list is actually updated. How could I add a listener to get updates for the list as they happen?Stovall
Maybe a TableCellEditor, seen here?Infancy
H
4

The problem is that when you click on a check box to change the value of the check box, the selection of all the rows will be lost. So you may need to use a right mouse click to display a popup menu that contains select/deselect values.

Then you can use table.getSelectedRows(), to get the indexes of all the selected rows you need to update.

Halfhardy answered 24/12, 2010 at 16:3 Comment(3)
you are right.... when ever the check box is clicked all the row selection is lost..... is there any way around this without using the right click..?Botha
You can't use a mouse click for two different functions. The solution is a proper UI. I gave one suggestion to use a popup menu. Another solution is to use menuItems from a menu with accelerators to select/deselect. This way the user can use the mouse or the keyboard. Or you can add buttons to the UI to do the same.Halfhardy
I have to agree with @camickr; I added an example nearby.Infancy
P
2

You can get the selection interval with code similar to this:

table.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
    public void valueChanged(ListSelectionEvent e) {
        minSelectedRow = ((DefaultListSelectionModel)e.getSource()).getMinSelectionIndex();
        maxSelectedRow = ((DefaultListSelectionModel)e.getSource()).getMaxSelectionIndex();
    }
});

Then, when one checkbox is checked (listen to ItemEvent) you should iterate from the minSelectedRow to the maxSelectedRow and change checked boxes state. That's it.

Prostitution answered 24/12, 2010 at 14:31 Comment(0)
B
2

I agree with Roman that his idea would work if you use a class field to hold the min and max selection. For instance:

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;

public class CheckABunch extends JPanel {
    private static final Object[][] DATA = {{"One", Boolean.TRUE}, {"Two", Boolean.FALSE},
        {"Three", Boolean.TRUE}, {"Four", Boolean.FALSE}, {"Five", Boolean.TRUE},
        {"Six", Boolean.FALSE}, {"Seven", Boolean.TRUE}, {"Eight", Boolean.FALSE}};
    private static final String[] COLUMNS = {"Number", "CheckBox"};
    private DefaultTableModel model = new DefaultTableModel(DATA, COLUMNS) {
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            if (columnIndex == 1) {
                return getValueAt(0, 1).getClass();
            }
            return super.getColumnClass(columnIndex);
        }
    };
    private JTable table = new JTable(model);
    private int minSelectedRow = -1;
    private int maxSelectedRow = -1;
    boolean tableModelListenerIsChanging = false;

    public CheckABunch() {
        add(new JScrollPane(table));

        table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                if (e.getValueIsAdjusting()) {
                    return;
                }
                minSelectedRow = ((DefaultListSelectionModel) e.getSource()).getMinSelectionIndex();
                maxSelectedRow = ((DefaultListSelectionModel) e.getSource()).getMaxSelectionIndex();
            }
        });

        model.addTableModelListener(new TableModelListener() {
            public void tableChanged(TableModelEvent e) {
                if (tableModelListenerIsChanging) {
                    return;
                }                
                int firstRow = e.getFirstRow();
                int column = e.getColumn();

                if (column != 1 || maxSelectedRow == -1 || minSelectedRow == -1) {
                    return;
                }
                tableModelListenerIsChanging = true;
                boolean value = ((Boolean)model.getValueAt(firstRow, column)).booleanValue();
                for (int i = minSelectedRow; i <= maxSelectedRow; i++) {
                    model.setValueAt(Boolean.valueOf(value), i, column);
                }

                // *** edit: added two lines
                minSelectedRow = -1;
                maxSelectedRow = -1;

                tableModelListenerIsChanging = false;
            }
        });
    }

    private static void createAndShowUI() {
        JFrame frame = new JFrame("CheckABunch");
        frame.getContentPane().add(new CheckABunch());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                createAndShowUI();
            }
        });
    }
}
Boatman answered 24/12, 2010 at 16:50 Comment(8)
@Hovercraft Full Of Eels though your code makes sense, when i executed on my system, its behaving in a quite unexpected way, the bunch selection works just fine, but after that the normal selection is also firing bunch selection......Botha
@Hovercraft Full Of Eels check box 2, uncheck box 2 and then check box 4..... then automatically box 2 is being checked......Botha
I'm not surprised as that should occur if 2 is still selected, which was I thought the desired behavior for the program: If you check or uncheck a check box, all selected rows will mimic that check box's state, including a single selected row left over from the last selection. If you don't desire this behavior, one way around it is to set the min and max selected row to -1 at the end of the TableModelListener's tableChanged method. I'll update the code below to show you what I mean.Boatman
+1 Good example. It works as described, but it only handles contiguous selections.Infancy
You are of course right, and thanks for the insights. Let me see what I can do...Boatman
as trashgod mentioned its working only for contiguous selections.... i have tried to store the values of the rows that are being selected, but no success here either..... :(Botha
@Hovercraft Full Of Eels: DefaultListSelectionModel has a clone() that might be helpful. Also, the data model's values are Boolean, so I think the conversion to boolean and Boolean.valueOf() can be eliminated.Infancy
@Hovercraft Full Of Eels: The clone() method proved fruitless, but I used your example to implement @camikr's suggestion.Infancy

© 2022 - 2024 — McMap. All rights reserved.