簡體   English   中英

JavaFX-在指定目錄中創建文件時如何創建新階段(打開新窗口)?

[英]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的基本用法

還有一個示例應用程序演示其用法(檢測用戶主目錄根目錄中的更改):

watchapp圖片

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM