线程中的 Javafx 将事件通知控制器 class 以根据模型设置标签、文本字段

Javafx from a thread notify events to controller class for setting Label,Textfield based on model

在线程中,我 运行 一些代码,我需要更新 JsonOverviewController 中的 javafx ui 元素。 我不想在模型或 class 控制器中传递 ui 元素。 我不要这个

        jfWatch = new WatchController();
        jfWatch.setPathDirectory(absPathSelDir);
        jfWatch.setMyJsonFilesTable(myJsonFilesTable);
        jfWatch.setMyIdTableColumn(myIdTableColumn);
        ...
        
        jfWatch.setMyStateLabel(myStateLabel);
        
        jfWatch.start();

下面的代码显示 Label.TextField 被传递给线程并在 运行 方法中更新它们。

package it.einaudi.storejson;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;

import com.fasterxml.jackson.core.JsonParseException;

import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import it.einaudi.storejson.model.ConnectionConfig;
import it.einaudi.storejson.model.JsonFile;
import it.einaudi.storejson.model.JsonFileManager;

public class JsonOverviewController {
    
    @FXML
    private Button myButton;
    
    @FXML
    private Label myDirWatch;
    
    @FXML
    private Label myCurrentJsonFileId;
    
    @FXML
    private Label myCurrentJsonFileName;
    
    @FXML
    private TextArea myCurrentJsonFileContent;
    
    @FXML
    private Button myImportButton;
    
    @FXML
    private TableView<JsonFile> myJsonFilesTable;
    
    @FXML
    private TableColumn<JsonFile, String> myIdTableColumn;
    
    @FXML
    private TableColumn<JsonFile, String> myNameFileTableColumn;
    
    @FXML
    private TableColumn<JsonFile, String> myStateTableColumn;
    
    @FXML
    private TextField myConnFullurlField;
    
    @FXML
    private TextField myConnUsernameField;
    
    @FXML
    private TextField myConnPasswordField;
    
    @FXML
    private TextField myConnNamedbField;
    
    @FXML
    private Label myStateLabel;
    
    //private JsonWatchService threadJsonWatch;
    private WatchController jfWatch;
    
    private JsonFileManager jfManager;
    
    /**
     * The constructor (is called before the initialize()-method).
     */
    public JsonOverviewController() {       
        //threadJsonWatch = null;
        jfManager = null;
        jfWatch = null;
    }
    
    
    @FXML
    private void handleButtonAction(ActionEvent event) {
        // Button was clicked, do something...
        System.out.println("myButton premuto");
        DirectoryChooser directoryChooser = new DirectoryChooser();
        Node node = (Node) event.getSource();
        File selectedDirectory;
        Stage thisStage = (Stage) node.getScene().getWindow();
        String absPathSelDir;
        
        selectedDirectory = directoryChooser.showDialog(thisStage);
        if(selectedDirectory == null ) return;
        
        absPathSelDir = selectedDirectory.getAbsolutePath();
        
        myDirWatch.setText(absPathSelDir);
            
        if(jfWatch != null) jfWatch.interrupt();
        
        jfWatch = new WatchController();
        jfWatch.setPathDirectory(absPathSelDir);
        jfWatch.setMyJsonFilesTable(myJsonFilesTable);
        jfWatch.setMyIdTableColumn(myIdTableColumn);
        jfWatch.setMyNameFileTableColumn(myNameFileTableColumn);
        jfWatch.setMyStateTableColumn(myStateTableColumn);
        
        jfWatch.setMyCurrentJsonFileId(myCurrentJsonFileId);
        jfWatch.setMyCurrentJsonFileName(myCurrentJsonFileName);
        jfWatch.setMyCurrentJsonFileContent(myCurrentJsonFileContent);
        
        jfWatch.setMyStateLabel(myStateLabel);
        
        jfWatch.start();
        
        myStateLabel.setText("La cartella " + 
                             absPathSelDir + 
                             " è monitorata.");    
    }
    
    /**
     * Initializes the controller class. This method is automatically called
     * after the fxml file has been loaded.
     */
    
    /**
     * Initializes the controller class. This method is automatically called
     * after the fxml file has been loaded.
     */
    @FXML
    private void initialize() {
        // Handle Button event.
        myButton.setOnAction(this::handleButtonAction);
        
        myIdTableColumn.setCellValueFactory(new PropertyValueFactory<JsonFile,String>("idFile"));
        myNameFileTableColumn.setCellValueFactory(new PropertyValueFactory<JsonFile,String>("nameFile"));
        myStateTableColumn.setCellValueFactory(new PropertyValueFactory<JsonFile,String>("stateFile"));
        
    }
    
}

public class WatchController extends Thread {
    
    private String pathDirectory;
    
    private Label myCurrentJsonFileId;
    
    private Label myCurrentJsonFileName;
    
    private TextArea myCurrentJsonFileContent; 
    
    private TableView<JsonFile> myJsonFilesTable;
    
    private TableColumn<JsonFile, String> myIdTableColumn;
    
    private TableColumn<JsonFile, String> myNameFileTableColumn;
    
    private TableColumn<JsonFile, String> myStateTableColumn;
    
    private Label myStateLabel;

    public void run() {
        if(!found) {
            Platform.runLater(() -> myCurrentJsonFileId.setText(""));
            Platform.runLater(() -> myCurrentJsonFileName.setText(""));
            Platform.runLater(() -> myCurrentJsonFileContent.setText("Anteprima file json non disponibile."));  
        }else {
            final JsonFile firstJCopy = firstJ;
            Platform.runLater(() -> myCurrentJsonFileId.setText(firstJCopy.getIdFile()));
            Platform.runLater(() -> myCurrentJsonFileName.setText(firstJCopy.getNameFile()));
            
            fullPathFile = pathDirectory + "\" + firstJ.getNameFile();
            System.out.println(fullPathFile);
            strJ = readFileAsList(fullPathFile);
            String strJCopy = strJ;
            Platform.runLater(() -> myCurrentJsonFileContent.setText(strJCopy));
        }
    }
}

相反,我希望我的线程通知已经发生的控制器并向控制器发送模型的一些数据。因此在基于数据模型的控制器class中正确设置了Label,Textfield。

在标准 MVC 架构中,您的模型应该包含一个侦听器列表,并且应该根据需要通知这些侦听器事件。您的控制器(在带有 FXML 的 JavaFX 中更像是一个 MVP 演示器)应该是一个侦听器,并且应该在模型中注册自己。

所以:

public interface WatchListener {
    public void startedProcessing();
    public void fileProcessed(String data);
    public void notFound();
}
public class WatchController extends Thread {
    
    private String pathDirectory;
    
    private final List<WatchListener> listeners = new ArrayList<>();

    public void addListener(WatchListener listener) {
        listeners.add(listener);
    }

    public void removeListener(WatchListener listener) {
        listeners.remove(listener);
    }

    public void run() {
        if(!found) {
            Platform.runLater(() -> {
                listeners.forEach(WatchListener::notFound);
            }); 
        } else {
            final JsonFile firstJCopy = firstJ;
            Platform.runLater(() -> {
                listeners.forEach(WatchListener::startedProcessing);
            });
            
            fullPathFile = pathDirectory + "\" + firstJ.getNameFile();
            System.out.println(fullPathFile);
            strJ = readFileAsList(fullPathFile);
            String strJCopy = strJ;
            Platform.runLater(() -> {
                listeners.forEach(listener -> listener.fileProcessed(strJCopy));
            });
        }
    }
}

然后在你的控制器中:

public class JsonOverviewController implements WatchListener {
    
    // fields and initialize method as before
    
    
    @FXML
    private void handleButtonAction(ActionEvent event) {
        // Button was clicked, do something...
        System.out.println("myButton premuto");
        DirectoryChooser directoryChooser = new DirectoryChooser();
        Node node = (Node) event.getSource();
        File selectedDirectory;
        Stage thisStage = (Stage) node.getScene().getWindow();
        String absPathSelDir;
        
        selectedDirectory = directoryChooser.showDialog(thisStage);
        if(selectedDirectory == null ) return;
        
        absPathSelDir = selectedDirectory.getAbsolutePath();
        
        myDirWatch.setText(absPathSelDir);
            
        if(jfWatch != null) jfWatch.interrupt();
        
        jfWatch = new WatchController();
        watchController.addListener(this);
        jfWatch.setPathDirectory(absPathSelDir);
        
        jfWatch.start();
        
        myStateLabel.setText("La cartella " + 
                             absPathSelDir + 
                             " è monitorata.");    
    }
    
    @Override
    public void startedProcessing() {
        myCurrentJsonFileId.setText("");
        myCurrentJsonFileName.setText("");
        myCurrentJsonFileContent.setText("Anteprima file json non disponibile.");
    }

    @Override
    public void fileProcessed(String data) {
        myCurrentJsonFileContent.setText(data);
    }

    @Override
    public void notFound() {
        myCurrentJsonFileId.setText("");
        myCurrentJsonFileName.setText("");
        myCurrentJsonFileContent.setText("Anteprima file json non disponibile.");  
    }
}

比这里使用普通线程更好的方法可能是使用 JavaFX 并发 API 并将 WatchController 实现为 Task。这支持在 FX 应用程序线程上执行任务开始、完成等时间的代码(它基本上会在状态更改时为您处理所有 Platform.runLater() 调用)。这看起来像:

public class WatchController extends Task<String> {

    private final String pathDirectory;

    public WatchController(String pathDirectory) {
        this.pathDirectory = pathDirectory ;
    }

    @Override
    public String call() throws Exception {
        if(!found) {
            throw new FileNotFoundException(pathDirectory + " not found");
        } else {
            final JsonFile firstJCopy = firstJ;
            fullPathFile = pathDirectory + "\" + firstJ.getNameFile();
            System.out.println(fullPathFile);
            return readFileAsList(fullPathFile);
        }
    }
}

然后在控制器中:

public class JsonOverviewController implements WatchListener {

    // fields and initialize method as before

    private final Executor exec = Executors.newCachedThreadPool();


    @FXML
    private void handleButtonAction(ActionEvent event) {
        // Button was clicked, do something...
        System.out.println("myButton premuto");
        DirectoryChooser directoryChooser = new DirectoryChooser();

        File selectedDirectory = directoryChooser.showDialog(myButton.getScene().getWindow());
        if(selectedDirectory == null ) return;

        String absPathSelDir = selectedDirectory.getAbsolutePath();

        myDirWatch.setText(absPathSelDir);

        if(jfWatch != null) jfWatch.cancel();

        jfWatch = new WatchController(absPathSelDir);
        watchController.setOnRunning(evt -> startedProcessing());
        watchController.setOnFailed(evt -> notFound());
        watchController.setOnSucceeded(evt -> fileProcessed(jfWatch.getValue());

        exec.execute(jfWatch);

        myStateLabel.setText("La cartella " + 
                             absPathSelDir + 
                             " è monitorata.");    
    }

    @Override
    public void startedProcessing() {
        myCurrentJsonFileId.setText("");
        myCurrentJsonFileName.setText("");
        myCurrentJsonFileContent.setText("Anteprima file json non disponibile.");
    }

    @Override
    public void fileProcessed(String data) {
        myCurrentJsonFileContent.setText(data);
    }

    @Override
    public void notFound() {
        myCurrentJsonFileId.setText("");
        myCurrentJsonFileName.setText("");
        myCurrentJsonFileContent.setText("Anteprima file json non disponibile.");  
    }
}

您可能需要在 Task 实现中做更多的工作,以完全支持以适当的方式取消任务。有关详细信息,请参阅 Task documentation