Get Selected Rows in JTable using AbstractTableModel
Asked Answered
S

2

4

I have a JTable using AbstractTableModel where I have a JCheckBox in the first column for selecting rows. Now, I need to get the selected rows from the table which are checked. Right now, I am sequentially traversing from first row to the last row and getting all the rows that are selected like the following,

List<Integer> selectedRows = new ArrayList<Integer>();
for(int i = 0; i < table.getRowCount(); i++) {
     if((Boolean) table.getValuAt(i, 0)) {
         selectedRows.add(i);
     }
}

The problem here is, I need to traverse all the rows when ever I need to get the selected rows. Right now I am having 10 to 20 rows. But in future I will get around 5000 rows. My question is, if there are 5000 rows and if the user selects only 5000nd (last record) row then I need to traverse all the 5000 rows to get the selected row. Which I think is not a good approach.

One approach which I want to implement is, to add a listener to the JCheckBox column, such that when ever there is a change (SELECTED/DESELECTED) then I need to update my array of the selected rows in the listener class. In this listener class when ever user selectes a JCheckBox I need to call table.getSelectedRow(..) and I need to store if that JCheckBox is selected.

Are there any better approaches ?

Soileau answered 17/12, 2012 at 13:26 Comment(7)
enhance the model: it can keep/synch an internal data structure containing its "selected" (aka: having a boolean true in first column) in setValueAt(...)Illustrative
Thanks. However to retrive the one which has boolean as true I need to traverse right?Soileau
If you are using a TableModel, you can basically use the same approach of keeping an array of selected rows. Only difference is that you can update it from the TableModel's setValueAt() method.Jeepers
@Che whats happening after user selected some row(s), s/he push to some Jbutton, or there is/are some automat :-), if is there user_action (JButtons click), then don't use any enhancement, use RowFilter with String ("true") for desired columnManifestation
@Manifestation wow .. :-) RowFilter and get the string 'true' values. Sounds great. I'll try, I think its an awesome suggestion.Soileau
sure RowFilter can uses Boolean, Integer, Date ... value too, but I'm used only String valueManifestation
haha .. I was just thinking that I have to make my Boolean values to String .. :(. Thanks you made it more easy .. :)Soileau
B
8

In the example below, the TableModel updates a Set<Integer> checked in the implementation of setValueAt(). The model of an adjacent JList listens to the table's model and displays the currently selected row numbers. The example assumes that the number of selected rows is small compared to the number of rows. Note the use of TreeSet, whose iterator retains the natural order of the elements.

image

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;

/** @see https://mcmap.net/q/1782137/-get-selected-rows-in-jtable-using-abstracttablemodel */
public class CheckTable {

    private static final CheckModel model = new CheckModel(5000);
    private static final JTable table = new JTable(model) {

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            return new Dimension(150, 300);
        }
    };

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

            @Override
            public void run() {
                JFrame f = new JFrame("CheckTable");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setLayout(new GridLayout(1, 0));
                f.add(new JScrollPane(table));
                f.add(new DisplayPanel(model));
                f.pack();
                f.setLocationRelativeTo(null);
                f.setVisible(true);
            }
        });
    }

    private static class DisplayPanel extends JPanel {

        private DefaultListModel dlm = new DefaultListModel();
        private JList list = new JList(dlm);

        public DisplayPanel(final CheckModel model) {
            super(new GridLayout());
            this.setBorder(BorderFactory.createTitledBorder("Checked"));
            this.add(new JScrollPane(list));
            model.addTableModelListener(new TableModelListener() {

                @Override
                public void tableChanged(TableModelEvent e) {
                    dlm.removeAllElements();
                    for (Integer integer : model.checked) {
                        dlm.addElement(integer);
                    }
                }
            });
        }
    }

    private static class CheckModel extends AbstractTableModel {

        private final int rows;
        private List<Boolean> rowList;
        private Set<Integer> checked = new TreeSet<Integer>();

        public CheckModel(int rows) {
            this.rows = rows;
            rowList = new ArrayList<Boolean>(rows);
            for (int i = 0; i < rows; i++) {
                rowList.add(Boolean.FALSE);
            }
        }

        @Override
        public int getRowCount() {
            return rows;
        }

        @Override
        public int getColumnCount() {
            return 2;
        }

        @Override
        public String getColumnName(int col) {
            return "Column " + col;
        }

        @Override
        public Object getValueAt(int row, int col) {
            if (col == 0) {
                return row;
            } else {
                return rowList.get(row);
            }
        }

        @Override
        public void setValueAt(Object aValue, int row, int col) {
            boolean b = (Boolean) aValue;
            rowList.set(row, b);
            if (b) {
                checked.add(row);
            } else {
                checked.remove(row);
            }
            fireTableRowsUpdated(row, row);
        }

        @Override
        public Class<?> getColumnClass(int col) {
            return getValueAt(0, col).getClass();
        }

        @Override
        public boolean isCellEditable(int row, int col) {
            return col == 1;
        }
    }
}
Bluet answered 17/12, 2012 at 18:27 Comment(6)
+1 .. is it a good practice to write logic (getting the selected rows) in the model itself. B'coz all these days I am separating the TableModel from my logics.Soileau
It's a trade-off, certainly worth profiling for large models. Also consider AbstractListModel for the JList or a shared model.Bluet
But I have an issue with this appraoach. When ever I delete some selected rows then those row numbrers have to be removed from the Set. But all the rows numbers in the Set that are having the previous row numbers will be inconsistent right? I have fuctionalities like delete, do_action, etc..Soileau
ok one solution which I can think is .. when ever I delete a row I will recreate the Set again and at one shot. So again it will come back to consistent state with all the selected rows.Soileau
Yes that's the trade-off: once you have determined that it's too expensive to recreate the Set each time, then you have to keep the Set up to date for all insert, update or delete operations. Your AbstractTableModel is the place to do it. Set<Integer> was just for demonstration; you may want to look at Set<Row> or some other data structure that supports your intended operations.Bluet
Thanks for the time. I will try to figure out what Data Structure it best fits and post here if that appraoch is better than this.Soileau
F
3

I agree with kleopatra. When you create a subclass of the AbstractTableModel, you'll override the setValue( Object value, int rowIndex, int colIndex ). In your overridden method, you just check if the column is the one with your check box, and if so, update the internal data structure appropriately. You can also add a method getCheckedRows() that returns a List< Integer > with the rows in which the check boxes have been selected.

Fakir answered 17/12, 2012 at 14:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.