Search code examples
javaeclipseswingjtree

JTree model.nodechanged(Node) loses data


I am testing some code with JTree, and I noticed that model.reload() gives different output than model.nodeChanged(currentNode). The correct output is obtained from model.reload(). Here are my two outputs for the same code:

enter image description here

My code is as follows:

public class test {

    private JFrame frame;
    JTree tree;
    DefaultMutableTreeNode root;
    DefaultTreeModel model;
    private Map<String, DefaultMutableTreeNode> treeMap;

    public static void main(String[] args) {
        test t = new test();
        try {
            Thread.sleep(1000 * 5);
        } catch (Exception e) {

        }
        t.add_new_folder("Data", "trial 0");
        t.add_new_folder("Data/trial 0", "trial 1");
        t.add_new_folder("Data/trial 0/trial 1", "trial 2");
        t.add_new_folder("Data", "trial 1");

        try {
            Thread.sleep(1000 * 5);
        } catch (Exception e) {

        }
        t.add_new_folder("Data/trial 1", "trial 2");
        t.add_new_folder("Data", "trial 2");
    }

    public test() {
        frame = new JFrame("using reload");
        tree = new JTree();
        root = new DefaultMutableTreeNode("Data");
        model = new DefaultTreeModel(root);
        tree.setModel(model);
        frame.getContentPane().add(tree, BorderLayout.WEST);
        frame.setVisible(true);
        frame.setSize(500, 500);
        treeMap = new HashMap<>();
        treeMap.put("Data", root);
    }

    public void add_new_folder(String path, String name) {
        DefaultMutableTreeNode currentNode = treeMap.get(path);
        DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(name);
        currentNode.add(childNode);
        treeMap.put(path + "/" + name, childNode);
        model.reload();
        //model.nodeChanged(currentNode);
    }
}

I need also to use model.nodeChanged() as it keeps expanded paths the same unlike model.reload. Any explanation and how to fix?


Solution

  • Any idea why this problem facing me with the Tree?

    As shown below, when correctly synchronized, the results are the same. As shown here, you can control visibility using expandRow() and scrollPathToVisible(). Using Thread.sleep() will simply block the thread on which it is called. Because Swing is single-threaded, the results are unpredictable. Instead use java.swing.Timer or SwingWorker as needed.

    Try to put some sleep between adding nodes and expand any path and in the continue code the problem will appear as stated in my question.

    For the reasons mentioned, results using Thread.sleep() are not reliable. Using java.swing.Timer reveals the problem: nodeChanged() reflects changes to the node itself. Because you've changed the children of the node, invoke nodeStructureChanged() instead.

    image

    import java.awt.BorderLayout;
    import java.awt.EventQueue;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.HashMap;
    import java.util.Map;
    import javax.swing.JFrame;
    import javax.swing.JTree;
    import javax.swing.Timer;
    import javax.swing.tree.DefaultMutableTreeNode;
    import javax.swing.tree.DefaultTreeModel;
    
    public class Test {
    
        private final JFrame frame;
        private final JTree tree;
        private final DefaultMutableTreeNode root;
        private final DefaultTreeModel model;
        private final Map<String, DefaultMutableTreeNode> treeMap;
        private final boolean reload;
        private int index;
        private final String[] folders = {
            "Data", "trial 0",
            "Data/trial 0", "trial 1",
            "Data/trial 0/trial 1", "trial 2",
            "Data", "trial 1",
            "Data/trial 1", "trial 2",
            "Data", "trial 2"
        };
    
        public static void main(String[] args) {
            EventQueue.invokeLater(() -> {
                new Test(true);
                new Test(false);
            });
        }
    
        public Test(boolean reload) {
            this.reload = reload;
            frame = new JFrame(String.valueOf(reload));
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            treeMap = new HashMap<>();
            root = new DefaultMutableTreeNode("Data");
            model = new DefaultTreeModel(root);
            tree = new JTree(model);
            treeMap.put("Data", root);
            frame.add(tree, BorderLayout.WEST);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
            Timer t = new Timer(100, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (index < folders.length) {
                        addNewFolder(folders[index++], folders[index++]);
                        for (int i = 0; i < tree.getRowCount(); i++) {
                            tree.expandRow(i);
                        }
                        frame.pack();
                    }
                }
            });
            t.start();
        }
    
        private void addNewFolder(String path, String name) {
            DefaultMutableTreeNode currentNode = treeMap.get(path);
            DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(name);
            currentNode.add(childNode);
            treeMap.put(path + "/" + name, childNode);
            if (reload) {
                model.reload();
            } else {
                model.nodeStructureChanged(currentNode);
            }
        }
    }