1

Please explain why the following is happening and what i need to change in order to make the FXML Button behave like the hardcoded one.

I am trying to change the CSS of an application on the fly by pressing a button. If i hardcode the button in my controller everything is working as expected, but when using FXML the same function call leads to a nullpointer.

Update to show full code (small test program anyways). pls Note i changed some names to make it easier to read. No functionality has been changed, but the added functionality that serves as a weak workaround meanwhile: Controller:

public class SoC extends Application
{
public Scene    myScene;
Button          btn     = new Button();
@FXML
Button          btnFXHack;
@FXML
Button          btnFXFail;
boolean         flipIt  = false;

// basic function i want to use
public void changeCSS()
{
    if (!flipIt)
    {
        myScene.getStylesheets().remove(0);
        myScene.getStylesheets().add(getClass().getResource("styleTwo.css").toExternalForm());
    }
    else
    {
        myScene.getStylesheets().remove(0);
        myScene.getStylesheets().add(getClass().getResource("styleOne.css").toExternalForm());
    }
    flipIt = !flipIt;
}

// workaround that is fine unless there are more then one scene
public void changeCSSHack(Scene s)
{
    if (!flipIt)
    {
        s.getStylesheets().remove(0);
        s.getStylesheets().add(getClass().getResource("styleTwo.css").toExternalForm());
    }
    else
    {
        s.getStylesheets().remove(0);
        s.getStylesheets().add(getClass().getResource("styleOne.css").toExternalForm());
    }
    flipIt = !flipIt;
}

/**
 * Button one changes CSS on his own scene only, which feels kinda hacked and lame.
 * 
 * @param event
 */
@FXML
private void handleCSSHack(ActionEvent event)
{
    // this button can access his own scene AND use it,....
    changeCSSHack(btnFXHack.getScene());
}

/**
 * Button two tries to change CSS with saved values in the mainController...
 * ...and fails.
 * 
 * @param event
 */
@FXML
private void handleCSSFail(ActionEvent event)
{
    // ...yet this button tells me the SAME scene is null. Yes, the one he is on himself.
    changeCSS();
}

@Override
public void start(Stage primaryStage)
{
    try
    {
        BorderPane pane = (BorderPane)   FXMLLoader.load(getClass().getResource("SoC.fxml"));
        myScene = new Scene(pane);
         myScene.getStylesheets().add(getClass().getResource("styleOne.css").toExternalForm());

        btn = new Button("hardcoded");
        btn.setOnAction(new EventHandler<ActionEvent>()
        {
            @Override
            public void handle(ActionEvent event)
            {
                changeCSS();
            }
        });
        pane.setCenter(btn);

        primaryStage.setScene(myScene);
        primaryStage.show();
        System.out.println("Started");
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}

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

Part of my FXML (board does not seem to like FXML, so can not paste in full)

  <Button fx:id="btnFXHack" mnemonicParsing="false" onAction="#handleCSSHack" text="FX Button Hack" BorderPane.alignment="CENTER" />
  <Button fx:id="btnFXFail" mnemonicParsing="false" onAction="#handleCSSFail" text="FX Button Fail" BorderPane.alignment="CENTER" />

As a side note: If i just add the FXML button to my pane via "children().add(btnFX)" i now have it twice BUT it is also working as well. Id really prefer to keep my GUI stuff out of my code tho, so id really love to hear how to fix this issue.

Thanks.

Edit to show my structure:

programmfolder
-src
--mainpackage
---controller.java
---.fxml-file
-themes folder
--default.css
--SciFi.css

So yes, the "/" is needed to reach the .css files. And again: changeCSS() is indeed working when being called by the hardcodes button. So i doubt the issue is on this end.

Also the FXML button can be pressed for println("stuff"). Right now i get the feeling he just does not see myScene and therefore gets the nullpointer. Even a simple system.out.println(myScene.toString()) leads to a nullpointer for the FXML button. The question is: Why is the same object suddenly null depending on where it is being called from?

Marco Heumann
  • 121
  • 1
  • 10
  • I don´t know if it´s relevant, but you should type "@FXML" before the changeCss method. Additionally, the btnFX doesn´t need to be initialized in the code. Just use "@FXMl Button btnFX;" – Franz Deschler Jan 20 '17 at 07:20
  • Sadly the FXML notation does not change anything. I tried that before. – Marco Heumann Jan 20 '17 at 07:25
  • Is the button the only component in your fxml? What about other elements? Do they work? Maybe the controller is not correctly set in the fxml. – Franz Deschler Jan 20 '17 at 07:35
  • There is a combobox as well and it is working fine. Again: Just like the button. It does work and execute code on click. It just thinks myScene is null which is obviously impossible since the button is part of said scene ;-) – Marco Heumann Jan 20 '17 at 07:50
  • Ah - now I see. myScene is null. I thought that the button is null. – Franz Deschler Jan 20 '17 at 08:00
  • Where is myScene declared? Did you try btnFX.getScene()? – Franz Deschler Jan 20 '17 at 08:04
  • You are onto something there. myScene is being declared in the controller. I only have a controller and the fxml file actually. To reduce possible issues while figuring this out. btnFX.getScene() does indeed get the scene and is not null. Functionality is given now (which is amazing in itself, thanks!), the question remains tho: WHY? Why does the button get a nullpointer if he uses the same function as the other button? – Marco Heumann Jan 20 '17 at 08:34
  • Could you post the entire controller class? (without imports) – Franz Deschler Jan 20 '17 at 08:57

2 Answers2

1

Oh boy, now I know whats the problem. Your controller is also the main application class!!!

When you start your application, then an instance of "SoC" is created and "start()" is called. And so, myScene is initialized in this instance.

When JavaFx creates a controller for a fxml file, a new instance is created! And in that instance, myScene is null. You should create a separate controller class from your main application class. Alternatively, set myScene to static.

Franz Deschler
  • 2,456
  • 5
  • 25
  • 39
  • Setting it to static does the trick, well spottet. How would i go about passing the scene to the "other scene"? Or sync them? I tried having a seperate controller and passed myScene as paramater. It still turned out to be null tho. So kinda lost on where to find that hidden Scene (and lost on why it is created in the first place actually.) – Marco Heumann Jan 20 '17 at 09:54
  • You cannot create a controller by yourself and initialize it. JavaFX creates that instance. To set the scene in various controllers, it also needs to be static. But I think thats not good. I think the best way is to use button.getScene(). See this post: http://stackoverflow.com/questions/26060859/javafx-getting-scene-from-a-controller – Franz Deschler Jan 20 '17 at 10:00
  • That is what i was afraid of. So with button.getScene() i can only access a single scene and using static just feels wrong as well. Whenever i get back to Java i remember why i prefer other languages ha! Either way, not liking an answer does not mean it is a wrong one. Thanks! – Marco Heumann Jan 20 '17 at 10:04
  • There may is an other way of accessing a central scene instance. I often use google guice for dependency injection. And its possible to define a custom factory for creating fxml controllers. So in the end, you are able to set the scene in your controller by dependency injection. That would be a nice solution. – Franz Deschler Jan 20 '17 at 10:25
  • See http://stackoverflow.com/questions/23471185/how-can-i-use-guice-in-javafx-controllers – Franz Deschler Jan 20 '17 at 10:29
0

Where you store the function changeCSS? Could you show your project structure?

This function should be inside a controller assigned to the view where the button is.

  • The controller is assigned correctly and does indeed execute system.out.println on button press just fine. Ill edit my question to include my structure. – Marco Heumann Jan 20 '17 at 07:06