JavaFX 重定向 System.out 流 TextArea,仅在不用作具有固定 ID 的 FXML 组件时才工作。为什么?

标签 java stream javafx fxml scenebuilder

所以这对我来说非常困惑。我决定将应用程序控制台重定向到 UI 中的 TextArea。

当我在 SceneBuilder 中使用固定文本区域(固定 ID)执行此操作,然后进行注释时

@FXML
private TextArea consoleTextArea;

什么也没发生。内容没有改变。是的,我确实在构造函数中初始化了它。并进一步初始化。 这不是工作代码:

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

    }

}

经过一番编辑后,我决定不使用fxml id。只将id放在AnchorPane父级上,并在java代码中添加textArea。

  @FXML
  private AnchorPane anchp;

  private TextArea consoleTextArea;

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

工作代码:

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

    }

}

为什么我无法在我正在使用的组件上使用固定ID来执行此操作?任何人都可以解释我做错了什么吗?

最佳答案

And yes i did initialized it in constructor.

这正是问题所在。切勿使用 @FXML 注释初始化注入(inject) Controller 的字段。

如果您使用 @FXML 注释字段,FXMLLoader 将使用 FXML 文件中声明的实例来初始化该字段,并与字段名称(“consoleTextArea”匹配) ") 到 fx:id 属性。显然,这一切都发生在构造函数完成之后,但在调用 initialize() 方法之前。因此,您传递给 Console 构造函数的 consoleTextArea 与您在调用 initalize() 方法时最终得到的实例是不同的实例被调用(以及稍后调用事件处理程序时)。

要解决此问题,请完全删除构造函数,并在 initialize() 方法中初始化您需要的其他部分(即 FXML 中未定义的内容)。

类似于:

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...
}

关于JavaFX 重定向 System.out 流 TextArea,仅在不用作具有固定 ID 的 FXML 组件时才工作。为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24915391/

相关文章:

java - JSP 中的通用登录授权

java - 将文件名分成组

java - 更新组合框模型 - 项目监听器不再触发

python - 在 Python 中使用音频流 RTMP 通过管道和 OpenCV 到 FFmpeg

java - JavaFX 中的 "automatic injection of location and resources properties into the controller"是什么?

maven - com.sun.javafx.fxml.FXMLLoaderHelper 类无法访问 com.sun.javafx.util.Utils 类

java - Struts + JSP中为下拉创建Mysql SELECT语句的逻辑是什么?

node.js - NodeJS - 为什么在 .on ('readable' ) 事件中从可读流中读取数据时使用 while 循环

asp.net - 使用 SharpZipLib 在 .net 中通过 http 流式传输 zip 文件

java - 如何从另一个 java 类(意味着在 Controller 之外)更改 .fxml 文件的标签文本?