簡體   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