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

Posted

技术标签:

【中文标题】JavaFX:将控制台输出重定向到在 SceneBuilder 中创建的 TextArea【英文标题】:JavaFX: Redirect console output to TextArea that is created in SceneBuilder 【发布时间】:2016-02-03 07:21:13 【问题描述】:

编辑 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);
    

【问题讨论】:

有例外吗? 如果你的文本区域是在场景构建器中创建的,它应该有一个 `@FXML' 注释。否则它会将其视为不同的对象。 你能显示你的Main类的代码吗?问题不就是你没有在logger中实现write(...)方法吗? @ItachiUchiha 不,没有。 @HARSHITASETHI 它确实有一个 @FXML 注释,但在 MainController 类中。由于 SceneBuilder 只允许一个 Controller 类,所以我必须在那里定义它。 【参考方案1】:

FXMLLoader 初始化之前,您正在使用console 的值初始化ps。即你有

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

当您将console 传递给new Console(...) 时,显然null 仍然是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>

【讨论】:

非常感谢。这工作得很好。它也适用于我的程序。但是我如何才能让System.setOut 从一开始就设置,而不仅仅是在按下按钮时设置?假设我有几个按钮,我是否必须在每个方法中使用System.setOut 如果您努力停下来思考为什么我的解决方案有效,以及为什么您的原始代码无效,您就不需要问这个问题。停止复制代码并开始尝试了解它在做什么。 不,它工作得很好,我在实施它时没有遇到任何麻烦。我正在尝试记录实现的每一个方法和代码,以便我理解它的作用,这一切都符合我自己的理解。 好吧,我明白你的意思。我偏离了轨道,并没有真正做点什么,我现在只是运行人们的代码,看看它是否像我想要的那样工作。不过,感谢您的建议,再次感谢您的回答。 这是否为它附加到 TextArea 的每个字符创建一个单独的 Runnable【参考方案2】:

您不使用带有FXMLLoader 的控制器。否则你会得到一个异常,因为这个类没有默认构造函数。

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

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

ytdlLogger 需要一个默认构造函数(即,要么删除你的构造函数,要么创建一个不带参数的新构造函数)。

@FXML 注释添加到您的loggerPane 字段,以允许FXMLLoader 访问该字段以将TextAreafx:id="loggerPane" 属性分配给它。

最好从控制器中删除基类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>

【讨论】:

我没有在控制器中使用FXMLLoader,而是在加载FXML的Main类中使用。控制器属于不同的类,MainController。在 MainController 内部是 FXML 为 TextArea 和其他的 fx:id 找到的位置。 我尝试在Main.java 类中添加System.setOutSystem.setErr,甚至在按钮执行实际工作之前,它会加载进程。 Eclipse 中的控制台变为空白,程序将挂起并耗尽我的资源,而不是什么都不做。 我想我的问题还不够清楚。我现在已经包含了所有内容,所以你可以得到我想要做的事情。对不起。

以上是关于JavaFX:将控制台输出重定向到在 SceneBuilder 中创建的 TextArea的主要内容,如果未能解决你的问题,请参考以下文章

尝试将 url 重定向到在 localhost 上运行的 iTunes 时的 Safari 警报

将 SLF4J 日志重定向到 JavaFX 中的 TextArea

如何将多个输入从文件重定向到在gdb中调试的二进制文件?

将输出重定向到文件,然后返回到 Java 中的控制台

将跟踪输出重定向到控制台

我们如何将 Java 程序控制台输出重定向到多个文件?