java - 如何实时更新 TextArea 的输入?

标签 java javafx

我目前正在使用 SceneBuilder 开发 JavaFX 程序,该程序也使用 Windows 命令行。为了向用户表明程序正在执行某些操作,我希望它将控制台输出放入文本区域。到目前为止,它只会在一切完成后更新,而不是实时更新,这是我的目标。

这是我到目前为止的代码,它输入“tree”作为测试。 “textareaOutput”是显示输出的文本区域:

try {
            Process p = Runtime.getRuntime().exec("cmd /C tree");
            BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String line = null;
            while ((line = in.readLine()) != null) {
                System.out.println(line);
                textareaOutput.appendText(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

我知道 Swing 中有 MessageConsole,但我不知道 JafaFX 中是否存在类似的东西

最佳答案

基于自定义日志记录框架的解决方案

生成一个线程来执行您的命令,然后使用以下问题的答案:

这将以线程安全的方式将来自生成线程启动的进程的消息记录回 UI。

您可以使用ExecutorService如果您愿意,可以协助线程管理。如果这样做,请记住彻底关闭它(通常在 JavaFX 应用程序的 stop() 方法中)。

基于 JavaFX 并发实用程序的解决方案

您还可以使用JavaFX concurrency utilities ,例如 TaskServicePlatform.runLater(如果您愿意)。请参阅 Task java documentation 的“返回部分结果的任务”或“修改场景图的任务”部分。有关此方法的更多信息。

sample output

使用 JavaFX 并发实用程序的示例。

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

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ConsoleLogger extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        TextArea outputArea = new TextArea();
        outputArea.setStyle("-fx-font-family: monospace");
        outputArea.setEditable(false);

        TextField commandField = new TextField();
        commandField.setOnAction(event ->
                executeTask(
                        commandField.getText(),
                        outputArea
                )
        );

        VBox layout = new VBox(
                10,
                new Label("Enter a command and press return to execute it."),
                commandField,
                outputArea
        );
        VBox.setVgrow(outputArea, Priority.ALWAYS);
        layout.setPadding(new Insets(10));
        stage.setScene(new Scene(layout));
        stage.show();
    }

    private void executeTask(String commandString, TextArea outputArea) {
        CommandExecutor commandExecutor = new CommandExecutor(
                commandString,
                outputArea
        );

        new Thread(commandExecutor).start();
    }

    public class CommandExecutor extends Task<Void> {
        private final String commandString;
        private final TextArea outputArea;
        private final ProcessBuilder processBuilder;

        public CommandExecutor(
                String commandString,
                TextArea outputArea
        ) {
            this.commandString = commandString;
            this.outputArea = outputArea;

            processBuilder = new ProcessBuilder(
                    commandString.trim().split("\\s+")
            )
                    .redirectErrorStream(true);

            exceptionProperty().addListener((observable, oldException, newException) -> {
                if (newException != null) {
                    newException.printStackTrace();
                }
            });
        }

        @Override
        protected Void call() throws IOException {
            Process process = processBuilder.start();
            try (
                    BufferedReader reader =
                            new BufferedReader(
                                    new InputStreamReader(
                                            process.getInputStream()
                                    )
                            )
            ) {
                String line;
                while ((line = reader.readLine()) != null) {
                    final String nextLine = line;
                    Platform.runLater(
                            () -> outputArea.appendText(nextLine + "\n")
                    );
                }
            }

            return null;
        }
    }

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

请注意,这是一个幼稚的解决方案,它会淹没 JavaFX 可运行队列,不包括强大的异常处理,没有线程池,不处理采用交互式输入 I/O 的进程,将并发执行进程的输出合并到单个文本区域,不限制文本区域大小等。特别是,如果生成的进程执行诸如拖尾大型日志文件之类的操作(该日志文件不断快速写入),则需要小心,因为简单的解决方案可能会通过多次调用 Platform.runLater 淹没 JavaFX 可运行队列,这不太好。

为了获得更有效的解决方案,之前链接的自定义日志记录系统可能会更好。然而,对于某些应用程序,本示例中基于任务的系统记录到 TextArea 或对其进行一些小的修改可能没问题。

附加说明

无论如何,请小心不要尝试直接从另一个线程修改文本区域或其任何属性(使用 Platform.runLater 来防止这种情况),否则程序可能会失败由于并发问题。

使用 Java Process API 时可能会发生很多技巧和意外(不良)的事情,因此,如果您不是很精通它,那么请四处搜索一下,看看这些是什么以及如何解决它们您需要一个非常强大的解决方案。

关于java - 如何实时更新 TextArea 的输入?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59491390/

相关文章:

styles - 如何在 JavaFX 8 中保留 JavaFX 7 皮肤?

java - 尝试将 Swing 转换为 Java-FX

java - spring-boot 应用程序的 Dockerfile 无法构建镜像

java - 通过另一个文件中的 Controller 实例引用在 MainController 中设置文本字段

java - BigInt 相乘

java - List 是将日期与假期集合进行比较的最佳集合方法吗?

JavaFx:获取点击的按钮值

JavaFX - 创建带有图像的自定义按钮

java - 如何找出特定端点是否在 Spring Boot 中启用了 keepalive?

java - 为什么我会收到此 ClassCastException?在 eclipse 上工作,但不在 IntelliJ 上工作