JavaFX:将控制台输出重定向到SceneBuilder中创建的TextArea



编辑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类:

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类中的System.setOut和System.setErr重定向到TextArea,但它也不起作用。

我还有另一个Main类,它是加载FXML的主要内容。我试着重定向那里的输出,它几乎奏效了。差不多了,因为我不再看到Eclipse中的控制台输出,我知道这是一个巨大的进步。

那么,这里的问题是什么呢?是因为FXML吗?我是Java和JavaFX的绝对初学者,这是我的第一个JavaFX应用程序。非常感谢任何指导。提前谢谢。


编辑1

这是主要类别:

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初始化ps之前,您正在使用值console初始化它。I.e你有

@FXML
private TextArea console;
private PrintStream ps = new PrintStream(new Console(console));

很明显,当您将console传递给new 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>

您不能将控制器与FXMLLoader一起使用。否则,您将得到一个异常,因为该类没有默认构造函数。

如果要使用FXMLLoader创建ytdlLogger,请将属性fx:controller="application.ytdlLogger"(其中fx是fxml命名空间前缀)添加到fxml文件的根元素中。

如果你想这样做,你还需要改变一些事情:

  • ytdlLogger需要一个默认的构造函数(即删除您的构造函数或创建一个没有参数的新构造函数)。

  • @FXML注释添加到loggerPane字段,以允许FXMLLoader访问该字段,从而为其分配具有fx:id="loggerPane"属性的TextArea

  • 最好从控制器中删除基类OutputStream,因为您不使用它
  • 添加一些打印到System.outSystem.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>

最新更新