0

I have a Jtable set up in a tabbed pane that is filled with an id and checkboxes. The table looks something along the lines of this, where . are the checkboxes.

         |       VAR1        |
ID | ALL | subVar1 | subVar2 |
------------------------------
id1|  .  |    .    |    .    |

Now, I also have a TableListener attached to this table. What I would like to happen is that, whenever a user presses the ALL checkbox, all the checkboxes in that row need to be selected (ie true). This is the table listener code.

@Override
public void tableChanged(TableModelEvent e) {
    if(e.getSource() == assignedTableModel) {
        for (int i = 0; i < aTable.getRowCount(); i++) {
            boolean isAllChecked = (Boolean) aTable.getValueAt(i, 1);
            if(isAllChecked) {
                assignedTableModel.setValueAt(Boolean.TRUE, i, j);
            }
            else {
            ...
            }
        }
    }
}

Clicking the ALL checkbox causing the table to change, assignedTableModel.setValueAt(Boolean.TRUE, i, j); is called, the table is changed again and therefore calls the listener, which calls this function again.

My question, is there another way of updating the checkboxes? Or is there a way to set a base to get out of the recursion?

EDIT The rows are added dynamically. I'm wondering if adding an actionListener to the ALL checkbox as it's being added will be a solution. I'll come back with how it turns out.

EDIT2 I'd forgot to mention that the whole table is generated dynamically. That means I have no way of knowing how many columns and rows will be present, the only columns I know are ID and the ALL col. Most answers already present deal with hard coded implementations.

mKorbel
  • 109,525
  • 20
  • 134
  • 319
Phreakradio
  • 176
  • 1
  • 18
  • Basically, whenever a cell is updated, the `TableModel` is likely to trigger a `TableModelEvent`, so, each time you call `setValueAt`, it's (indirectly) calling `tableChanged`. A better solution would be to override the `setVaueAt` method of the `TableModel` and take appropriate action within in, [for example](http://stackoverflow.com/questions/35517299/jtable-setvalueat-stackoverflowerror/35517380#35517380). Another solution might also be to dynamically generate the value for other columns when `getValueAt` is called, but I don't think that would suit your needs – MadProgrammer Feb 22 '16 at 20:37
  • *"EDIT The rows are added dynamically. I'm wondering if adding an actionListener to the ALL checkbox as it's being added will be a solution. I'll come back with how it turns out."* - No. You should have only a single `JCheckBox` (wrapped in a `TableCellEditor`) for the column. When the cell editing is "stopped", the `JTable` will call the models `setValueAt` method so you can make determinations about which cell was edited and potentially calculate any side effects that might need to occur – MadProgrammer Feb 22 '16 at 20:42
  • Maybe take closer look at [How to Use Tables](http://docs.oracle.com/javase/tutorial/uiswing/components/table.html) and [Using Other Editors](http://docs.oracle.com/javase/tutorial/uiswing/components/table.html#editor) – MadProgrammer Feb 22 '16 at 20:43

2 Answers2

3

whenever a user presses the ALL checkbox, all the checkboxes in that row are selected

So why are you looping through all the rows in your code? The event will be generated only for the row you click and you only need to select the check marks for the columns on that row. Get rid of the looping code.

the table is changed again and therefore calls the listener, which calls this function again.

You need an if condition to identify when the check box in the first column is checked:

if (e.getType() == TableModelEvent.UPDATE)
{
    int row = e.getFirstRow();
    int column = e.getColumn();

    if (column == 0)
    {
        TableModel model = (TableModel)e.getSource();
        model.setValueAt(Boolean.true, row, 1);
                ...
    }
}

Now the change of state on the other columns will be ignored.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Implementing your code, I found that it's basically the same thing as what I posted earlier. To reiterate, here's the stacktrace that occurs when I click the ALL button: (Click ALL button) -> (tableChanged) -> (check if ALL button checked) -> (iterate through other checkboxes in row and switch them to true) -> (tableChanged) -> (check if ALL button checked) -> (iterate through other checkboxes in row and switch them to true) -> etcetcetc The setValueAt function is updating the table and causing it to recurse. – Phreakradio Feb 22 '16 at 20:32
  • @Phreakradio, You implemented the code incorrectly. Here is a complete working example showing how this works: http://stackoverflow.com/questions/3540661/tablemodellistener-and-multiple-column-validation/3541876#3541876 – camickr Feb 22 '16 at 20:39
  • `Most answers already present deal with hard coded imlpementations` - the number of rows is irrelevant. You only want to change the checkboxes on the current row. The number of columns is also irrelevant. It just means that you need to loop through each column for the given row to see if the column is a Boolean.class. If so you update the value. I have already given you the basic logic for the answer and a complete working example. So yes. you will need to modify it slightly to replace the hard coding with a loop. But that is minor, don't expect us to write the entire code for you. – camickr Feb 22 '16 at 21:12
  • 1
    @Phreakradio, `Based off of @camickr's response...` So the structure of this code fixed the recursion problem. Glad it helped. Don't forget to "accept" this answer by clicking on the check mark so people know the problem has been solved. – camickr Feb 22 '16 at 21:12
0

Based off of @camickr's response, the following piece of code seems to be doing the trick. Thanks.

    @Override
    public void tableChanged(TableModelEvent e) {
        if (e.getType() == TableModelEvent.UPDATE && e.getSource() == assignedTableModel)
        {
            int row = e.getFirstRow();
            int column = e.getColumn();

            if (column == 1)
            {
                DefaultTableModel model = (DefaultTableModel) e.getSource();
                for(int i=2 ; i<model.getColumnCount() ; i++) {
                    if((boolean) model.getValueAt(row, 1))
                        model.setValueAt(Boolean.TRUE, row, i);
                    else
                        model.setValueAt(Boolean.FALSE, row, i);
                }
            }
        }
}
Phreakradio
  • 176
  • 1
  • 18
  • There is no need to check if the source is the assignedTableModel. You only add the listener to one model. This also removes the need for the listener to reference an external variable. Also then is no need for the if/else statement, just assign the value of the Boolean variable in the first column, since it looks like you code is used to either select all or deselect all. – camickr Feb 22 '16 at 21:19