有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java在后台更新JavaFX窗口

我目前正在使用一个文件资源管理器。我想实现一个搜索功能,它显示当前目录和所有子目录中的所有匹配文件。我用shell命令“find”尝试了一下,但没有成功。之后,我尝试使用Java8API的文件。步行(…)作用这起了部分作用。我现在可以得到所有文件并打印出来。唯一的问题是,我不能在GUI中为找到的每个文件添加一个窗格

我尝试了一个任务,但是GUI没有更新。在下面的最终版本中,有两个线程。一个获取文件并将其填充到队列中,另一个轮询队列中的对象并将其显示在GUI上。通过调试,我知道队列的大小始终在0和2之间。这意味着,我的系统正在工作。但是GUI仍然没有更新

谁能帮我一下吗

public static final ConcurrentLinkedQueue<FilePane> files = new ConcurrentLinkedQueue<>();

public void search(String key) {
        Task<Boolean> listLoader = new Task<Boolean>() {
        {
            setOnSucceeded(workerStateEvent -> {
                System.out.println("Done");
            });

            setOnFailed(workerStateEvent -> getException().printStackTrace());
        }

        @Override
        protected Boolean call() throws Exception {
            try {
                Files.walkFileTree(Paths.get(currentDirectoryController().getDir().getAbsolutePath()), new HashSet<FileVisitOption>(Arrays.asList(FileVisitOption.FOLLOW_LINKS)),
                        Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
                            @Override
                            public FileVisitResult visitFile(Path file , BasicFileAttributes attrs) throws IOException {
                                return FileVisitResult.CONTINUE;
                            }

                            @Override
                            public FileVisitResult visitFileFailed(Path file , IOException e) throws IOException {
                                return FileVisitResult.SKIP_SUBTREE;
                            }

                            @Override
                            public FileVisitResult preVisitDirectory(Path dir , BasicFileAttributes attrs) throws IOException {
                                files.add(new FilePane(new KFile(dir.toFile()), cont));
                                return FileVisitResult.CONTINUE;
                            }
                        });
            } catch (IOException e) {
                e.printStackTrace();
            }

            return true;
        }
    };

    Thread loader = new Thread(listLoader);
    loader.setDaemon(true);
    loader.start();

    new Thread(() -> {
        Platform.runLater(() -> {
            while(true) {
                if(!files.isEmpty()) {
                    fileContainer.getChildren().add(files.poll());
                }
            }
        });
    }).start();
}

共 (2) 个答案

  1. # 1 楼答案

    new Thread(() -> {
        Platform.runLater(() -> {
            while(true) {
                if(!files.isEmpty()) {
                    fileContainer.getChildren().add(files.poll());
                }
            }
        });
    }).start();
    

    这句话实际上有以下几点:

    Runnable action1 = () -> {
            while(true) {
                if(!files.isEmpty()) {
                    fileContainer.getChildren().add(files.poll());
                }
            };
    
    Runnable action2 = () -> {
        Platform.runLater(action1);
    };
    
    new Thread(action2).start();
    

    因此,action2执行得非常快,在单独的线程上运行它是没有意义的。然而,action2有一个无限循环并永远运行,它在GUI线程上运行,实际上阻塞了这个线程并阻止它重新绘制屏幕

    您可能听说过,您可能不会占用GUI线程很长时间。也就是说,您应该在GUI线程之外执行循环,如下所示:

    Runnable action1 = () -> {
            while(true) {
                Platform.runLater(
                    fileContainer.getChildren().add(files.take());
                });
            };
    
    new Thread(action1).start();
    

    现在我们看到这个线程所做的很少——从队列中获取文件窗格,用一个简单的方法包装它们,并将它们放入GUI线程的输入队列。因此可以消除它,所有的工作都可以在加载器线程中完成:而不是files.add(new FilePane(...))do

     Platform.runLater(
         fileContainer.getChildren().add(new FilePane(...));
     });
    
  2. # 2 楼答案

    new Thread(() -> {
        Platform.runLater(() -> {
            while(true) {
                if(!files.isEmpty()) {
                    fileContainer.getChildren().add(files.poll());
                }
            }
        });
    }).start();
    

    这只是启动一个不同的线程,然后发布一个长时间运行的任务(实际上是一个无限循环)在应用程序线程上运行

    为了不阻塞应用程序线程,您只需要发布可以在应用程序线程上快速完成的任务

    其他优化:

    • 为了避免在短时间内在应用程序线程上发布太多任务,可以添加延迟
    • 您应该有办法让第二个线程关闭。否则,寻找第一个线程发布的新文件的线程将永远运行。最好自己进行同步,以确保指示任务已完成的标志未设置为
    • 尝试使用虚拟化控件ListViewTableView来显示文件。这避免了在内存中创建大量的节点,也加快了布局,因为它只需要考虑可见节点。<李>

    实际上,您可以通过简单地使用原始任务中的Platform.runLater来避免使用者/生产者实现的复杂性:

    Task<Boolean> listLoader = new Task<Boolean>() {
        {
            setOnSucceeded(workerStateEvent -> {
                System.out.println("Done");
            });
    
            setOnFailed(workerStateEvent -> getException().printStackTrace());
        }
    
        @Override
        protected Boolean call() throws Exception {
            try {
                Files.walkFileTree(Paths.get(currentDirectoryController().getDir().getAbsolutePath()), new HashSet<FileVisitOption>(Arrays.asList(FileVisitOption.FOLLOW_LINKS)),
                        Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
                            @Override
                            public FileVisitResult visitFile(Path file , BasicFileAttributes attrs) throws IOException {
                                return FileVisitResult.CONTINUE;
                            }
    
                            @Override
                            public FileVisitResult visitFileFailed(Path file , IOException e) throws IOException {
                                return FileVisitResult.SKIP_SUBTREE;
                            }
    
                            @Override
                            public FileVisitResult preVisitDirectory(Path dir , BasicFileAttributes attrs) throws IOException {
                                FilePane filePane = new FilePane(new KFile(dir.toFile()), cont);
                                Platform.runLater(() -> fileContainer.getChildren().add(filePane)); // TODO bulk updates to prevent posting runables too frequently
                                return FileVisitResult.CONTINUE;
                            }
                        });
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            return true;
        }
    };
    
    Thread loader = new Thread(listLoader);
    loader.setDaemon(true);
    loader.start();