Reputation: 18063
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:
SwingUtilities.invokeLater();
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!
Upvotes: 3
Views: 2009
Reputation: 147164
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.)
Upvotes: 0
Reputation: 182878
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.
Upvotes: 5
Reputation: 88846
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.
Upvotes: -1