繁体   English   中英

JavaFX:从第二类访问FXML元素

[英]JavaFX: Accessing FXML elements from a second class

我正在编写一个试图执行以下操作的程序:

  1. 取两个文本文件并将其内容写入两个单独的TextAreas
  2. 使用多线程(特别是Runnable接口)同时写入这些单独的区域

我创建了一个“ MyRunnable”类:

public class MyRunnable implements Runnable {

    @FXML
    private TextArea textField;

    public MyRunnable() throws IOException {
    }

    public void run() {

        String firstFileName = "test.txt";
        File inFile = new File(firstFileName);
        Scanner in = null;
        try {
            in = new Scanner(inFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        while (in.hasNextLine()) {
            textField.appendText("Hello");
            textField.appendText("\n");
        }
    }
}

我的控制器类只有一种方法

public void Main() throws IOException {
    Runnable r = new MyRunnable();
    Thread t = new Thread(r);
    t.start();
}

IntelliJ告诉我从来没有分配textField,并且当我运行主程序并单击调用Main()的按钮时,在下一行得到一个空指针异常。

textField.appendText("Hello");

如何完成我想完成的工作?

您能否将textField移动到控制器类并将其作为参数传递给可运行对象?

您的代码有两个问题。 首先,您已经基本观察到的一个:

注释字段@FXML被注入FXMLLoader到当控制器FXMLLoader加载FXML文件。 显然, FXMLLoader不了解任何其他对象,因此仅使用@FXML注释任意对象中的字段并不意味着它已初始化。

其次,您的runnable的run()方法在后台线程上执行。 对UI的更改必须在FX Application线程上进行(请参阅“线程”部分) 因此,即使已初始化textField ,也不能保证您对textField.appendText(...)的调用行为正确。

最后,更普遍地说,您的设计违反了“关注点分离”。 您可运行的实现实际上只是从文件中读取一些文本。 它不应该关心该文本发生了什么,当然也不应该对UI有所了解。 (简单地说,将UI元素暴露在控制器外部始终是一个错误的设计决定。)

这里最好的方法是给可运行的实现一个“回调”:即一个“用字符串做某事”的对象。 您可以将其表示为Consumer<String> 所以:

import java.util.Scanner ;
import java.util.function.Consumer ;
import java.io.File ;
import java.io.FileNotFoundException ;

public class MyRunnable implements Runnable {

    private Consumer<String> textProcessor;

    public MyRunnable(Consumer<String> textProcessor)  {
        this.textProcessor = textProcessor ;
    }

    public void run() {

        String firstFileName = "test.txt";
        File inFile = new File(firstFileName);
        Scanner in = null;
        try {
            in = new Scanner(inFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        while (in.hasNextLine()) {
            textProcessor.accept(in.nextLine());
        }
    }
}

然后,在您的控制器中,您可以执行以下操作:

@FXML
private TextArea textField ;

public void Main() throws IOException {
    Runnable r = new MyRunnable(s -> 
        Platform.runLater(() -> textField.appendText(s+"\n")));
    Thread t = new Thread(r);
    t.start();
}

注意使用Platform.runLater(...) ,它会更新FX Application线程上的文本区域。

现在,根据您阅读文本行的速度,此方法可能会使FX Application Thread的更新过多,从而使UI无响应。 (如果只是从本地文件读取,肯定会是这种情况。)有两种方法可以解决此问题。 一种是简单地将所有数据读入字符串列表,然后在读取整个列表时对其进行处理。 为此,您可以使用Task代替普通的runnable:

public class ReadFileTask extends Task<List<String>> {

    @Override
    protected List<String> call {

        List<String> text = new ArrayList<>();

        String firstFileName = "test.txt";
        File inFile = new File(firstFileName);
        Scanner in = null;
        try {
            in = new Scanner(inFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        while (in.hasNextLine()) {
            text.add(in.nextLine());
        }       

        return text ;
    }
}

现在,在您的控制器中,您可以将其用于:

@FXML
private TextArea textField ;

public void Main() throws IOException {


    Task<List<String>> r = new ReadFileTask();

    // when task finishes, update text area:
    r.setOnSucceeded(e -> {
        textArea.appendText(String.join("\n", r.getValue()));
    }
    Thread t = new Thread(r);
    t.start();
}

如果您真的想在阅读文本时不断更新文本区域,那么事情会变得有些复杂。 您需要将字符串放入来自后台线程的某种缓冲区中,然后以不会淹没FX Application Thread的方式将它们读入文本区域。 您可以将BlockingQueue<String>用于缓冲区,然后在AnimationTimer读回。 动画计时器每当将一帧渲染到屏幕时都执行一次handle()方法(因此,它不会运行得太频繁,这与之前的Platform.runLater()方法不同):基本策略是抓取尽可能多的内容。每次运行时都可以从缓冲区中获取,并更新文本区域。 重要的是要在完成动画时停止动画计时器,我们可以通过对从文件中读取的行进行计数来停止动画计时器,并在将它们全部放入文本区域后停止动画计时器。

看起来像这样:

public class BackgroundFileReader extends Runnable {

    public static final int UNKNOWN = -1 ;

    private final AtomicInteger lineCount = new AtomicInteger(UNKNOWN);
    private final BlockingQueue<String> buffer = new ArrayBlockingQueue<>(1024);

    @Override
    public void run() {
        String firstFileName = "test.txt";
        File inFile = new File(firstFileName);
        Scanner in = null;
        try {
            in = new Scanner(inFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        int count = 0 ;
        try {
            while (in.hasNextLine()) {
                buffer.put(in.nextLine());
                count++ ;
            }
        } catch (InterruptedException exc) {
            Thread.currentThread.interrupt();
        }
        lineCount.set(count);
    }

    // safe to call from any thread:
    public int getTotalLineCount() {
        return lineCount.get();
    }

    public int emptyBufferTo(List<String> target) {
        return buffer.drainTo(target);
    }
}

然后在控制器中,您可以执行

@FXML
private TextArea textField ;

public void Main() throws IOException {

    ReadFileTask r = new ReadFileTask();

    // Read as many lines as possible from the buffer in each
    // frame, updating the text area:

    AnimationTimer updater = new AnimationTimer() {
        private int linesRead = 0 ;
        @Override
        public void handle(long timestamp) {
            List<String> temp = new ArrayList<>();
            linesRead = linesRead + r.emptyBufferTo(temp);
            if (! temp.isEmpty()) {
                textField.appendText(String.join("\n", temp));
            }
            int totalLines = r.getTotalLineCount() ;
            if (totalLines != BackgroundFileReader.UNKNOWN && linesRead >= totalLines) {
                stop();
            }
        }
    };
    updater.start();

    Thread t = new Thread(r);
    t.start();
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM