Search code examples
javabuttonjavafxscene

Javafx how to create buttons via function?


Please help me make it works. It freezes instead of changing Scenes. It works fine when buttons are created this way:

Button b1 = new Button("Go to s2");
b1.setOnAction(e -> window.setScene(s2));
Button b2 = new Button("Go to s1");
b2.setOnAction(e -> window.setScene(s1));

But I'd like to make it more elegant...

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;

public class Main extends Application {

    Stage window;
    Scene s1,s2;

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        window = primaryStage;

        Button b1 = makeButton("Go to s2", s2);
        Button b2 = makeButton("Go to s1", s1);

        s1 = new Scene(b1);
        s2 = new Scene(b2);

        primaryStage.setScene(s1);
        primaryStage.show();
    }

    public Button makeButton(String name, Scene destScene)  {
        Button button = new Button(name);
        button.setOnAction(e -> window.setScene(destScene));
        return button;
    }
}

Thank you very much in advance!


Solution

  • Look at the order for how everything is initialized:

    Button b1 = makeButton("Go to s2", s2);
    Button b2 = makeButton("Go to s1", s1);
    
    s1 = new Scene(b1);
    s2 = new Scene(b2);
    

    When you call makeButton, you pass in the value of the reference currently stored in s1 and s2. Since it was never initialized, it takes the default value of null. This doesn't get changed when you letter set s1 and s2 because of the copied reference.

    You don't have the same problem in the first case, because you never make a copy of s1 and s2. Instead, you have the EventHandler refer to the field in the current instance of Main, which does get correctly updated once you set it. So your original code is something equivalent to this:

    Button b1 = new Button("Go to s2");
    b1.setOnAction(e -> window.setScene(this.s2));
    

    So you're copying the reference to the enclosing Main instance, rather than the reference to the button itself.

    I don't see any trivial fix for it unfortunately. The easiest solution I see would be to change your function to makeButton(String, EventHandler<ActionEvent>), and call it like this:

    Button b1 = makeButton("Go to s2", e -> window.setScene(s2));
    

    Not as nice as what you want, but it should work.

    Another possible solution is to put all of your Buttons into an array, and then pass an index into that array into makeButton. That would look like tihs:

    public class Main extends Application {
        Stage window;
        Scene[] scenes = new Scene[2];
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            window = primaryStage;
    
            Button b1 = makeButton("Go to s2", 1);
            Button b2 = makeButton("Go to s1", 0);
    
            scenes[0] = new Scene(b1);
            scenes[1] = new Scene(b2);
    
            primaryStage.setScene(scenes[0]);
            primaryStage.show();
        }
    
        public Button makeButton(String name, int destScene)  {
            Button button = new Button(name);
            button.setOnAction(e -> window.setScene(scenes[destScene]));
            return button;
        }
    }
    

    That changes the EventHandler to reference a field of Main (scenes) rather than a local variable (destScene).