Tuesday, March 9, 2010

Yet more JXTreeTable Knowledge - autoselecting a row after construction

This JXTreeTable knowledge - I'm just full of it.  :-)

My current client wants to read persisted data into a JXTreeTable, nothing special there, but then wants to automatically select the root node, which, in their application, would usually save the user a click, plus it enables all the cool gizmos for beginners to see.  My simple brute force approach was to read the data, set that into the model, and call

setRowSelectionInterval(0,0);

It wasn't working,  I could occasionally see the first row briefly highlight, but then it would go away, with no selection on the table, no cool enabled gizmos.  Drat.  After wasting time on futile efforts to set the selection later or more often, I eventually set breakpoints in setRowSelectionInterval() and clearSelection() to see who else was calling them.


The JXTreeTable constructor indirectly calls clearSelection().  It actually happens twice, through setModel() and setRowSorter().  But these happen during construction, well before my call, and are completely expected.

Somewhere after I read in the data, model.setRoot() gets called.  This triggers TreeTableMode.setColumnIdentifiers, which calls
TreeModelSupport.fireNewRoot(), which calls
TreeModelSupport.fireTreeStructureChanged(), which broadcasts to all the TreeModelListeners.

Other than sounding like all the "begats" in Genesis, this still makes sense.  One of the listeners is JXTreeTable's internal TreeModelListener, with a method, treeStructureChanged().  This method calls delayedFireTableStructureChanged() 

Now we are getting somewhere, that "delayed" should be a big hint. The code does the fairly obvious:

   SwingUtilities.invokeLater(new Runnable() {
      public void run() {
         fireTableStructureChanged();
      }
   });


Eventually, fireTableStructureChanged() will call JTable.clearSelectionAndLeadAnchor(), which, duh, calls clearSelection().

But, since this is called via invokeLater, it ends up happening after my call to select row 0.  Now, sharp purists would, at this time, point out that it was a mistake all along for me to select a row on a UI from a non-AWT thread.  But, but...  Anyway, the simple fix was, in my code, to also delay the selection.  After reading in the data and setting it into the model, instead of directly calling setRowSelectionInterval(0,0), wrap that call inside an invokeLater() i.e.

SwingUtilities.invokeLater(new Runnable() {
   public void run() {
      myTable.setRowSelectionInterval(0,0);
   } 
});


Now it works.  So, if you are struggling with selecting a row after setting up a JXTreeTable, now you know the trick too.