MVC:观察目录变化的最佳方式

时间:2015-08-04 15:06:08

标签: java model-view-controller javafx filesystems watchservice

上下文
我是JavaFX的新手,但是我正在尝试构建一个应用程序,其中一个基本功能是向用户显示某个目录中的所有文件夹,并在删除新文件夹或文件夹时自动更新视图在那个目录中。这些文件夹可以被视为对象(例如,在ListView中),用户应该能够与它们进行交互。 我想使用MVC架构构建该应用程序。

到目前为止我做了什么:
所以我有一个视图(fxml),一个控制器类和我的模型来处理我的app逻辑。我在模型中使用WatchDir example from Oracle作为辅助类,并在控制器中启动WatchService,如下所示:

@Override
public void initialize(URL location, ResourceBundle resources) {
    this.config = loadConfig(configFile);
    this.facade = new facade(config);
    Path rootPath = Paths.get(config.getDlRootPath());
    try {
        // register WatchService
        new WatchDir(rootPath, false).processEvents();
        statusText(rootPath + "is now being watched for changes");
    } catch (IOException e) {
        statusError("Directory " + e.getLocalizedMessage() + " does not exist.");
    }
}

在WatchDir方法中,processEvents()可以执行以下操作:

if (!recursive && (kind == ENTRY_CREATE)) {
  // new folder was created
}

我的问题:
告诉我的控制器有什么变化并更新ListView的最佳/最优雅的方法是什么?我想保持MVC风格。

也欢迎不同的方法:)

1 个答案:

答案 0 :(得分:4)

我将使用的方法是在WatchDir中提供用于注册回调的方法。最简单的方法是使用Consumer属性进行回调:

public class WatchDir {

    private Consumer<Path> onCreate ;

    public void setOnCreate(Consumer<Path> onCreate) {
        this.onCreate = onCreate ;
    }

    // other callbacks as needed...

    // ...

    void processEvents() {

        // ...

        Path child = ... ; // file/folder that was created

        if (! recursive && kind == ENTRY_CREATE) {
            if (onCreate != null) {
                onCreate.accept(child);
            }
        }

        // ...
    }

    // ...
}

请注意WatchDir.processEvents()是一个(非终止)阻塞调用,因此您需要在后台线程中运行它。所以从你的控制器你做:

WatchDir watchDir = new WatchDir(rootPath, false) ;
watchDir.setOnCreate(path -> 
    Platform.runLater(() -> listView.getItems().add(path)));
Thread watchThread = new Thread(watchDir::processEvents);
watchThread.setDaemon(true);
watchThread.start();

请注意,由于将在后台线程上调用回调,因此UI的更新应包含在Platform.runLater(...)中。如果您愿意,可以使用WatchDir装备Executor来执行回调,这样您只需告诉它一次即可通过Platform.runLater(...)执行回复:

public class WatchDir {

    // Executor for notifications: by default just run on the current thread
    private Executor notificationExecutor = Runnable::run ;
    public void setNotificationExecutor(Executor notificationExecutor) {
        this.notificationExecutor = notificationExecutor ;
    }

    private Consumer<Path> onCreate ;
    // etc...

    void processEvents() {

        // ...

        if (! recursive && kind == ENTRY_CREATE) {
            if (onCreate != null) {
                notificationExecutor.execute(() -> onCreate.accept(child));
            }
        }

        // ...
    }

}

然后

WatchDir watchDir = new WatchDir(rootPath, false) ;
watchDir.setNotificationExecutor(Platform::runLater);
watchDir.setOnCreate(listView.getItems()::add); /* or path -> listView.getItems().add(path) */
Thread watchThread = new Thread(watchDir::processEvents);
watchThread.setDaemon(true);
watchThread.start();