I have successfully created a small program that will display the file system contents in a JTree. It shows only folders and plain text files by design. I would now like to add a folder to the tree when it is created. The example program will display the tree and create a folder, but it will not insert a new node for the folder that was just created. How can I insert a node into the tree when a folder is created and have the tree display the new folder?
Many thanks!
This class creates and displays the tree.
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
public class TreeFrame extends JFrame{
private JTree fileTree = null;
private String USER_HOME = System.getProperty("user.home");
public TreeFrame(){
super("File Tree");
setDefaultCloseOperation(EXIT_ON_CLOSE);
//Create tree
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(new File(USER_HOME));
fileTree = new JTree(new FileTreeModelTest(rootNode));
fileTree.setCellRenderer(new TreeCellRendererTest());
fileTree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
//Commented out the next part because it causes a null pointer exception.
//I believe it is caused by the tree not inserting a node.
//JTree tree = (JTree)e.getSource();
//DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
//Object obj = node.getUserObject();//Causes a null pointer exception
//if(obj instanceof File){
// File f = (File)node.getUserObject();
// System.out.println(f.getAbsolutePath());
//}
}
});
JPanel panel = new JPanel();
JLabel folderLabel = new JLabel("Name:");
panel.add(folderLabel, BorderLayout.WEST);
final JTextField textField = new JTextField(25);
textField.setPreferredSize(new Dimension(100, 28));
panel.add(textField, BorderLayout.CENTER);
JButton button = new JButton("Create Foder");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//Add a folder when the menu item is clicked
DefaultMutableTreeNode node = (DefaultMutableTreeNode)fileTree.getLastSelectedPathComponent();
File f = (File)node.getUserObject();
if(f.isDirectory()){
File dir = new File(f.getAbsolutePath()+"/"+textField.getText().trim());
if(dir.mkdirs()){
System.out.println("ADDED: " + dir.getAbsolutePath());
//Insert node into tree model -- this is the part that does not seem to work.
FileTreeModelTest model = (FileTreeModelTest)fileTree.getModel();
DefaultMutableTreeNode child = new DefaultMutableTreeNode(dir);
model.insertNodeInto(child, node, node.getChildCount());
model.reload(node);
TreeNode[] nodes = model.getPathToRoot(child);
TreePath path = new TreePath(nodes);
fileTree.scrollPathToVisible(path);
fileTree.setSelectionPath(path);
}
}
}
});
panel.add(button, BorderLayout.EAST);
getContentPane().add(panel, BorderLayout.NORTH);
JScrollPane scrollPane = new JScrollPane(fileTree);
scrollPane.setPreferredSize(new Dimension(200, 400));
getContentPane().add(scrollPane, BorderLayout.CENTER);
pack();
}
public static void main(String[] args){
TreeFrame frame = new TreeFrame();
frame.setVisible(true);
}
}
This class is the tree model.
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
public class FileTreeModelTest extends DefaultTreeModel {
public FileTreeModelTest(DefaultMutableTreeNode root){
super(root);
}
private File[] getFiles(File parent){
File[] f = parent.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
if(file==null)
return false;
if(file.isDirectory())
return true;
if(file.getName().toLowerCase().endsWith("txt")){
return true;
}
return false;
}
});
return f;
}
@Override
public Object getChild(Object parent, int index){
DefaultMutableTreeNode node = (DefaultMutableTreeNode)parent;
File p = (File)node.getUserObject();
File[] pFile = getFiles(p);
DefaultMutableTreeNode child = new DefaultMutableTreeNode(pFile[index]);
return child;
}
@Override
public int getChildCount(Object parent){
DefaultMutableTreeNode node = (DefaultMutableTreeNode)parent;
File f = (File)node.getUserObject();
if(!f.isDirectory()){
return 0;
}else{
//Is a directory, return number of folders and text files
File[] children = getFiles(f);
if(children==null) return 0;
return children.length;
}
}
@Override
public int getIndexOfChild(Object parent, Object child){
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode)parent;
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)child;
File p = (File)parentNode.getUserObject();
File c = (File)childNode.getUserObject();
File[] pFile = getFiles(p);
return Arrays.asList(pFile).indexOf(c);
}
@Override
public Object getRoot(){
return root;
}
@Override
public boolean isLeaf(Object parent){
DefaultMutableTreeNode node = (DefaultMutableTreeNode)parent;
File f = (File)node.getUserObject();
if(f.isDirectory()){
File[] children = getFiles(f);
if(children==null) return true;
//F is a directory. If it has no files, then it is a leaf.
return children.length==0;
}else{
//F is a file. It is a leaf.
return true;
}
}
}
This last class is the tree cell renderer.
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import java.awt.*;
import java.io.File;
public class TreeCellRendererTest extends DefaultTreeCellRenderer {
private FileSystemView fsv = FileSystemView.getFileSystemView();
public TreeCellRendererTest(){
}
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if(value instanceof DefaultMutableTreeNode){
Object obj = ((DefaultMutableTreeNode) value).getUserObject();
if(obj instanceof File){
File f = (File)obj;
if(row==0){
setIcon(fsv.getSystemIcon(f));
setText(f.getPath());
}else
if(f.isFile()){
setIcon(fsv.getSystemIcon(f));
setText(f.getName());
}else{
setIcon(fsv.getSystemIcon(f));
setText(f.getName());
}
}
}
return this;
}
}
There seems to be a basic mismatch of approaches. You are using DefaultMutableTreeNode
s, which cache there child nodes, but are managing the actual nodes directly via the model, this is causing confusion at multiple different levels...
If you want to continue monitoring the disk contents directly (or proxying it), I would create a custom TreeNode
whose sole responsibility was to monitor a single directory.
I would then create a custom TreeModel
(probably from the DefaultTreeModel
), which provided a makeDirectory
method, which you would pass the currently selected TreeNode
and the name of the directory.
This method would then be responsible for the physical creation of the directory and notification of the structural changes to the JTree
(via the nodesWereInserted
method). I'd probably have this node return an instance a TreeNode
which represented the child...
There are a lot of problems with this approach, namly with the referencing of objects. In your current approach, you are creating a new DefaultMutableTreeNode
whenever getChild
is called, this could cause issues if parts of the API are relying on those references remaining constant for the given position/data, this would require you to maintain some kind of internal cache, linking the File
with the TreeNode
...which kind of defeats the purpose...
A better approach might be to utilising the existing "mutable tree node" API, each node would still be responsible for a single File
, but it would also cache the results. The problem here is managing when a node should be populated (as you don't want to populate directory nodes that are not expanded).
If you are planning on utilising the "Watch Service" API, then I would go for the "mutable tree node" API and cache the child files within it (as you need to), it will simplify the issues...