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
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();
}
});
}
}
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.