Search code examples
javaswingjcombobox

How to display the name of files without extensions in a combobox?


My use case is as follows: I would like a combobox that displays a filtered list of files in a directory that is refreshed each time it is opened. I would also like the string displayed in the combobox to be the file name without the directory or extension (they are an implementation detail I don't want to expose to the user).

I have implemented a ComboBoxModel as follows:

public class XMLComboModel extends DefaultComboBoxModel<Path> implements PopupMenuListener {

public static final String EXT = ".xml";
private final Path directory;

public XMLComboModel(String pathName) {
    this.directory = Paths.get(pathName);
    populateModel();
}

public Path getSelectedPath() {
    return (Path) getSelectedItem();
}

@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
    populateModel();
}

private void populateModel() {
    removeAllElements();
    try {
        Files.newDirectoryStream(directory, "*" + EXT).forEach(this::addElement);
        if (getSelectedPath() != null) {
            for (int i = 0; i < getSize(); i++) {
                if (getElementAt(i).compareTo(getSelectedPath()) == 0)
                    setSelectedItem(getElementAt(i));
            }
        }
    } catch (IOException ex) {
        LOG.log(Level.SEVERE, null, ex);
    }
}

I've left out a few methods from PopupMenuListener for clarity.

This works fine except that it displays the full path in the combobox which is not what I want. I thought that the simplest way to tailor the display of the path would be to customise the Renderer. I attempted this with:

comboBox.setRenderer(new BasicComboBoxRenderer() {
    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index,
                boolean isSelected, boolean cellHasFocus) {
        Path path = (Path) value;
        String fileName = path.getFileName().toString().replace(EXT, "");
        Component component = super.getListCellRendererComponent(list, fileName, index,
                    isSelected, cellHasFocus);
        return component;
    }
});

Unfortunately this doesn't work. It displays the truncated file name correctly but now calls setSelectedItem with the String rather than the Path - which makes sense but won't match the paths in the model.

My options seem to be:

  • write my own custom component that replicates functionality in BasicComboBoxRenderer except for the text in the label.
  • override setSelectedItem in the model to look for the path with the given file name.

The second of these is more straightforward to implement:

@Override
public void setSelectedItem(Object item) {
    for (int i = 0; i < getSize(); i++) {
        if (super.getElementAt(i).equals(Paths.get(item.toString()))) {
            super.setSelectedItem(super.getElementAt(i));
            return;
        }
    }
}

But this seems pretty clunky given I've passed a Path from the model it'd be good to get a Path back. Am I missing a simpler way to achieve this?


Solution

  • but now calls setSelectedItem with the String rather than the Path

    The renderer has nothing to do with the other methods of the combo box model.

    If you want to pass a Path object to the setSelectedItem(...) method, then you need to make sure that your Path object implements the equals(...) method so the model can select the proper Path.

    Then just create the custom renderer to display whatever part of the Path object you want to display.

    it'd be good to get a Path back.

    If you model contains Path object then the getSelectedItem() method will return a Path object. You will need to cast it to a Path, or use your getSelectedPath() method.

    So you don't need to override any methods of the DefaultComboBoxModel, only add the getSelectedPath() method.