Может ли JavaFX FileChooser запомнить последний открытый каталог?

Мой контроллер представления имеет один экземпляр FileChooser, используемый как для открытия, так и для сохранения файлов.

Каждый раз, когда я вызываю showOpenDialog() или showSaveDialog() из этого экземпляра, я ожидаю, что результирующий диалог будет в том же каталоге, в котором я оставил его в последний раз, когда я вызывал один из них.

Вместо этого каждый раз, когда я вызываю один из этих методов, диалоги открываются в домашнем каталоге пользователя.

Как сделать так, чтобы «текущий каталог» диалогов сохранялся при разных вызовах?


Пример текущего поведения:

import java.io.File;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

/**
 * Demonstrates the use of an open dialog.
 * 
 * @author N99x
 */
public class FileChooserTest extends Application {

    private final FileChooser open = new FileChooser();
    private File lastOpened = null;

    @Override
    public void start(Stage primaryStage) {
        Label lbl = new Label("File Opened: <null>");
        lbl.setPadding(new Insets(8));
        Button btn = new Button();
        btn.setPadding(new Insets(8));
        btn.setText("Open");
        btn.setOnAction((ActionEvent event) -> {
            open.setInitialDirectory(lastOpened);
            File selected = open.showOpenDialog(primaryStage);
            if (selected == null) {
                lbl.setText("File Opened: <null>");
                // lastOpened = ??;
            } else {
                lbl.setText("File Opened: " + selected.getAbsolutePath());
                lastOpened = selected.getParentFile();
            }
        });

        VBox root = new VBox(lbl, btn);
        root.setPadding(new Insets(8));
        root.setAlignment(Pos.TOP_CENTER);
        Scene scene = new Scene(root, 300, 300);

        primaryStage.setTitle("FileChooser Testing!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}

Мне удалось обойти часть проблемы, сохранив открытое значение, но это не работает, если диалог закрыт или отменен.


person Community    schedule 28.04.2016    source источник


Ответы (4)


Как сделать так, чтобы «текущий каталог» диалогов сохранялся при разных вызовах?

Вы можете изменить подход Singleton Pattern для этого

При этом вы будете использовать только один FileChooser и отслеживать/контролировать исходный каталог там, но не подвергать экземпляр непосредственно изменениям вне класса.


Например:

public class RetentionFileChooser {
    private static FileChooser instance = null;
    private static SimpleObjectProperty<File> lastKnownDirectoryProperty = new SimpleObjectProperty<>();

    private RetentionFileChooser(){ }

    private static FileChooser getInstance(){
        if(instance == null) {
            instance = new FileChooser();
            instance.initialDirectoryProperty().bindBidirectional(lastKnownDirectoryProperty);
            //Set the FileExtensions you want to allow
            instance.getExtensionFilters().setAll(new ExtensionFilter("png files (*.png)", "*.png"));
        }
        return instance;
    }

    public static File showOpenDialog(){
        return showOpenDialog(null);
    }

    public static File showOpenDialog(Window ownerWindow){
        File chosenFile = getInstance().showOpenDialog(ownerWindow);
        if(chosenFile != null){
            //Set the property to the directory of the chosenFile so the fileChooser will open here next
            lastKnownDirectoryProperty.setValue(chosenFile.getParentFile());
        }
        return chosenFile;
    }

    public static File showSaveDialog(){
        return showSaveDialog(null);
    }

    public static File showSaveDialog(Window ownerWindow){
        File chosenFile = getInstance().showSaveDialog(ownerWindow);
        if(chosenFile != null){
            //Set the property to the directory of the chosenFile so the fileChooser will open here next
            lastKnownDirectoryProperty.setValue(chosenFile.getParentFile());
        }
        return chosenFile;
    }
}

Это установит начальный каталог FileChooser в качестве каталога файла, который пользователь последний раз открывал/сохранял при повторном использовании.

Пример использования:

File chosenFile = RetentionFileChooser.showOpenDialog();

Однако здесь есть ограничение:

//Set the FileExtensions you want to allow 
instance.getExtensionFilters().setAll(new ExtensionFilter("png files (*.png)", "*.png"));

Поскольку без предоставления каких-либо ExtensionFilter, FileChooser становится менее удобным для пользователя, требуя от пользователя добавления типа файла вручную, но предоставление фильтров при создании экземпляра без возможности их обновления ограничивает его теми же фильтрами.

Один из способов улучшить это — предоставить дополнительные фильтры в RetentionFileChooser, которые могут быть предоставлены с помощью varargs, благодаря чему вы можете выбирать, когда изменять фильтры при отображении диалогов.

Например, опираясь на предыдущий:

public class RetentionFileChooser {
    public enum FilterMode {
        //Setup supported filters
        PNG_FILES("png files (*.png)", "*.png"),
        TXT_FILES("txt files (*.txt)", "*.txt");

        private ExtensionFilter extensionFilter;

        FilterMode(String extensionDisplayName, String... extensions){
            extensionFilter = new ExtensionFilter(extensionDisplayName, extensions);
        }

        public ExtensionFilter getExtensionFilter(){
            return extensionFilter;
        }
    }

    private static FileChooser instance = null;
    private static SimpleObjectProperty<File> lastKnownDirectoryProperty = new SimpleObjectProperty<>();

    private RetentionFileChooser(){ }

    private static FileChooser getInstance(FilterMode... filterModes){
        if(instance == null) {
            instance = new FileChooser();
            instance.initialDirectoryProperty().bindBidirectional(lastKnownDirectoryProperty);
        }
        //Set the filters to those provided
        //You could add check's to ensure that a default filter is included, adding it if need be
        instance.getExtensionFilters().setAll(
                Arrays.stream(filterModes)
                        .map(FilterMode::getExtensionFilter)
                        .collect(Collectors.toList()));
        return instance;
    }

    public static File showOpenDialog(FilterMode... filterModes){
        return showOpenDialog(null, filterModes);
    }

    public static File showOpenDialog(Window ownerWindow, FilterMode...filterModes){
        File chosenFile = getInstance(filterModes).showOpenDialog(ownerWindow);
        if(chosenFile != null){
            lastKnownDirectoryProperty.setValue(chosenFile.getParentFile());
        }
        return chosenFile;
    }

    public static File showSaveDialog(FilterMode... filterModes){
        return showSaveDialog(null, filterModes);
    }

    public static File showSaveDialog(Window ownerWindow, FilterMode... filterModes){
        File chosenFile = getInstance(filterModes).showSaveDialog(ownerWindow);
        if(chosenFile != null){
            lastKnownDirectoryProperty.setValue(chosenFile.getParentFile());
        }
        return chosenFile;
    }
}

Пример использования:

//Note the previous example still holds
File chosenFile = RetentionFileChooser.showOpenDialog();
File file = RetentionFileChooser.showSaveDialog(FilterMode.PNG_FILES);

но это не работает, если диалог закрыт или отменен.

К сожалению, FileChooser не предоставляет информацию о том, какой каталог проверялся до его закрытия/прекращения. Если вы декомпилируете класс и проследите его, он в конечном итоге столкнется с вызовом native. Таким образом, даже если FileChooser не было final разрешено создание подкласса, вряд ли он сможет получить эту информацию.

Единственная выгода, которую дает описанный выше подход, заключается в том, что он не меняет начальный каталог, если этот сценарий срабатывает.


Если вам интересно, есть несколько очень хороших ответов на ключевое слово native в этом вопросе и связанных ссылках:

person Peter    schedule 25.06.2016
comment
Простым решением для предложений расширения файлов было бы сбрасывать расширения файлов каждый раз, когда вы его вызываете. IE: fileChooser.getExtensionFilters().clear(); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("PNG", "*.png")); - person Stevoisiak; 07.04.2017

Может тоже интересно...

Если вы хотите сохранить файл, выбранный после закрытия приложения, и снова использовать его в качестве исходного значения файла после повторного запуска приложения (и у вас не так уж много вещей, которые вы хотите сохранить), вы можете вернуться к старые добрые файлы .ini для Windows.

Хорошим вспомогательным классом для этого является ini4j. В нем есть все, что нужно для базовых вещей. Понятно, просто и без заморочек.

person GeertVc    schedule 19.08.2018

Мое предложение: создайте файл журнала без добавления и создайте класс записи, используя FileReaders и Writers, чтобы всякий раз, когда вы открываете файл, как только FileChooser закрывается после успешного выбора, вы записываете абсолютный путь к последнему доступному каталогу в лог-файл.

Создайте метод чтения, который возвращает путь из файла. Всякий раз, когда вы открываете пользователя FileChooser, возвращаемое значение чтения в качестве значения для метода setInitialDirectory(the-read-path)

Это гарантирует, что всякий раз, когда вы снова открываете FileChooser, даже если последний экземпляр был отменен, он откроет зарегистрированную запись, которая была успешной. Я обнаружил, что программы чтения и записи файлов без добавления файлов работают намного быстрее. Вы можете попробовать этот метод, если он подходит для вашего случая.

person briancollins081    schedule 24.01.2019

person    schedule
comment
Просто и эффективно! - person GeertVc; 16.08.2018