6

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);
    }
}
Wake Cabbage
  • 100
  • 1
  • 1
  • 9
  • Are there any exceptions ? – ItachiUchiha Nov 03 '15 at 10:45
  • If you text area is created in scene builder, It should have a `@FXML' annotation. Otherwise it will consider it as a different object. – Harshita Sethi Nov 03 '15 at 12:14
  • Can you show the code for your `Main` class? Isn't the problem simply that you haven't implemented the `write(...)` method in the logger? – James_D Nov 03 '15 at 12:15
  • @ItachiUchiha No there weren't any. – Wake Cabbage Nov 03 '15 at 13:01
  • @HARSHITASETHI It does have an `@FXML` annotation, but in the MainController class. Since SceneBuilder only allows for one Controller class, so I had to define it there. – Wake Cabbage Nov 03 '15 at 13:03
  • @James_D I've updated my question. I thought of using the write(...) method as you said, but I didn't have to use it previously with Swing. – Wake Cabbage Nov 03 '15 at 13:13
  • Also, FYI I'm using ProcessBuilder to start the process. – Wake Cabbage Nov 03 '15 at 13:29
  • I guess I don't really understand your code. Why is `ytdLogger` a subclass of `OutputStream`? How does this fit together: is `ytdLogger` the controller class for Main.fxml? – James_D Nov 03 '15 at 13:59
  • @James_D No, it is not. `ytdlLogger` is another class that will do the logging. The controller resides in the `MainController` class. I don't know why it's a subclass of `OutputStream`, that was something I used when I Google'd around for solutions. It worked, but I never really gave a thought about it. Everything from this was actually a result of me trying to implement the same that worked with Swing, but it doesn't work with JavaFX, in this case. – Wake Cabbage Nov 03 '15 at 14:09
  • So is it the controller for *anything*? If not, why does it implement `Initializable`, and where are you invoking the `initialize` method? You need to show enough code for people to see how it fits together. – James_D Nov 03 '15 at 14:12
  • @James_D I guess it's because if I don't extend the OutputStream class, I can't use the `loggerPane` inside `ytdlLogger` class to redirect the output. But the actual controller that the FXML will look for is still the `MainController` class. `ytdlLoader` and `ytdlLogger` are both in two different files, their own `.java` classes. So does `Main` and `MainController`, – Wake Cabbage Nov 03 '15 at 14:18
  • Please create a [MCVE]. Your code examples right now are completely unclear. – James_D Nov 03 '15 at 14:22
  • @James_D Sorry, I just did one. A very simple yet exactly the same problem as I'm having right now with my actual code. – Wake Cabbage Nov 03 '15 at 15:17
  • Updated my answer to include a SSCCE, since you seem to be having trouble implementing it. Just some (unrequested) advice: you seem to be blindly copying code with little or no understanding of how it works. You need to stop doing that. You will not complete your major with this approach. – James_D Nov 03 '15 at 15:45

2 Answers2

6

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>
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thank very much. This worked just fine. It worked absolutely fine with my program too. But how do I make it so that the `System.setOut` is set from the very beginning and not just when the button is pressed? Say if I have a few buttons, do I have to use `System.setOut` in every methods? – Wake Cabbage Nov 03 '15 at 15:47
  • If you make the effort to stop and think about why my solution works, and why your original code did not, you would not need to ask that question. Stop copying code and start trying to understand what it is doing. – James_D Nov 03 '15 at 15:49
  • No, it worked fine and I had no troubles implementing it. I'm trying to document every single methods and codes implemented so that I understand what it does, all to my own understanding. – Wake Cabbage Nov 03 '15 at 16:29
  • Well, I get what you meant though. I got off track and instead of actually doing something, I'm now just running people's code just to see if it works like I wanted. Still, thanks for the advice and thanks again for your answer. – Wake Cabbage Nov 03 '15 at 16:30
  • Does this create a separate Runnable for each character it appends to TextArea – ed22 Aug 24 '17 at 10:08
  • @ed22 Yes. If you a generating a lot of data, you need to coalesce these into a single runnable for some chunk of data. – James_D Aug 24 '17 at 11:59
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>
fabian
  • 80,457
  • 12
  • 86
  • 114
  • I did not use `FXMLLoader` with the controller, but in the `Main` class which loads the FXML. The controller is in a different class, `MainController`. Inside `MainController` is where the FXML finds for the `fx:id` of the TextArea and the others. – Wake Cabbage Nov 03 '15 at 14:05
  • I tried with adding the `System.setOut` and `System.setErr` in the `Main.java` class, or even before the button does it's actual job, where it loads the process. The console in Eclipse becomes blank and the program will hang and use up my resources, instead of just doing nothing. – Wake Cabbage Nov 03 '15 at 14:11
  • I guess my question weren't clear enough. I've included everything now so you can get what I'm trying to do. Sorry. – Wake Cabbage Nov 03 '15 at 14:30