【问题标题】:How do I update the input of a TextArea in real-time?如何实时更新 TextArea 的输入?
【发布时间】:2020-04-16 20:54:57
【问题描述】:

我目前正在使用 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 中是否存在类似的东西

【问题讨论】:

  • 这能回答你的问题吗? jTextArea as IO console
  • this 可以帮到你
  • 请查看@jewelsea 的回答,让您的应用更高效!

标签: java javafx console output textarea


【解决方案1】:

这是一个简单的应用程序,它具有与文本区域进行实时命令行绑定的功能。

  • input(树、时间等)文本字段上输入命令并点击Enter
  • 结果会不断追加到text-area

演示

public class CommandLineTextAreaDemo extends Application {

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

    @Override
    public void start(Stage primaryStage) {
        BorderPane root = new BorderPane();
        root.setCenter(getContent());
        primaryStage.setScene(new Scene(root, 200, 200));
        primaryStage.show();
    }

    private BorderPane getContent() {
        TextArea textArea = new TextArea();
        TextField input = new TextField();

        input.setOnAction(event -> executeTask(textArea, input.getText()));

        BorderPane content = new BorderPane();
        content.setTop(input);
        content.setCenter(textArea);
        return content;
    }

    private void executeTask(TextArea textArea, String command) {
        Task<String> commandLineTask = new Task<String>() {
            @Override
            protected String call() throws Exception {
                StringBuilder commandResult = new StringBuilder();
                try {
                    Process p = Runtime.getRuntime().exec(command);
                    BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
                    String line;
                    while ((line = in.readLine()) != null) {
                        commandResult.append(line + "\n");
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return commandResult.toString();
            }
        };

        commandLineTask.setOnSucceeded(event -> textArea.appendText(commandLineTask.getValue()));

        new Thread(commandLineTask).start();
    }
}

如果你想独立使用TextArea(不使用input TextField),你可以这样做。

textArea.setOnKeyReleased(event -> {
    if(event.getCode() == KeyCode.ENTER) {
        String[] lines = textArea.getText().split("\n");
        executeTask(textArea, lines[lines.length - 1]);
    }
});

【讨论】:

  • 使用此解决方案,如果命令执行时间过长,UI 将挂起,直到命令完成。
  • @jewelsea 感谢您指出这一点!更新代码(使用任务)!
  • 啊,好多了,但是现在你不会得到请求的实时反馈。按照目前的解决方案,文本区域只会在任务完成后更新。阅读标题为“返回部分结果的任务”或“修改场景图的任务”的 task javadoc 部分,了解如何对其进行修改以实现实时反馈。
【解决方案2】:

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

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

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

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

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

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

使用 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 时可能会发生很多技巧和意想不到的(不受欢迎的)事情,所以如果你不是很精通它,然后谷歌看看这些是什么以及如何解决它们如果您需要一个非常强大的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-03
    • 1970-01-01
    • 2018-04-24
    • 1970-01-01
    • 2015-07-14
    • 1970-01-01
    相关资源
    最近更新 更多