JavaFX:将控制台输出重定向到在 SceneBuilder 中创建的 TextArea
JavaFX: Redirect console output to TextArea that is created in SceneBuilder
编辑 4
我创建了一个简单的示例,应该可以让您了解当前正在发生的事情。
现在发生的事情是,每当我单击按钮将 "HELLO WORLD" 打印到 TextArea 时,程序将挂起并使用 100% 的 CPU。 Eclipse 控制台面板中也没有输出。
Main.java
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/application/test.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
MainController.java
public class MainController {
@FXML
private TextArea console;
private PrintStream ps = new PrintStream(new Console(console));
public void button(ActionEvent event) {
System.setOut(ps);
System.setErr(ps);
System.out.println("Hello World");
}
public class Console extends OutputStream {
private TextArea console;
public Console(TextArea console) {
this.console = console;
}
public void appendText(String valueOf) {
Platform.runLater(() -> console.appendText(valueOf));
}
public void write(int b) throws IOException {
appendText(String.valueOf((char)b));
}
}
}
编辑 2:看来我的问题太长而且难以理解。我正在重组这个。
编辑 3
我想我应该在这里展示所有内容。我想要做的是为 CLI 应用程序创建一个简单的 GUI 前端。我是学CS的,Java是我们的主要语言,所以主要是为了练习。
我到处找了好几个小时,但仍然没有解决办法。我试过像以前使用 Swing 那样做同样的事情。该方法适用于 Swing,但不适用于 JavaFX。
这是我的(当前)logger.java Class:
package application;
import java.io.*;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
public class ytdlLogger extends OutputStream implements Initializable
{
private TextArea loggerPane;
public ytdlLogger(TextArea loggerPane) {
this.loggerPane = loggerPane;
}
public void appendText(String valueOf) {
Platform.runLater(() -> loggerPane.appendText(valueOf));
}
@Override
public void initialize(URL location, ResourceBundle resources) {
OutputStream out = new OutputStream() {
@Override
public void write(int b) throws IOException {
appendText(String.valueOf((char)b));
}
};
System.setOut(new PrintStream(out, true));
System.setErr(new PrintStream(out, true));
}
@Override
public void write(int b) throws IOException {
// TODO Auto-generated method stub
}
}
我认为这没有任何实际问题。我还确实添加了 PrintStream 对象以将 MainController class 中的 System.setOut 和 System.setErr 重定向到 TextArea,但它也不起作用。
我还有另一个 Main class,这是加载 FXML 的主要内容。我尝试从那里重定向输出,它几乎成功了。差不多了,因为我不再看到 Eclipse 中的控制台输出,我知道这是一个很大的进步。
那么,这里的问题似乎是什么?是因为 FXML 吗?我是 Java 和 JavaFX 的绝对初学者,这是我的第一个 JavaFX 应用程序。非常感谢任何指导。提前谢谢你。
编辑 1
这是主要内容 class:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/application/Main.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
您没有将控制器与 FXMLLoader
一起使用。否则你会得到一个异常,因为 class 没有默认构造函数。
如果要使用 FXMLLoader
创建 ytdlLogger
,请将属性 fx:controller="application.ytdlLogger"
(其中 fx
是 fxml 命名空间前缀)添加到根元素你的 fxml 文件。
如果你想这样做,你还需要改变一些东西:
ytdlLogger
需要默认构造函数(即删除构造函数或创建一个不带参数的新构造函数)。
将 @FXML
注释添加到您的 loggerPane
字段以允许 FXMLLoader
访问该字段以将 TextArea
分配给 fx:id="loggerPane"
属性给它。
- 最好从控制器中移除底座 class
OutputStream
,因为您不用它。
- 添加一些打印到
System.out
或 System.err
的代码。否则,没有任何内容写入您的 TextArea
也就不足为奇了。确保在控制器初始化后执行此操作。
更改后您的控制器应如下所示:
public class ytdlLogger implements Initializable
{
@FXML
private TextArea loggerPane;
public void appendText(String valueOf) {
Platform.runLater(() -> loggerPane.appendText(valueOf));
}
@Override
public void initialize(URL location, ResourceBundle resources) {
OutputStream out = new OutputStream() {
@Override
public void write(int b) throws IOException {
appendText(String.valueOf((char)b));
}
};
System.setOut(new PrintStream(out, true));
System.setErr(new PrintStream(out, true));
}
}
fxml 应该类似于此
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8"
fx:controller="application.ytdlLogger"> <!-- controller goes here -->
<children>
<TextArea fx:id="loggerPane" /> <!-- the TextArea you want to use for logging -->
</children>
</AnchorPane>
您正在用 console
的值初始化 ps
,然后它被 FXMLLoader
初始化。即你有
@FXML
private TextArea console;
private PrintStream ps = new PrintStream(new Console(console));
当你将它传递给 new Console(...)
时,显然 console
仍然是 null
。
您需要在 FXMLLoader
初始化注入字段后初始化 ps
,您可以使用 initialize
方法完成此操作。
SSCCE:
MainController.java:
package application;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
public class MainController {
@FXML
private TextArea console;
private PrintStream ps ;
public void initialize() {
ps = new PrintStream(new Console(console)) ;
}
public void button(ActionEvent event) {
System.setOut(ps);
System.setErr(ps);
System.out.println("Hello World");
}
public class Console extends OutputStream {
private TextArea console;
public Console(TextArea console) {
this.console = console;
}
public void appendText(String valueOf) {
Platform.runLater(() -> console.appendText(valueOf));
}
public void write(int b) throws IOException {
appendText(String.valueOf((char)b));
}
}
}
Main.java:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("test.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
test.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
<center>
<TextArea fx:id="console"/>
</center>
<bottom>
<Button onAction="#button" text="Output">
<BorderPane.alignment>CENTER</BorderPane.alignment>
<BorderPane.margin><Insets top="5" left="5" right="5" bottom="5"/></BorderPane.margin>
</Button>
</bottom>
</BorderPane>
编辑 4
我创建了一个简单的示例,应该可以让您了解当前正在发生的事情。
现在发生的事情是,每当我单击按钮将 "HELLO WORLD" 打印到 TextArea 时,程序将挂起并使用 100% 的 CPU。 Eclipse 控制台面板中也没有输出。
Main.java
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/application/test.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
MainController.java
public class MainController {
@FXML
private TextArea console;
private PrintStream ps = new PrintStream(new Console(console));
public void button(ActionEvent event) {
System.setOut(ps);
System.setErr(ps);
System.out.println("Hello World");
}
public class Console extends OutputStream {
private TextArea console;
public Console(TextArea console) {
this.console = console;
}
public void appendText(String valueOf) {
Platform.runLater(() -> console.appendText(valueOf));
}
public void write(int b) throws IOException {
appendText(String.valueOf((char)b));
}
}
}
编辑 2:看来我的问题太长而且难以理解。我正在重组这个。
编辑 3
我想我应该在这里展示所有内容。我想要做的是为 CLI 应用程序创建一个简单的 GUI 前端。我是学CS的,Java是我们的主要语言,所以主要是为了练习。
我到处找了好几个小时,但仍然没有解决办法。我试过像以前使用 Swing 那样做同样的事情。该方法适用于 Swing,但不适用于 JavaFX。
这是我的(当前)logger.java Class:
package application;
import java.io.*;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
public class ytdlLogger extends OutputStream implements Initializable
{
private TextArea loggerPane;
public ytdlLogger(TextArea loggerPane) {
this.loggerPane = loggerPane;
}
public void appendText(String valueOf) {
Platform.runLater(() -> loggerPane.appendText(valueOf));
}
@Override
public void initialize(URL location, ResourceBundle resources) {
OutputStream out = new OutputStream() {
@Override
public void write(int b) throws IOException {
appendText(String.valueOf((char)b));
}
};
System.setOut(new PrintStream(out, true));
System.setErr(new PrintStream(out, true));
}
@Override
public void write(int b) throws IOException {
// TODO Auto-generated method stub
}
}
我认为这没有任何实际问题。我还确实添加了 PrintStream 对象以将 MainController class 中的 System.setOut 和 System.setErr 重定向到 TextArea,但它也不起作用。
我还有另一个 Main class,这是加载 FXML 的主要内容。我尝试从那里重定向输出,它几乎成功了。差不多了,因为我不再看到 Eclipse 中的控制台输出,我知道这是一个很大的进步。
那么,这里的问题似乎是什么?是因为 FXML 吗?我是 Java 和 JavaFX 的绝对初学者,这是我的第一个 JavaFX 应用程序。非常感谢任何指导。提前谢谢你。
编辑 1
这是主要内容 class:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/application/Main.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
您没有将控制器与 FXMLLoader
一起使用。否则你会得到一个异常,因为 class 没有默认构造函数。
如果要使用 FXMLLoader
创建 ytdlLogger
,请将属性 fx:controller="application.ytdlLogger"
(其中 fx
是 fxml 命名空间前缀)添加到根元素你的 fxml 文件。
如果你想这样做,你还需要改变一些东西:
ytdlLogger
需要默认构造函数(即删除构造函数或创建一个不带参数的新构造函数)。将
@FXML
注释添加到您的loggerPane
字段以允许FXMLLoader
访问该字段以将TextArea
分配给fx:id="loggerPane"
属性给它。- 最好从控制器中移除底座 class
OutputStream
,因为您不用它。 - 添加一些打印到
System.out
或System.err
的代码。否则,没有任何内容写入您的TextArea
也就不足为奇了。确保在控制器初始化后执行此操作。
更改后您的控制器应如下所示:
public class ytdlLogger implements Initializable
{
@FXML
private TextArea loggerPane;
public void appendText(String valueOf) {
Platform.runLater(() -> loggerPane.appendText(valueOf));
}
@Override
public void initialize(URL location, ResourceBundle resources) {
OutputStream out = new OutputStream() {
@Override
public void write(int b) throws IOException {
appendText(String.valueOf((char)b));
}
};
System.setOut(new PrintStream(out, true));
System.setErr(new PrintStream(out, true));
}
}
fxml 应该类似于此
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8"
fx:controller="application.ytdlLogger"> <!-- controller goes here -->
<children>
<TextArea fx:id="loggerPane" /> <!-- the TextArea you want to use for logging -->
</children>
</AnchorPane>
您正在用 console
的值初始化 ps
,然后它被 FXMLLoader
初始化。即你有
@FXML
private TextArea console;
private PrintStream ps = new PrintStream(new Console(console));
当你将它传递给 new Console(...)
时,显然 console
仍然是 null
。
您需要在 FXMLLoader
初始化注入字段后初始化 ps
,您可以使用 initialize
方法完成此操作。
SSCCE:
MainController.java:
package application;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
public class MainController {
@FXML
private TextArea console;
private PrintStream ps ;
public void initialize() {
ps = new PrintStream(new Console(console)) ;
}
public void button(ActionEvent event) {
System.setOut(ps);
System.setErr(ps);
System.out.println("Hello World");
}
public class Console extends OutputStream {
private TextArea console;
public Console(TextArea console) {
this.console = console;
}
public void appendText(String valueOf) {
Platform.runLater(() -> console.appendText(valueOf));
}
public void write(int b) throws IOException {
appendText(String.valueOf((char)b));
}
}
}
Main.java:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("test.fxml"));
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
test.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
<center>
<TextArea fx:id="console"/>
</center>
<bottom>
<Button onAction="#button" text="Output">
<BorderPane.alignment>CENTER</BorderPane.alignment>
<BorderPane.margin><Insets top="5" left="5" right="5" bottom="5"/></BorderPane.margin>
</Button>
</bottom>
</BorderPane>