JavaFX: Redirect console output to TextArea that is created in SceneBuilder

為{幸葍}努か 提交于 2021-02-07 19:22:10

问题


EDIT 4

I've created a simple example that should give you an idea about what's happening right now.

What's happening right now is that whenever I click the button to print "HELLO WORLD" to the TextArea, the program will hang and use 100% of the CPU. There's also no output in the Eclipse console panel too.

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));
        }
    }
}

EDIT 2: It seems that my question is way too long and hard to understand. I'm in the middle of restructuring this one.


EDIT 3

I guess I should just show everything here. What I'm trying to do is a simple GUI front-end for a CLI application. I'm a CS student and Java is our main language, so this is mainly for practice.


I've been looking every where for hours and hours but there's still no solution to this. I've tried doing the same like I did previously with Swing. The method worked fine with Swing but not with JavaFX.

Here's my (current) 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

    }
}

I don't think there's any actual problems with this. I also did add the PrintStream object to redirect System.setOut and System.setErr in the MainController class to the TextArea, but it didn't work either.

I also have another Main class, which is the main thing that loads the FXML. I tried redirecting the output from there, it almost worked. Just almost, because i stopped seeing the console outputs inside Eclipse and I knew that was a great progress.

So, what seems to be the problem here? Is it because of the FXML? I'm absolute beginner in Java and also JavaFX, this is my first JavaFX application. Any guidance is very much appreciated. Thank you in advance.


EDIT 1

Here's the Main 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);
    }
}

回答1:


You are initializing ps with the value of console before it has been initialized by the FXMLLoader. I.e you have

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

Clearly console is still null when you pass it to new Console(...).

You need to initialize ps after the FXMLLoader has initialized the injected fields, which you can do using the initialize method.

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>



回答2:


You do not use your controller with an FXMLLoader. Otherwise you'd get an exception, since the class has no default constructor.

If you want to use the FXMLLoader to create your ytdlLogger, add the attribute fx:controller="application.ytdlLogger" (where fx is the fxml namespace prefix) to the root element of your fxml file.

If you want to do this, you also need to change some things:

  • ytdlLogger needs a default constructor (i.e. either remove your constructor or create a new one without arguments).

  • Add the @FXML annotation to your loggerPane field to allow the FXMLLoader to access that field to assign the TextArea with the fx:id="loggerPane" attribute to it.

  • better remove the base class OutputStream from the controller, since you don't use it.
  • Add some code that prints to System.out or System.err. Otherwise it's not surprising nothing is written to your TextArea. Make sure you do this after the controller is initialized.

Your controller should look like this after the changes:

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));
    }

}

And the fxml should look similar to this

<?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>


来源:https://stackoverflow.com/questions/33494052/javafx-redirect-console-output-to-textarea-that-is-created-in-scenebuilder

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!