t4dohx
t4dohx

Reputation: 668

How to attach handler for JFileChooser buttons inside custom JFrame in Java

I have a singleton pattern class called MyFileChooser which adapts if JFileChooser is FILES_ONLY or DIRECTORIES_ONLY according to constructor parameter.

I wish to have the JFileChooser inside JFrame, so I can add additional information above and below JFileChooser frame, so the structure will look:

JFrame
 | JLabel
 | JFileChooser
 | JLabel
// end of JFrame

import javax.swing.JFrame;
import javax.swing.JFileChooser;

public class MyFileChooser {
    private JFrame frame;
    private boolean isFilesOnly;
    private static final MyFileChooser instance_files = new MyFileChooser( true );
    private static final MyFileChooser instance_dirs = new MyFileChooser( false );

    private JFileChooser dynamicChooser;

    private MyFileChooser( boolean filesOnly ) {
        this.frame = new JFrame();
        this.isFilesOnly = filesOnly;
        this.dynamicChooser = new JFileChooser();
        this.frameSetup();
        this.chooserSetup();
    }

    public MyFileChooser getInstance( boolean filesOnly ) {
        if ( filesOnly ) {
            return MyFileChooser.instance_files;
        } else {
            return MyFileChooser.instance_dirs;
        }
    }

    public void frameSetup() {
        // jframe and labels setup code
        this.frame.getContentPane().add( this.dynamicChooser );
    }

    public void chooserSetup() {
        if ( this.isFilesOnly ) {
            this.dynamicChooser.setFileSelectionMode( JFileChooser.FILES_ONLY );
        } else {
            this.dynamicChooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY );
        }
        this.dynamicChooser.setMultiSelectionEnabled(true);
        this.dynamicChooser.setDialogType(JFileChooser.CUSTOM_DIALOG);
    }

The issue is that I dont know how to attach handler for "Close" and "Open" buttons of JFileChooser. The only thing I've found is:

public void handleSelectedFiles() {
   int returnVal = this.dynamicChooser.showDialog(this.frame, "Open");
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            File[] files = this.dynamicChooser.getSelectedFiles();
            // do something
        }
}

But, it opens both instances if this method is called and even without JFrame wrap & JLabels from required structure. So my question is how can I get button of "Open" and "Close" from JFileChooser to attach handler (different for instances) or how to handle the selected files by other way.

Upvotes: 1

Views: 1114

Answers (1)

MadProgrammer
MadProgrammer

Reputation: 347332

Your first problem is here

int returnVal = this.dynamicChooser.showDialog(this.frame, "Open");

It's call the underlying JFileChooser's showDialog method, so it's constructing its own window, ignoring yours

A better solution is create a JDialog on demand and use it to container your file chooser and other controls

Something a little more like...

public class MyFileChooser {

    private final boolean isFilesOnly;
    private static final MyFileChooser INSTANCE_FILES = new MyFileChooser(true);
    private static final MyFileChooser INSTANCE_DIRS = new MyFileChooser(false);

    private final JFileChooser dynamicChooser;

    private MyFileChooser(boolean filesOnly) {
        this.isFilesOnly = filesOnly;
        this.dynamicChooser = new JFileChooser();
        dynamicChooser.setControlButtonsAreShown(false);
        this.chooserSetup();
    }

    public MyFileChooser getInstance(boolean filesOnly) {
        if (filesOnly) {
            return MyFileChooser.INSTANCE_FILES;
        } else {
            return MyFileChooser.INSTANCE_DIRS;
        }
    }

    public void frameSetup(Container parent) {
        // jframe setup code
        parent.add(this.dynamicChooser);
    }

    public void chooserSetup() {
        if (this.isFilesOnly) {
            this.dynamicChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        } else {
            this.dynamicChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        }
        this.dynamicChooser.setMultiSelectionEnabled(true);
        this.dynamicChooser.setDialogType(JFileChooser.CUSTOM_DIALOG);
    }
    
    public File[] showOpenDialog(Component parent, String title) {
        JDialog dialog = new JDialog(parent == null ? null : SwingUtilities.getWindowAncestor(parent), title);
        dialog.setModal(true);
        frameSetup(dialog);
        
        dialog.pack();
        dialog.setLocationRelativeTo(parent);
        
        dialog.setVisible(true);
        
        return dynamicChooser.getSelectedFiles();
    }
}

The intention is to mimic the actions of showDialog from JFileChooser. In this example, I've hidden the "normal" control buttons, as I assume you'll be providing your own, through which you can change the return file

Now, if you still want to use the standard button controls, you can attach an ActionListener to the JFileChooser

dynamicChooser.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        String cmd = e.getActionCommand();
        //...
    }
});

When I was testing, the Cancel button return CancelSelection and Choose button return ApproveSelection

Now, because my example uses a dynamic dialog, you may need to create a dynamic ActionListener which can control the dialog, because you don't want to keep adding ActionListeners to the FileChooser without removing them, something like...

public File[] showOpenDialog(Component parent, String title) {
    JDialog dialog = new JDialog(parent == null ? null : SwingUtilities.getWindowAncestor(parent), title);
    dialog.setModal(true);
    frameSetup(dialog);

    ActionListener listener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            String cmd = e.getActionCommand();
            //..
            dialog.dispose();
        }
    };
    dynamicChooser.addActionListener(listener);

    dialog.pack();
    dialog.setLocationRelativeTo(parent);

    dialog.setVisible(true);
    dynamicChooser.removeActionListener(listener);

    return dynamicChooser.getSelectedFiles();
}

as an example

Observation

Because I know someone will kick me to curb if I don't...

Sington's in Java are tricky things and there are countless threads on the subject, however, it's generally accepted that the best approach (now) is to use a enum, which solves all the synchronisation issues of old

So, instead, you might consider using something like..

public static enum MyFileChooser {

    INSTANCE_FILES(true),
    INSTANCE_DIRS(false);

    private final boolean isFilesOnly;

    private final JFileChooser dynamicChooser;

    private MyFileChooser(boolean filesOnly) {
        this.isFilesOnly = filesOnly;
        this.dynamicChooser = new JFileChooser();
        //dynamicChooser.setControlButtonsAreShown(false);
        this.chooserSetup();
    }

    public void frameSetup(Container parent) {
        // jframe setup code
        parent.add(this.dynamicChooser);
    }

    public void chooserSetup() {
        if (this.isFilesOnly) {
            this.dynamicChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        } else {
            this.dynamicChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        }
        this.dynamicChooser.setMultiSelectionEnabled(true);
        this.dynamicChooser.setDialogType(JFileChooser.CUSTOM_DIALOG);
    }

    public File[] showOpenDialog(Component parent, String title) {
        JDialog dialog = new JDialog(parent == null ? (JDialog)null : SwingUtilities.getWindowAncestor(parent), title);
        dialog.setModal(true);
        frameSetup(dialog);

        ActionListener listener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String cmd = e.getActionCommand();
                //..
                dialog.dispose();
            }
        };
        dynamicChooser.addActionListener(listener);

        dialog.pack();
        dialog.setLocationRelativeTo(parent);

        dialog.setVisible(true);
        dynamicChooser.removeActionListener(listener);

        return dynamicChooser.getSelectedFiles();
    }
}

Which you can call simply using something like...

File[] files = MyFileChooser.INSTANCE_FILES.showOpenDialog(null, "Open");

"I've mentioned I wish to have JLabels between chooser,"

I had thought that laying out comments was just a matter of reading any one of the thousands of examples which are available, apparently I was mistaken

So, a simple update to the frameSetup method solves that issue...

public void frameSetup(Container parent) {
    // jframe setup code
    parent.setLayout(new BorderLayout());
    parent.add(new JLabel("I'm on top"), BorderLayout.NORTH);
    parent.add(this.dynamicChooser);
    parent.add(new JLabel("I'm on bottom"), BorderLayout.SOUTH);
}

Simple Layout

Have a look at How to Use BorderLayout for more details

To make sure it works correctly, I updated the showOpenDialog method to use

frameSetup(dialog.getContentPane());

instead of just passing the instance of the JDialog, this ensures that we're effecting the correct container

Upvotes: 3

Related Questions