[英]JavaFX - How to create a new stage (open new window) when a file is created in specified directory?
我對JavaFX非常陌生,目前正在編寫一個簡單的GUI應用程序,該應用程序應對指定目錄中的文件創建作出反應。 到目前為止,我已經在經典的Java應用程序中使用了WatchService,但是我不確定如何使GUI應用程序對WatchService做出反應。 我也許可以在其他線程中運行WatchService,並且在創建文件時,可以設置主GUI類將對其做出反應的某種標志。
這是一個JavaFX服務,它將使用WatchService
監視另一個線程上的事件。
演示服務的示例應用程序偵聽WatchService檢測到的更改並將其記錄在列表中。 但是您可以過濾變更事件,並在收到變更事件時做任何您想做的事情(例如,打開一個您希望做的舞台)。 如果您願意,甚至可以將檢測到的事件轉換為JavaFX事件 ,盡管該實現不在此答案的范圍之內。
WatchService目錄的ScheduledService實現
WatchService的實現基於Oracle關於監視文件目錄的教程和JavaFX ScheduledService
。
import javafx.beans.property.*;
import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.*;
/**
* Watch a directory (or tree) for changes to files.
*/
public class WatchDirService extends ScheduledService<List<WatchEvent<Path>>> {
private final WatchService watcher;
private final Map<WatchKey,Path> keys;
private boolean trace;
private ReadOnlyStringWrapper dir = new ReadOnlyStringWrapper(this, "dir");
public final String getDir() { return dir.get(); }
public final ReadOnlyStringProperty dirProperty() { return dir.getReadOnlyProperty(); }
private ReadOnlyBooleanWrapper recursive = new ReadOnlyBooleanWrapper(this, "recursive");
public boolean isRecursive() { return recursive.get(); }
public ReadOnlyBooleanProperty recursiveProperty() { return recursive; }
@SuppressWarnings("unchecked")
private static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
/**
* Creates a WatchService and registers the given directory
*/
public WatchDirService(Path dir, boolean recursive) throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<>();
this.dir.set(dir.toString());
this.recursive.set(recursive);
if (recursive) {
System.out.format("Scanning %s ...\n", dir);
registerAll(dir);
System.out.println("Done.");
} else {
register(dir);
}
// enable trace after initial registration
this.trace = true;
}
/**
* Register the given directory with the WatchService
*/
private void register(Path dir) throws IOException {
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
if (trace) {
Path prev = keys.get(key);
if (prev == null) {
System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
System.out.format("update: %s -> %s\n", prev, dir);
}
}
}
keys.put(key, dir);
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(final Path start) throws IOException {
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException
{
register(dir);
return FileVisitResult.CONTINUE;
}
});
}
@Override
protected Task<List<WatchEvent<Path>>> createTask() {
return new WatchTask();
}
class WatchTask extends Task<List<WatchEvent<Path>>> {
@Override
protected List<WatchEvent<Path>> call() {
// wait for key to be signalled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
if (isCancelled()) {
updateMessage("Cancelled");
}
return Collections.emptyList();
}
Path dir = keys.get(key);
if (dir == null) {
System.err.println("WatchKey not recognized");
return Collections.emptyList();
}
List<WatchEvent<Path>> interestingEvents = new ArrayList<>();
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> pathWatchEvent = cast(event);
Path name = pathWatchEvent.context();
Path child = dir.resolve(name);
interestingEvents.add(pathWatchEvent);
// if directory is created, and watching recursively, then
// register it and its sub-directories
if (recursive.get() && (kind == ENTRY_CREATE)) {
try {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
registerAll(child);
}
} catch (IOException x) {
System.err.println("Unable to register created directory for watching: " + child);
}
}
}
// reset key and remove from set if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
// if all directories are inaccessible
// even the root watch directory
// might wight want to cancel the service.
if (keys.isEmpty()) {
System.out.println("No directories being watched");
}
}
return Collections.unmodifiableList(
interestingEvents
);
}
}
}
為簡單起見,JavaFX WatchDirService的輸入參數被編碼為只讀屬性。 這意味着,如果您想將監視方式從遞歸更改為非遞歸,反之亦然,或者更改要監視的目錄,則必須取消現有服務,並使用新設置創建一個新服務。 可能有可能具有讀寫屬性,以便可以修改現有的運行服務以監視不同的目錄,但是對我來說,使其正常工作似乎有些棘手,因此我沒有嘗試過。
我非常高興地感到很驚訝,WatchService和JavaFX ScheduledService的集成在實現方式,應用程序使用方式和執行方式方面看起來都很好。
目錄監視WatchService的基本用法
還有一個示例應用程序演示其用法(檢測用戶主目錄根目錄中的更改):
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
public class WatchDirApp extends Application {
private static final String WATCH_DIR = System.getProperty("user.home");
private WatchDirService watchDirService;
@Override
public void init() throws Exception {
watchDirService = new WatchDirService(
Paths.get(WATCH_DIR),
false
);
}
@Override
public void start(Stage stage) throws Exception {
ListView<String> events = new ListView<>();
watchDirService.start();
watchDirService.valueProperty().addListener((observable, previousEvents, newEvents) -> {
if (newEvents != null) {
newEvents.forEach(event ->
events.getItems().add(eventToString(event))
);
}
});
Scene scene = new Scene(new StackPane(events));
stage.setScene(scene);
stage.show();
}
private String eventToString(WatchEvent<Path> event) {
return event.kind() + ":" + event.context() + ":n=" + event.count();
}
@Override
public void stop() throws Exception {
watchDirService.cancel();
}
public static void main(String[] args) {
launch(args);
}
}
事件過濾示例
這是使用JavaFX的WatchService的更廣泛的示例,它將監視用戶主目錄中新文本文件的創建(或對現有文件的修改),並啟動新窗口以在文件中顯示文本。
該示例演示如何過濾監視服務生成的事件,以便可以基於該事件采取不同的操作(例如,在修改新文件時在新窗口中查看文本文件或更新現有窗口的內容)。
在運行示例時,應注意,在生成事件的操作(例如,保存新的文本文件)與UI更新(例如,顯示新保存的文本文件)之間存在一秒或兩秒的延遲。 這是因為監視服務不會實時通知更改事件(至少在運行OS X的本機上),而是稍微延遲地通知更改事件。
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.WatchEvent;
import java.util.HashMap;
import java.util.Map;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
public class WatchedTextFileViewerApp extends Application {
private static final String TEXT_FILE_EXTENSION = ".txt";
private static final String WATCH_DIR = System.getProperty("user.home");
private WatchDirService watchDirService;
private Stage appStage;
private Map<Path, FileViewer> fileViewers = new HashMap<>();
@Override
public void init() throws Exception {
watchDirService = new WatchDirService(
Paths.get(WATCH_DIR),
false
);
}
@Override
public void start(Stage stage) throws Exception {
this.appStage = stage;
ListView<String> events = new ListView<>();
watchDirService.start();
watchDirService.valueProperty().addListener((observable, previousEvents, newEvents) -> {
if (newEvents != null) {
newEvents.forEach(event ->
events.getItems().add(eventToString(event))
);
newEvents.stream()
.filter(event -> ENTRY_CREATE.equals(event.kind()) && isForTextFile(event.context()))
.forEach(event -> view(event.context()));
newEvents.stream()
.filter(event -> ENTRY_MODIFY.equals(event.kind()) && isForTextFile(event.context()))
.forEach(event -> refresh(event.context()));
}
});
Scene scene = new Scene(new StackPane(events));
stage.setScene(scene);
stage.show();
}
@Override
public void stop() throws Exception {
watchDirService.cancel();
}
private boolean isForTextFile(Path path) {
return path != null
&& !Files.isDirectory(path)
&& path.toString().endsWith(TEXT_FILE_EXTENSION);
}
private FileViewer view(Path path) {
FileViewer fileViewer = new FileViewer(appStage, path);
fileViewers.put(path, fileViewer);
fileViewer.show();
return fileViewer;
}
private void refresh(Path path) {
FileViewer fileViewer = fileViewers.get(path);
if (fileViewer == null) {
fileViewer = view(path);
}
fileViewer.refreshText();
fileViewer.show();
}
private String eventToString(WatchEvent<Path> event) {
return event.kind() + ":" + event.context() + ":n=" + event.count();
}
private static class FileViewer extends Stage {
private TextArea textArea = new TextArea();
private Path path;
FileViewer(Stage owner, Path path) {
this.path = Paths.get(WATCH_DIR).resolve(path);
initOwner(owner);
setTitle(path.toString());
textArea.setEditable(false);
refreshText();
setScene(new Scene(textArea));
}
void refreshText() {
try {
textArea.setText(String.join("\n", Files.readAllLines(path)));
} catch (IOException e) {
System.err.println("Unable to read the content of: " + path);
}
}
}
public static void main(String[] args) {
launch(args);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.