如何在组合框中显示不带扩展名的文件名?

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

我的用例如下:我想要一个组合框,它在每次打开时刷新的目录中显示经过过滤的文件列表。我还希望组合框中显示的字符串是没有目录或扩展名的文件名(它们是我不想向用户公开的实现细节)。

我实现了一个ComboBoxModel如下:

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

为了清楚起见,我省略了 PopupMenuListener 中的一些方法。

这很好用,只是它在组合框中显示了完整路径,这不是我想要的。我认为定制路径显示的最简单方法是定制渲染器。我尝试这样做:

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

不幸的是,这不起作用。它正确显示截断的文件名,但现在使用 String 而不是 Path 调用 setSelectedItem - 这是有道理的,但与模型中的路径不匹配。

我的选择似乎是:

第二个更容易实现:

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

但这看起来很笨拙,因为我已经从模型中传递了一个 Path,最好能得到一个 Path。我是否缺少实现此目的的更简单方法?

but now calls setSelectedItem with the String rather than the Path

渲染器与组合框模型的其他方法无关。

如果要将 Path 对象传递给 setSelectedItem(...) 方法,则需要确保 Path 对象实现了 equals(...) 方法,以便模型可以 select 适当的 Path.

然后只需创建自定义渲染器即可显示要显示的 Path 对象的任何部分。

it'd be good to get a Path back.

如果您的模型包含 Path 对象,那么 getSelectedItem() 方法将 return 一个 Path 对象。您需要将其转换为路径,或使用您的 getSelectedPath() 方法。

所以你不需要覆盖DefaultComboBoxModel的任何方法,只添加getSelectedPath()方法。

为了清楚起见,如果另一个 reader 发现了这个问题,这里是基于@camickr 的回答的完整工作代码。请注意,我已使用现有渲染器创建新渲染器以保持外观和感觉一致。

private JComboBox createDirectoryCombo(Path directory) {
    ComboBoxModel<Path> model = new DefaultComboBoxModel<>();
    populateModel(model);
    JComboBox<Path> comboBox = new JComboBox(model);
    comboBox.addPopupMenuListener(new PopupMenuListener() {
        @Override
        public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
            populateModel(model);
        }
        // Other methods omitted for clarity
    });
    ListCellRenderer renderer = comboBox.getRenderer();
    comboBox.setRenderer((list, path, index, selected, focus) -> {
        String fileName = path == null ? "" : path.getFileName().toString().replace(EXT, "");
        return renderer.getListCellRendererComponent(list, fileName, index, selected, focus);
    });

}

private void populateModel(ComboBoxModel<Path> model) {
    try {
        Path previousSelection = getSelectedPath();
        removeAllElements();
        for (Path path : Files.newDirectoryStream(directory, "*" + EXT)) {
            addElement(path);
            if (path.equals(previousSelection))
                setSelectedItem(path);
        }
    } catch (IOException ex) {
        LOG.log(Level.SEVERE, null, ex);
    }
}