【问题标题】:JavaFX - How to create a new stage (open new window) when a file is created in specified directory?JavaFX - 在指定目录中创建文件时如何创建新阶段(打开新窗口)?
【发布时间】:2020-01-18 15:32:41
【问题描述】:

我是 JavaFX 的新手,我目前正在编写一个简单的 GUI 应用程序,它应该对指定目录中的文件创建做出反应。到目前为止,我已经在经典的 Java 应用程序中使用了 WatchService,但我不确定如何让 GUI 应用程序对 WatchService 做出反应。我也许可以在不同的线程中运行 WatchService,当文件创建发生时,我可以设置某种标志,主 GUI 类将对其做出反应。

【问题讨论】:

  • 您提出的解决方案对我来说听起来很合理。我建议你实现它,如果你遇到困难,你可以在这里发布你的代码,希望得到帮助。
  • 好的,谢谢。我要试试这个想法,我会发布更新。

标签: java javafx watchservice


【解决方案1】:

这是一个 JavaFX 服务,它将使用 WatchService 来监视另一个线程上的事件。

演示该服务的示例应用程序会侦听 WatchService 检测到的更改并将其记录在列表中。但是您可以过滤更改事件,并在收到它们时做任何您想做的事情(例如按照您的意愿打开一个阶段)。如果你愿意,你甚至可以translate the detected events to JavaFX events,尽管它的实现超出了这个答案的范围。

目录WatchService的ScheduledService实现

WatchService 的实现基于watching a file directory 上的Oracle 教程和ScheduledService 上的JavaFX。

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

【讨论】:

    猜你喜欢
    • 2017-02-21
    • 2018-02-28
    • 1970-01-01
    • 1970-01-01
    • 2016-08-04
    • 1970-01-01
    • 2014-09-30
    • 2017-03-09
    • 1970-01-01
    相关资源
    最近更新 更多