JavaFX:ExecutorService.awaitTermination 不通过绑定(bind)属性更新 UI

标签 java javafx

我有多个任务在单个线程中运行。效果很好,相应的表格单元格进度条会按预期更新。现在,在执行它们之前,我想禁用 UI 上的所有按钮(这是我的 enableControls 方法)。当最后一个任务运行时,我重新启用按钮。

我尝试了以下操作,添加了关闭和 awaitTermination 但随后 UI 不会更新,除非所有操作都完成。

ExecutorService exec = Executors.newSingleThreadExecutor(r -> {
   Thread t = new Thread(r);
   t.setDaemon(true); 
   return t ;
});

enableControls(false);
for (Segment seg : selectedItems) {
    Task loadingTask = new Task<Void>() {
        @Override
        public Void call() {
            for (int i = 1; i <= 100; i++) {
                updateProgress(i, 100);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    };
    seg.progressBarProperty().bind(loadingTask.progressProperty());
    loadingTask.setOnSucceeded(e -> seg.progressBarProperty().unbind());
    loadingTask.setOnFailed(e -> {
        loadingTask.getException().printStackTrace();
        enableControls(true);
    });
    exec.submit(loadingTask);
}
exec.shutdown();
try {
    exec.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("test");
enableControls(true);

或者,我考虑确定当前任务是否是最后一个(循环中不再有段)并在 setOnSucceeded 中重新启用 UI 控件。至少可以说并不优雅。

知道为什么 UI(通过绑定(bind)值)没有更新 - 这是 awaitTermination 的限制吗?

最佳答案

awaitTermination是一个阻塞调用,您不应该在 JavaFX 应用程序线程上调用它。只需创建一个新任务来管理任务执行并等待其他任务终止。您可以使用新任务的绑定(bind)来在 UI 运行时禁用部分 UI。

run

import javafx.application.*;
import javafx.concurrent.Task;
import javafx.event.Event;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class Waiter extends Application {

    private static final int N_TASKS = 3;
    private static final int TOTAL_TASK_EXECUTION_TIME_LIMIT_SECS = 20;

    private final VBox layout = new VBox(10);

    @Override
    public void start(Stage stage) throws Exception {
        Button run = new Button("Run");
        run.setOnAction(this::work);

        layout.getChildren().add(run);
        layout.setPadding(new Insets(10));
        layout.setPrefSize(100, 150);

        stage.setScene(new Scene(layout));
        stage.show();
    }

    private void work(Event event) {
        Node controlNode = (Node) event.getSource();

        ExecutorService exec = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "load");
            t.setDaemon(true);
            return t;
        });

        ExecutorService monitor = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "monitor");
            t.setDaemon(true);
            return t;
        });

        final List<LoadingTask> tasks = new ArrayList<>();
        for (int j = 0; j < N_TASKS; j++) {
            LoadingTask loadingTask = new LoadingTask();
            ProgressBar progressBar = new ProgressBar();
            progressBar.progressProperty().bind(loadingTask.progressProperty());
            layout.getChildren().add(progressBar);

            tasks.add(loadingTask);
        }

        Task<Void> taskRunner = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                tasks.forEach(exec::submit);
                exec.shutdown();
                boolean cleanShutdown = exec.awaitTermination(
                        TOTAL_TASK_EXECUTION_TIME_LIMIT_SECS,
                        TimeUnit.SECONDS
                );
                if (!cleanShutdown) {
                    exec.shutdownNow();
                }

                Platform.runLater(() ->
                        layout.getChildren()
                                .removeIf(
                                        node -> node instanceof ProgressBar
                                )
                );

                return null;
            }
        };
        controlNode.disableProperty().bind(taskRunner.runningProperty());
        monitor.submit(taskRunner);
        monitor.shutdown();
    }

    public static void main(String[] args) {
        launch(args);
    }

    private static class LoadingTask extends Task<Void> {
        @Override
        public Void call() {
            for (int i = 1; i <= 100; i++) {
                if (isCancelled() || Thread.interrupted()) {
                    break;
                }
                updateProgress(i, 100);
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            return null;
        }
    }
}

I need to disable all buttons and textfields on the stage

如果禁用父节点,它将递归地禁用父节点的所有子节点。因此禁用场景中的所有内容,只需绑定(bind)场景根的disableProperty即可:

controlNode.getScene().getRoot()
    .disableProperty().bind(taskRunner.runningProperty());

禁用父阶段上的输入的另一种方法是使用 WINDOW_MODALAPPLICATION_MODAL与所有者集的对话。例如 ControlsFX ProgressDialog .

关于JavaFX:ExecutorService.awaitTermination 不通过绑定(bind)属性更新 UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38024933/

相关文章:

java - 如何让子类覆盖父类的值,但使用父类的方法

java - 使用Java流合并一对 `int`数组

javafx - 从 bin 文件夹以外的文件夹加载 fxml 文件时出错

java - setText() 上的 NullPointer 异常,而所有内容都已正确加载和注释

java - 如何解决spring-modules-validation.jar vlang.tld中遇到XMLStreamException Unexpected value 'body-content'

Java RMI服务器: method to be called on events

javafx - 带有 ToggleButtons JavaFX 的 ToggleGroup

java - SceneBuilder 不会加载我的自定义控件,它通过 FXML 引用另一个自定义控件

JavaFX 中的 JavaBeanProperties,无需引入 java.desktop(Swing、AWT)

java - 使用 mysql 在 Spring Boot 应用程序中避免 "set session transaction read"