-1

I'm trying to set a JavaFx scene where a nested FlowPane can be changed during the runtime. I want to be able to new elements to the Pane, and have them rendered out.

Main

package renderable;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
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.FlowPane;
import javafx.stage.Stage;

public class Main extends Application implements EventHandler<ActionEvent{
Button button;
@FXML public FlowPane flowPane;

public static void main(String[] args){
    launch(args);
}

@Override
public void start(Stage primaryStage) throws Exception {
    FXMLLoader loader = new FXMLLoader();
    Parent root = loader.load(getClass().getResource("/renderable/ClientRender.fxml"));
    Scene scene = new Scene(root, 1000, 720);
    primaryStage.setTitle("The Game");
    primaryStage.setScene(scene);
    primaryStage.show();
    loader.getController().updateFlowPanel();
    primaryStage.show();
}

public void updateFlowPanel(){
    flowPane.getChildren().add(button);
}

@Override
public void handle(ActionEvent event) {
    if (event.getSource() == button)
        System.out.println("The first button was pushed.");
    }
}

RenderManager

public class RenderManager implements EventHandler<ActionEvent>{
cCard tableCards[];
cHand thisPlayerHand;
@FXML public FlowPane flowPane;
Button button;

public RenderManager() {
}
public void updateFlowPanel(){
    flowPane.getChildren().add(button);
}

@Override
public void handle(ActionEvent event) {
    if (event.getSource() == button)
        System.out.println("The first button was pushed.");
}
}

ClientRender.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.*?>
<?package renderable?>
<?import javafx.geometry.*?>

<FlowPane id="flowPane" fx:id="flowPane" hgap="4.0" nodeOrientation="LEFT_TO_RIGHT" prefHeight="480.0" prefWidth="600.0" prefWrapLength="1400.0" style="-fx-background-color: #006600;" vgap="4.0" BorderPane.alignment="CENTER" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="renderable.Main"/>

I am unable to correctly reference the RenderManager object associated with my current root to then run it's `updateFlowPanel()' method.

How do I correctly add elements to the Pane during runtime?

James_D
  • 201,275
  • 16
  • 291
  • 322
  • 1. It's not really clear why you seem to have two `Application` classes. The controller class should not be a subclass of `Application` subclass. 2. If you want to change the content of the existing `FlowPane` that is defined in the FXML, you shouldn't be creating a new `FlowPane`. Just make the changes to the existing `FlowPane`. 3. You probably need to create a [MCVE] here so we can see how all the pieces fit together. Create the simplest possible example of what you are trying to do, and edit your question to include it in its entirety. – James_D Apr 03 '18 at 23:16
  • I don't understand how to make changes to the existing FlowPane. When I try to reference it using the fxid, I get a NullPointerException. – Ryan Desmond Apr 03 '18 at 23:36
  • 2
    Then you're probably not calling `fillFlowPane()` on the actual controller (you're probably calling it on another instance of `RenderManagerController`). But it's impossible to tell from what you've posted here. We can't even tell which class you're actually running as the main class (again, why are there two `Application` classes????). You need to post a [MCVE]. – James_D Apr 03 '18 at 23:38
  • 1
    Also, ``? I'm pretty certain that is not valid FXML. – James_D Apr 03 '18 at 23:41
  • You need to call `updateFlowPanel()` on the controller: you are calling it on the wrong instance of `Main`. Use a separate class for the controller (*not* the `Application` class). You should never use the `Application` class as the controller class. – James_D Apr 04 '18 at 00:45
  • See, e.g. https://stackoverflow.com/questions/33303167/javafx-can-application-class-be-the-controller-class – James_D Apr 04 '18 at 00:51
  • The changes you made *after* I had posted the answer mean this no longer compiles. Please change it back to the previous version. – James_D Apr 04 '18 at 01:12
  • Provided an answer to the new version as well. Please stop changing the question. – James_D Apr 04 '18 at 01:22
  • Is there some part of "please stop changing the question" that you're not getting? In the current version (revision 6), the stack trace you posted is not the one you get from your code. The current version throws a `ClassCastException`, not a `NullPointerException`. I am reverting this back to a version that is consistent with my answer. (I am getting really tired of playing this game of cat-and-mouse where I answer your question, and then you change it so my answer is no longer consistent.) If you have a *different* question, post a new question. – James_D Apr 04 '18 at 01:36
  • StackOverflow refuses to let me ask a new question the same day apparantly. I am trying to get a single answer of how to implement this solution, but every implementation I've tried from you has not worked. I'm sorry for not communicating clearly, but all I want is this one program artifact to work correctly. – Ryan Desmond Apr 04 '18 at 01:43
  • So ask a new question tomorrow. Here's a radical idea: try to understand the (free) help you have been given and try to do some troubleshooting yourself. – James_D Apr 04 '18 at 01:44
  • Well, thank you for the help anyway. I was able to get it to work after changing the resource load to the FXMLLoader declaration, rather than the Parent object declaration, and then calling `load` with no parameters. If you would be willing to help one last time, do you know why this makes a difference? It seems like it would correctly initialize regardless. – Ryan Desmond Apr 04 '18 at 01:57
  • You want me to repeat the same exact explanation that is under the "Edit" section of my answer? Or can you not be bothered to scroll down and read it? – James_D Apr 04 '18 at 01:58

1 Answers1

0

The reason you are getting a null pointer exception is that the @FXML-injected field flowPane is only initialized in the controller. You are calling updateFlowPanel() on an instance of Main that is not the controller, hence flowPane is null in that instance, and consequently flowPane.getChildren() throws a null pointer exception.

Specifically: Application.launch() creates an instance of the Application class and calls start() on that instance (among other things).

Calling FXMLLoader.load(), by default, creates an instance of the class specified by the fx:controller attribute in the FXML root element. Any @FXML-injected fields are initialized in that instance, event handler methods are called on that instance, and the initialize() method is called on that instance.

Since you use the same class Main for both the Application and the controller class - which is always a mistake, you end up with two instances of Main. You call updateFlowPanel() from start(), i.e. you call it on the instance created by Application.launch(); however flowPane is only initialized in the instance created by the FXMLLoader.

You should use a separate controller class. I.e. do

package renderable;
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("/renderable/ClientRender.fxml"));
        Scene scene = new Scene(root, 1000, 720);
        primaryStage.setTitle("The Game");
        primaryStage.setScene(scene);
        primaryStage.show();
        primaryStage.show();
    }

}

and then define a controller class:

package renderable ;

import javafx.fxml.FXML ;
import javafx.scene.layout.FlowPane ;
import javafx.scene.control.Button ;

public class Controller {

    @FXML
    private FlowPane flowPane ;

    public void updateFlowPanel(){
        Button button = new Button("...");
        flowPane.getChildren().add(button);
    }


}

and update the FXML to refer to it:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?package renderable?>
<?import javafx.geometry.*?>

<FlowPane id="flowPane" fx:id="flowPane" hgap="4.0" 
    nodeOrientation="LEFT_TO_RIGHT" prefHeight="480.0" prefWidth="600.0" 
    prefWrapLength="1400.0" style="-fx-background-color: #006600;" vgap="4.0" 
    BorderPane.alignment="CENTER" xmlns="http://javafx.com/javafx/8" 
    xmlns:fx="http://javafx.com/fxml/1" 
    fx:controller="renderable.Controller"/>

Now you can either just call updateFlowPanel() in the initialize() method:

package renderable ;

import javafx.fxml.FXML ;
import javafx.scene.layout.FlowPane ;
import javafx.scene.control.Button ;

public class Controller {

    @FXML
    private FlowPane flowPane ;

    public void initialize() {
        updateFlowPanel();
    }

    public void updateFlowPanel(){
        Button button = new Button("...");
        flowPane.getChildren().add(button);
    }


}

or you can call it on the correct controller instance from your start() method:

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/renderable/ClientRender.fxml"));
        Parent root = loader.load();
        Controller controller = loader.getController();
        controller.updateFlowPanel();
        Scene scene = new Scene(root, 1000, 720);
        primaryStage.setTitle("The Game");
        primaryStage.setScene(scene);
        primaryStage.show();
        primaryStage.show();
    }

Edit:

The update you made to your question actually prevents the code from compiling. If you fix the compile error by replacing

loader.getController().updateFlowPanel();

with

loader.<Main>getController().updateFlowPanel();

then the (entirely different) issue you have created is that you create an instance of FXMLLoader:

FXMLLoader loader = new FXMLLoader();

but then instead of using that FXMLLoader instance to load the FXML, you call the static method load(URL):

Parent root = loader.load(getClass().getResource("/renderable/ClientRender.fxml"));

Since you are calling the static method, you never call load() on the FXMLLoader instance, and so its controller property is never initialized. Consequently, loader.<Main>getController() is null, and loader.<Main>getController().updateFlowPanel() throws a null pointer exception (entirely different to the null pointer exception you had in the previous version of your question; it is not even thrown from the same method).

If you want to reference the controller, use the code in the second version of the start() method that I posted above.

James_D
  • 201,275
  • 16
  • 291
  • 322