JavaFX Concurrency: How to updateMessage() to the controller with interface? - javafx

Before I learned about running tasks on the background thread I learned how to post messages to my UI via my controller with an Interface. The interface allows me to have a systems messages in any controller. but I cant figure out how to bind it to updateMessage from a task.
My interface:
public interface SystemMessage {
void postMessage(String outText);
}
My controller:
public class MainController implements SystemMessage {
#FXML
public DialogPane systemMessage;
#Override
public void postMessage(String outText) {
systemMessage.setContentText(outText);
}
//***I have tried the following to bind the dialog pane to the updateMessage
systemMessage.contentTextProperty().bind(task.messageProperty());
//***InteliJ tells me that it can not resolve contetTextProperty or task.
}
My Main class with the Task on a new thread.
private SystemMessage mainController;
FXMLLoader loader = new FXMLLoader(getClass().getResource("fxml/entry.fxml"));
Parent root = loader.load();
this.mainController = (SystemMessage) loader.getController();
api = new Api();
machineId = new Identity();
db = new SqLite();
//mainController.postMessage(task.messageProperty()).bind();
Thread th = new Thread(task);
th.setDaemon(true);
th.start();
Task<String> task = new Task<String>() {
#Override protected String call() throws Exception {
checkAsiServer();
updateMessage("API Checked.");
checkMachineId();
updateMessage("Machine Id Checked.");
checkDb();
updateMessage("Server Checked.");
return "done";
}
};
I have tried to bind my DialogPane in the cotroller by:
systemMessage.contentTextProperty().bind(task.messageProperty());
But it can not resolve contentTextProperty and I don't know how to tell the controller about the task object either.

Not sure I understand the problem. Why don't you just expose the property from the controller? You can do
public class MainController implements SystemMessage {
#FXML
private DialogPane systemMessage;
public StringProperty messageProperty() {
return systemMessage.contentTextProperty();
}
// ...
}
and then
FXMLLoader loader = new FXMLLoader(getClass().getResource("fxml/entry.fxml"));
Parent root = loader.load();
MainController mainController = loader.getController();
Task<String> task = new Task<String>() { /* existing code... */};
mainController.messageProperty().bind(task.messageProperty());
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
If you want to keep the type of the controller as SystemMessage and only rely on the postMessage method, then obviously you can't do this with a binding (because you don't have any way to access the property); you would need to use a listener instead:
FXMLLoader loader = new FXMLLoader(getClass().getResource("fxml/entry.fxml"));
Parent root = loader.load();
SystemMessage mainController = loader.getController();
Task<String> task = new Task<String>() { /* existing code... */};
task.messageProperty().addListener((obs, oldMessage, newMessage) ->
mainController.postMessage(newMessage));
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();

Related

JavaFX - parsing child arguments back to parent

I have 2 Controllers and I want to pass values from second controller to first controller, here are code sample:
FXMLController.java
private int paramAnswers;
private int paramNotificationTime;
private int paramNotificationDelay;
#FXML
private void handleMenuItemOptionsAction(ActionEvent event) throws IOException{
Parent root = FXMLLoader.load(getClass().getResource("/fxml/Options.fxml")); // UNDECORATED*
Scene scene = new Scene(root, Color.TRANSPARENT);
final Stage stage = new Stage();
stage.setTitle("Options");
stage.setScene(scene);
stage.show();
}
public void setOptionsParams(int paramAnswers, int paramNotificationTime, int paramNotificationDelay){
this.paramAnswers = paramAnswers;
this.paramNotificationTime = paramNotificationTime;
this.paramNotificationDelay = paramNotificationDelay;
}
and second controller:
OptionsController.java
private FXMLController parentController;
private int paramAnswers;
private int paramNotificationTime;
private int paramNotificationDelay;
#Override
public void initialize(URL location, ResourceBundle resources) {
.... }
#FXML
private void handleButtonSaveAction(ActionEvent event) throws IOException{
/*Pass these parameteres OptionsController parameters back to the FXMLController like parentController.setOptionsParams(paramAnswers, paramNotificationTime, paramNotificationDelay);
*/
(((Button)event.getSource()).getScene().getWindow())).close();
}
Arleady tried with parsing FXMLControler as .this into OptionsController initialize method, tried making listers and bunch of other resolved problems on stackoverflow but it just don't want work :< I need to pass that atributes back to FXMLController and close child window, so my main app would change behavior depending on passed values... :X
For any help I will be grateful
you can have a function in the second controller let's say passParams() set it's parameters what every you want to pass to that controller and from the first controller when you click on a button or something
this line
FXMLLoader.load(getClass().getResource("/fxml/Options.fxml"));
need to be changed to
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Options.fxml"));
Parent root = (Parent) loader.load();
OptionsController controller = loader.getController();
EDIT
you need to pass the first controller to the second controller
controller.setParentController(this); // this is the first controller
in Second controller
FirstController mController;
public void setParams(FirstController controller) {
this.mController = controller;
}
now in the button click function you use the mController you got from the previous step
mController.setOptionsParams(...); //send the params collected from the textfields
this function is implemented in the FirstController
Note: a more general way to do this by using call-backs it's the same but your code depends on interfaces not concrete classes by implementing a general interface in FirstController that have the setOptionParams() method
First you add a callback interface to your parent controller class:
public interface OptionCallback {
public void setOptionsParams(int paramAnswers, int paramNotificationTime, int paramNotificationDelay);
}
public class YourParentController implements Initializable, OptionCallback {
...
}
Using the FXMLLoader object you can get a hold of your child controller object and pass the parent object to it:
FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource("yourFXML.fxml"));
Parent root = (Parent) loader.load();
YourChildController yourChildController = loader.<YourChildController>getController();
yourChildConrtoller.registerCallback(this);
In the childController you save that callback:
private OptionCallback optionCallback;
public void registerCallback(OptionCallback callback) {
optionCallback = callback;
}
And whenever results are ready you use it to pass it to parent:
optionCallback.setOptionsParams(...);

JavaFX - load FXML-file without FXLoader

I am experementing with the "new" JavaFX and it worked very well.
Now I am at a point which is incomprehensible for me. I have a Controller for my View and I want to load my Controller from a main-method so the controller can load a view or do whatever it likes.
My problem ist, that I have to load my FXML-File with the FXMLLoader.load() method. The FXMLLoader himselfe loads the controller. So in fact, with my method I will load the controller two times: I load the controller with XController xcontroller = new XController(); and inside that controller I load te view with the FXMLLoader.load() which will load the controller again.
do I have to use FXMLLoader or can I let my controller load the view with an other method?
edit I want to use the Presentation-Abstraction-Control (PAC) Pattern (variation of MVC), that's why I think it's importand to let the controller load the View.
the main class
public class Main extends Application
{
Override
public void start(Stage primaryStage)
{
LoginController loginController = null;
try
{
loginController = new LoginController();
loginController.loadSceneInto(primaryStage);
primaryStage.show();
}
.......
public static void main(String[] args)
{
launch(args);
}
}
the controller
public class LoginController
{
.....
public void loadSceneInto(Stage stage) throws IOException
{
this.stage = stage;
Scene scene = null;
Pane root = null;
try
{
root = FXMLLoader.load(
getClass().getResource(
this.initialView.getPath()
)
);
scene = new Scene(root, initialWidth, initialHeight);
this.stage.setTitle(this.stageTitle);
this.stage.setScene(scene);
this.centralizeStage();
}
.....
}
}
If I understand correctly, instead of
root = FXMLLoader.load(
getClass().getResource(
this.initialView.getPath()
)
);
Just do
FXMLLoader loader = new FXMLLoader(
getClass().getResource(this.initialView.getPath());
);
loader.setController(this);
root = loader.load();
You will need to remove the fx:controller attribute from the FXML file for this to work.

How to return value from a stage before closing it?

I have a "main stage" where I press a button to open a "second stage" where I have a table, the user selects one item of the the table and click on "asignar" button (which is just a confirm button), once clicked, it must return the code of the item selected in the table to the main stage and close the second stage.
Here is the code that matters.
I have an INT variable which must take the value of a function:
codigo = controller.setVista(this, usuario, password);
The "setVista" function goes like this:
public int setVista(ListHorarios vista, String usuario, String password) {
this.vista = vista;
this.usuario = usuario;
this.password = password;
this.inicializarTabla();
this.actualizarTabla(0, "%");
btnSeleccionar.setOnAction(e -> {
asignarSeleccion();
Stage stage = (Stage) btnSeleccionar.getScene().getWindow();
stage.close();
});
return codigo_horario;
}
And the "asignarSeleccion" like this:
private void asignarSeleccion() {
final HorarioTableModelo aux_horario = getTablaSeleccionada();
posicion = datos.indexOf(aux_horario);
if (aux_horario != null) {
codigo_horario = aux_horario.getCodigo();
}
}
My problem is that I can't get the "codigo_horario" value into the first variable "codigo" before the stage closes, what do I am missing?
Here is a possible example. The structure is the same as in the answer in my comment.
The second Stage is opened through a "controller" that is stores the data that should be returned even when the Stage is closed and exposes a getter to be used to retrieve the value from the outer world.
import javafx.application.Application;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
Button bSecondStage = new Button("Show second Stage");
bSecondStage.setOnAction(e -> {
WindowController wc = new WindowController();
wc.showStage();
System.out.println(wc.getData());
});
root.setCenter(bSecondStage);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
class WindowController {
private String data;
void showStage() {
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
VBox root = new VBox();
Scene scene = new Scene(root);
TextField tf = new TextField();
Button submit = new Button("Submit");
submit.setOnAction(e -> {
data = tf.getText();
stage.close();
});
root.getChildren().addAll(tf, submit);
stage.setScene(scene);
stage.showAndWait();
}
String getData() {
return data;
}
}
}
You can write your own Stage class with a return statement.
public class MyStage extends Stage {
public String showAndReturn(myFXControll controll) {
super.showAndWait();
return controll.getReturn();
}
}
After that you have to define a return function to your controller.
public class TableFilterControll implements Initializable {
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
}
public String getReturn() {
return "I'm a nice return value"; //return what you want controlled by your controller class
}
}
Now you can controll your return from the parent controller.
String retValue=myStage.showAndReturn(childControll);
System.out.println(retValue);
I think this is a good solution for clean code. And you can style your FXML with Screne Builder.
There is some error in 4baad4's example. If the method in the controller is iCanGetDataBeforeClose, then that's what should be called:
String someValue = controller.iCanGetDataBeforeClose();
But even that didn't work right for me. I actually got this to work without using setOnCloseRequest at all. In the form controller, I had a method like this:
public boolean getCompleted() {
return this.finished;
}
Then in the form calling it:
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("myView.fxml"));
AnchorPane pane = (AnchorPane) loader.load();
myViewController controller = loader.getController();
Scene scene = new Scene(pane);
Stage stage = new Stage();
stage.setScene(scene);
stage.showAndWait();
if (controller.getCompleted()){
doStuff();
}
One might think that since the stage had exited that the controller would throw a null reference exception, but it didn't, and it returned the correct response.
This solution works and is simplest proposed IMHO.
In my main controller I create Stage. Load controller which I can use like any class. And by creating an event I can get data just before closing the window.
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("/package/mySceneBuilderWindow.fxml"));
final Pane rootPane = (Pane)loader.load();
Scene scene = new Scene(rootPane);
Stage stage = new Stage();
stage.setTitle("Optional title");
stage.setScene(scene);
mySceneBuilderWindowController controller = loader.<mySceneBuilderWindowController>getController();
controller.iCanSendDataToCtrl("String for now"); // sending data or init textFields...
stage.show();
/* set event which is fired on close
// How to close in the other window... (pressing X is OK too)
#FXML private Button fxidSave = new Button(); // global var
#FXML private void handleSaveButton() {
Stage stage = (Stage) fxidSave.getScene().getWindow();
stage.close(); // closes window
}
*/
stage.setOnCloseRequest((EventHandler<WindowEvent>) new EventHandler<WindowEvent>() {
public void handle(WindowEvent we) {
String iCanGetDataBeforeClose = controller.getData();
System.out.println(iCanGetDataBeforeClose);
// static class can be used aswell -> System.out.println(Context.getMyString());
}
});
} catch (IOException e) {
System.out.println("something wrong with .fxml - name wrong? path wrong? building error?");
}
My other mySceneBuilderWindowController methods:
public void iCanSendDataToCtrl(String giveMe) {
// do something ex. myTextBox.setText(giveMe);
}
public String iCanGetDataBeforeClose() {
// do something ex. return myTextBox.getText();
}

JavaFx handler override

I have a fxml file build from fxml builder and I am using it by a loader in Java.
URL resource = getClass().getClassLoader().getResource("fxmlFile.fxml");
FXMLLoader loader = new FXMLLoader(resource, resourceBundle);
Pane rootPane = (Pane) loader.load();
this fxml file maps click event to my class;
<Group id="Group" layoutX="0.0" layoutY="0.0" onMouseReleased="#handleThis" scaleX="1.0" scaleY="1.0">
...
<Group/>
so I implement my handler in my class, lets call it MyClass;
public class MyClass {
public void createScene() throws IOException
{
URL resource = getClass().getClassLoader().getResource("fxmlFile.fxml");
FXMLLoader loader = new FXMLLoader(resource, resourceBundle);
Pane rootPane = (Pane) loader.load();
...
}
#FXML
public void handleThis(ActionEvent event) {
System.out.println("from MyClass");
}
...
}
Now I extend MyClass as MyExtendedClass and override handleThis method;
public class MyExtendedClass extends MyClass {
#Override
public void handleThis(ActionEvent event) {
System.out.println("from MyExtendedClass");
}
}
My question is, I cannot manage to work handle method in my extended class. It does not overrides it. How can I achieve to make it print "from MyExtendedClass" instead of "from MyClass"?
When createScene() is called on an instance of MyExtendedClass, the FXMLLoader parses the FXML file, reads the fx:controller="MyClass" attribute and instantiates a new object of type MyClass. That is why the base method is always called. The FXMLLoader doesn't know about MyExtendedClass.
There is a - hackish - way to achieve what you want (i.e. doing the loading in MyClass and still defining the controller in FXML):
public class MyClass
{
public void createScene()
{
try
{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXML.fxml"));
// set a controller factory that returns this instance as controller
// (works in this case, but not recommended)
loader.setControllerFactory(controllerType -> this);
pane = (Pane) loader.load();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}
It would be cleaner to instantiate the controller and pass it to the FXMLLoader.
For this the fx:controller="" attribute must be removed from the FXML file.
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
MyClass controller = new MyExtendedClass();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXML.fxml"));
loader.setController(controller);
Pane pane = (Pane) loader.load();
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}
Or use fx:controller="MyClass" to define the base type in the FXML file and let a controller factory decide the actual implementation.
public class Main extends Application
{
#Override
public void start(Stage primaryStage) throws Exception
{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("FXML.fxml"));
loader.setControllerFactory(controllerType -> {
if (MyClass.class.equals(controllerType))
return new MyExtendedClass();
else
return null; // return some other controller
});
Pane pane = (Pane) loader.load();
MyClass controller = (MyClass) loader.getController();
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
public static void main(String[] args)
{
launch(args);
}
}

Getting a variable from a FXML to an imported FXML

I'm currently doing a Java (+ using MySQL) application for my studies : an Database for an Hopital
I code my interface using JavaFX.
I have a Main FXML(for the general view) where I have tabs and in each tab I import another FXML using (fx:include). So that each module of my application has his own Controller and own designed View.
How can pass a variable from the main Controller to the others controllers?
Thanks!
Edit : Let me show you my code
So first there it's the class in which I load my fxml (I have on window of Connexion first and if the informations required for the connexion are ok I load the fxml Main with the main interface) And I set the connexion (THE VARIABLE I NEED TO SEND) that I got from my fxml Connexion to the FXML Main
public class MainApp extends Application {
private Stage primaryStage;
private Connection conn;
MainController controllermain = new MainController();
//ConnexionController controllerconnex;
#Override
public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("BASE DE L'HOPITAL DU ZOB");
showConnexion();
}
public void showConnexion() {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("Connexion.fxml"));
Parent page = (AnchorPane) loader.load();
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.show();
ConnexionController controller = loader.getController();
controller.setMainApp(this);
} catch (IOException e) {
e.printStackTrace();
}
}
public void showMainApp(Connection conn) {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("Main.fxml"));
AnchorPane page = (AnchorPane) loader.load();
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.show();
this.conn = conn;
controllermain = loader.getController();
controllermain.setMainApp(this);
controllermain.setConnexion(conn); // I want to send the variable conn to the others
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Then this is my Main Controller and you can see that I get the variable connexion only with the set method and then I can send it the the other controller
public class MainController implements Initializable {
private MainApp mainApp;
private Button retour;
protected Connection conn;
FXML AchorPane ;
public MainController() {
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
}
public void setConnexion(Connection conn){
this.conn=conn;
}
public void handleRetour(){
mainApp.showConnexion();
}
}
}
You just need a reference to the controller corresponding to the included fxml in the controller corresponding to the "main" fxml. You can do this using the Nested Controllers mechanism.
Briefly, if you have a "main" fxml with a <fx:include> tag, add an fx:id to the <fx:include>:
Main.fxml:
<!-- imports etc -->
<!-- root element, e.g. BorderPane -->
<BorderPane fx:controller="com.example.MainController" xmlns="..." ... >
<!-- ... -->
<fx:include source="tab.fxml" fx:id="tab" />
<!-- ... -->
</BorderPane>
Then in the MainController you can inject the controller from the included fxml using #FXML. The rule is that you append the word "Controller" to the fx:id used in the fx:include. For example, if the controller class for tab.fxml is TabController, given the fx:id is tab, you would do:
public class MainController {
#FXML
private TabController tabController ;
private Connection conn ;
// other injected fields, etc...
public void setConnexion(Connection conn) {
this.conn = conn ;
// pass Connection to TabController:
tabController.setConnexion(conn);
}
}
Now just define a setConnexion(...) method in TabController (if you haven't already) to receive the Connection object (and update anything it needs to update as a result).

Resources