Swing Puzzler #1
February 15th, 2010 | 8 Comments »I don’t like programming puzzles. If a small piece of code does not do what it appears to be doing after skimming through it, it is either the problem in the code itself, or the underlying libraries that it is using. Code should be easy to read, and easy to understand. This is why i didn’t enjoy any of the “Java puzzles” sessions during the last few JavaOne conferences. I have the original book and i’ve seen the presentation slides. Personally, i don’t think that i can learn much from them – except seeing that the puzzles originate from the intricacies of the language specification, unfortunate naming of the core APIs, or the artificial restrictions of binary compatibility that resulted in the mess that is core Java generics.
Unfortunately, all of these are still part of the platform – whether we want them or not. Which is why i’d like to present you with this simple Swing program based on the code from QStorm in one of the Substance forums. Note that the following code does not use Substance and runs under the default look-and-feel.
public class Puzzle1 { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame frame = new JFrame("Puzzle 1"); frame.setSize(new Dimension(300, 200)); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JTable table = new JTable(); table.getSelectionModel().addListSelectionListener( new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; System.out.println("Table: " + table.getRowCount() + ", model: " + table.getModel().getRowCount()); } }); table.setAutoCreateRowSorter(true); table.setModel(new DefaultTableModel( new Object[][] { new Object[] { "Steven", 10 } }, new Object[] { "Name", "Value" })); frame.add(table, BorderLayout.CENTER); JPanel controls = new JPanel( new FlowLayout(FlowLayout.TRAILING)); JButton selectAllRowsBtn = new JButton("Select all rows"); selectAllRowsBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { table.selectAll(); } }); controls.add(selectAllRowsBtn); JButton resetModelBtn = new JButton("Reset model"); resetModelBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { table.setModel(new DefaultTableModel()); } }); controls.add(resetModelBtn); frame.add(controls, BorderLayout.SOUTH); frame.setVisible(true); } }); } }
This is how the UI looks like – it has a table with one row, and two buttons on the bottom:
When the “Select all rows” button is clicked, the JTable.selectAll() is called:
When the “Reset model” is called, the table model is reset to an empty model (no rows):
After the table is created, a selection listener is added to it. It prints the number of table rows as returned by JTable.getRowCount() and as returned by JTable.getModel().getRowCount().
What is the last line printed to the console after the two buttons have been activated?
- Table: 0, model: 0
- Table: 1, model: 0
- Table: 1, model: 1
- None of the above
It will be Table: 1, model: 0
The reason is because when you add the new DefaultTableModel you do not specify the an of the data (rows or columns) for the new TableModel and thus it is constructed with zero rows and zero columns and thus remembers the previous number of rows from a cached value in the JTable.getRowCount() I believe. This is illustrated by adding a button to add new rows of data then selecting all the rows and then adding a new TableModel. The output will be Table: , model: 0. This can be solved by changing the code to “new DefaultTableModel(new Object[0][], new Object[] { “Column”, “Names” })” instead of the default constructor.
Adam,
Did you run the code with your suggested change (explicitly passing parameters to the new instance of DefaultTableModel)? I don’t see any change in the results.
Thanks
Kirill
Should it not be 0 & 0? If you reset the table model to a DefautlTableModel then it should be 0 & 0, according to the javadocs
“Constructs a default DefaultTableModel which is a table of zero columns and zero rows.”
and (for table.getRowCount())
“Returns the number of rows in this table’s model. “
It gets obvious when looking at JTable’s source code. JTable.getRowCount() takes count from RowSorter, but replacing RowSorter is very last part in setModel() method. Of course it is not good practice at all, and as for me it would be better to remove all those methods from JTable class to make everyone use MVC without compromises, but Java’s backward compatibility trap will never allow it.
The answer is: “Table: 1, model: 0″
It is due to the line that says: table.setAutoCreateRowSorter(true);
This line creates a TableModel that wraps the current TableModel in the table (RowSorter). Then, when you call table.getRowCount(), the table returns the row count returned by RowSorter instead of the DefaultTableModel.
When you reset the TableModel, you are changing only the TableModel, but the table still has a RowSorter. Thats why table.getRowCount() returns a different value of table.getModel().getRowCount().
If you comment the line table.setAutoCreateRowSorter(true); the last line printed will be: “Table: 0, model: 0″ as we expected.
Doesn’t matter what the new model is (as Aekold already pointed out) – the base problem is that “during self-cleanup” of the table its state is more or less indeterminate. That’s hard to overcome, whatever the sequence of updating the sorter/selection/ internals are. In the ol’ SwingX sorting we forced the selectionModel to adjusting while processing any tableEvent … note to myself: should do so again, at least on structureChagned ;-)
Cheers
Jeanette
Great puzzler !
Reminded me from the good old days, Who said Swing backward compatibility was sometimes a real pain ;-)
Nice answer by Jeanette, as always !
[...] Grouchnikov posted a Swing Puzzler asking what you expect following a short snippet of code. I share his sentiment about not liking [...]