views:

444

answers:

4

I try to dynamically add nodes to a Java Swing JTree, and the user should be able to browse and to collapse hierarchy while nodes are constantly added. When I add a Thread.sleep(10) in my loop, it works fine; but this is a dirty hack...

Here is the stripped down code that triggers this problem. Whenever I run this and doubleclick on the root node to expand/collapse it (while nodes are added), I get an ArrayIndexOutOfBoundsException. When I add a Thread.sleep(10) this does not happen. I guess this is a threading issue, but I have no idea how to synchronize this? Any hints would be greatly appreciated!

public static void main(String[] args) throws InterruptedException {
    final JFrame frame = new JFrame();
    frame.setSize(600, 800);
    frame.setVisible(true);

    MutableTreeNode root = new DefaultMutableTreeNode("root");
    final DefaultTreeModel model = new DefaultTreeModel(root);
    final JTree tree = new JTree(model);
    frame.add(new JScrollPane(tree));

    while (true) {
        MutableTreeNode child = new DefaultMutableTreeNode("test");
        model.insertNodeInto(child, root, root.getChildCount());
        tree.expandRow(tree.getRowCount() - 1);

        // uncommenting this to make it work
        // Thread.sleep(10);
    }
}

I want to use this for a search-on-typing application, so giving (almost) instant results is essential for me.

EDIT: Thanks for the quick answers! SwingUtilities.invokeLater() solves the problem.

I now do this:

  1. Add 100 items within SwingUtilities.invokeLater();
  2. After 100 items, I run this so that the GUI can get updated:

    // just wait so that all events in the queue can be processed
    SwingUtilities.invokeAndWait(new Runnable() {
        public void run() { }; 
    });
    

This way I have a very responsive GUI and it works perfectly. Thanks!

A: 

You may want to have the tree.expandRow command run from a TreeModelListener's treeNodesInserted event so that it only runs after the model is updated.

R. Bemrose
I have just tried this now, but I get exactly the same result.
martinus
+1  A: 

Try to enclose the while loop into an

SwinUtilities.invokeLater(new Runnable() { public void run() { // while(true... } }

This will execute your code in a swing thread.

Boris Pavlović
You aren't seriously suggesting to put an infinite loop in a invokeLater, are you? Your gui will stop responding if the invokeLater never returns.
Paul Tomblin
You've got the point
Boris Pavlović
+3  A: 

tree.expandRow needs to be done in the event thread, so change the loop as follows:

while (true) 
{
        MutableTreeNode child = new DefaultMutableTreeNode("test");
        model.insertNodeInto(child, root, root.getChildCount());
        final int rowToExpand = tree.getRowCount() - 1; // ? does this work ?
        SwingUtilities.invokeLater(new Runnable()
        {
           public void run()
           {
               tree.expandRow(rowToExpand);
           }
        });

}

While you're at it, you probably need to make sure whatever list your tree model is using is synchronized, so you don't insert into the collection while the tree is being traversed by the paint thread.

Paul Tomblin
Wouldn't you need to go ahead and save tree.getRowCount() - 1, considering there may have been 50 rows inserted before that Runnable finally kicks off?
Richard Campbell
@Richard, good point.
Paul Tomblin
A: 

Swing is thread-hostile, so do your Swing manipulation in the AWT Event Diaptch Thread (EDT).

The infinite loop is nonsense, so difficult to come up with a suggestion. The best equivalent I can think of is iterating posting an even to run the code again. Because there are certain priorities in the event queue, I'm not sure even that works.

public static void main(String[] args) throws InterruptedException {
    java.awt.EventQueue.invokeLater(new Runnable() { public void run() {
        runEDT();
    }});
}
private static void runEDT() {
    assert java.awt.EventQueue.isDispatchThread();

    final JFrame frame = new JFrame();
    frame.setSize(600, 800);
    frame.setVisible(true);

    final MutableTreeNode root = new DefaultMutableTreeNode("root");
    final DefaultTreeModel model = new DefaultTreeModel(root);
    final JTree tree = new JTree(model);
    frame.add(new JScrollPane(tree));
    frame.validate();

    new Runnable() { public void run() {
        final MutableTreeNode child = new DefaultMutableTreeNode("test");
        model.insertNodeInto(child, root, root.getChildCount());
        tree.expandRow(tree.getRowCount() - 1);

        final Runnable addNode = this; // Inner class confusion...
        java.awt.EventQueue.invokeLater(addNode);
    }}.run();
}

(Disclaimer: Not compiled or tested.)

Tom Hawtin - tackline