MVC:观察目录变化的最佳方式
MVC: Best way of watching a directory for changes
上下文:
我是 JavaFX 的新手,但我正在尝试构建一个应用程序,它的基本功能之一是向用户显示特定目录中的所有文件夹,并在该目录中有新文件夹或文件夹被删除时自动更新视图。这些文件夹可以被视为对象(例如在 ListView 中)并且用户应该能够与它们进行交互。
我想使用 MVC 架构构建该应用程序。
到目前为止我做了什么:
所以我有一个视图 (fxml)、一个控制器 class 和处理我的应用程序逻辑的模型。我在我的模型中使用 WatchDir example from Oracle 作为助手 class 并像这样在控制器中启动 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
}
我的问题:
best/most 告诉我的控制器某些内容已更改并更新 ListView 的优雅方式是什么?我想保持它的 MVC 风格。
也欢迎不同的方法:)
我将使用的方法是在 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();
上下文:
我是 JavaFX 的新手,但我正在尝试构建一个应用程序,它的基本功能之一是向用户显示特定目录中的所有文件夹,并在该目录中有新文件夹或文件夹被删除时自动更新视图。这些文件夹可以被视为对象(例如在 ListView 中)并且用户应该能够与它们进行交互。
我想使用 MVC 架构构建该应用程序。
到目前为止我做了什么:
所以我有一个视图 (fxml)、一个控制器 class 和处理我的应用程序逻辑的模型。我在我的模型中使用 WatchDir example from Oracle 作为助手 class 并像这样在控制器中启动 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
}
我的问题:
best/most 告诉我的控制器某些内容已更改并更新 ListView 的优雅方式是什么?我想保持它的 MVC 风格。
也欢迎不同的方法:)
我将使用的方法是在 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();