[英]How do I update the input of a TextArea in real-time?
我目前正在使用 SceneBuilder 开发 JavaFX 程序,它也使用 Windows 的命令行。 为了向用户显示程序正在执行某些操作,我希望它将控制台输出放入文本区域。 到目前为止,它只在一切完成后更新,而不是实时更新,这是我的目标。
这是我到目前为止的代码,它输入“树”作为测试。 “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 中是否存在类似的东西
这是一个简单的应用程序,它具有与文本区域实时命令行绑定的功能。
input
(树、时间等)TextField 上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。
如果您愿意,您可以使用ExecutorService来协助线程管理。 如果这样做,请记住彻底关闭它(通常在 JavaFX 应用程序的stop()
方法中)。
基于 JavaFX 并发实用程序的解决方案
如果您愿意,您还可以使用JavaFX 并发实用程序,例如Task
、 Service
和Platform.runLater
。 有关此方法的更多信息,请参阅Task
java 文档的“返回部分结果的任务”或“修改场景图的Task
”部分。
使用 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 的进程,将并发执行进程的输出合并为单个文本区域,不限制文本区域大小等。特别是,如果生成的进程执行诸如尾随一个不断快速写入的大型日志文件之类的操作,您需要小心,因为幼稚的解决方案可能会淹没 JavaFX对Platform.runLater
进行多次调用的可运行队列,这不会很好。
对于更有效的解决方案,较早链接的自定义日志记录系统可能会更好。 但是,对于某些应用程序,在此示例中基于任务的系统记录到 TextArea 或对其进行一些小的修改可能没问题。
补充说明
在任何情况下,请注意不要尝试直接从另一个线程修改文本区域或其任何属性(使用Platform.runLater
来防止这种情况),否则程序可能会因并发问题而失败。
使用 Java Process API 时可能会发生很多技巧和意外(不受欢迎)的事情,所以如果你不是很精通它,那么谷歌看看这些是什么以及如果你需要如何解决它们非常强大的解决方案。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.