Issue
In thread I run some code and I need update javafx ui elements that are in JsonOverviewController. I don't want to pass ui elements in model or class controller. I don't want this
jfWatch = new WatchController();
jfWatch.setPathDirectory(absPathSelDir);
jfWatch.setMyJsonFilesTable(myJsonFilesTable);
jfWatch.setMyIdTableColumn(myIdTableColumn);
...
jfWatch.setMyStateLabel(myStateLabel);
jfWatch.start();
The code below show that Label.TextField are pass to thread and in run method update them.
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));
}
}
}
On the contrary I want that my thread notify to controller that has occurred and send to controller some data of model. Thus in controller class based on data model a set Label,Textfield correctly.
Solution
In the standard MVC architecture, your model should contain a list of listeners, and should notify those listeners of events as needed. Your controller (which in JavaFX with FXML is more like a MVP presenter) should be a listener and should register itself with the model.
So:
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));
});
}
}
}
Then in your controller:
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.");
}
}
A better approach than using a plain thread here may be to use the JavaFX concurrent API and implement WatchController
as a Task
. This has support for executing code for when the task is started, completes, etc., on the FX Application Thread (it basically handles all the Platform.runLater()
calls for you as the state changes). This would look something like:
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);
}
}
}
and then in the controller:
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.");
}
}
You probably need to do a little more work in the Task
implementation to fully support cancelling the task in an appropriate way. See the Task
documentation for more details.
Answered By - James_D
Answer Checked By - Willingham (JavaFixing Volunteer)