4

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

}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
BJ Dela Cruz
  • 5,194
  • 13
  • 51
  • 84
  • 2
    Why don't you use a FileFilter. It wouldn't even let the user select an invalid directory. See http://docs.oracle.com/javase/6/docs/api/javax/swing/JFileChooser.html#setFileFilter%28javax.swing.filechooser.FileFilter%29 – JB Nizet Dec 27 '12 at 22:06

3 Answers3

11

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);
      }
    }     
}
Sergey Ushakov
  • 2,425
  • 1
  • 24
  • 15
Wajdy Essam
  • 4,280
  • 3
  • 28
  • 33
  • 3
    Good example, but do not use `b.getText().equals("Open")`, use `b.getText().equals(chooser.getApproveButtonText())` to support internationalization. – Stephan Nov 13 '14 at 09:41
1

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()

Pr0gr4mm3r
  • 6,170
  • 1
  • 18
  • 23
1

Disable Open button in JFileChooser?

Even better, install a custom FileSystemView that hides the invalid directories. Look into overriding isHiddenFile(File) for that.

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • 2
    I have the same requirement, but for me, I need to only allow the selection of directories that satisfy certain criteria. I cannot use a file filter because if I did, the user wouldn't be able to navigate anywhere on the filesystem! I need to allow full directory navigation, but only allow selection i.e. clicking on the Open button, of a directory if it satisfies my criteria. Any ideas? – Matthew Wise Jul 17 '13 at 16:07
  • @MatthewWise A `FileSystemView` can display files **and directories.** – Andrew Thompson Jul 18 '13 at 02:52
  • 1
    Yes, but I need all directories to be **displayed** to allow full navigation but only certain of them to be **selectable** i.e. by clicking the Open button. My workaround is to override approveSelection(), put the criteria logic in here and display an error message if the selected directory does not satisfy the criteria. Hope this makes it clearer what I need to achieve... – Matthew Wise Jul 18 '13 at 08:03