1

So this was pretty confusing for me.I decided to redirect application console, into TextArea in my UI.

Bud when i did that with Fixed TextArea(Fixed ID) in SceneBuilder and then annotated

@FXML
private TextArea consoleTextArea;

Nothing happen.No change to content.And yes i did initialized it in constructor.And further in initialize. This is not working code:

public class ConsoleController implements Initializable {

    Thread t;
    @FXML
    private Label totalM;
    @FXML
    private Label freeM;
    @FXML
    private Label maxM;
    @FXML
    private TextArea consoleTextArea;

    private Console console;
    private PrintStream ps;


    public ConsoleController() {
        System.out.println("Called constructor");
        totalM = new Label();
        freeM = new Label();
        maxM = new Label();
        consoleTextArea = new TextArea();
        console = new Console(consoleTextArea);
        ps = new PrintStream(console, true);

    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        redirectOutput(ps);


        t = new Thread(() -> {
            while (true) {
                try {
                    Platform.runLater(() -> {
                        updateMemInfo();
                    });
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }

        });
        t.setPriority(Thread.MIN_PRIORITY);
        t.setName("MemUpdateInfoThread");
        t.setDaemon(true);
        t.start();

    }

    private void updateMemInfo() {
        totalM.setText("Total Memory (in bytes): " + Runtime.getRuntime().totalMemory());
        freeM.setText("Free Memory (in bytes): " + Runtime.getRuntime().freeMemory());
        maxM.setText("Max Memory (in bytes): " + Runtime.getRuntime().maxMemory());
    }

    private void redirectOutput(PrintStream prs) {
        System.setOut(prs);
        System.setErr(prs);
    }

    private void updateConsole(String text) {
        for (int c : text.toCharArray()) {
            try {
                console.write(c);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    private class Console extends OutputStream {

        private TextArea txtArea;

        public Console(TextArea txtArea) {
            this.txtArea = txtArea;
        }

        @Override
        public void write(int b) throws IOException {
            txtArea.appendText(String.valueOf((char) b));
        }

    }

}

Bud after some edditing.I decided to not use fxml id.And only put id on AnchorPane parent,and added textArea in java code.

  @FXML
  private AnchorPane anchp;

  private TextArea consoleTextArea;

  //then added to anchor
  anchp.getChildren().add(consoleTextArea);

working code:

public class ConsoleController implements Initializable {

    Thread t;
    @FXML
    private Label totalM;
    @FXML
    private Label freeM;
    @FXML
    private Label maxM;
    @FXML
    private AnchorPane anchp;

    private TextArea consoleTextArea;

    private Console console;
    private PrintStream ps;

    public ConsoleController() {
        System.out.println("Called constructor");
        totalM = new Label();
        freeM = new Label();
        maxM = new Label();
        anchp=new AnchorPane();
        consoleTextArea = new TextArea();
        console = new Console(consoleTextArea);
        ps = new PrintStream(console, true);

    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        anchp.getChildren().add(consoleTextArea);
        AnchorPane.setTopAnchor(consoleTextArea, 0d);
        AnchorPane.setLeftAnchor(consoleTextArea, 0d);
        AnchorPane.setRightAnchor(consoleTextArea, 0d);
        AnchorPane.setBottomAnchor(consoleTextArea, 0d);

        redirectOutput(ps);

        t = new Thread(() -> {
            while (true) {
                try {
                    Platform.runLater(() -> {
                        updateMemInfo();
                    });
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }

        });
        t.setPriority(Thread.MIN_PRIORITY);
        t.setName("MemUpdateInfoThread");
        t.setDaemon(true);
        t.start();

    }

    private void updateMemInfo() {
        totalM.setText("Total Memory (in bytes): " + Runtime.getRuntime().totalMemory());
        freeM.setText("Free Memory (in bytes): " + Runtime.getRuntime().freeMemory());
        maxM.setText("Max Memory (in bytes): " + Runtime.getRuntime().maxMemory());
    }

    private void redirectOutput(PrintStream prs) {
        System.setOut(prs);
        System.setErr(prs);
    }

    private void updateConsole(String text) {
        for (int c : text.toCharArray()) {
            try {
                console.write(c);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    private class Console extends OutputStream {

        private TextArea txtArea;

        public Console(TextArea txtArea) {
            this.txtArea = txtArea;
        }

        @Override
        public void write(int b) throws IOException {
            txtArea.appendText(String.valueOf((char) b));
        }

    }

}

Why i wasnt able to do this with fixedID on component that i was using?Can anyone explain on what i did wrong?

Tomas Bisciak
  • 2,801
  • 5
  • 33
  • 57

1 Answers1

2

And yes i did initialized it in constructor.

This is exactly the problem. Never initialize fields that are injected into the controller with an @FXML annotation.

If you annotate a field with @FXML, the FXMLLoader is going to initialize that field with the instance declared in the FXML file, matching the field name ("consoleTextArea") to the fx:id attribute. Obviously, all this happens after the constructor is completed, but before the initialize() method is called. Hence the consoleTextArea you passed to your Console constructor is a different instance to the one that you end up with by the time the initalize() method is invoked (and when event handlers are invoked later).

To fix this, get rid of the constructor entirely, and initialize the other pieces you need (i.e. things that aren't defined in your FXML) in the initialize() method.

Something like:

public class ConsoleController implements Initializable {

    Thread t;
    @FXML
    private Label totalM;
    @FXML
    private Label freeM;
    @FXML
    private Label maxM;
    @FXML
    private TextArea consoleTextArea;

    private Console console;
    private PrintStream ps;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        console = new Console(consoleTextArea);
        ps = new PrintStream(console, true);

        redirectOutput(ps);


        t = new Thread(() -> {
            while (true) {
                try {
                    Platform.runLater(() -> {
                        updateMemInfo();
                    });
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }

        });
        t.setPriority(Thread.MIN_PRIORITY);
        t.setName("MemUpdateInfoThread");
        t.setDaemon(true);
        t.start();

    }

    // other methods as before...
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • I always tought that i need to still initialize them when i want to use them/modify further in my code(Avoiding nullPointer ex).This explains it quite nicely.Thanks for clarifying,will refactor some of my code. – Tomas Bisciak Jul 23 '14 at 16:21