JavaFX - Loading/creating multiple controllers - javafx

I have researched this for a couple hours now, and even though there is a lot about "multiple controllers", none of the solutions worked for my problem. I don't know if I am just structuring my program wrong, so I need some input.
I got a "MainView" (App class with AppView and AppController) and a "SubView" (SubView and SubViewController). In App, I am loading the mainView as primaryStage and loading the AppController. I created a stageManager class to handle different views. My problem is: Where and how do I create/load my subViewController?
public class App extends Application {
private Stage primaryStage;
private AnchorPane rootLayout;
private AppController appController;
MainModel model = new MainModel();
StageManager stageManager;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("App");
initRootLayout();
appController.initModel(model);
appController.setStageManager(stageManager);
}
public void initRootLayout() {
try {
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("mainView.fxml"));
rootLayout = (AnchorPane) loader.load();
appController = loader.<AppController>getController();
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
stageManager = new StageManager(primaryStage, rootLayout, appController);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
public class AppController implements Initializable {
private MainModel model;
private StageManager stageManager;
#FXML
private Button btn;
#FXML
private void handleButtonAction(ActionEvent event) {
stageManager.changeScene("subView.fxml");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
public void initModel(MainModel model) {
this.model = model;
}
public void setStageManager(StageManager stageManager){
this.stageManager = stageManager;
}
}
public class StageManager {
private Stage primaryStage;
private AnchorPane rootLayout;
private AppController appController;
public StageManager(Stage primaryStage, AnchorPane rootLayout, AppController appController){
this.primaryStage = primaryStage;
this.rootLayout = rootLayout;
this.appController = appController;
}
public void changeScene(String scene){
try {
Parent parentPane = FXMLLoader.load(getClass().getResource(scene));
primaryStage.getScene().setRoot(parentPane);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class SubController implements Initializable {
private MainModel model;
#Override
public void initialize(URL url, ResourceBundle rb) {
}
public void setModel(MainModel model){
this.model = model;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.text.Font?>
<AnchorPane id="AnchorPane" prefHeight="524.0" prefWidth="850.0" xmlns="http://javafx.com/javafx/9.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="app.SubController">
<children>
<Label fx:id="lbl" layoutX="52.0" layoutY="13.0" prefHeight="38.0" prefWidth="214.0" text="just a label">
<font>
<Font name="Consolas" size="24.0" />
</font>
</Label>
</children>
</AnchorPane>
I tried the following things to load the other controller:
First I tried doing it the same way I am loading the appController (since that works), but without setting the view as the scene. So in the initRootLayout() method I simply added:
FXMLLoader subloader = new FXMLLoader();
subloader.setLocation(getClass().getResource("subView.fxml"));
subController = loader.<SubController>getController();
This didn't work. In fact something really weird happened. When I tried calling a method from subController, I got a NullPointerException. Debugging revealed that the subController is being loaded, but right after its loaded it disappears and subController Object is just "null". Why is that?
The next thing I tried after reading about this approach was to add the subController as a FXML variable. In the AppController I added #FXML private SubController subController; and in the AppController's initialize() method I tried making a call from the controller:
#Override
public void initialize(URL url, ResourceBundle rb) {
subController.setModel(model);
}
In this case, I am getting a NullPointerException, but it is actually pointing to the line where I call appController.initModel(model); in the App class. Even though this line works fine when I am not trying to use the subController.
So, I am extremely confused and I think I need a whole new structure for this. Any kinda help would be greatly appreciated! :)
EDIT:
This seems to be working: I changed the changeScenes() method to this:
public void changeScene(String scene){
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource(scene));
Parent root = (Parent) loader.load();
subViewController = loader.getController();
subViewController.setModel(mainModel);
Scene newScene = new Scene(root);
Stage newStage = new Stage();
newStage.setScene(newScene);
newStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}

Related

Using JavaFX to pass data to an already opened window

I'm new to JavaFX. This was easy to do without FXML, but the FXML controllers are stumping me.
What I'm trying to do: Set up a main window that has a button. When clicked, the button launches a second popup window in which the user submits a value. Upon closing the second window (done currently with a button click on the pop-up), I'd like the user's input to be passed back to the main controller-- the main window that is already open.
So far, I've got 2 .fxml files(one for a main window the other for a popup), and the corresponding controllers: MainWindowController:
public class MainController implements Initializable {
#FXML
public Label label;
#FXML
private Button button;
#FXML
private void popBtnClick(ActionEvent event) throws IOException {
//creates new pop-up window
Stage popupSave = new Stage();
popupSave.initModality(Modality.APPLICATION_MODAL);
popupSave.initOwner(ComWins.stage);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("PopUp.fxml"));
Parent root = loader.load();
PopUpController controller = loader.getController();
//calls a method in the PopUpController, and uses it to pass data to
//the Popup window.
controller.dataToPopUp(7);
Scene scene = new Scene(root);
popupSave.setScene(scene);
popupSave.showAndWait();
}
I also tried calling this method from the popup window with no success in
changing Main's label.
public void dataPass(String name){
label.setText(name);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
And PopUpController:
public class PopUpController implements Initializable {
#FXML
private Button ok_btn;
#FXML
public TextField input_tf;
#FXML
private String input;
#FXML
private void okBtnClick() throws IOException {
input = input_tf.getText();
/*my attempt to pass the variable-- using a loader to get the
controller and then referencing the public label. */
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("Main.fxml"));
Parent root = loader.load();
FXMLDocumentController controller = loader.getController();
//this line works, and retrieves the label's text.
String temp = controller.label.getText();
//but this line does not work. Why?
controller.label.setText(input);
//closes this pop-up
Stage stage = (Stage)input_tf.getScene().getWindow();
stage.close();
}
//this method is called in the maincontroller and used to pass data to
//the popup's textfield.
public void dataToPopUp(int x){
input_tf.setText(Integer.toString(x));
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
Using the above, Main passes ('7') into the PopUp's textfield. But if the user enters something else into the textfield, I cannot seem to get that data back to Main. This is like having a Settings Pop-up window, and then passing the user's selections from the Settings popup back to the main window. I just cannot figure out how to pass things back to the main window.
I am not using SpringBoot, but thanks for the suggestion.
Thank you in advance!
If you are using Spring Boot, MPV Java on YouTube has great examples of connecting your controllers together allowing you to pass information cleanly and easily between them.In my application I've been able to implement these examples. Each controller is registered as a bean using #Component which means you can #Autowire the controller into another controller. In your main controller I would recommend setting up some basic getters/setters to allow outside interaction with your fields so other controllers can "talk" to the main controller.
A really basic example would be:
#Component
public class MyMainController {
#FXML private TextField exampleTextField;
...
...
/* Get the text of a field from this controller: can be accessed from another controller */
public String getExampleTextField() {
exampleTextField.getText();
}
/* Set the text of a field on this controller: can be accessed from another controller */
public void setExampleTextField(String text) {
exampleTextField.setText(text);
}
}
#Component
public class AnotherController {
#Autowired private MyMainController myMainController;
...
...
public void someMethod(String newText) {
// Do some work here and set some text back to the main controller
myMainController.setExampleTextField(newText);
}
}
MPV Java does a much better job of explaining this concept.
https://www.youtube.com/watch?v=hjeSOxi3uPg
I reviewed the suggestions and was unable to get anything to work for me-- many of the concepts were over my head, as I'm new to Java. After several attempts, I was able to get a fairly simple solution to the problem. It is likely far from best practices, but it works:
In the main window's controller, use the popup's controller to call the Pop Up's string variable and set it as the label's text (label.setText(controller.test)). The string variable has to be public, and is set once the pop-up is closed by the button click. See the code below:
Main.fxml:
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="switchingpass.MainController">
<children>
<Button fx:id="button" layoutX="126" layoutY="90" onAction="#popBtnClick" text="Click Me!" />
<Label fx:id="label2" layoutX="126" layoutY="120" minHeight="16" minWidth="69" />
<Label fx:id="label" layoutX="143.0" layoutY="38.0" text="Label" />
</children>
</AnchorPane>
MainController:
public class MainController implements Initializable {
#FXML
public Label label;
#FXML
private Button button;
#FXML
private void popBtnClick(ActionEvent event) throws IOException {
//creates new pop-up window
Stage popupSave = new Stage();
popupSave.initModality(Modality.APPLICATION_MODAL);
popupSave.initOwner(SwitchingPass.stage);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("PopUp.fxml"));
Parent root = loader.load();
PopUpController controller = loader.getController();
Scene scene = new Scene(root);
popupSave.setScene(scene);
popupSave.showAndWait();
//after the popup closes, this will run, setting the label's text to the popup's test variable, which is public.
label.setText(controller.test);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
PopUp.fxml:
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="switchingpass.PopUpController">
<children>
<TextField fx:id="input_tf" layoutX="207.0" layoutY="65.0" />
<Button fx:id="close_btn" layoutX="268.0" layoutY="185.0" mnemonicParsing="false" onAction="#closeBtnClick" text="Close" />
</children>
</AnchorPane>
PopUpController:
public class PopUpController implements Initializable {
#FXML
public Button close_btn;
#FXML
public TextField input_tf;
#FXML
public String test;
#FXML
private void closeBtnClick() throws IOException {
//stores textfield input as a string
test = input_tf.getText();
Stage stage = (Stage)input_tf.getScene().getWindow();
stage.close();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
Please note that the code's main class that extends Application must declare the stage variable as a public static variable:
public class SwitchingPass extends Application {
//this is necessary to initialize the owner of the popup
public static Stage stage;
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Again, probably not the best way to accomplish this, but it works, and perhaps it is helpful to someone else.
I did that this way.
First create an interface
public interface DataSender {
void send(String data);
}
and then implement that interface to your Main Window Controller
public class FXMLDocumentController implements Initializable,DataSender {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
try {
Stage popupSave = new Stage();
popupSave.initModality(Modality.NONE);
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getResource("PopUp.fxml"));
Parent root = loader.load();
PopUpController controller = loader.getController();
controller.setX(7);
//this is the line use to get data from popup
controller.setSendDataSender(this);
Scene scene = new Scene(root);
popupSave.setScene(scene);
popupSave.show();
} catch (IOException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
}
}
//this method can call from popus controller
#Override
public void send(String data) {
label.setText(data);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
}
and then override implemented interface method and inside the method you can change Main window UI data I changed label text.
and then create variable in pupup controller with type DataSender(created interface befor) and create method to set that interface
public class PopUpController implements Initializable {
private int x;
//this is the variable
private DataSender dataSender;
#FXML
private Button btnOk;
#FXML
private TextField txtText;
#Override
public void initialize(URL url, ResourceBundle rb) {
btnOk.setOnAction(e->{
//When Click this button will call Main Window send method
dataSender.send(txtText.getText());
});
}
public void setX(int x){
this.x=x;
}
//this is the method to set variable value from Main Window
public void setSendDataSender(DataSender ds){
this.dataSender=ds;
}
}
and then when popup window button clicked then call send method with data and then will change main window label text.
This solution is work for me.hope this useful for you also .

How to handle Null Pointer Exception of AnchorPane object in javafx

I am building a javafx application using afterburner. I have main window with a AnchorPane. In this AnchorPane i have MenuBar and an another AnchorPane with fx:id=contentPane. In this contentPane i am trying to load another scene which have a different Presenter which gives me an NPE while it is fine if i am doing so with a menuAction. See the code below and more details..
This is my Main class which start the stage.
public class App extends Application
{
#Override
public void start(Stage stage) throws Exception {
MainView mainView = new MainView();
Scene scene = new Scene(mainView.getView());
stage.setTitle("Main");
stage.setScene(scene);
stage.show();
}
#Override
public void stop() throws Exception {
Injector.forgetAll();
}
public static void main(String[] args) {
launch(args);
}
}
Here is main presenter
public class MainPresenter implements Initializable {
#FXML
AnchorPane contentBox;
private ObjectProperty<FormOpener> newFormProperty ;
ReturnsInputView returnsView ;
ReturnsInputPresenter returnsPresenter;
InwardsInputView inputView;
InwardsInputPresenter inwardsPresenter;
#PostConstruct
public void init(){
this.newFormProperty = new SimpleObjectProperty();
this.newFormProperty.addListener(new ChangeListener<FormOpener>() {
#Override
public void changed(ObservableValue<? extends FormOpener> observable, FormOpener oldValue, FormOpener newValue) {
if(newValue!=null){
InwardsInputView inputView = new InwardsInputView();
inwardsPresenter = (InwardsInputPresenter) inputView.getPresenter();
contentBox.getChildren().add(inputView.getView());
}
}
});
}
#Override
public void initialize(URL location, ResourceBundle resources) {
this.returnsView = new ReturnsInputView();
this.returnsPresenter = (ReturnsInputPresenter) returnsView.getPresenter();
this.contentBox.getChildren().add(returnsView.getView());
}
public void showIncomingForm(){
this.returnsView = new ReturnsInputView();
this.returnsPresenter = (ReturnsInputPresenter) returnsView.getPresenter();
contentBox.getChildren().add(returnsView.getView());
}
public ObjectProperty<FormOpener> newFormProperty(){
return newFormProperty;
}
}
Here is main.fxml
<AnchorPane id="AnchorPane" minHeight="180.0" prefHeight="362.0" prefWidth="503.0" styleClass="airpad" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.qaf.MainPresenter">
<children>
<AnchorPane fx:id="contentBox" layoutX="1.0" layoutY="25.0" minHeight="0.0" minWidth="0.0" prefHeight="336.0" prefWidth="503.0" style="-fx-background-color: aqua;" />
<MenuBar fx:id="menuBar" prefHeight="25.0" prefWidth="504.0">
<menus>
<Menu mnemonicParsing="false" text="Stock">
<items>
<MenuItem fx:id="returned" mnemonicParsing="false" onAction="#showReturnedForm" text="Returned" />
</items>
</Menu>
</menus>
</MenuBar>
</children>
</AnchorPane>
Now here is ReturnsInputPresenter
public class ReturnsInputPresenter implements Initializable {
#FXML
Button saveButton;
#FXML
TextField orderNo;
#Inject
MainPresenter main;
private ObjectProperty<FormOpener> newFormProperty ;
#Override
public void initialize(URL location, ResourceBundle resources) {
this.newFormProperty = new SimpleObjectProperty();
this.newFormProperty.addListener(new ChangeListener<FormOpener>() {
#Override
public void changed(ObservableValue<? extends FormOpener> observable, FormOpener oldValue, FormOpener newValue) {
if(newValue!=null){
main.newFormProperty().set(newValue);
}
}
});
}
public void save() {
FormOpener fOpener = new FormOpener();
fOpener.setInwards(false);
this.newFormProperty.set(fOpener);
}
public ObjectProperty<FormOpener> newFormProperty(){
return newFormProperty;
}
Here in returnsinput.fxml i have a text box and a button. On button's action i am setting the newFormProperty() to new Value which have a listener that changes the value of newFormProperty() belongs to mainPresenter and here when i try to access contentPanethen it gives me a NPE while with menu Action performing the same process have no issue.
Why it's happening and what is the solution. Please help me with this.
Thank you.
The instance of MainPresenter that is injected into ReturnsInputPresenter is not the same instance as the one created for you when MainView is instantiated. (Imagine you had two instances of MainView, which is perfectly plausible, and consequently two instances of MainPresenter. How would the framework know which instance it should inject into the ReturnsInputPresenter instance?)
The problem here is really that you are trying to use the framework in a way that it is not intended to be used. Afterburnerfx is a framework for implementing MVP and the injection mechanism is really designed as a convenience for injecting elements of the model so that model instances can be shared by multiple presenters. It is not designed to pass around references to the presenters from one presenter to another.
But actually all you are trying to do is something that fits very naturally into MVP: you are trying to share a piece of data (specifically, an ObjectProperty<FormOpener>) among different presenters. So just define a class to hold that data (and other data you might need to share):
public class FormOpenerModel /* you can probably think of a better name */ {
private final ObjectProperty<FormOpener> formOpener = new SimpleObjectProperty<>();
public ObjectProperty<FormOpener> formOpenerProperty() {
return formOpener ;
}
public final FormOpener getFormOpener() {
return formOpenerProperty().get();
}
public final void setFormOpener(FormOpener formOpener) {
formOpenerProperty().set(formOpener);
}
// other properties as needed...
}
Now your MainPresenter should look something like
public class MainPresenter implements Initializable {
#FXML
AnchorPane contentBox;
#Inject
private FormOpenerModel formOpenerModel ;
// you probably don't need any of these references any more:
ReturnsInputView returnsView ;
ReturnsInputPresenter returnsPresenter;
InwardsInputView inputView;
InwardsInputPresenter inwardsPresenter;
#Override
public void initialize(URL location, ResourceBundle resources) {
this.returnsView = new ReturnsInputView();
this.returnsPresenter = (ReturnsInputPresenter) returnsView.getPresenter();
this.contentBox.getChildren().add(returnsView.getView());
formOpenerModel.formOpenerProperty().addListener(new ChangeListener<FormOpener>() {
#Override
public void changed(ObservableValue<? extends FormOpener> observable, FormOpener oldValue, FormOpener newValue) {
if(newValue!=null){
InwardsInputView inputView = new InwardsInputView();
inwardsPresenter = (InwardsInputPresenter) inputView.getPresenter();
contentBox.getChildren().add(inputView.getView());
}
}
});
}
public void showIncomingForm(){
this.returnsView = new ReturnsInputView();
this.returnsPresenter = (ReturnsInputPresenter) returnsView.getPresenter();
contentBox.getChildren().add(returnsView.getView());
}
}
You probably don't need all those references to the other views and presenters, etc, but I left them in. You should remove them if you don't need them.
Your ReturnsInputPresenter:
public class ReturnsInputPresenter implements Initializable {
#FXML
Button saveButton;
#FXML
TextField orderNo;
#Inject
private FormOpenerModel formOpenerModel;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
public void save() {
FormOpener fOpener = new FormOpener();
fOpener.setInwards(false);
formOpenerModel.formOpenerProperty().set(fOpener);
}
}
(By the way, FXML controller classes haven't needed to implement Initializable since version 2.0, so you can get rid of implements Initializable, and if the initialize(...) method is a no-op as above, you can just remove it entirely.)
The injector is smart enough to cache instances when it injects them and reuse them (i.e. it uses the equivalent of "singleton scope" from CDI or Spring), so both presenters will get a reference to the same FormOpenerModel instance.

javaFX - how to use function or object of a controller from another controller

I have seen a lot of question similar to mine, but I didn't figure it out how to solve my problem, so there is my code:
I have a simple main:
MainClassFXGui.java
public class MainClassFXGui extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("MyProgramMainGuiTab.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Assicurazione");
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
and three fxml files, one of them that contains the other two, by the tag
<fx:include source="MyTab1.fxml" />
MyProgramMainGuiTab.fxml
...
<TabPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="5000.0" prefWidth="5000.0" tabClosingPolicy="UNAVAILABLE">
<tabs>
<Tab closable="false" text="MyTab1">
<content>
<fx:include source="MyTab1.fxml" />
</content>
</Tab>
<Tab closable="false" text="MyTab2">
<content>
<fx:include source="MyTab2.fxml" />
</content>
</Tab>
</tabs>
</TabPane>
...
and three controller:
MyProgramMainGuiTabController.java
....
#FXML
private Label LeftSt;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
LeftSt.setText("");
}
public void setLeftSt(String st){
LeftSt.setText(st);
}
...
MyTab1Controller.java
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
setLeftSt("Can I change the label of the MyProgramMainGuiTabController?");
}
I tried in this way:
MyTab1Controller.java
private MyProgramMainGuiTabController controller;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
controller.setLeftSt("Can I change the label of the MyProgramMainGuiTabController?");
}
#Override
public void initialize(URL url, ResourceBundle rb) {
FXMLLoader fxmlLoader = new
FXMLLoader(getClass().getResource("MyProgramMainGuiTab.fxml"));
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
controller = (MyProgramMainGuiTabController)fxmlLoader.getController();
}
But I have a null pointer exception or if I move the code for the instanzialization of the object 'controller' in the handleButtonAction function, I don't have any error, but the label does not change.
Maybe I can create a static Scene and a static Stage in the MainClassFXGui.java?
but then I don't know how I could use them...
MainClassFXGui.java
public class MainClassFXGui extends Application {
static private Scene scene;
static private Stage stage;
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("MyProgramMainGuiTab.fxml"));
this.scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Assicurazione");
stage.show();
this.stage = stage;
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public static Scene getScene(){
return AssicurazioneFXGui.scene;
}
public static Stage getStage(){
return AssicurazioneFXGui.stage;
}
}
can I do something with getScene() or getStage()? any idea or suggest?
thanks to everyone
Define a StringProperty in MyTab1Controller, with the usual methods:
public class MyTab1Controller {
private final StringProperty leftSt = new SimpleStringProperty();
public StringProperty leftStProperty() {
return leftSt ;
}
public final String getLeftSt() {
return leftStProperty().get();
}
public final void setLeftSt(String leftSt) {
leftStProperty().set(leftSt);
}
// ...
#FXML
public void handleButtonAction() {
setLeftSt(...);
}
// ...
}
Now assign an fx:id attribute to the fx:include tag:
<Tab closable="false" text="MyTab1">
<content>
<fx:include fx:id="myTab1" source="MyTab1.fxml" />
</content>
</Tab>
This means you can inject the controller for MyTab1.fxml into the controller for the FXML where it is included (MyProgramMainGuiTabController):
public class MyProgramMainGuiTabController {
#FXML
private MyTab1Controller myTab1Controller ;
}
and then you can just observe the property and update the label when it changes:
public class MyProgramMainGuiTabController {
#FXML
private MyTab1Controller myTab1Controller ;
#FXML
private Label leftSt;
public void initialize() {
leftSt.setText("");
myTab1Controller.leftStProperty().addListener((obs, oldValue, newValue) ->
leftSt.setText(newValue));
}
}
Note the rule here is that the field name for the controller is the value of the fx:id attribute with the word "Controller" appended: myTab1 in the fx:id resolves to myTab1Controller for the field where the controller is injected. See NestedControllers for more information.
At the moment I have solved in this way. If anyway someone has a better solution, please write. Also because this does not solve the problem if I need to execute a function.. thanks
MyProgramMainGuiTabController.fxml
...
#FXML
private Label LeftSt;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
LeftSt.setText("");
}
...
MyTab1Controller.fxml
#FXML
private Button myObject;
private Scene scene;
private Label lblDataLeft;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
setLeftSt("this works!");
}
public void setLeftSt(String st){
if(this.scene == null)
this.scene = myObject.getScene();
if(this.lblDataLeft==null)
this.lblDataLeft = (Label) scene.lookup("#LeftSt");
if (this.lblDataLeft!=null)
this.lblDataLeft.setText(st);
}

Changing the text of a label from a different class in JavaFX

This question was already asked here but was not able to find any answers. I have reproduced a similar situation where I would like to change the text of a label from another class using the controller
FXMLDocumentController.java
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("FXMLDocumentController.#handleButtonAction");
label.setText("Hello World!");
Connector.Connecting();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
public void setLabelText(String text)
{
System.out.println("FXMLDocumentController.setLabelText(): Called");
label.setText(text);
}
}
FXMLDocument.fxml
<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" fx:controller="demo5.FXMLDocumentController">
<children>
<Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
<Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
</children>
</AnchorPane>
Demo5.java
public class Demo5 extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Connector.java
public class Connector {
public static void Connecting() {
try {
System.out.println("Connector.Connecting(): Called");
FXMLLoader loader = new FXMLLoader(FXMLDocumentController.class.getResource("FXMLDocument.fxml"));
loader.load();
FXMLDocumentController controller = (FXMLDocumentController) loader.getController();
controller.setLabelText("Bye World");
} catch (IOException ex) {
Logger.getLogger(Connector.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
Output at Console
Connector.Connecting(): Called
FXMLDocumentController.setLabelText(): Called
But could see no changes in the label. Am I missing something major here ?
You can change your Connector class to receive the Controller instance:
public class Connector {
public static void Connecting(FXMLDocumentController controller) {
try {
System.out.println("Connector.Connecting(): Called");
controller.setLabelText("Bye World");
} catch (IOException ex) {
Logger.getLogger(Connector.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("FXMLDocumentController.#handleButtonAction");
label.setText("Hello World!");
Connector.Connecting(this);
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
public void setLabelText(String text)
{
System.out.println("FXMLDocumentController.setLabelText(): Called");
label.setText(text);
}
}
Note:
If your Connector is going to take longer to execute whatever it needs to, you might want to use a Task, so you don't freeze your UI. To update the Label, you have to bind the text property and then update the Text value using the updateMessage() method.
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("FXMLDocumentController.#handleButtonAction");
label.setText("Hello World!");
Task<Boolean> connectorTask = new ConnectorTask();
label.textProperty().bind(connectorTask.messageProperty());
connectorTask.setOnSucceeded(e -> {
// this is going to be called if the task ends up without error
label.textProperty().unbind();
});
new Thread(connectorTask).start();
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
//public void setLabelText(String text)
//{
// System.out.println("FXMLDocumentController.setLabelText(): Called");
// label.setText(text);
//}
public class ConnectorTask extends Task<Boolean> {
#Override
protected Boolean call() throws Exception {
// ... do whatever you need here
// then you call this method to update the TextProperty from the Label that was bound.
updateMessage("Bye World");
return Boolean.TRUE;
}
}
}
Your Demo5 class and Connector class are both creating unique instances of the FXMLDocumentController via the call to FXMLLoader.load(). The instance in the Demo5 class is being placed in the scene graph and becomes visible. The instance in the connector is not being made visible. When you call setLabelText() it is changing the text for an unseen label. What you may want to do is get the FXMLDocumentController instance in Demo5 and provide it to the Connector class through the constructor or a setter method. You may need to change some things around depending on what the Connector class is used for. Alternatively, you could use the connector class to load the FXML root and controller and provide methods for accessing them, then use those methods in Demo5 to make the scene visible.
I made it in a simple way by defining the Label as static in the FXMLDocumentController.java:
#FXML GridPane myGridPane;
public static Label totLabel = new Label("Total");
and add it to myGridPane in the initialize method of FXMLDocumentController class:
#Override
public void initialize(URL url, ResourceBundle rb) {
myGridPane.add(totLabel, 0, 3);
}
and at any other class you can call the setText() of this label like this:
String message = "this message will appear in the total label";//your string
FXMLDocumentController.totLabel.setText(message);

How can a textfield from fxml file be updated by setText in java file?

I am looking to update text in a textfield based on some value. In order to make this sample simpler I have made my program smaller. The problem seems to be when I put top.setText("This is my new Text");
I looked at this:
how to change the text of TextField in java fx 2
but the answer does not seem to make sense. I don't know why you'd initialize a textfield that has already been implemented. Regardless it did not work.
I have also looked at:
NullPointerException (JavaFX Label.setText())
This seems to be the closest to what I think is the issue, but when I did the following I get an error. Just for clarity this is in the JavaFXApplication5 class.
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("FXML.fxml")
);
FXMLLoader.setController(this); // non-static method setController(Object)
// can not be referenced from static context ERROR****
Parent root = (Parent) loader.load();
/*Parent root = FXMLLoader.load(getClass().getResource("FXML.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("java code");
stage.show();
*/
}
From reading on the internet I wondered if there was a race condition: http://gotoanswer.stanford.edu/nullpointerexception_in_javafx_initialize_method-8807679/
So I tried:
Platform.runLater(new Runnable() {
#Override public void run() {
top.setText("This is my new Text");
}
});
But that did not work. I know that it can be set in Scene Builder but I need a way to dynamically change it based on values from another class. I can figure out how to do that part if I can just figure out how to set it to begin with. Hopefully this explains enough to get some help.
FXMLController Class:
public class FXMLController implements Initializable {
#FXML private TextField top;
public FXMLController() {
System.out.println("Hi");
top.setText("This is my new Text"); // This breaks the program *********
}
#Override
public void initialize(URL url, ResourceBundle rb) {
}
}
FXML.fxml class:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="javafxapplication5.FXMLController">
<children>
<TextField fx:id="top" layoutX="171.0" layoutY="68.0" />
</children>
</AnchorPane>
JavaFXApplication5 class: // main class
public class JavaFXApplication5 extends Application {
#Override
public void start(Stage stage) throws Exception {
try {
Parent root = FXMLLoader.load(getClass().getResource("FXML.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("java code");
stage.show();
}
catch (Exception ex) {
Logger.getLogger(JavaFXApplication5.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void main(String[] args) {
launch(args);
}
}
You can't use a constructor on your controller class (FXMLController), since it will be initilized by the FXMLLoader.
And you are right, the first link has a wrong answer, since the textfield will be initialized (because of the #FXML annotation) in this process.
So for starters, you can add some text to the textfield inside initialize, as it will be loaded from the beginning by the loader, and top will be already instantiated.
public class FXMLController implements Initializable {
#FXML private TextField top;
#Override
public void initialize(URL url, ResourceBundle rb) {
top.setText("This is my first Text");
}
}
Try this first, with your posted version of JavaFXApplication5, and check that works.
There are many ways to set the content on the field, but if you need to modify the text field from another class, just add a public method for that:
public class FXMLController implements Initializable {
#FXML private TextField top;
#Override
public void initialize(URL url, ResourceBundle rb) {
top.setText("This is my new Text");
}
public void setTopText(String text) {
// set text from another class
top.setText(text);
}
}
As an example, you could get an instance of your controller in your main class, and use it to pass the content to the text field, after the stage is shown. This will override the previous content.
#Override
public void start(Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("FXML.fxml"));
Parent root = loader.load();
FXMLController controller = (FXMLController)loader.getController();
Scene scene = new Scene(root);
stage.setTitle("Java code");
stage.setScene(scene);
stage.show();
controller.setTopText("New Text");
}

Resources