0

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;
        }


}
meyerjp3
  • 197
  • 1
  • 3
  • 16
  • *"As a second problem"* - One problem per question please... – MadProgrammer Dec 07 '14 at 23:48
  • I believe the two problems are related. I think the second problem is caused by the first. – meyerjp3 Dec 07 '14 at 23:49
  • I don't know, I see distinct questions, but that's me... – MadProgrammer Dec 07 '14 at 23:50
  • OK, I just changed it to focus on a single problem. – meyerjp3 Dec 07 '14 at 23:53
  • 1
    There seems to be a disconnection between where you want to insert the new node and where the model thinks it resides, this is caused by the fact that rather than actually "modelling" the file system, you are continuously reading from it. – MadProgrammer Dec 08 '14 at 00:04
  • If you want to continue down this track, you will need to add a method to your model that can take a parent node and the name of the directory you want to create. It will create the directory, list the files within the parent directory, determine the appropriate index the node should be created based on the location of the new directory and call `insertNodeInto` itself... – MadProgrammer Dec 08 '14 at 00:05
  • I might add, this approach is actually, very slow, as you are doing a lot of I/O – MadProgrammer Dec 08 '14 at 00:05
  • The other problem you will have is the fact that the children should belong to the node, the model is an interface into the nodes... – MadProgrammer Dec 08 '14 at 00:18
  • My ultimate goal is to use a [Watch Service](https://docs.oracle.com/javase/tutorial/essential/io/notification.html) to monitor a directory and update the tree any time there is a change in the directory (e.g. file or directory added or deleted). I figured it would be easier to just read from the file system rather than model it. Should I reconsider? – meyerjp3 Dec 08 '14 at 00:37
  • IMHO - Yes. The use of `DefaultMutableTreeNode` is confusing the situation. If you want to simply read straight from the disk, you should create a custom `TreeNode` which does not cache it's children, like `DefaultMutableTreeNode` does... – MadProgrammer Dec 08 '14 at 00:46

1 Answers1

0

There seems to be a basic mismatch of approaches. You are using DefaultMutableTreeNodes, 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).

I 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 simply the issues...

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • You've given me a lot to think about! I will try your suggestion and see how it goes. I may be back with more questions. Thanks! – meyerjp3 Dec 08 '14 at 01:50