I have three scenes in my program. When I start it I see the first scene with all the buttons. Then, when I go to the second scene I only see the the label, the buttons are not showing up. On the third scene everything is working fine, I see the label and the buttons.

So the question is, why are my buttons not showing up on my second scene?

I tried to switch the order of setting up the buttons and scenes, so that scene 2 (volume) is the third scene and the third scene (resolution) is the second scene. Everytime the second scene not showing the buttons.

package view.options;

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Options extends Application {
    Scene optionsMenu, volumeMenu, resolutionMenu;

    // Create the labels
    Label optionslabel= new Label("This is the options scene");
    Label volumelabel= new Label("This is the volume scene");
    Label resolutionlabel= new Label("This is the resolution scene");
    Label labl_message = new Label("Volume settings");
    Label labl_generalvolume = new Label("General volume");
    Label labl_effectvolume = new Label("Effect volume");
    Label labl_musicvolume = new Label("Music volume");

    // Create the buttons
    Button optionsButton= new Button("Go to options");
    Button volumeButton= new Button("Go to volume settings");
    Button resolutionButton= new Button("Go to resolution settings");
    Button saveButton = new Button("save");
    Button exitButton = new Button("exit");

    // Create the sliders
    Slider slider_generalvolume;
    Slider slider_effectvolume;
    Slider slider_musicvolume;

    @Override
    public void start(Stage optionsStage) {
        // Setup the sliders
        slider_generalvolume = new Slider();
        slider_generalvolume.setMin(0);
        slider_generalvolume.setMax(100);
        slider_generalvolume.setValue(50);
        slider_generalvolume.setShowTickLabels(true);
        slider_generalvolume.setShowTickMarks(true);
        slider_generalvolume.setBlockIncrement(10);

        slider_effectvolume = new Slider();
        slider_effectvolume.setMin(0);
        slider_effectvolume.setMax(100);
        slider_effectvolume.setValue(50);
        slider_effectvolume.setShowTickLabels(true);
        slider_effectvolume.setShowTickMarks(true);
        slider_effectvolume.setBlockIncrement(10);

        slider_musicvolume = new Slider();
        slider_musicvolume.setMin(0);
        slider_musicvolume.setMax(100);
        slider_musicvolume.setValue(50);
        slider_musicvolume.setShowTickLabels(true);
        slider_musicvolume.setShowTickMarks(true);
        slider_musicvolume.setBlockIncrement(10);

        // Setup the main button
        optionsButton.setOnAction(e -> optionsStage.setScene(optionsMenu));
        volumeButton.setOnAction(e -> optionsStage.setScene(volumeMenu));
        resolutionButton.setOnAction(e -> optionsStage.setScene(resolutionMenu));
        exitButton.setOnAction(e -> Platform.exit());
        saveButton.setOnAction(e -> Platform.exit());

        // Setup the layout and add elements to it
        VBox optionsLayout = new VBox(20);
        optionsLayout.getChildren().addAll(optionslabel, volumeButton, resolutionButton, exitButton);
        optionsLayout.setStyle("-fx-padding: 10;" +
                "-fx-border-style: solid inside;" +
                "-fx-border-width: 2;" +
                "-fx-border-insets: 5;" +
                "-fx-border-radius: 5;" +
                "-fx-border-color: blue;");
        optionsMenu = new Scene(optionsLayout, 300, 250);

        VBox volumeLayout = new VBox(20);
        volumeLayout.getChildren().addAll(volumelabel, saveButton, optionsButton);
        volumeLayout.setStyle("-fx-padding: 10;" +
                "-fx-border-style: solid inside;" +
                "-fx-border-width: 2;" +
                "-fx-border-insets: 5;" +
                "-fx-border-radius: 5;" +
                "-fx-border-color: blue;");
        volumeMenu = new Scene(volumeLayout, 300, 250);

        VBox resolutionLayout = new VBox(20);
        resolutionLayout.getChildren().addAll(resolutionlabel, saveButton, optionsButton);
        resolutionLayout.setStyle("-fx-padding: 10;" +
                "-fx-border-style: solid inside;" +
                "-fx-border-width: 2;" +
                "-fx-border-insets: 5;" +
                "-fx-border-radius: 5;" +
                "-fx-border-color: blue;");
        resolutionMenu = new Scene(resolutionLayout, 300, 250);

        // Setup stage and start it
        optionsStage.setTitle("Options");
        optionsStage.setScene(optionsMenu);
        optionsStage.show();
    }

    public static void main(String[] args) {
        launch(args);
        System.out.println("exited successfully!");
    }
}

So, where is the damn bug? I don't get it. Is it some kind of Java or JavaFX voodoo that you can't see the buttons of the second scene?

2 Answers

3
nero. On Best Solutions

The issue is from saveButton and optionButton from layout.getChildren().addAll(..., saveButton, optionButton). Java FX nodes are restricted to at most 1 parent and at most 1 scene. Trying to use it in multiple places either removes it from the old parent or results in an exception. In your case, the old parent is removed and replaced by the new one. To visualize the issue, you can change the order of your layout initialization, and so, you will see that the volume layout will be working, but not the other one.

So, if you create unique instances of saveButton, and optionsButton for each layout, it will work.

Example:

Button saveButtonVolume = new Button("save");
Button saveButtonResolution = new Button("save");

// ...

saveButtonVolume.setOnAction(e -> Platform.exit());
saveButtonResolution.setOnAction(e -> Platform.exit());

// ...

resolutionLayout.getChildren().addAll(resolutionlabel, saveButtonResolution, optionsButtonResolution);
volumeLayout.getChildren().addAll(volumelabel, saveButtonVolume, optionsButtonVolume);

Sadly, I did not found any way to duplicate or clone a Java FX node, which would have simplified the problem (example: saveButton.clone()).

1
mipa On

So, here is a working example with just one Scene, Stage and layout. All buttons can be reused that way. This is not how I would normally prefer to do it but it works and makes the code even a lot shorter too.

package view.options;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Options extends Application {
    // Create the labels
    Label optionslabel= new Label("This is the options scene");
    Label volumelabel= new Label("This is the volume scene");
    Label resolutionlabel= new Label("This is the resolution scene");
    Label labl_message = new Label("Volume settings");
    Label labl_generalvolume = new Label("General volume");
    Label labl_effectvolume = new Label("Effect volume");
    Label labl_musicvolume = new Label("Music volume");

    // Create the buttons
    Button optionsButton= new Button("Go to options");
    Button volumeButton= new Button("Go to volume settings");
    Button resolutionButton= new Button("Go to resolution settings");
    Button saveButton = new Button("save");
    Button exitButton = new Button("exit");

    VBox theOneAndOnlyLayout = new VBox(20);    
    Scene theOneAndOnlyScene;

    @Override
    public void start(Stage theOneAndOnlyStage) {
        theOneAndOnlyLayout.setStyle("-fx-padding: 10;" +
                "-fx-border-style: solid inside;" +
                "-fx-border-width: 2;" +
                "-fx-border-insets: 5;" +
                "-fx-border-radius: 5;" +
                "-fx-border-color: blue;");

        theOneAndOnlyScene = new Scene(theOneAndOnlyLayout, 300, 250);

        theOneAndOnlyLayout.getChildren().setAll(optionslabel, volumeButton, resolutionButton, exitButton);

        optionsButton.setOnAction(e -> {
            theOneAndOnlyStage.setTitle("Options");
            theOneAndOnlyLayout.getChildren().setAll(optionslabel, volumeButton, resolutionButton, exitButton);
        });
        volumeButton.setOnAction(e -> {
            theOneAndOnlyStage.setTitle("Volume");
            theOneAndOnlyLayout.getChildren().setAll(volumelabel, saveButton, optionsButton);
        });
        resolutionButton.setOnAction(e -> {
            theOneAndOnlyStage.setTitle("Resolution");
            theOneAndOnlyLayout.getChildren().setAll(resolutionlabel, saveButton, optionsButton);
        });
        exitButton.setOnAction(e -> Platform.exit());
        saveButton.setOnAction(e -> Platform.exit());

        // Setup stage and start it
        theOneAndOnlyStage.setTitle("Options");
        theOneAndOnlyStage.setScene(theOneAndOnlyScene);
        theOneAndOnlyStage.show();
    }

    public static void main(String[] args) {
        launch(args);
        System.out.println("exited successfully!");
    }
}