Expand Collapse Expand Issue with JTree Lazy loadi

2019-02-07 06:35发布

问题:

I have implemented a tree using Lazy Loading. The 1st level nodes are created at the time of tree creation, where as the child nodes are created only when a user expands any particular node.

The data is coming from DB, and we fire a query to the database so as to populate the child nodes. Implemented TreeExpansionListener and using overridden implementation of treeExpanded method. On expansion, I remove all child nodes for the selected node, make a DB query and add the records as child to the selected node. Before adding any node to a tree, a dummy child is added to the node. Working with DefaultMutableTreeNode.

So far so good, it was working fine per expectation.

Problem 1 - As you, its a DB call per expansion, so if a node is collapsed and again expanded I will be doing a DB trip and processing again an again... The idea is to not to load the nodes fresh if already expanded...

Problem 2 - If I had to do a force refresh i.e. re load the tree and keep expansion state. This is in working state now... How can I achieve the same along with fix to Problem 1 above ?

Appreciate any help on this.

回答1:

As explained by MadProgrammer, here is a small demo example showing a JTree loading dynamically data as the tree expands on nodes (and with a nice loading bar as a bonus):

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.Transient;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;

public class TestJTree {

    public static class MyTreeNode extends DefaultMutableTreeNode {

        private boolean loaded = false;

        private int depth;

        private final int index;

        public MyTreeNode(int index, int depth) {
            this.index = index;
            this.depth = depth;
            add(new DefaultMutableTreeNode("Loading...", false));
            setAllowsChildren(true);
            setUserObject("Child " + index + " at level " + depth);
        }

        private void setChildren(List<MyTreeNode> children) {
            removeAllChildren();
            setAllowsChildren(children.size() > 0);
            for (MutableTreeNode node : children) {
                add(node);
            }
            loaded = true;
        }

        @Override
        public boolean isLeaf() {
            return false;
        }

        public void loadChildren(final DefaultTreeModel model, final PropertyChangeListener progressListener) {
            if (loaded) {
                return;
            }
            SwingWorker<List<MyTreeNode>, Void> worker = new SwingWorker<List<MyTreeNode>, Void>() {
                @Override
                protected List<MyTreeNode> doInBackground() throws Exception {
                    // Here access database if needed
                    setProgress(0);
                    List<MyTreeNode> children = new ArrayList<TestJTree.MyTreeNode>();
                    if (depth < 5) {
                        for (int i = 0; i < 5; i++) {
                            // Simulate DB access time
                            Thread.sleep(300);
                            children.add(new MyTreeNode(i + 1, depth + 1));
                            setProgress((i + 1) * 20);
                        }
                    } else {
                        Thread.sleep(1000);
                    }
                    setProgress(0);
                    return children;
                }

                @Override
                protected void done() {
                    try {
                        setChildren(get());
                        model.nodeStructureChanged(MyTreeNode.this);
                    } catch (Exception e) {
                        e.printStackTrace();
                        // Notify user of error.
                    }
                    super.done();
                }
            };
            if (progressListener != null) {
                worker.getPropertyChangeSupport().addPropertyChangeListener("progress", progressListener);
            }
            worker.execute();
        }

    }

    protected void initUI() {
        JFrame frame = new JFrame(TestJTree.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        MyTreeNode root = new MyTreeNode(1, 0);
        final DefaultTreeModel model = new DefaultTreeModel(root);
        final JProgressBar bar = new JProgressBar();
        final PropertyChangeListener progressListener = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                bar.setValue((Integer) evt.getNewValue());
            }
        };
        JTree tree = new JTree() {
            @Override
            @Transient
            public Dimension getPreferredSize() {
                Dimension preferredSize = super.getPreferredSize();
                preferredSize.width = Math.max(400, preferredSize.width);
                preferredSize.height = Math.max(400, preferredSize.height);
                return preferredSize;
            }
        };
        tree.setShowsRootHandles(true);
        tree.addTreeWillExpandListener(new TreeWillExpandListener() {

            @Override
            public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
                TreePath path = event.getPath();
                if (path.getLastPathComponent() instanceof MyTreeNode) {
                    MyTreeNode node = (MyTreeNode) path.getLastPathComponent();
                    node.loadChildren(model, progressListener);
                }
            }

            @Override
            public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {

            }
        });
        tree.setModel(model);
        root.loadChildren(model, progressListener);
        frame.add(new JScrollPane(tree));
        frame.add(bar, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            UnsupportedLookAndFeelException {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TestJTree().initUI();
            }
        });
    }
}


回答2:

Once a node has populated, simple don't repopulate it. On treeExpanded, simply check to see of the parent node has any children, if it does, don't reload anything.

You will need to supply the ability to refresh a parent node. I normally do this with a JPopupMenu.