Customizing Swing's file chooser

The javax.swing.JFileChooser class describes a Swing component for choosing files, usually via its int showOpenDialog(Component parent) and int showSaveDialog(Component parent) methods. In this post, I enumerate various ways to customize a file chooser.

Creating and showing file choosers

Q: How do I create and show a file chooser?

A: Before you can customize a file chooser, you need to know how to create one and then show it. Create a file chooser by instantiating JFileChooser via one of its constructors, such as JFileChooser(), which initializes the file chooser to the user's default directory.

After creating a file chooser, show it by invoking one of JFileChooser's show-prefixed methods, such as the aforementioned showOpenDialog() and showSaveDialog(). Listing 1 presents a small application that demonstrates these tasks.

Listing 1. Creating and showing open and save file choosers

import java.awt.EventQueue;
import java.awt.GridLayout;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class FCDemo extends JFrame
{
   JFileChooser fc = new JFileChooser();

   public FCDemo(String title)
   {
      super(title);
      setDefaultCloseOperation(EXIT_ON_CLOSE);

      JPanel pnl = new JPanel();
      pnl.setLayout(new GridLayout(2, 1));

      JButton btn = new JButton("JFileChooser.showOpenDialog() Demo");
      ActionListener al;
      al = new ActionListener()
           {
              @Override
              public void actionPerformed(ActionEvent ae)
              {
                 switch (fc.showOpenDialog(FCDemo.this))
                 {
                    case JFileChooser.APPROVE_OPTION:
                       JOptionPane.showMessageDialog(FCDemo.this, "Selected: "+
                                                     fc.getSelectedFile(),
                                                     "FCDemo",
                                                     JOptionPane.OK_OPTION);
                       break;

                    case JFileChooser.CANCEL_OPTION:
                       JOptionPane.showMessageDialog(FCDemo.this, "Cancelled",
                                                     "FCDemo",
                                                     JOptionPane.OK_OPTION);
                       break;
                 
                    case JFileChooser.ERROR_OPTION:
                       JOptionPane.showMessageDialog(FCDemo.this, "Error",
                                                     "FCDemo",
                                                     JOptionPane.OK_OPTION);
                 }
              }
           };
      btn.addActionListener(al);
      pnl.add(btn);

      btn = new JButton("JFileChooser.showSaveDialog() Demo");
      al = new ActionListener()
           {
              @Override
              public void actionPerformed(ActionEvent ae)
              {
                 switch (fc.showSaveDialog(FCDemo.this))
                 {
                    case JFileChooser.APPROVE_OPTION:
                       JOptionPane.showMessageDialog(FCDemo.this, "Selected: "+
                                                     fc.getSelectedFile(),
                                                     "FCDemo",
                                                     JOptionPane.OK_OPTION);
                       break;

                    case JFileChooser.CANCEL_OPTION:
                       JOptionPane.showMessageDialog(FCDemo.this, "Cancelled",
                                                     "FCDemo",
                                                     JOptionPane.OK_OPTION);
                       break;
                 
                    case JFileChooser.ERROR_OPTION:
                       JOptionPane.showMessageDialog(FCDemo.this, "Error",
                                                     "FCDemo",
                                                     JOptionPane.OK_OPTION);
                 }
              }
           };
      btn.addActionListener(al);
      pnl.add(btn);

      setContentPane(pnl);

      pack();
      setVisible(true);
   }

   public static void main(String[] args)
   {
      Runnable r = new Runnable()
                   {
                      @Override
                      public void run()
                      {
                         new FCDemo("FileChooser Demo");
                      }
                   };
      EventQueue.invokeLater(r);
   }
}

Listing 1 reveals a simple pattern for working with open and save file choosers: instantiate JFileChooser, invoke showOpenDialog() or showSaveDialog() to display an appropriate modal dialog box for the chooser, and test the return value against one of these constants:

  • JFileChooser.CANCEL_OPTION: the user clicked the Cancel button or closed the dialog box
  • JFileChooser.APPROVE_OPTION: the user clicked the approve (e.g., Open or Save) button
  • JFileChooser.ERROR_OPTION: an internal error occurred

Compile Listing 1 (javac FCDemo.java) and run the application (java FCDemo). Figure 1 shows the resulting user interface, which presents buttons for creating/showing open and save file choosers.

Figure 1. Click either button to reveal the appropriate file chooser

Click either button to reveal the appropriate file chooser.

For example, click the JFileChooser.showSaveDialog() Demo button to display the save file chooser. Figure 2 reveals the resulting dialog box.

Figure 2. You can also cancel the file chooser by clicking the X button on the title bar

You can also cancel the file chooser by clicking the X button on the title bar.

Showing directories only

Q: Is it possible to have a file chooser show directories only?

A: Yes, you can configure a file chooser to show directories only. Alternatively, you can configure it to show files only. The default setting is to show directories and files.

JFileChooser declares a void setFileSelectionMode(int mode) method that lets you tell a file chooser to show directories, files, or both. Pass one of JFileChooser's DIRECTORIES_ONLY, FILES_ONLY, or FILES_AND_DIRECTORIES constants to mode.

For example, you could configure Listing 1's file chooser to show directories only, by placing the following line in the constructor before creating the user interface:

fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

Changing open/save file chooser approve button text

Q: Can I change the approve button text for an open or save file chooser?

A: You can specify approve button text for an open or save file chooser, but the text doesn't always appear when the chooser is displayed. Consider the following code fragment for an open file chooser -- the code fragment for a save file chooser is similar:

fc.setApproveButtonText("OPEN...");
switch (fc.showOpenDialog(FCDemo.this))

This code fragment employs JFileChooser's void setApproveButtonText(String approveButtonText) method to set the approve button's text to OPEN....

If you merge fc.setApproveButtonText("OPEN..."); into Listing 1, compile the source code, and run the application; and if you click the button to display the open file chooser, you should observe Figure 3.

Figure 3. The approve button's text has been changed to OPEN...

The approve button's text has been changed to OPEN....

However, if you tried to do the analogous thing with the save file chooser, you would still observe Figure 2's dialog box the first time you displayed the save file chooser.

You might think that JFileChooser contains a bug. However, an investigation of the source code proves otherwise. Consider the following code fragment, which reveals the contents of the showOpenDialog() and showSaveDialog() methods:

public int showOpenDialog(Component parent) throws HeadlessException 
{
   setDialogType(OPEN_DIALOG);
   return showDialog(parent, null);
}

public int showSaveDialog(Component parent) throws HeadlessException 
{
   setDialogType(SAVE_DIALOG);
   return showDialog(parent, null);
}

Each method first invokes void setDialogType(int dialogType) to specify the kind of dialog box to display before invoking int showDialog(Component parent, String approveButtonText), which does the actual work of creating and displaying the dialog box.

The following code fragment reveals the setDialogType() method:

public void setDialogType(int dialogType) 
{
   if (this.dialogType == dialogType) 
   {
      return;
   }
   if (!(dialogType == OPEN_DIALOG || dialogType == SAVE_DIALOG || 
       dialogType == CUSTOM_DIALOG)) 
   {
      throw new IllegalArgumentException("Incorrect Dialog Type: " + dialogType);
   }
   int oldValue = this.dialogType;
   this.dialogType = dialogType;
   if (dialogType == OPEN_DIALOG || dialogType == SAVE_DIALOG) 
   {
      setApproveButtonText(null);
   }
   firePropertyChange(DIALOG_TYPE_CHANGED_PROPERTY, oldValue, dialogType);
}

There are two things to note about this method. Taken together, they explain why you cannot always change the open or save file chooser's approve button text:

  • When dialogType equals OPEN_DIALOG or SAVE_DIALOG, setApproveButtonText(null); is executed to cancel any previously specified approve button text, which results in the default text being shown.
  • This method first tests

    dialogType

    to see if it equals the current dialog type, returning (and not executing

    setApproveButtonText(null);

    to erase previously specified text for the approve button, ensuring that only the default text is shown) when this is the case.

    The value passed to dialogType equals this.dialogType when you call showOpenDialog() at startup. This is due to passing OPEN_DIALOG to the dialogType parameter when showOpenDialog() invokes setDialogType(), and due to the dialogType field being initialized to OPEN_DIALOG (private int dialogType = OPEN_DIALOG;). This expression is also true on successive calls to showOpenDialog() or showSaveDialog() after switching from the open dialog box to the save dialog box (and vice versa).

The previous discussion provides a solution for changing the approve button text and making sure this text appears each time either the open or save file chooser is displayed. This solution is demonstrated below for the open file chooser:

fc.setDialogType(JFileChooser.OPEN_DIALOG);
switch (fc.showDialog(FCDemo.this, "OPEN..."))

First invoke setDialogType() to ensure that any registered property change listeners are notified. Then invoke showDialog(), passing the approve button text as the second argument to this method.

You'll notice that OPEN... or SAVE... appears on the dialog box's titlebar as well as on the approve button. If you want different text on the titlebar, invoke JFileChooser's void setDialogTitle(String dialogTitle) method, as demonstrated below:

fc.setDialogType(JFileChooser.SAVE_DIALOG);
fc.setDialogTitle("Save");
switch (fc.showDialog(FCDemo.this, "SAVE..."))

Installing file filters

Q: I want the user to select text files only (i.e., files with a .txt extension). How do I accomplish this task?

A: JFileChooser offers a void setFileFilter(FileFilter filter) method that lets you set the file filter used by the file chooser to filter out files from the user's view. The companion FileFilter getFileFilter() method returns the current file filter.

The abstract javax.swing.filechooser.FileFilter class declares a boolean accept(File f) method that returns true to accept the file described by f or false to reject it. The companion String getDescription() method returns a description of the file filter.

The following code fragment shows you how to create a file filter to accept text files only, and register this filter with the file chooser:

fc.setFileFilter(new FileFilter()
                 {
                    @Override
                    public boolean accept(File file)
                    {
                       return file.getName().toUpperCase().equals(".TXT");
                    }

                    @Override
                    public String getDescription()
                    {
                       return ".txt files";
                    }
                 });

You can easily integrate this feature into Listing 1's FCDemo.java source code. Simply place it in the constructor before creating the user interface.

If you run the aforementioned application, you'll notice that you can also select all files. If you don't want to give the user this choice, you can remove it by passing false to JFileChooser's void setAcceptAllFileFilterUsed(boolean b) method, as follows:

fc.setAcceptAllFileFilterUsed(false); // remove the accept-all (.*) file filter

After compiling the refactored source code and running the resulting application, either file chooser should let you select .txt files only. Figure 4 shows you what this looks like in the context of the open file chooser.

Figure 4. The user can select .txt files only

The user can select .txt files only.

You'll probably find it difficult to navigate the directory structure because you won't be able to see any directories. You can overcome this problem by changing accept()'s expression to the following:

Installing choosable file filters

Q: How do I install multiple file filters?

A: The setFileFilter() method lets you install a single file filter. If you want to support multiple file extensions, you could do so by extending the single FileFilter's methods, as follows:

fc.setFileFilter(new FileFilter()
                 {
                    @Override
                    public boolean accept(File file)
                    {
                       return file.getName().toUpperCase().equals(".TXT")||
                              file.getName().toUpperCase().equals(".DOC");
                    }

                    @Override
                    public String getDescription()
                    {
                       return ".txt files or .doc files";
                    }
                 });

Although this technique works, it's awkward, especially when the number of supported extensions increases. Instead, the user should be able to select a specific extension from the Files of Type drop-down list of extensions.

JFileChooser provides void addChoosableFileFilter(FileFilter filter) for appending a file filter to this drop-down list. The following code fragment demonstrates:

fc.addChoosableFileFilter(new FileFilter()
                                {
                                   @Override
                                   public boolean accept(File file)
                                   {
                                      return file.getName().toUpperCase().
                                             equals(".DOC");
                                   }

                                   @Override
                                   public String getDescription()
                                   {
                                      return ".doc files";
                                   }
                                });

This code fragment appends a file filter for viewing .doc files to the list of choosable file filters. This list will include any file filter installed via setFileFilter() and the accept-all (.*) file filter unless uninstalled (e.g., fc.setAcceptAllFileFilterUsed(false);).

Customizing file views

Q: I'd like to display a small icon beside each filename in the file chooser window. Is this possible?

A: Yes, you can display a small icon beside each of the filenames in a file chooser window. To accomplish this task, you first need to extend the javax.swing.filechooser.FileView class.

FileView is an abstract class that provides user interface information for a java.io.File object. This class provides an Icon getIcon(File f) method that you can override to return a suitable javax.swing.Icon for the specified File object.

After instantiating your FileView subclass, pass it to JFileChooser's void setFileView(FileView fileView) method to register this file view with the file chooser. The companion FileView getFileView() method returns the current file view.

Listing 2 presents the contents of a file view that provides icons for BMP, GIF, JPEG, and PNG files.

Listing 2. Describing an image file view

import java.io.File;

import javax.swing.Icon;
import javax.swing.ImageIcon;

import javax.swing.filechooser.FileView;

public class ImageFileView extends FileView
{
   private Icon bmpicon, gificon, jpgicon, pngicon;

   // Create ImageFileView to serve as a viewer for file icons.

   ImageFileView()
   {
      // Preload icons for the file view. The getClass().getResource()
      // construct is used so that an application can be packaged into a JAR 
      // file and still obtain these images.

      bmpicon = new ImageIcon(getClass().getResource("images/bmpicon.gif")); 
      gificon = new ImageIcon(getClass().getResource("images/gificon.gif"));
      jpgicon = new ImageIcon(getClass().getResource("images/jpgicon.gif"));
      pngicon = new ImageIcon(getClass().getResource("images/pngicon.gif"));
   }

   // Return a description of the file's type. The look and feel determines
   // what to do with this description (including a null description).

   @Override
   public String getTypeDescription(File f)
   {
      String s = f.getName();
      int i = s.lastIndexOf('.');
      if (i > 0 && i < s.length()-1)
      {
         String ext = s.substring(i+1).toLowerCase();
         if (ext.equals("bmp"))
            return "BMP Image";
         else
         if (ext.equals("gif"))
            return "GIF Image";
         else
         if (ext.equals("jpeg") || ext.equals("jpg"))
            return "JPEG Image";
         else
         if (ext.equals("png"))
            return "PNG Image";
      }
      return null;
   }

   // Return the icon that associates with the file's type. If null returns, a
   // default icon is used.

   @Override
   public Icon getIcon(File f)
   {
      String s = f.getName();
      int i = s.lastIndexOf('.');

      if (i > 0 && i < s.length()-1)
      {
         String ext = s.substring(i+1).toLowerCase();
         if (ext.equals("bmp"))
            return bmpicon;
         else
         if (ext.equals("gif"))
            return gificon;
         else
         if (ext.equals("jpeg") || ext.equals("jpg"))
            return jpgicon;
         else
         if (ext.equals("png"))
            return pngicon;
      }
      return null;
   }

   // Return the file's name minus its extension for files with the bmp, gif,
   // jpeg, jpg, or png extensions.
   
   @Override
   public String getName(File f)
   {         
      String s = f.getName();
      int i = s.lastIndexOf('.');

      if (i > 0 && i < s.length()-1)
      {
         String ext = s.substring(i+1).toLowerCase();
         if (ext.equals("bmp") || ext.equals("gif") ||
            ext.equals("jpeg") || ext.equals("jpg") || ext.equals("png"))
            return s.substring(0, i);
      }
      return null;
   }

   // Return an individual file's description.

   @Override
   public String getDescription(File f)
   {
      // Let the look and feel figure out the description.

      return null;
   }

   // Determine if a directory is traversable.

   @Override
   public Boolean isTraversable(File f)
   {
      // Let the look and feel determine if the directory is traversable.

      return null;
   }
}

You can easily integrate this feature into Listing 1's FCDemo.java source code. Simply place the following line in the constructor before creating the user interface:

fc.setFileView(new ImageFileView());

After compiling the refactored source code and running the resulting application, you should see image icons for BMP, GIF, JPEG, and PNG files in both the open and save file choosers. Figure 5 gives you an idea of what you might see.

Figure 5. The highlighted file entry shows the dimensions of its JPG icon

The highlighted file entry shows the dimensions of its JPG icon.

Providing an image viewer accessory

Q: Does JFileChooser provide a feature for previewing the content of image or other files?

A: Yes, JFileChooser supports the concept of an accessory, which is a javax.swing.JComponent instance that can be integrated into the file chooser's user interface to present file previews or another feature.

Extend JComponent to describe the new component, instantiate the resulting component class, and pass it to JFileChooser's void setAccessory(JComponent newAccessory) method to register the component with the file chooser.

Listing 3 presents the contents of an image preview accessory.

Listing 3. Describing an image preview accessory

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import java.io.File;

import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFileChooser;

public class ImagePreview extends JComponent implements PropertyChangeListener
{
   // Dimensions of image preview's preferred size.

   final static int WIDTH = 150;
   final static int HEIGHT = 100;

   // Reference to ImageIcon whose image is displayed in accessory area. If
   // null reference, nothing is displayed in accessory area.

   private ImageIcon icon;

   // Create ImagePreview component to serve as a file chooser accessory.

   public ImagePreview(JFileChooser fc)
   {
      // Register a property change listener with the file chooser so that
      // the ImagePreview component is made aware of file chooser events (such
      // as a user selecting a file).

      fc.addPropertyChangeListener(this);

      // Set the ImagePreview's dimensions to accommodate image thumbnails.
      // The specified values determine the overall size of the file chooser.

      setPreferredSize(new Dimension(WIDTH, HEIGHT));
   }

   // Paint the ImagePreview component in response to a repaint() method call.

   @Override
   protected void paintComponent(Graphics g)
   {
      // When this method is called, the background has already been painted.
      // If icon is null, do nothing. This action causes the current image
      // thumbnail to disappear when the user selects a directory, for example.

      if (icon != null)
      {
         // Paint a white background behind the pixels so that a GIF image's
         // transparent pixel causes white (instead of gray or whatever color
         // is appropriate for this look and feel) to show through.

         Graphics2D g2d = (Graphics2D) g;
         Rectangle bounds = new Rectangle(0, 0, icon.getIconWidth(),
                                          icon.getIconHeight());
         g.setColor(Color.white);
         g2d.fill(bounds);

         // Paint the image -- (0, 0) is the image's upper-left corner, and
         // the upper-left corner of the accessory area.

         icon.paintIcon(this, g, 0, 0);
      }
   }

   // Respond to property change events sent to this ImagePreview component by
   // the file chooser.

   @Override
   public void propertyChange(PropertyChangeEvent e)
   {                
      // Extract property name from event object.

      String propName = e.getPropertyName();

      // Erase any displayed image if user moves up the directory hierarchy.
                      
      if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(propName))
      {
         icon = null;
         repaint();
         return;
      }                  

      // Display selected file. If a directory is selected, erase any
      // displayed image.

      if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(propName))
      {
         // Extract selected file's File object.

         File file = (File) e.getNewValue();

         // If file is null, a directory was selected -- the user is moving
         // between directories. In response, any displayed image in the
         // accessory area must be erased.

         if (file == null)
         {
            icon = null;
            repaint();
            return;
         }

         // Obtain the selected file's icon.

         icon = new ImageIcon(file.getPath());

         // The ImageIcon constructor invokes a Toolkit getImage() method to
         // obtain the image identified by file.getPath(). The image is read
         // from the file unless the image (together with file path/name
         // information) has been cached (for performance reasons). Suppose
         // the user has specified the name of a file and that file does not
         // exist. Toolkit's getImage() method will return an Image with the
         // width and height each set to -1. The "image" associated with this
         // Image will be cached. Suppose the user activates the open file
         // chooser and selects the file to which the image was saved. The
         // previous ImageIcon() constructor will execute, but the image
         // will not be read from the file -- it will be read from the cache
         // (with -1 as the width and as the height). No image will appear in 
         // the preview window; the user will be confused. The solution to
         // this problem is to test the Image's width for -1. If this value
         // is present, Image's flush() method is called on the Image, and a
         // new ImageIcon is created. Internally, Toolkit's getImage() method
         // will read the image from the file -- not from the cache.

         if (icon.getIconWidth() == -1)
         {
            icon.getImage().flush();
            icon = new ImageIcon(file.getPath());
         }

         // Scale icon to fit accessory area if icon too big.

         if (icon.getIconWidth() > WIDTH)
            icon = new ImageIcon(icon.getImage().getScaledInstance (WIDTH, -1,
                                                          Image.SCALE_DEFAULT));

         // Display image.

         repaint();
      }
   }
}

Listing 3 is fairly self-explanatory. This image preview component will display a small thumbnail of the currently selected image file (JPEG, GIF, PNG, and a few other formats), or will display nothing when the file isn't an image file.

An interesting aspect of this component is that it registers itself as a property change listener with the file chooser so that it's made aware of file chooser events, especially the "directory changed" and "selected file changed" events.

You can easily integrate this feature into Listing 1's FCDemo.java source code. Simply place the following line in the constructor before creating the user interface:

fc.setAccessory(new ImagePreview(fc));

After compiling the refactored source code and running the resulting application, you should see image previews for both the open and save file choosers. Figure 6 gives you an idea of what you might see.

Figure 6. Select an image file to see its preview

Select an image file to see its preview.

Displaying information from a directory service or from another data source

Q: Can I customize JFileChooser to display information from a directory service or from another data source?

A: You can customize JFileChooser to display information from a directory service or from another data source, such as a database management system. JFileChooser's javax.swing.filechooser.FileSystemView class is the key to making this happen.

JFileChooser doesn't interact directly with a filesystem. Instead, it interacts indirectly via a FileSystemView object, which interacts with the filesystem via File objects.

FileSystemView declares a FileSystemView getFileSystemView() class method, which provides a view of the local filesystem. JFileChooser invokes this method unless you provide an alternate FileSystemView.

You can provide an alternate FileSystemView by extending this class, instantiating the subclass, and passing the instance to a JFileChooser constructor that takes a FileSystemView instance, such as JFileChooser(FileSystemView fsv).

Check out Justin Hill's Customizing JFileChooser article for an example that extends JFileChooser to display information from a generic directory service.

What's next?

Next time, I present five idioms for accomplishing common Collections Framework tasks: easy list creation, collection to array conversion, removal of null elements, removal of duplicate elements, and iteration over a map's entries.

download
Get the source code for this post's applications. Created by Jeff Friesen for JavaWorld

The following software was used to develop the post's code:

  • 64-bit JDK 7u6

The post's code was tested on the following platform(s):

  • JVM on 64-bit Windows 7 SP1
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.