Repurposing JFileChooser

2019-08-07 15:06发布

问题:

I need to implement file browsing feature in my app and while I am aware of possibility of making a JList item and doing it manually I had an idea to just implement JFileChooser for this. I managed to reduce the JFileChooser just to list of directories and files but I wasn't able to override some of it's functionalities. I have been going through source code but no luck. My idea is for it to handle as following: On the top of the list to have a /... directory so when clicked on it it returns to parent folder. Also when double clicked on directory it sets it as current directory. When double clicked on file it returns the file as selected.

This is the code I used so far:

final JFileChooser fc = new JFileChooser();
fc.setControlButtonsAreShown(false);
fc.setCurrentDirectory(paths[list.getSelectedIndex()]);
/*remove unwanted components*/
for(int i = 0; i < fc.getComponentCount(); i++) {
    fc.getComponent(0).setVisible(false);
    fc.getComponent(1).setVisible(false);
    fc.getComponent(3).setVisible(false);
}
add(fc, BorderLayout.CENTER);

I tried adding custom MouseListener to the JFileChooser but it didn't work.

This is the result I have so far:

Any idea which classes or listeners to overwrite/replace so I can achieve 2 desired effects?

This is what I am looking for in visual terms:

回答1:

On the top of the list to have a /... directory so when clicked on i it returns to parent folder.

JFileChooser has method with name changeToParentDirectory(). So you could simply add a Button and call that method.

JButton toParent = new JButton("/..");
toParent.addActionListener(new ActionListener(){
    @Override
    public void actionPerformed(ActionEvent a) {
        fc.changeToParentDirectory();
    }
});
fc.add(toParent, BorderLayout.NORTH);

Also when double clicked on directory it sets it as current directory.

You can set a PropertyChangeListener to listen for JFileChooser.DIRECTORY_CHANGED_PROPERTY property that is fired whenever the current directory has changed using double click or the internal commands.

fc.addPropertyChangeListener(new PropertyChangeListener(){
    @Override
    public void propertyChange(PropertyChangeEvent e) {
        String command = e.getPropertyName();
        if (command.equals(JFileChooser.DIRECTORY_CHANGED_PROPERTY)) {
            File currentDir = fc.getCurrentDirectory();
            System.out.println(currentDir.getAbsolutePath());
        }
    }   
});

When double clicked on file it returns the file as selected.

You can set an ActionListener to listen for JFileChooser.APPROVE_SELECTION action that is fired whenever a file is chosen by double click.

fc.addActionListener(new ActionListener(){
    @Override
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        if (command.equals(JFileChooser.APPROVE_SELECTION)) {
            File selectedFile = fc.getSelectedFile();
            System.out.println(selectedFile.getAbsolutePath());
        }   
    }   
});


EDIT:

you misunderstood me for the parent folder action. Here is an image to describe it.

This could be achieved by using a manipulated FileSystemView with the JFileChooser that is responsible for interacting with the content of the filesystem. My implementation manipulates the getFiles method to smuggle a special File in the list that has a defined name and points to the parent directory.
I'm not quite sure if this is a very good idea, since this is really not meant to be in the JFileChooser Code but here we go.

fc.setFileSystemView(new FileSystemView(){
    // this method is abstract but since you don't
    // want to create directories here you don't 
    // need to implement it.
    @Override
    public File createNewFolder(File f) throws IOException {
        return null;
    }

    // manipulate the default getFiles method that creates
    // the list of files in the current directory
    @Override
    public File[] getFiles(File dir, boolean useFileHiding){
        // get the list of files from default implementation
        File[] files = super.getFiles(dir,useFileHiding);
        // get the parent directory of current
        File parent = getParentDirectory(dir);
        // skip the next for problematic folders with
        // empty names and root folders
        if(!dir.getName().isEmpty() && !isRoot(dir)){
            // create a new list of files with one extra place
            File[] nfiles = new File[files.length + 1];
            // add a special file to list that points to parent directory
            nfiles[0] = new File(parent.getAbsolutePath()){
                // set a special name for that file
                @Override
                    public String getName(){return "...";}
                };
            // add the rest of files to list
            for(int i = 0; i < files.length; i++)
                nfiles[i+1] = files[i];
            // use the new list
            files = nfiles; 
        }
        // return list of files
        return files;
    }

    // some special folders like "c:" gets converted
    // in shellfolders.Then our setted name "..." would
    // get converted to "local drive (c:)". This garantees
    // that our setted name will be used.
    @Override
    public String getSystemDisplayName(File f) {
        return f.getName();
    }   
});

EDIT2:

is there a quick solution to set scroll from horizontal to vertical for JFileChooser?

There are two possibilities. The simple one is to change the style of your JFileChooser to the Details View by adding this code:

Action details = fc.getActionMap().get("viewTypeDetails");
details.actionPerformed(null);

The more complex one is to change the LayoutOrientation of the file view JList that is a component of sun.swing.FilePane that is the Component with ID:2 in the JFileChooser.
One problem here is that FilePane is not part of the Java Library but part of the Core Library and not accessible by default. But you can use Reflection to get the field private JList list; in the FilePane and change its LayoutOrientation to JList.VERTICAL with this code:

// get the FilePane Component of JFileChooser
Object filepane = fc.getComponent(2);
// get the list field with reflection
Field field_list = filepane.getClass().getDeclaredField("list");
// get access to this private field
field_list.setAccessible(true);
// read the value of the field
JList<?> list = (JList<?>)field_list.get(filepane);
// change the layout orientation
list.setLayoutOrientation(JList.VERTICAL);