I extended a JFileChooser
and overrode the approveSelection
method so that when a user chooses an invalid directory and then clicks on the Open button, an error message in a JOptionPane
will be displayed. But I want to make my JFileChooser
more user-friendly and make the Open button become disabled when a user clicks on an invalid directory and then become re-enabled when a user clicks on a valid directory. Is it possible to customize my JFileChooser
even further and get access to the Open button so that I can change the status of the button accordingly (possibly via a listener that listens for a directory selection)?
public class MyFileChooser extends JFileChooser {
private final JFrame mainFrame;
public MyFileChooser(JFrame mainFrame) {
this.mainFrame = mainFrame;
setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
}
@Override
public void approveSelection() {
if (/* directory is valid */) {
super.approveSelection();
return;
}
JOptionPane.showMessageDialog(mainFrame, "Invalid directory", "Error", JOptionPane.ERROR_MESSAGE);
}
}
you can hide the accept/cancel buttons by calling chooser.setControlButtonsAreShown(false) when detect any selecting change on files/directories:
myChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
myChooser.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(evt.getPropertyName())) {
File file = (File) evt.getNewValue();
if (file != null && file.isFile()) { // your condition
myChooser.setControlButtonsAreShown(false);
}
else if ( file != null ) {
System.out.println(file.getName());
myChooser.setControlButtonsAreShown(true);
}
}
myChooser.repaint();
}
});
but it may confuse the user, its better to make custom FileFilter and showing only the files/directories you need:
public static class MyDirectoryFilter extends javax.swing.filechooser.FileFilter {
@Override
public boolean accept( File file ) {
return file.isDirectory() && customeCondition(file) ;
}
@Override
public String getDescription() {
return "this only my custom dir";
}
}
myChooser.setFileFilter( new MyDirectoryFilter () );
Edit:
I found a way to disable the button, by iterating over the components and getting handle to Open button :
http://www.coderanch.com/t/468663/GUI/java/Disabling-Enabling-level-button-folder
example:
myChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
myChooser.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(evt.getPropertyName())) {
File file = (File) evt.getNewValue();
if (file != null && file.isFile()) {
setOpenButtonState(myChooser, false);
}
else if ( file != null ) {
setOpenButtonState(myChooser, true);
System.out.println(file.getName());
}
}
myChooser.repaint();
}
});
public static void setOpenButtonState(Container c, boolean flag) {
int len = c.getComponentCount();
for (int i = 0; i < len; i++) {
Component comp = c.getComponent(i);
if (comp instanceof JButton) {
JButton b = (JButton) comp;
if ( "Open".equals(b.getText()) ) {
b.setEnabled(flag);
}
} else if (comp instanceof Container) {
setOpenButtonState((Container) comp, flag);
}
}
}
As Wajdy Essam already answered you can usually hide/show the controlbuttons using
javax.swing.JFileChooser#setControlButtonsAreShown(boolean)
but this method does not work for all Look&Feel out there, at least not the ones I'm using.
I wrote a quick workaround which will work in most cases.
Further you have full access to the cancel and approveButton of the FileChooser.
public class JFileChooserTest extends JFileChooser {
private JButton approveButton, cancelButton;
public JFileChooserTest() {
// Lookup the Buttons
if (approveButton == null) {
approveButton = lookupButton(JFileChooserTest.this, getUI().getApproveButtonText(this));
}
if (cancelButton == null) {
cancelButton = lookupButton(JFileChooserTest.this, UIManager.getString("FileChooser.cancelButtonText"));
}
setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
//Creating the Listener
PropertyChangeListener propertyChangeListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
//Do action only if the selected file changed
if (evt.getPropertyName().equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) {
File newFile = (File) evt.getNewValue();
//Validate the new File
boolean validate = validateFile(newFile);
//Enable/Disable the buttons
if (approveButton != null) {
approveButton.setEnabled(validate);
}
if (cancelButton != null) {
cancelButton.setEnabled(validate);
}
}
}
};
//Adding the listeners
addPropertyChangeListener(SELECTED_FILE_CHANGED_PROPERTY, propertyChangeListener);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
JFileChooserTest test = new JFileChooserTest();
test.showOpenDialog(null);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
Logger.getLogger(JFileChooserTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
/**
* Simple validation method; only for testing purpose
*
* @param f
* @return
*/
private boolean validateFile(File f) {
return f != null && !f.getName().startsWith("A");
}
/**
* Looksup the first JButton in the specific Container (and sub-containers)
* with the given text.
*
* @param c
* @param text
* @return
*/
private JButton lookupButton(Container c, String text) {
JButton temp = null;
for (Component comp : c.getComponents()) {
if (comp == null) {
continue;
}
if (comp instanceof JButton && (temp = (JButton) comp).getText() != null && temp.getText().equals(text)) {
return temp;
} else if (comp instanceof Container) {
if ((temp = lookupButton((Container) comp, text)) != null) {
return temp;
}
}
}
return temp;
}
}
I also recommend using javax.swing.filechooser.FileFilter to validate selected files rather then overriding approveSelection()
Disable Open button in JFileChooser?
Even better, install a custom FileSystemView
that hides the invalid directories. Look into overriding isHiddenFile(File)
for that.