Null exception when I try to access a controller that I passed using JavaFX - javafx

I have a root layout with multiples Tabs. From my main App I open the root layout. There I included multiple FXMLs with their own controllers.
I am trying to pass the main controller to one of the Tabes controller.
The issue I am having, everything works as expected, but I get a null exception when I try to click on an action button from the new tab.
RootLayout FXML
<fx:indlue fx:id="myNewTabAnchorPane" source="NewTabFXML.fxml"/>
RootLayout Controller
#FXML NewTabController newTabController;
mainTabPane.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Tab>(){
#override
public void change(ObservableValue<? extends Tab> observable, Tab oldValue, Tab newValue){
if(newValue == myTab){
newTabController.setMyRootController(this);
}
NewTabController
public void setMyRootController(RootController rootController){
this.rootController = rootController;
System.out.println(rootController.getID); // this prints fine
}
However, if I trigger this action I get blank from the same controller
#FXML
public void createAction(ActionEvent event) throws IOException{
System.out.println(rootController.getID); // with this I get null value.
}
What am I missing?

Here is the problem:
#FXML NewTabController newTabController;
It should be myNewTabAnchorPaneController which is not a partially lowercased class name, but fx:id + Controller concatenation.

Related

How to access JavaFx node elements values in methods of FXML Controller class?

It seems values of nodes (textfields, checkboxes, etc...) can only be accessed via an event listener on them. Why am I saying that ?
- First I'm new to JavaFx
- Second : below is the scenario I'm using
public class FXMLDocumentController implements Initializable {
#FXML
private JFXCheckBox myCheckBox;
#FXML
private JFXButton myButton;
#Override
public void initialize(URL url, ResourceBundle rb) {
myCheckBox.addEventHandler(MouseEvent.MOUSE_CLICKED, (e)->{
if(myCheckBox.isSelected()){
System.out.Println("Printed from event handler");
});
myButton.addEventHandler(MouseEvent.MOUSE_CLICKED, (e) -> {
myMethod();
});
}
public void myMethod(){
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
for(Node node : root.getChildrenUnmodifiable()){
if(node.getId().equals("myCheckBox"){
JFXCheckBox myChk = (JFXCheckBox) node;
if(myChk.isSelected()){
System.out.Println("Printed from my inner method");
}
System.out.Println(myChk.isSelected());
}
}
}
}
What happend is when I check the box, the event handler does how supposed. It prints Printed from event handler. But right afetr that, when clicking myButton, the myMethod always prints false.
When I use a class variable indicating if the checkbox is selected and read it in myMethod, it says the check box has been checked ...
What is going on ?
The same thing happens with textfields ...

How to Change label text from loaded fxml

public class ProjectxController implements Initializable {
#FXML
private AnchorPane LandingPane;
#Override
public void initialize(URL url, ResourceBundle rb) {
// TODO
}
#FXML
private void onLoad(ActionEvent event) throws IOException{
pane = FXMLLoader.load(getClass().getResource("connectedPage.fxml"));
LandingPane.getChildren().setAll(pane);
}
Note: Having two fxml files Frontpage.fxml and ConnectedPage.fxml with one controller projectxController i.e coded above
Frontpage.fxml has one button that will load connectedPage.fxml.
Connectedpage.fxml has one label
Now I want to set the Label text after connectedpage.fxml is loaded
public class ProjectX extends Application {
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("FrontPage.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
When you create you fxml document you have to give a unique id for you Label:
The same you have done for the AnchorPane.
Example
#FXML
private Label myLabel;
Then inside the initialize method of your fxml controller class you can modify it as you want.
If you problem is how to access the Label from another controller you can use a get method or pass an instance of the controller you need . All that after the fxml has successfully loaded .
I recommend also that you use different controllers for different fxml files , cause it is more clear .
Have a search on fxml loading techniques and how to connect different controllers over the web and you will find what you need .

Event handling in multiple FXML

I have made this UI (see image) in FXMl. The Top.fxml, Bottom.fxml, Left.fxml, Right.fxml are put under a Main.fxml and arranged via BorderPane. The Left.fxml and Right.fxml are in SplitPane.
What I am trying to do is, when the button from Top.fxml clicked it should execute the function in Controller file. Below is the code from the controller file. I am getting java.lang.NullPointerException on list.setItems(listItems); .This also happens when I put list.setItems(listItems); in the initialize method.
#FXML
private Button btn;
#FXML
private ListView<String> list;
ObservableList<String> listItems = FXCollections.observableArrayList("A","B","C","D");
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
}
public void addToList(ActionEvent event) {
list.setItems(listItems);
}
The 'list' variable is null if the code has not been injected correctly, i.e. Top.fxml has not been loaded correctly.
Please provide the code that load the fxml file if you cannot solve the issue.

How to call several controls when creating tabs dinamically

I am creating tabs dinamically and i am using the same fxml file for all of them. So, all type of controls that I've included has de same "fx:id". I use this: "#FXML TextField textField". How could I use the TextField of the second tab, the TextField of the first tab, etc?
#Controller
public class MascotaTabControllerImpl implements MascotaTabController
{
private AnchorPane anchorPane;
private Tab tab;
private Mascota mascota;
#FXML
private ComboBox<String> comboMascota;
#FXML
private ComboBox<String> comboTamano;
#FXML
private TextField fieldNombreMascota;
#FXML
private RadioButton radioAlergiaSi;
#FXML
private RadioButton radioAlergiaNo;
#FXML
private TextField fieldRaza;
#FXML
private TextField fieldPeso;
#FXML
private ComboBox<String> comboSexo;
#FXML
private ComboBox<String> comboAgresividad;
#FXML
private TextArea areaObservaciones;
#FXML
private Button buttonEditar;
#FXML
private Button buttonCancelar;
#Override
public void inicializacionFxmlFile(TabPane tabPane, Collection<Mascota> mascotas)
{
try
{
for(Mascota mascota : mascotas)
{
anchorPane = new AnchorPane();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/mascotaTab.fxml"));
loader.setController(this);
anchorPane.getChildren().setAll(loader.load());
tab = new Tab();
tab.setContent(anchorPane);
tabPane.getTabs().add(tab);
tab.setText(mascota.getNombre());
fieldNombreMascota.setText(mascota.getNombre());
fieldRaza.setText(mascota.getRaza());
comboSexo.setValue(mascota.getSexo());
fieldPeso.setText(String.valueOf(mascota.getPeso()));
comboTamano.setValue(mascota.getTamano());
comboAgresividad.setValue(mascota.getAgresividad());
areaObservaciones.setText(mascota.getObservaciones());
mascota.setNombre(fieldNombreMascota.getText());
}
tabSelected(tabPane, mascotas);
buttonEditar.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
mascota.setNombre(fieldNombreMascota.getText());
mascota.setRaza(fieldRaza.getText());
mascota.setSexo(comboSexo.getValue());
mascota.setPeso(Float.parseFloat(fieldPeso.getText()));
mascota.setTamano(comboTamano.getValue());
mascota.setAgresividad(comboAgresividad.getValue());
mascota.setObservaciones(areaObservaciones.getText());
clienteService.actualizarMascota(mascota);;
}
});
buttonCancelar.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
return;
}
});
}
catch(IOException ioe)
{
System.out.println(ioe.getMessage());
}
}
Edit: imagine "for" statement runs twice. How could call controls (TextFields, ComboBoxes, etc.) of the first tab?
For example, when "for" statement runs the first time "fieldNombreMascota" reference is "id=110" but when "for" statement runs the second time, the same TextField reference is "id=113". So how can I call them with annotation #FXML?
Because you call
loader.setController(this);
inside your loop, every time you call loader.load() you will reuse the same object as the controller for the FXML you are loading. This means each time you go through that for loop, the #FXML-injected fields will get redefined. So by doing this, you essentially make it impossible to access any FXML fields except the ones from the last iteration of the loop.
You must not call loader.setController(this). The "standard" way to combine FXML files and controllers is to use an fx:controller attribute in the root element of the FXML file, specifying a class name. This will cause the FXMLLoader to create a new instance of that class every time you call loader.load(). If there is some other problem you are trying to solve by attempting the non-standard way you are doing things, then the solution you are attempting is not the right approach.
If you organize your code well, the FXML creates a controller instance, and there should be no need whatsoever to access that controller externally. You may need some of your controllers to access a shared data model of some kind, but this is certainly not the right approach to achieve that.

javafx fxml is null outside initialize()

In this code:
public class ESM extends Application {
private Stage primaryStage;
#FXML
private ToolBar mainToolBar;
#Override
public void start(final Stage stage) throws Exception {
try{
this.primaryStage = stage;
Parent root = FXMLLoader.load(getClass().getResource("/nz/co/great_ape/esm3/main_window.fxml"));
Scene scene = new Scene(root, 800, 700);
// Setup main stage to be full screen, no min or max buttons.
// TODO: How will this handle multiple screens? Apparently not well :-(
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
primaryStage.setX(bounds.getMinX());
primaryStage.setY(bounds.getMinY());
primaryStage.setWidth(bounds.getWidth());
primaryStage.setHeight(bounds.getHeight());
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setTitle("ESM three");
primaryStage.setScene(scene);
primaryStage.show();
System.out.println("This will fail because mainToolBar is null. Why?");
assert mainToolBar != null : "fx:id=\"mainToolBar\" was null check your FXML ";
} catch (Exception ex) {
Logger.getLogger(ESM.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* Use initialize() to setup widgets from scenebuilder files, it is
* called by FXMLLoader.
*/
#FXML
public void initialize(){
System.out.println("initialize() But when here all is good and mainToolBar is a ToolBar.");
assert mainToolBar != null : "fx:id=\"mainToolBar\" was null check your FXML ";
}
/**
* The main() method is ignored in correctly deployed JavaFX application.
* main() serves only as fallback in case the application can not be
* launched through deployment artifacts, e.g., in IDEs with limited FX
* support.
*
* #param args The command line arguments.
*/
public static void main(String[] args) {
launch(args);
}
}
I cant see why it's got a value in the initialise() but in the start it's null. When debuging it's clear that initiialize() is called by FXMLLOader from inside start()
I was going to post the fxml but it does not seem to work as nothig shows in the preview. Any way, it's a real basic file, a BordePane and a ToolBar.
Any clues?
Always create a new class for your FXML Controller, don't try to reuse an Application class as a Controller class.
An Application instance is created by the JavaFX application launcher.
A Controller instance is created by the JavaFX FXML loader.
You don't supply the FXML that you use, but I am going to guess that it has it's Controller class erroneously set to be your application class.
So in your code, what happens is:
An instance of the application is created when you run the program (via the launch method).
In your application start method, you invoke the FXMLLoader, which instantiates a new Controller (in your case a new instance of the application class).
The FXMLLoader injects the #FXML tagged members into the new application object and invokes the initialize on the new object.
But your original application object doesn't know anything about the new application object and hence doesn't have a menu bar set in it.
In summary, to fix this:
Create a new controller class that the FXMLLoader can instantiate.
Change your fxml to reference the new controller class.
If your application really needs to reference the controller, then you can use the getController method on the FXML loader and in your controller class provide public methods to retrieve required elements (like your menu bar). See my answer to Passing Parameters JavaFX FXML for some more examples of this method.
import javafx.scene.control.ToolBar;
import javafx.fxml.FXML;
public class ESMController {
#FXML
private ToolBar mainToolBar;
public ToolBar getMainToolBar() { return mainToolBar; }
#FXML
public void initialize(){
assert mainToolBar != null : "fx:id=\"mainToolBar\" was null check your FXML ";
}
}

Resources