I need to manipulate an element from the Controller class. However, every example I’ve managed to find does this manipulation through the class that extends Application.
I need to manipulate the TextField inputText from VBoxSampleController in the class Main.
The manipulation is as follows:
inputText.setText(“TEST”);
The desired result is once the FXML application is launched, the inputText TextField is pre-populated with the text “TEST”.
My confusion stems from how to manipulate this field anywhere but:
VBoxSampleController
UIManager
The act of setting VBoxsampleController static so that I can access it by using the class name:
UIManager.vbsc.inputText.setText("TEST");
Does nothing. The element in the launched FXML window does not change. It remains empty.
This project is taken from https://examples.javacodegeeks.com/desktop-java/javafx/javafx-applications-efxclipse/ because I can’t create a new FXML application at this time.
Thanks.
FULL CODE:
/* Main */
public class Main {
public static void main(String[] args) {
UIManager.main(args);
// Does nothing
// UIManager.vbsc.inputText.setText("TEST");
// I want to change inputText here
}
}
/* UIManager */
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.fxml.FXMLLoader;
public class UIManager extends Application {
public static VBoxSampleController vbsc;
public FXMLLoader loader;
#Override
public void start(Stage primaryStage) {
try {
loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("VBoxSample.fxml"));
VBox root = loader.load();
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
vbsc = loader.getController();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
/* VBoxController */
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
public class VBoxSampleController {
#FXML
// The reference of inputText will be injected by the FXML loader
public TextField inputText;
// The reference of outputText will be injected by the FXML loader
#FXML
private TextArea outputText;
// location and resources will be automatically injected by the FXML loader
#FXML
private URL location;
#FXML
private TabPane mainPane;
#FXML
private ResourceBundle resources;
// Add a public no-args constructor
public VBoxSampleController() {
}
#FXML
private void initialize() {
}
#FXML
private void printOutput() {
outputText.setText(inputText.getText());
}
}
<!-- FXML -->
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextArea?>
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="VBoxSampleController">
<children>
<Label alignment="CENTER_LEFT" cache="true" cacheHint="SCALE" prefHeight="30.0" prefWidth="200.0" text="Please insert Your Input here:" textAlignment="LEFT" />
<TextField fx:id="inputText" prefWidth="100.0" />
<Button alignment="CENTER_RIGHT" contentDisplay="CENTER" mnemonicParsing="false" onAction="#printOutput" text="OK" textAlignment="CENTER" />
<Label alignment="CENTER_LEFT" cache="true" cacheHint="SCALE" prefHeight="30.0" prefWidth="200.0" text="Your Input:" textAlignment="LEFT" />
<TextArea fx:id="outputText" prefHeight="100.0" prefWidth="200.0" wrapText="true" />
</children>
</VBox>
I think this happens because you're trying to modify a static variable from a non-static method:
#Override
public void start (Stage primaryStage) { // non-static method
vbsc = loader.getController(); // vbsc is a static variable
}
If I'm guessing right, you're trying to dynamically update an FXML field when the application starts. So you can simply achieve this by using controller's initialize() method:
public class VBoxSampleController {
#FXML // I suggest you to always keep class variable
private TextField inputText; // field private or non-public to prevent
// unauthorized modifications
...
#FXML
public void initialize() {
String text = "TEST"; // or any other at-runtime generated string
inputText.setText(text);
}
...
}
public class Main extends Application {
//controllers
static HomeController homeController;
static StageController stageController;
//...
}
In the initialize methode of your HomeControllers class write :
homeController = this;
and in the initialize methode of your StageController class write :
StageController = this;
Your problem appears to stem from the fact you're doing your update on the main thread and not on the platform thread. There are two problems here.
You should always make ui changes on the platform thread.
You are executing the UIManager.vbsc.inputText.setText("TEST"); before the launch method.
Try.
Platform.runLater( () -> UIManager.vbsc.inputText.setText("TEST") );
Related
I am trying to use multiple fxml files in an application I am making, and in doing some research, I found that using Custom Controllers for the fxml files is the best approach towards doing this type of application.
I followed an Oracle Docs tutorial on "Mastering FXML" and set the root and controller as "this" in the CustomController.java file.
The problem arises when intellij discovers there is no controller specified in the fxml file for the onAction handler, while I am specifying the controller programmatically.
tester.java
package task01;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class tester extends Application
{
#Override
public void start(Stage stage) throws Exception
{
CustomController customController = new CustomController();
customController.getStylesheets().add("/task01/stylize.css");
stage.setScene(new Scene(customController,1920,1080));
stage.setTitle("Seneca ATM Program");
stage.setWidth(1920);
stage.setHeight(1080);
stage.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
CustomContoller.java
package task01;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.fxml.*;
import javafx.scene.layout.Pane;
import java.io.IOException;
public class CustomController extends GridPane
{
#FXML
private Pane viewableContent;
#FXML
private Button vigilanteButton;
public CustomController()
{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("root.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try
{
fxmlLoader.load();
} catch (IOException exception)
{
throw new RuntimeException(exception);
}
}
#FXML
private void vigilanteAction(ActionEvent actionEvent)
{
System.out.println("Hello, World");
}
}
root.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.GridPane?>
<?import task01.MainMenuController?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.Pane?>
<fx:root type="javafx.scene.layout.GridPane" xmlns:fx="http://javafx.com/fxml" alignment="CENTER">
<ImageView fitWidth="229.67" fitHeight="149.67" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.halignment="CENTER">
<Image url="/task01/logo.png"/>
</ImageView>
<Pane fx:id="viewableContent" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.halignment="CENTER">
<MainMenuController/>
</Pane>
<Button fx:id="vigilanteButton">Vigilante</Button>
</fx:root>
MainMenuController.java
package task01;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.io.IOException;
public class MainMenuController extends GridPane
{
private CustomController customController = new CustomController();
public MainMenuController()
{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("mainmenu.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try
{
fxmlLoader.load();
} catch (IOException exception)
{
throw new RuntimeException(exception);
}
}
#FXML
private VBox buttonSet;
#FXML
private HBox buttonSetOne;
#FXML
private HBox buttonSetTwo;
#FXML
private Button changePinButton;
#FXML
private Button accountInquiryButton;
#FXML
private Button withdrawMoneyButton;
#FXML
private Button depositMoneyButton;
#FXML
private Button balanceInquiryButton;
#FXML
private Button createAccountButton;
#FXML
private GridPane gridpane;
#FXML
public void createAccountAction(ActionEvent actionEvent)
{
}
}
mainmenu.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<GridPane fx:id="gridpane" alignment="CENTER" vgap="50" hgap="50" xmlns:fx="http://javafx.com/fxml">
<padding><Insets top="10" bottom="10" left="10" right="10"/></padding>
<VBox fx:id="buttonSet" spacing="25" GridPane.columnIndex="0" GridPane.rowIndex="1">
<HBox fx:id="buttonSetOne" spacing="25">
<Button styleClass="menuButton" fx:id="createAccountButton" onAction="#createAccountAction">Create account</Button>
<Button styleClass="menuButton" fx:id="changePinButton">Change PIN</Button>
<Button styleClass="menuButton" fx:id="accountInquiryButton">Account Inquiry</Button>
</HBox>
<HBox fx:id="buttonSetTwo" spacing="25">
<Button styleClass="menuButton" fx:id="withdrawMoneyButton">Withdraw Money</Button>
<Button styleClass="menuButton" fx:id="depositMoneyButton">Deposit Money</Button>
<Button styleClass="menuButton" fx:id="balanceInquiryButton">Balance Inquiry</Button>
</HBox>
</VBox>
</GridPane>
Here's the problem, you can bind a FXML file to a Controller from the controller, but when you do this the IDE doesn't know it until it's up and running. That's why the IDE causes you troubles. If you want to set the onAction handler you'll have to do it from the controller. You have to create a method like this and add the onAction listener to the button:
#FXML
public void initialize() {
createAccountButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// TODO Auto-generated method stub
createAccountAction(event);
}
});
}
Divide and conquer: break complex problems to smaller ones. Start with a simple setup like the following.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class tester extends Application
{
#Override
public void start(Stage stage) throws Exception
{
CustomController customController = new CustomController();
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("root.fxml"));
fxmlLoader.setController(customController);
Parent root = (Parent)fxmlLoader.load();
stage.setScene(new Scene(root,1920,1080));
stage.setTitle("Seneca ATM Program");
stage.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
//controller should be just that. It is not a pane
class CustomController /* extends GridPane */
{
#FXML
private Pane viewableContent;
#FXML
private Button vigilanteButton;
#FXML
private void vigilanteAction(ActionEvent actionEvent)
{
System.out.println("Hello, World");
}
}
root.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.GridPane?>
<!-- ?import task01.MainMenuController?-->
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.Pane?>
<GridPane xmlns:fx="http://javafx.com/fxml" alignment="CENTER">
<ImageView fitWidth="229.67" fitHeight="149.67" GridPane.columnIndex="0" GridPane.rowIndex="0"
GridPane.halignment="CENTER">
<Image url="task01/logo.png"/> <!-- todo: set correct path -->
</ImageView>
<Button fx:id="vigilanteButton" onAction="#vigilanteAction">Vigilante</Button>
</GridPane>
I hope this helps you to structure your application.
There's no way for the IDE to know about you setting the controller yourself somewhere in your java code. Autocompletion features and the like are off for this reason. The fact that the IDE shows these kind of "issues" is unfortunate in this case, but they could indicate an issue that can only be recognized at runtime.
In your case assuming you don't have any typos in your fxml, it's safe to ignore these warnings.
You could of course simply add the fx:controller attribute temporarily while editing the fxml and remove it when you're done.
Another way of dealing with this issue would be specifying the fx:controller attribute in the fxml and setting the controllerFactory property instead of the controller property for the loader. I don't really like this approach though:
<GridPane fx:id="gridpane" alignment="CENTER" vgap="50" hgap="50" xmlns:fx="http://javafx.com/fxml" fx:controller="task01.MainMenuController">
...
public MainMenuController() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("mainmenu.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setControllerFactory(clazz -> {
if (!clazz.isInstance(this)) {
throw new IllegalArgumentException(String.format("class of controller (%s) not assignable to class specified in fxml (%s)", this.getClass().getName(), clazz.getName()));
}
return this;
});
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
If you don't want to check assignment compatibility between the controller type specified in the fxml and the type of the controller you specify, the controllerFactory could be simplified to clazz -> this.
If you want to use this approach for multiple classes I strongly recommend creating a class for the controller factory to avoid repetitions, assuming you want to do the check...
i have tried all solution in youtube and google but none of them work. The problem is when i want to switch to new scene along sending data to another scene i got error saying
Null pointer Exception
public class FirstController implements Initializable {
#FXML private TextField textField;
#FXML private Button btn;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
// TODO Auto-generated method stub
}
public void onClick(ActionEvent event){
//switching to new scene
FXMLLoader loader=new FXMLLoader();
loader.setLocation(getClass().getResource("/application/Second.fxml"));
try{
loader.load();
}catch(Exception e){ }
SecondController sn=loader.getController();
sn.setText(textField.getText());
Parent p=loader.getRoot();
Stage window=new Stage();
window.setScene(new Scene(p));
window.setTitle("dfd");
window.show();
}
}
main.fxml:
<AnchorPane prefHeight="450.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Main">
<children>
<Button fx:id="btn" layoutX="285.0" layoutY="264.0" mnemonicParsing="false" onAction="#onClick" text="Button" />
<TextField fx:id="textField" layoutX="237.0" layoutY="213.0" />
</children>
</AnchorPane>
Second controller :
public class SecondController implements Initializable {
#FXML private Label labelField;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
// TODO Auto-generated method stub
}
public void setText(String name){
this.labelField.setText(name);
}
}
Second.fxml:
`<AnchorPane prefHeight="450.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label fx:id="labelField" layoutX="297.0" layoutY="208.0" text="Label" />
</children>
</AnchorPane>
Your second FXML file is missing the fx:controller attribute. Therefore, when you load it, there is no controller, and so loader.getController() returns null.
Consequently, when you call sn.setText(...), you get a null pointer exception.
Just add the missing fx:controller attribute to the second FXML file. Assuming that SecondController is in the application package, this will look like:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.Label?>
<AnchorPane prefHeight="450.0" prefWidth="650.0" fx:controller="application.SecondController" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label fx:id="labelField" layoutX="297.0" layoutY="208.0" text="Label" />
</children>
</AnchorPane>
Also note that it's very bad practice to silently "squash" exceptions, by using
try {
/* ... */
} catch (Exception e) { }
It's especially bad if the ensuing code depends on the code in the try block succeeding. In particular, in this case if your call to load() results in an exception (which may happen for a multitude of reasons, including the FXML not being found or being invalid, the fx:id and field names, or types,
not matching, method names for handlers not matching or having the wrong parameters, etc.), then the load method will silently fail and the controller will not be set in the loader. This will again result in sn being null.
Here's a complete example using your code, but with the other errors fixed as well as the missing fx:controller attribute inserted:
application/Main.java:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(FXMLLoader.load(getClass().getResource("First.fxml")));
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
application/First.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<AnchorPane prefHeight="450.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.FirstController">
<children>
<Button fx:id="btn" layoutX="285.0" layoutY="264.0" mnemonicParsing="false" onAction="#onClick" text="Button" />
<TextField fx:id="textField" layoutX="237.0" layoutY="213.0" />
</children>
</AnchorPane>
application/FirstController.java:
package application;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
public class FirstController implements Initializable {
#FXML private TextField textField;
#FXML private Button btn;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
// TODO Auto-generated method stub
}
public void onClick(ActionEvent event) throws Exception {
//switching to new scene
FXMLLoader loader=new FXMLLoader();
loader.setLocation(getClass().getResource("/application/Second.fxml"));
loader.load();
SecondController sn=loader.getController();
sn.setText(textField.getText());
Parent p=loader.getRoot();
Stage window=new Stage();
window.setScene(new Scene(p));
window.setTitle("dfd");
window.show();
}
}
application/Second.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.Label?>
<AnchorPane prefHeight="450.0" prefWidth="650.0" fx:controller="application.SecondController" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label fx:id="labelField" layoutX="297.0" layoutY="208.0" text="Label" />
</children>
</AnchorPane>
application/SecondController.java
package application;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
public class SecondController implements Initializable {
#FXML private Label labelField;
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
// TODO Auto-generated method stub
}
public void setText(String name){
this.labelField.setText(name);
}
}
I'm trying to set up a simple example for my program using JavaFX.
What I want to have is a Controller with a main.fxml; then the main.fxml will have a TabbedPane as root, with 2 tabs (tab1.fxml and tab2.fxml), each with its controller (Tab1Controller and Tab2Controller).
Now this might be the prolem, yet I don't see why it would be a problem:
Tab1Controller and Tab2Controller both extend Controller; because they share various fields which are manipulated, for example a bottom status bar which needs constant updates.
So far I managed to have working all controllers and .fxml.
When I try to set up the text of the label in the Controller from one of the subclasses it just points to null, yet the label is initialised in the gui with its default text..
Compiles correctly and lblController seems to be correctly linked to its fx:id in main.fxml.
Any help/links are much appreciated.
I've been looking at various posts but the only one getting close to what I need it this one:
JavaFX 8, controllers inheritance
Main:
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("../view/main.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 600, 700));
primaryStage.show();
}
}
Main controller:
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class Controller {
#FXML
public Label lblController;
public Controller() {
System.out.println("CONTROLLER MAIN - CONSTRUCTOR");
}
#FXML
public void initialize() {
System.out.println("CONTROLLER MAIN - INITIALIZER");
}
protected void setSts(String sts) {
lblController.setText(sts);
}
}
main.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx/8.0.60"
fx:controller="sample.Controller">
<BorderPane>
<center>
<TabPane tabClosingPolicy="UNAVAILABLE">
<Tab text="Tab 1">
<AnchorPane>
<!--FOR fx:id HAS TO HAVE THE SAME NAME BUT IN LOWER CASE AS THE .fxml-->
<fx:include fx:id="tab1" source="tab/tab1.fxml"/>
</AnchorPane>
</Tab>
<Tab text="Tab 2">
<AnchorPane>
<fx:include fx:id="tab2" source="tab/tab2.fxml"/>
</AnchorPane>
</Tab>
</TabPane>
</center>
<bottom>
<AnchorPane>
<Label fx:id="lblController" text="Controller Test"/>
</AnchorPane>
</bottom>
</BorderPane>
</AnchorPane>
Tab1Controller
package sample.ctrTab;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import sample.Controller;
public class Tab1Controller extends Controller {
#FXML
Label lbl1;
#FXML
TextField txt1;
#FXML
Button btn1Save, btn1Load;
public Tab1Controller() {
super();
System.out.println("CONTROLLER TAB 1 - CONSTRUCTOR");
}
#FXML
void btn1Save() {
System.out.println("CONTROLLER TAB 1 - SAVE CLICK");
// lblController.setText("ANYTHING"); //NULL POINTER HERE
// setSts(txt1.getText()); //OR HERE
}
#FXML
void btn1Load() {
System.out.println("CONTROLLER TAB 1 - LOAD CLICK");
}
protected void setSts(String sts) {
super.setSts(sts);
}
}
tab1.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx/8.0.60" fx:controller="sample.ctrTab.Tab1Controller">
<children>
<Label fx:id="lbl1" layoutX="100" layoutY="50" text="Def tab 1"/>
<TextField fx:id="txt1" layoutX="100" layoutY="70"/>
<Button fx:id="btn1Save" onAction="#btn1Save" layoutX="100" layoutY="140" mnemonicParsing="false"
text="Save text"/>
<Button fx:id="btn1Load" onAction="#btn1Load" layoutX="200.0" layoutY="140" mnemonicParsing="false"
text="Send to tab 2"/>
</children>
</AnchorPane>
The fields may be present in the controller classes, however FXMLLoader still creates new controller instances for every fxml loaded given these fxmls. Since tab1.fxml does not contain a Label tag with fx:id="lblController" as attribute, the lblController field is never injected to the Tab1Controller instance.
You would be better off not extending Controller, but passing a reference of it to the child controllers:
public class Controller {
#FXML
public Label lblController;
#FXML
private Tab1Controller tab1Controller;
#FXML
private Tab2Controller tab2Controller;
public Controller() {
System.out.println("CONTROLLER MAIN - CONSTRUCTOR");
}
#FXML
public void initialize() {
System.out.println("CONTROLLER MAIN - INITIALIZER");
tab1Controller.initParentController(this);
tab2Controller.initParentController(this);
}
public void setSts(String sts) {
lblController.setText(sts);
}
}
public class Tab1Controller {
#FXML
Label lbl1;
#FXML
TextField txt1;
#FXML
Button btn1Save, btn1Load;
public Tab1Controller() {
System.out.println("CONTROLLER TAB 1 - CONSTRUCTOR");
}
private Controller parentController;
public void initParentController(Controller controller) {
parentController = controller;
}
#FXML
void btn1Save() {
System.out.println("CONTROLLER TAB 1 - SAVE CLICK");
parentController.lblController.setText("ANYTHING");
parentController.setSts(txt1.getText());
}
#FXML
void btn1Load() {
System.out.println("CONTROLLER TAB 1 - LOAD CLICK");
}
}
Note that for the child controllers you could actually use a abstract superclass implement the initParentController method only once.
I'm really struggling to understand JavaFX controllers, my aim is to write to a TextArea to act as a log.
My code is below, but I want to be able to change values ETC from another class that I can call when needed. I have tried to create a controller class that extents Initializable but i cant get it to work. Could some one steer me in the correct direction?
I want to move the #FXML code at the bottom to another class and it update the Scene.
package application;
import javafx.event.ActionEvent;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
Parent root = FXMLLoader.load(getClass().getResource("Root.fxml"));
Scene scene = new Scene(root,504,325);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
public Thread thread = new Thread(new webimporter());
#FXML
public Label runningLabel;
#FXML
public TextArea txtArea;
#FXML
void runClick(ActionEvent event) throws IOException{
changeLabelValue("Importer running...");
thread.start();
}
#FXML
protected void stopClick(ActionEvent event){
changeLabelValue("Importer stopped...");
thread.interrupt();
}
#FXML
void changeLabelValue(String newText){
runningLabel.setText(newText);
}
void changeTextAreaValue(String newText1){
txtArea.setText(newText1);
}
}
Don't make the Application class a controller. It's a sin. There are other questions and answers which address this, but my search skills cannot find them at this time.
The reason it is a sin is:
You are only supposed to have one Application instance, and, by default, the loader will make a new instance, so you end up with two application objects.
Referencing the member objects is confusing, because the original launched application doesn't have the #FXML injected fields, but the loader created application instance does have #FXML inject fields.
Also, unrelated advice: Don't start trying to write multi-threaded code until you have the application at least working to the extent where it displays your UI.
A multi-threaded logger for JavaFX is in the answer to Most efficient way to log messages to JavaFX TextArea via threads with simple custom logging frameworks, though unfortunately it is not straight-forward in its implementation and comes with little documentation.
textlogger/Root.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="400.0" spacing="10.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="textlogger.ImportController">
<children>
<HBox alignment="BASELINE_LEFT" minHeight="-Infinity" minWidth="-Infinity" spacing="10.0">
<children>
<Button mnemonicParsing="false" onAction="#run" text="Run" />
<Button mnemonicParsing="false" onAction="#stop" text="Stop" />
<Label fx:id="runningLabel" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</HBox>
<TextArea fx:id="textArea" editable="false" prefHeight="200.0" prefWidth="200.0" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</VBox>
textlogger.ImportController.java
package textlogger;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import java.io.IOException;
public class ImportController {
#FXML
private Label runningLabel;
#FXML
private TextArea textArea;
private WebImporter importer;
#FXML
void run(ActionEvent event) throws IOException {
changeLabelValue("Importer running...");
if (importer == null) {
importer = new WebImporter(textArea);
Thread thread = new Thread(
importer
);
thread.setDaemon(true);
thread.start();
}
}
#FXML
void stop(ActionEvent event){
changeLabelValue("Importer stopped...");
if (importer != null) {
importer.cancel();
importer = null;
}
}
private void changeLabelValue(String newText){
runningLabel.setText(newText);
}
}
textlogger.WebImporter.java
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.control.TextArea;
import java.time.LocalTime;
public class WebImporter extends Task<Void> {
private final TextArea textArea;
public WebImporter(TextArea textArea) {
this.textArea = textArea;
}
#Override
protected Void call() throws Exception {
try {
while (!isCancelled()) {
Thread.sleep(500);
Platform.runLater(
() -> textArea.setText(
textArea.getText() + LocalTime.now() + "\n"
)
);
}
} catch (InterruptedException e) {
Thread.interrupted();
}
return null;
}
}
textlogger.TextLoggingSample.java
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TextLoggingSample extends Application {
#Override
public void start(Stage stage) {
try {
FXMLLoader loader = new FXMLLoader();
Parent root = loader.load(
getClass().getResourceAsStream(
"Root.fxml"
)
);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
This question already has an answer here:
javafx 8 compatibility issues - FXML static fields
(1 answer)
Closed 8 years ago.
I am using JavaFX 8 and FXML in this project and trying to update my text area with the results from other classes within the program.
The Text Area is defined in the FXML document as such:
<TextArea fx:id="feedBack" editable="false" focusTraversable="false"
layoutX="203.0" layoutY="32.0" prefHeight="205.0" prefWidth="308.0"
wrapText="true"/>
It is called in the controller here, note that originally it was a "private" item but to make it work with the classes I made it public:
#FXML
public static TextArea feedBack;
EDIT: It is worth noting that when the TextArea is identified as "private" I have no issue getting the set/append text method to work but this does not allow me to use the text area in other classes, which is what I need to do.
However now when I try to do either appendText() or setText(), such as follows:
feedBack.setText("Connection Made");
I am given a null point exception. Why is this? I have made this work in JavaFX 7 with FXML by doing the same thing and it works fine. What am I missing to make this work?
EDIT, test/proof of brokenness with complete tiny program.
I did not make another class for this one as the textarea won't work in the control anyway.
FXML:
<?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="200" prefWidth="320" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="textareatester.FXMLDocumentController">
<children>
<Label fx:id="label" layoutX="126" layoutY="120" minHeight="16" minWidth="69" />
<TextArea fx:id="feedback" layoutX="61.0" prefHeight="200.0" prefWidth="200.0" wrapText="true" />
<Button fx:id="dostuff" layoutX="261.0" layoutY="62.0" mnemonicParsing="false" onAction="#handleButtonAction" text="Button" />
</children>
</AnchorPane>
Main Method:
package textareatester;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class TextAreaTester 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);
}
}
Controller Class:
package textareatester;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
public class FXMLDocumentController implements Initializable {
#FXML
private Label label;
#FXML
public static TextArea feedback;
#FXML
private Button dostuff;
#FXML
private void handleButtonAction(ActionEvent event) {
System.out.println("You clicked me!");
feedback.setText("Hello World!"); ///<-- this is what gives me the NPE
}
#Override
public void initialize(URL url, ResourceBundle rb) {
// not touching anything
}
}
Solution for those who are curious:
I ended up simply returning my results strings from my methods and then appending them to the TextArea.
The problem is the static at the definition of the TextArea. Since static attributes are bound to the class and not the object, variable feedback is never initiated and thus is null when accessed in the EventHandler.