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 .
Related
My UI has a adding button and I want to assign a keyboard shortcut combination for that. I have failed to use the setAcceleartor for this purpose.
What is the easiest way to set up keyboard shortcuts in javafx applications?
button declaration in the UI:
<Button fx:id="addButton" alignment="CENTER" minWidth="-Infinity" mnemonicParsing="false" onAction="#addAction" prefHeight="31.0" prefWidth="130.0" text="Add" HBox.hgrow="ALWAYS" />
Controller button binding:
#FXML
private Button addButton;
The method that wants to setOnAction for the shortcut for the button:
public void addAction(ActionEvent event) throws SQLException, ClassNotFoundException {
if (validateInput()) {
String productName = productField.getText();
double unitPrice = Double.parseDouble(priceField.getText());
int quantity = Integer.parseInt(quantityField.getText());
double total = unitPrice * quantity;
ITEMLIST.add(new Item(productName, unitPrice, quantity, total));
calculation();
resetAdd();
productTableView.getSelectionModel().clearSelection();
ObservableList<Product> productsData = ProductDAO.searchProducts();
populateProducts(productsData);
searchField.setText("");
}
}
initialize() method:
#FXML
private void initialize() throws SQLException, ClassNotFoundException, IOException {
setSaveAccelerator(addButton);
}
The code I tried with setAccelerator:
private void setSaveAccelerator(final Button button) {
Scene scene = button.getScene();
if (scene == null) {
throw new IllegalArgumentException("setSaveAccelerator must be called when a button is attached to a scene");
}
scene.getAccelerators().put(
new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN),
new Runnable() {
#FXML public void run() {
button.fire();
}
}
);
}
In your setSaveAccelerator method, instead of directly calling addAction(ActionEvent event), just instruct the button to fire its event to its listeners such as: button.fire(). For example:
private void setSaveAccelerator(Button button) {
if(button==null) {
System.out.println("Button is null! "); // check that the button was injected properly through your fxml
}
Scene scene = button.getScene();
if (scene == null) {
throw new IllegalArgumentException("setSaveAccelerator must be called when a button is attached to a scene");
}
scene.getAccelerators().put(
new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN),
new Runnable() {
#FXML public void run() {
button.fire();
}
}
);
}
EDIT
To also avoid the IllegalArgumentException you must attach the accelerator after the button is attached to a scene. I managed to achieve this by creating a public method in the controller to attach the accelerator after the scene has been set. Then, in the class where the scene is loaded the controller's method can be called which sets up this functionality. See the example below:
In the controller class (in my case MainController):
public void setup() {
setSaveAccelerator(button);
}
In your main class when loading the fxml file:
FXMLLoader loader = new FXMLLoader(MainController.class.getResource("mainFXML.fxml"));
AnchorPane page = (AnchorPane) loader.load();
MainController controller = loader.getController();
Scene scene = new Scene(page);
controller.setup(); // calls the setup method attaching the accelerators
FULL EXAMPLE
Main class:
public class Main extends Application{
public static Stage primaryStage;
#Override
public void start(Stage primaryStage) throws Exception {
Main.primaryStage=primaryStage;
FXMLLoader loader = new FXMLLoader(MainController.class.getResource("mainFXML.fxml"));
AnchorPane page = (AnchorPane) loader.load();
MainController controller = loader.getController();
Scene scene = new Scene(page);
primaryStage.setTitle("Shortcut example");
primaryStage.setScene(scene);
controller.setup();
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Maincontroller:
public class MainController {
#FXML
private ResourceBundle resources;
#FXML
private URL location;
#FXML
private Button button;
#FXML
private AnchorPane rootPane;
#FXML
private TextArea textarea;
#FXML
void action(ActionEvent event) {
textarea.setText("Action fired!!");
}
#FXML
void initialize() {
assert button != null : "fx:id=\"button\" was not injected: check your FXML file 'MainFXML.fxml'.";
assert rootPane != null : "fx:id=\"rootPane\" was not injected: check your FXML file 'MainFXML.fxml'.";
assert textarea != null : "fx:id=\"textarea\" was not injected: check your FXML file 'MainFXML.fxml'.";
}
public void setup() {
setSaveAccelerator(button);
}
private void setSaveAccelerator(Button button) {
if(button==null) {
System.out.println("Button null!!");
}
Scene scene = button.getScene();
if (scene == null) {
throw new IllegalArgumentException("setSaveAccelerator must be called when a button is attached to a scene");
}
scene.getAccelerators().put(
new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN),
new Runnable() {
#FXML public void run() {
button.fire();
}
}
);
}
}
MainFXML.fxml
<AnchorPane fx:id="rootPane" prefHeight="408.0" prefWidth="330.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainController">
<children>
<Button fx:id="button" layoutX="139.0" layoutY="350.0" mnemonicParsing="false" onAction="#action" text="Button" />
<TextArea fx:id="textarea" layoutX="73.0" layoutY="38.0" prefHeight="200.0" prefWidth="200.0" />
</children>
</AnchorPane>
I'm getting a NullPointerException when trying to access a Button from within initialize() method.
I have marked the line below which causes the exception.
public class MyController implements Initializable {
#FXML
public Button connect;
public MyController() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Layouts/ClientLayout.fxml"));
loader.setController(this);
Parent root = loader.load();
Stage stage = new Stage();
stage.setScene(new Scene(root, 460, 470));
stage.show();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
connect.setOnAction(e -> { // this line causes the nullpointerexception
connect.setDisable(true);
});
}
}
You did not initialize your button. Check your FXML. It should contain the correct fx:id:
<Button fx:id="connect" graphicTextGap="2.0" layoutX="716.0" layoutY="274.0" mnemonicParsing="false">
You should also define what do to on action with the addition of an onAction method in your FXML:
<Button fx:id="connect" graphicTextGap="2.0" layoutX="716.0" layoutY="274.0" mnemonicParsing="false" onAction="#handleConnectButtonAction">
and in your controller instead of putting it in the initialize method:
#FXML
private void handleConnectButtonAction(ActionEvent event){
connect.setDisable(true);
}
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.
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);
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");
}