6

I have a JTable with checkbox as one of the column. I also have a checkbox in header to check/uncheck all. AFAIK the default behaviour of JTable is it deselects all the rows selected before if select a new row. But can we achieve CTRL click like behavior with checkbox. That retaining the previously selected row. The main problem I am facing is while enabling multiple selection of JTable rows using checkbox.

Expected output

1st row is checked then first row is selected and if third row is checked then third is selected along with first row (which is already checked and selected)

Actual output

When first row is checked and selected and if select the third row then it deselects all the rows previously selected and the third one is only selected.

I have a sample code which simulates the scenario I want to achieve same as the Add Another One button does, but with checkbox selection.

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumn;
import javax.swing.event.CellEditorListener;

public class JTableRowSelectProgramatically extends JPanel {

final JTable table = new JTable(new MyTableModel());

public JTableRowSelectProgramatically() {
    initializePanel();
}

private void initializePanel() {
    setLayout(new BorderLayout());
    setPreferredSize(new Dimension(475, 150));


    table.setFillsViewportHeight(true);
    JScrollPane pane = new JScrollPane(table);

    JLabel label2 = new JLabel("Row: ");
    final JTextField field2 = new JTextField(3);
    JButton add = new JButton("Select");

    table.setRowSelectionAllowed(true);
    table.setColumnSelectionAllowed(false);
    table.getSelectionModel().addListSelectionListener(new ListSelectionListenerImpl());
    TableColumn tc = table.getColumnModel().getColumn(3);
    tc.setCellEditor(table.getDefaultEditor(Boolean.class));
    tc.setCellRenderer(table.getDefaultRenderer(Boolean.class));
    ((JComponent) table.getDefaultRenderer(Boolean.class)).setOpaque(true);
    tc.getCellEditor().addCellEditorListener(new CellEditorListenerImpl());

    add.addActionListener(new ActionListener() {

        public void actionPerformed(ActionEvent event) {
            int index2 = 0;
            try {
                index2 = Integer.valueOf(field2.getText());
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
            table.addRowSelectionInterval(index2, index2);
            field2.setText(String.valueOf(index2));
        }
    });

    JPanel command = new JPanel(new FlowLayout());
    command.add(label2);
    command.add(field2);
    command.add(add);

    add(pane, BorderLayout.CENTER);
    add(command, BorderLayout.SOUTH);
}

public static void showFrame() {
    JPanel panel = new JTableRowSelectProgramatically();
    panel.setOpaque(true);

    JFrame frame = new JFrame("JTable Row Selection");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setContentPane(panel);
    frame.pack();
    frame.setVisible(true);
}

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

        public void run() {
            JTableRowSelectProgramatically.showFrame();
        }
    });
}

public class MyTableModel extends AbstractTableModel {

    private String[] columns = {"ID", "NAME", "AGE", "A STUDENT?"};
    private Object[][] data = {
        {1, "Alice", 20, new Boolean(false)},
        {2, "Bob", 10, new Boolean(false)},
        {3, "Carol", 15, new Boolean(false)},
        {4, "Mallory", 25, new Boolean(false)}
    };

    public int getRowCount() {
        return data.length;
    }

    public int getColumnCount() {
        return columns.length;
    }

    public Object getValueAt(int rowIndex, int columnIndex) {
        return data[rowIndex][columnIndex];
    }

    @Override
    public String getColumnName(int column) {
        return columns[column];
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnIndex == 3;
    }

    //
    // This method is used by the JTable to define the default
    // renderer or editor for each cell. For example if you have
    // a boolean data it will be rendered as a check box. A
    // number value is right aligned.
    //
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return data[0][columnIndex].getClass();
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        if (columnIndex == 3) {
            data[rowIndex][columnIndex] = aValue;
            fireTableCellUpdated(rowIndex, columnIndex);
        }
    }
}

class ListSelectionListenerImpl implements ListSelectionListener {

    public void valueChanged(ListSelectionEvent lse) {
        ListSelectionModel lsm = (ListSelectionModel) lse.getSource();
        int row = table.getRowCount();
        if (lsm.isSelectionEmpty()) {
        } else {
//                If any column is clicked other than checkbox then do normal selection
//                i.e select the click row and deselects the previous selection
            if (table.getSelectedColumn() != 3) {
                for (int i = 0; i < row; i++) {
                    if (lsm.isSelectedIndex(i)) {
                        table.setValueAt(true, i, 3);
                    } else {
                        table.setValueAt(false, i, 3);
                    }
                }

            }
        }
    }
  }
public class CellEditorListenerImpl implements CellEditorListener{

    public void editingStopped(ChangeEvent e) {
        for(int i=0; i<table.getRowCount();i++){
            if((Boolean)table.getValueAt(i, 3)){
                table.addRowSelectionInterval(i, i);
            }
            else{
                table.removeRowSelectionInterval(i, i);
            }
        }
    }

    public void editingCanceled(ChangeEvent e) {
        System.out.println("do nothing");
    }

}
}
Community
  • 1
  • 1
rcnpl
  • 111
  • 1
  • 2
  • 9

2 Answers2

4

Once you implement these TableModel methods, you can use setValueAt() in your button listeners to condition the model as required to keep the checkbox state and selection model in synchrony. There's a related example here.

@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
    return columnIndex == 3;
}

@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
    if (columnIndex == 3) {
        data[rowIndex][columnIndex] = aValue;
        fireTableCellUpdated(rowIndex, columnIndex);
    }
}

Addendum: As a concrete example, your clear listener might invoke a method in the TableModel such as clearChecks():

MyTableModel model = (MyTableModel) table.getModel();
model.clearChecks();
...
private void clearChecks() {
    for (int i = 0; i < data.length; i++) {
        data[i][3] = false;
    }
    fireTableRowsUpdated(0, data.length);
}
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Thanks for the answer. But one thing how can we control the selection of the row? What i have already implemented in my application is -Check/UnCheck All rows with header check box - can check the checkbox but wothout highlighting of the row I tried your code but its the same i can check but the checked row is not highlighted. Code i provided as sscce is just a simulation. I want same functionality of Add Another one button but through Checkbox – rcnpl May 21 '12 at 05:44
  • You have to manipulate the table's `ListSelectionModel`; while I'm away, please update your question's code to reflect the changes I've proposed and any changes you've since made. – trashgod May 21 '12 at 05:45
  • implemented your suggestion and edited the sscce but still mu question is i want to select the rows based upon the checkbox selection.i.e. If I check the checkbox the row should be selected and uncheck the checkbox then row should be deselected and should apply for multiple row not just the single row. – rcnpl May 21 '12 at 06:20
  • The `clearChecks()` method should go in your model and be called from your (now missing) `clear` handler. You should also restore your `select` handler and add a similar `syncChecks()`. – trashgod May 21 '12 at 14:21
  • Resolved. I implemented the ListSelectionListener to the checkbox. Which means i can catch the event when it is edited and then select the row based on the checkbox value. And in ListSelectionListener i would do nothing if checkbox is clicked and do the normal selection if any other column is clicked. @trashgod: I have removed clearChecks() because i am handling check/uncheck All with separate CheckboxHandlerHeader class which i have not shown in sscce for the simplicity. – rcnpl May 22 '12 at 00:36
  • 1
    Could you please share your implementation of ListSelectionListener? – FoxyBOA Nov 29 '18 at 07:57
  • Here's a typical [example](https://stackoverflow.com/a/12306104/230513); more [here](https://stackoverflow.com/search?tab=votes&q=user%3a230513%20%5bjtable%5d%20ListSelectionListener). – trashgod Nov 29 '18 at 08:03
0

At a high level, I would invoke a key listener and check if VK_CTRL is flagged when inside your selection listener. This sounds like a logic block issue or a listener conflict.

Barring that, a JList box allows for multiple selections.

roguequery
  • 964
  • 13
  • 28
  • Can you elaborate bit what do you mean by checking VK_CTRL. I want to select the row if the checkbox is checked and vice-versa. As checkbox is defaulteditor for Boolean class in JTable that means should i write a separate editor for checkbox and add keylistener there or should i add keylistener to JTable. I would be very thankful if you can throw light on your approach. – rcnpl May 21 '12 at 04:29
  • When you said CTRL like behavior, I thought you wanted to be able to check multiple boxes at once to affect their particular rows. – roguequery May 21 '12 at 04:32
  • in this case, I would make a class where the checkbox is the parent of that table. So an abstract class would remove some of the tedious field2 like variables you have. It could be rewritten as simply changing a boolean in a sub class called MY_Table_Row or such. I have a hunch that there is a naming mismatch that is causing the eccentricities. – roguequery May 21 '12 at 04:34
  • Also, it seems that you have a sort of radio-button behavior going on with your check boxes, as modding one mods the states of the others. Maybe, in addition to this subclass I'm promoting, store the states of these class's checkboxes in an array that contains all selected checkboxes. – roguequery May 21 '12 at 04:36
  • Sorry if i was not clear on my question. CTRL like behavior means If i had already selected one row then if i click another row then normally simple click would disselects previous selection but CTRL click would retain previous selection. Want to do the same thing with checkbox if any row is already selected then retain its selection and also select the new one. – rcnpl May 21 '12 at 04:37
  • Ok then, a combination of the two is in order. I would recommend making a class that contains the checkbox as well as its child row. Then, in the Table class you've implemented, check for a Key Listener that checks if the CTRL key is depressed. [http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/event/KeyListener.html](Key Listeners) – roguequery May 21 '12 at 04:40
  • This sounds like they normally act like radio buttons, but if CTRL is pressed, they act like a multi-selected list – roguequery May 21 '12 at 04:41
  • Yes you are right. Checkbox beahves as Radiobutton with row selection. But I can select all row (using header checkbox), multiple rows via mouse dragging and CTRL/SHIFT row click. Want to implement Hotmail/Yahoo checkbox like thing but unable due to JTable row selection limitation. – rcnpl May 21 '12 at 04:47
  • I think using a keylistener for SHIFT and CTRL respectively will give you the functionality you want – roguequery May 21 '12 at 04:50