Dynamic JTree and SwingUtilities.invokeLater() does nothing
-
29-04-2021 - |
Question
To create my dynamic JTree, I was reading the tutorial about Dynamic JTrees on this website in chapter "4.2 OutlineNode.java"
Now I have implemented it and recognize that loading the data in the GUI thread takes long and is ugly as well. Therefore I added a thread which expands the children and then adds the TreeNode
element to the tree.
private void getChildNodes() {
areChildrenDefined = true;
Thread t = new Thread(new Runnable()
{
@Override
public void run() {
System.out.println("Expand");
final List<DECTTreeNode> listNodes = new ArrayList<DECTTreeNode>();
if (castNode().canExpand())
{
for(DECTNode crt : castNode().getChildren())
{
DECTTreeNode treeNode = new DECTTreeNode(crt);
listNodes.add(treeNode);
}
try {
SwingUtilities.invokeAndWait(new Runnable()
{
@Override
public void run() {
System.out.println(listNodes.size());
for (DECTTreeNode crt : listNodes)
{
add(crt); // <==== Adds the node to the JTree
}
}
});
//}).run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
t.start();
}
Without the thread it works without problems. If I add the thread and put the add
-calls into a SwingUtilities.invokeAndWait(...)
, the children seem to be expanded, but they're not visible in the tree.
I've already tried revalidate()
and repaint()
on the tree - without any effect.
Any idea how to make these elements visible?
Thank you in advance.
Solution
@Override
public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException {
CustomTreeNode t = (CustomTreeNode) e.getPath().getLastPathComponent();
t.addChildLoadedListener(new ChildLoadedListener() {
@Override
public void childLoaded(TreeNode parent) {
((CustomTreeNode) parent).setExpanded(true);
expandPath(new TreePath(((CustomTreeNode) parent).getPath()));
}
});
if (!t.isExpanded()) {
factory.loadChildren(t);
throw new ExpandVetoException(null);
}
}
public void treeWillCollapse(TreeExpansionEvent e) throws ExpandVetoException {
CustomTreeNode t = (CustomTreeNode) e.getPath().getLastPathComponent();
t.setExpanded(false);
}
Jugde me or not. This works for me. CustomTreeNode is extended from defaultMutableTreeNode and has been added a selfwritten ChildLoadedListener which is called when the children have been loaded by the factory. The isExpanded boolean is to avoid a endless loop. the factory creates a SwingWorker that loads the children and executes it. after that the ChilLoadedListener is called and the tree is expanded again.
Hope this will help or at least help you think about your problem ;-)
EDIT :
@Override
public void loadChildren(CustomTreeNode tn) {
ctn = tn;
LoadChildrenWorker worker = new LoadChildrenWorker();
worker.execute();
}
private class LoadChildrenWorker extends SwingWorker<String, Object> {
@Override
protected String doInBackground() throws Exception {
//load source here and return a string when finished.
//In my case its a string repesentation of a directory
}
@Override
protected void done() {
//with get(), you get the string from doBackground()
for (String str : parseFromOutput(get())) {
if (str.endsWith("/")) {
ctn.add(new CustomTreeNode("Directory");
} else {
ctn.add(new CustomTreeNode("Leaf");
}
}
//call listeners
ctn.fireChildrenLoaded();
}
OTHER TIPS
Check that your add() method fires the correct TreeModelEvent
to expand suggestion by Walter +1, invoke Runnable#Thread
from JButton addButton
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
public class DynamicTreeDemo extends JPanel implements ActionListener {
private static final long serialVersionUID = 1L;
private int newNodeSuffix = 1;
private static String ADD_COMMAND = "add";
private static String REMOVE_COMMAND = "remove";
private static String CLEAR_COMMAND = "clear";
private DynamicTree treePanel;
public DynamicTreeDemo() {
super(new BorderLayout()); // Create the components.
treePanel = new DynamicTree();
populateTree(treePanel);
JButton addButton = new JButton("Add");
addButton.setActionCommand(ADD_COMMAND);
addButton.addActionListener(this);
JButton removeButton = new JButton("Remove");
removeButton.setActionCommand(REMOVE_COMMAND);
removeButton.addActionListener(this);
JButton clearButton = new JButton("Clear");
clearButton.setActionCommand(CLEAR_COMMAND);
clearButton.addActionListener(this); // Lay everything out.
treePanel.setPreferredSize(new Dimension(300, 150));
add(treePanel, BorderLayout.CENTER);
JPanel panel = new JPanel(new GridLayout(0, 3));
panel.add(addButton);
panel.add(removeButton);
panel.add(clearButton);
add(panel, BorderLayout.SOUTH);
}
public void populateTree(DynamicTree treePanel) {
String p1Name = "Parent 1";
String p2Name = "Parent 2";
String c1Name = "Child 1";
String c2Name = "Child 2";
DefaultMutableTreeNode p1, p2;
p1 = treePanel.addObject(null, p1Name);
p2 = treePanel.addObject(null, p2Name);
treePanel.addObject(p1, c1Name);
treePanel.addObject(p1, c2Name);
treePanel.addObject(p2, c1Name);
treePanel.addObject(p2, c2Name);
}
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if (ADD_COMMAND.equals(command)) { // Add button clicked
treePanel.addObject("New Node " + newNodeSuffix++);
} else if (REMOVE_COMMAND.equals(command)) { // Remove button clicked
treePanel.removeCurrentNode();
} else if (CLEAR_COMMAND.equals(command)) { // Clear button clicked.
treePanel.clear();
}
}
private static void createAndShowGUI() { // Create and set up the window.
JFrame frame = new JFrame("DynamicTreeDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Create and set up the content pane.
DynamicTreeDemo newContentPane = new DynamicTreeDemo();
newContentPane.setOpaque(true); // content panes must be opaque
frame.setContentPane(newContentPane); // Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowGUI();
}
});
}
}
class DynamicTree extends JPanel {
private static final long serialVersionUID = 1L;
private DefaultMutableTreeNode rootNode;
private DefaultTreeModel treeModel;
private JTree tree;
private Toolkit toolkit = Toolkit.getDefaultToolkit();
public DynamicTree() {
super(new GridLayout(1, 0));
rootNode = new DefaultMutableTreeNode("Root Node");
treeModel = new DefaultTreeModel(rootNode);
treeModel.addTreeModelListener(new MyTreeModelListener());
tree = new JTree(treeModel);
tree.setEditable(true);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setShowsRootHandles(true);
JScrollPane scrollPane = new JScrollPane(tree);
add(scrollPane);
}
public void clear() {
rootNode.removeAllChildren();
treeModel.reload();
}
public void removeCurrentNode() {
TreePath currentSelection = tree.getSelectionPath();
if (currentSelection != null) {
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection.getLastPathComponent());
MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent());
if (parent != null) {
treeModel.removeNodeFromParent(currentNode);
return;
}
} // Either there was no selection, or the root was selected.
toolkit.beep();
}
public DefaultMutableTreeNode addObject(Object child) {
DefaultMutableTreeNode parentNode = null;
TreePath parentPath = tree.getSelectionPath();
if (parentPath == null) {
parentNode = rootNode;
} else {
parentNode = (DefaultMutableTreeNode) (parentPath.getLastPathComponent());
}
return addObject(parentNode, child, true);
}
public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child) {
return addObject(parent, child, false);
}
public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child, boolean shouldBeVisible) {
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child);
if (parent == null) {
parent = rootNode;
}
// It is key to invoke this on the TreeModel, and NOT DefaultMutableTreeNode
treeModel.insertNodeInto(childNode, parent, parent.getChildCount());
// Make sure the user can see the lovely new node.
if (shouldBeVisible) {
tree.scrollPathToVisible(new TreePath(childNode.getPath()));
}
return childNode;
}
class MyTreeModelListener implements TreeModelListener {
@Override
public void treeNodesChanged(TreeModelEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent()); /*
* If the event lists children, then the changed node is the child of the
* node we've already gotten. Otherwise, the changed node and the
* specified node are the same.
*/ int index = e.getChildIndices()[0];
node = (DefaultMutableTreeNode) (node.getChildAt(index));
System.out.println("The user has finished editing the node.");
System.out.println("New value NodesChanged: " + node.getUserObject());
}
@Override
public void treeNodesInserted(TreeModelEvent e) {
DefaultMutableTreeNode node= (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent()); /*
* If the event lists children, then the changed node is the child of the
* node we've already gotten. Otherwise, the changed node and the
* specified node are the same.
*/ int index = e.getChildIndices()[0];
node = (DefaultMutableTreeNode) (node.getChildAt(index));
System.out.println("New value NodesInserted : " + node.getUserObject());
}
@Override
public void treeNodesRemoved(TreeModelEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent()); /*
* If the event lists children, then the changed node is the child of the
* node we've already gotten. Otherwise, the changed node and the
* specified node are the same.
*/ int index = e.getChildIndices()[0];
node = (DefaultMutableTreeNode) (node.getChildAt(index));
System.out.println("New value NodesRemoved : " + node.getUserObject());
}
@Override
public void treeStructureChanged(TreeModelEvent e) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent()); /*
* If the event lists children, then the changed node is the child of the
* node we've already gotten. Otherwise, the changed node and the
* specified node are the same.
*/ int index = e.getChildIndices()[0];
node = (DefaultMutableTreeNode) (node.getChildAt(index));
System.out.println("New value StructureChanged : " + node.getUserObject());
}
}
}
I've had the same problems in the project I am working on right now.
I use an TreeWillExpandListener
to determine when my tree has to be loaded. (LazyLoading)
When the tree expanded I catched the node an loaded it's children inside a thread, because i had to parse the nodes from a server output.
The problem you are facing is that the tree expands before your children are loaded. so you have to throw a ExpandVetoException
or something like that and wait till your children are loaded. then expand your tree. in this case everything will show right.
Hope that will hope your problem.
Exapand-> Stop expand-> loadchildren-> addChildren -> now expand tree -> see your nodes
EDIT :
If you work with swing, you better use SwingWorker. Works better for me.
Try replacing invokeAndWait
with invokeLater
.