JTable Cell Update doesn't work
Asked Answered
M

3

9

In my application I have use a java table which is similar to this example. My problem is when I change a value of a cell (even in above example) the data model doesn't get updated until I click on a different cell. Even I click on the gray area below the table after changing the cell value the model won't change. I think the reason is cell stay on focused until I click on a different cell. How can I avoid this and update the model without clicking on the table cell. Thanks in advance

I have edit the above sample code to reflect the problem

public class JTableDemo extends JApplet {
  private JTextArea txt = new JTextArea(4, 20);

  // The TableModel controls all the data:
  class DataModel extends AbstractTableModel {
    Object[][] data = { { "one", "two", "three", "four" },
        { "five", "six", "seven", "eight" },
        { "nine", "ten", "eleven", "twelve" }, };

    // Prints data when table changes:
    class TML implements TableModelListener {
      public void tableChanged(TableModelEvent e) {
        txt.setText(""); // Clear it
        for (int i = 0; i < data.length; i++) {
          for (int j = 0; j < data[0].length; j++)
            txt.append(data[i][j] + " ");
          txt.append("\n");
        }
      }
    }

    public DataModel() {
      addTableModelListener(new TML());
       fireTableDataChanged();
    }

    public int getColumnCount() {
      return data[0].length;
    }

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

    public Object getValueAt(int row, int col) {
      return data[row][col];
    }

    public void setValueAt(Object val, int row, int col) {
      data[row][col] = val;
      System.out.println(val);
      // Indicate the change has happened:
      fireTableDataChanged();
    }

    public boolean isCellEditable(int row, int col) {
      return true;
    }
  }

  public void init() {
    Container cp = getContentPane();
    JTable table = new JTable(new DataModel());
    cp.add(new JScrollPane(table));
    cp.add(BorderLayout.SOUTH, txt);
  }

  public static void main(String[] args) {
    run(new JTableDemo(), 350, 200);
  }

  public static void run(JApplet applet, int width, int height) {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(applet);
    frame.setSize(width, height);
    applet.init();
    applet.start();
    frame.setVisible(true);
  }
}

When you run the example you can see the table content in the text area below the table. After you update a cell it should appear in the text box below. But 'setValueAt' method wont call until you click on a different cell.

Maples answered 31/10, 2011 at 8:27 Comment(1)
Can you edit your question and add an SSCCE that shows the problem?Categorical
B
8

The default update mechanism only changes the model when the cell editor loses the focus. Either tabbing out of the cell or clicking in a different cell will cause the vital "focus lost" event which triggers the model change.

Clicking in the gray area has no effect because there are no active elements there which could process the event - the table ignores the click.

If you want to change this, you need to find an event which tells the computer that the user is "done with editing". How do you know that?

[EDIT] Note that adding a "Save" button can disrupt the edit "flow" of users (click table cell, edit, grab mouse, aim, click save, click next cell, go back to keyboard, edit, ...)

Bondsman answered 31/10, 2011 at 9:16 Comment(6)
Just add a button that does nothing with a label "Save". Along with the client property from kleopatra's answer, that should do the trick. Note that it will greatly annoy your users because it means an additional mouse click. Try add a mnemonic so people can stop editing with "Alt+S" (without leaving the keyboard).Bondsman
no: a focusLost by default does not trigger a commit (which was a long-standing problem for ages, until THEY could be convinced to at least support to optionally set that behaviour). In fact, the commit on tabbing or clicking into another cell is explicitly triggered by tableUI codeInnocency
@Kleo: You probably missed "Along with the client property from kleopatra's answer". If that doesn't work, you'll need to call fireEditingStopped() of the cell editor in the button's click handler.Bondsman
I have done it following way. When user click on the save button in save actionListner I call the jTable.getCellEditor().stopCellEditing(); method.Maples
hach ... indeed, I did miss that line :-) - blind me - didn't read the comment) The point I insist on making is to not relate focus and commit via tabbing/clicking another cell, they are different beasts. And, no, you never call fireEditingStopped from some handler outside of the editor (instead call stopCellEditing). In fact, never-ever call any fireXX from outside the class that defines it ... (five minute edit limit forced me to delete old and write new comment ;-)Innocency
Note jTable.getCellEditor() will return null if there is no edit "in progress"Mutinous
I
14

That's expected behaviour: the edited value isn't committed to the backing model until an explicit user gesture, like f.i. pressing enter or tabbing out or clicking elsewhere in the table ...

One oddity (some call it bug :-) of JTable is that editing isn't by default terminated when transfering focus to the "outside" of the table. To force it doing so, you need to configure it like:

  table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);

BTW (unrelated, just for sanity): always fire the most fine-grained event type, here that would be a cellUpdated instead of the dataChanged hammer.

Innocency answered 31/10, 2011 at 9:9 Comment(1)
I love your Mondays, great and valuable answer +1Breathe
B
8

The default update mechanism only changes the model when the cell editor loses the focus. Either tabbing out of the cell or clicking in a different cell will cause the vital "focus lost" event which triggers the model change.

Clicking in the gray area has no effect because there are no active elements there which could process the event - the table ignores the click.

If you want to change this, you need to find an event which tells the computer that the user is "done with editing". How do you know that?

[EDIT] Note that adding a "Save" button can disrupt the edit "flow" of users (click table cell, edit, grab mouse, aim, click save, click next cell, go back to keyboard, edit, ...)

Bondsman answered 31/10, 2011 at 9:16 Comment(6)
Just add a button that does nothing with a label "Save". Along with the client property from kleopatra's answer, that should do the trick. Note that it will greatly annoy your users because it means an additional mouse click. Try add a mnemonic so people can stop editing with "Alt+S" (without leaving the keyboard).Bondsman
no: a focusLost by default does not trigger a commit (which was a long-standing problem for ages, until THEY could be convinced to at least support to optionally set that behaviour). In fact, the commit on tabbing or clicking into another cell is explicitly triggered by tableUI codeInnocency
@Kleo: You probably missed "Along with the client property from kleopatra's answer". If that doesn't work, you'll need to call fireEditingStopped() of the cell editor in the button's click handler.Bondsman
I have done it following way. When user click on the save button in save actionListner I call the jTable.getCellEditor().stopCellEditing(); method.Maples
hach ... indeed, I did miss that line :-) - blind me - didn't read the comment) The point I insist on making is to not relate focus and commit via tabbing/clicking another cell, they are different beasts. And, no, you never call fireEditingStopped from some handler outside of the editor (instead call stopCellEditing). In fact, never-ever call any fireXX from outside the class that defines it ... (five minute edit limit forced me to delete old and write new comment ;-)Innocency
Note jTable.getCellEditor() will return null if there is no edit "in progress"Mutinous
G
0

For my case

public void setValueAt(Object val, int row, int col) {
  data[row][col] = val;
  System.out.println(val);
  // Indicate the change has happened:
  fireTableDataChanged();
}

you need to call super function also.

public void setValueAt(Object val, int row, int col) {
  data[row][col] = val;
  System.out.println(val);
  // Indicate the change has happened:
  fireTableDataChanged();
  super.setValueAt(val,row,col);
}
Grasping answered 14/4, 2020 at 16:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.