How do i display my MenuBar across all my scenes in JavaFX

Viewed 92

I am new to JavaFX

I wrote this code however, I dont know how to Display the Menu Bar in all my Scenes. Also I would like to create/fill my scenes with the Layout in my HelloApplication (however thats another issue).

I have a controller, for setting the Stage and launching it. My MenuBar is in the class MenuLeiste, but I would like it to appear in my Credits class aswell. Im very sorry for the lack of comments and the Layout of this comment.

    
    public class HelloApplication extends Application {

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

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

    //set window as primaryStage
    Stage window = primaryStage;


    //Layout of MenuLeiste is put in l1 and setted as scene1
    MenuLeiste l1 = new MenuLeiste();
    //menuscene gets its objects fromsceneViewMenu
    Scene menuscene = new Scene(l1.sceneViewMenu());

    window.setScene(menuscene);

    window.setHeight(600);
    window.setWidth(800);
    window.setTitle("Game Title");
    window.show();
    }
    }

My MenuLeiste class

    import javafx.scene.Scene;
    import javafx.scene.control.Menu;
    import javafx.scene.control.MenuBar;
    import javafx.scene.control.MenuItem;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;


    public class MenuLeiste {

        public VBox sceneViewMenu() {

        MenuBar menuBar = new MenuBar();
        VBox menuBox = new VBox(menuBar);

        Menu dataMenu = new Menu("Data");
                MenuItem exitItem = new MenuItem("Exit");
                exitItem.setOnAction(e -> System.exit(0));

        Menu extrasMenu = new Menu("Extras");
                MenuItem creditsItem = new MenuItem("Credits");
                creditsItem.setOnAction(e -> {
                        Credits c = new Credits();
                        Scene scene3 = new Scene(c.sceneView3());
                        Stage window = (Stage) menuBox.getScene().getWindow();
                        window.setScene(scene3);
                        });
        

        extrasMenu.getItems().addAll(creditsItem);
        dataMenu.getItems().addAll( exitItem);


        menuBar.getMenus().addAll(dataMenu,extrasMenu);

            //the scenes layout is saved in layout1
            VBox layout1 = new VBox(20);
            layout1.getChildren().addAll(menuBox);
        
            return layout1;
        }
        }

My Credits Class

package view;

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 Credits {
    public VBox sceneView3() 
    {

    Label label = new Label("Thanks");
    Button backButton = new Button("Back");

    backButton.setOnAction(e -> {
        MenuLeiste l1 = new MenuLeiste();
        Scene menuscene = new Scene(l1.sceneViewMenu());
        Stage window = (Stage) backButton.getScene().getWindow();
        window.setScene(menuscene);
    });
    
    VBox layout1 = new VBox(20);
    layout1.getChildren().addAll(label, backButton);
    
    return layout1;
    }
    }
2 Answers

When you want a node, or a group of nodes, to exist between different "scenes", often the best solution is to not replace the Scene. Instead, you'll want to modify the nodes displayed in the current scene. For example, in your case, you could have a BorderPane as the root of the scene with the MenuBar set as the top node. Then you replace the center node when you want to change the "view".

Here's a minimal runnable example demonstrating this concept. It makes use of "callbacks" to modify the center node of the BorderPane, while using the same MenuBar instance throughout. Though note the example only consists of views. If you have a backing model (i.e., data, business logic, etc.), you'll want to modify the code so you can pass it around. Also, if you write your application similarly to this example, it might be prudent to have all the views implement a common interface in a real application.

I don't have a separate "view class" for the menu bar, but that doesn't mean you can't have one (might even be better that way).

Main.java:

import java.util.function.Consumer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application {

  @Override
  public void start(Stage primaryStage) {
    var root = new BorderPane();
    root.setTop(createMenuBar(root::setCenter));
    root.setCenter(new TitleView().getNode());

    primaryStage.setScene(new Scene(root, 600, 400));
    primaryStage.setTitle("Demo");
    primaryStage.show();
  }

  private MenuBar createMenuBar(Consumer<Node> onUpdateView) {
    var exitItem = new MenuItem("Exit");
    exitItem.setOnAction(e -> Platform.exit());

    var creditsItem = new MenuItem("Credits");
    creditsItem.setOnAction(e -> {
      e.consume();

      var view = new CreditsView();
      view.setOnGoBack(() -> onUpdateView.accept(new TitleView().getNode()));
      onUpdateView.accept(view.getNode());
    });

    return new MenuBar(
        new Menu("File", null, exitItem),
        new Menu("Extras", null, creditsItem)
    );
  }
}

TitleView.java:

import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;

public class TitleView {

  private Node node;

  public Node getNode() {
    if (node == null) {
      node = new StackPane(new Label("Welcome!"));
    }
    return node;
  }
}

CreditsView.java:

import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;

public class CreditsView {

  private Runnable onGoBack;
  private Node node;

  public Node getNode() {
    if (node == null) {
      var stack = new StackPane();
      stack.setPadding(new Insets(10));

      var label = new Label("Credits View");
      stack.getChildren().add(label);

      var goBackBtn = new Button("Go back");
      goBackBtn.setOnAction(e -> {
        e.consume();
        if (onGoBack != null) {
          onGoBack.run();
        }
      });
      stack.getChildren().add(goBackBtn);
      StackPane.setAlignment(goBackBtn, Pos.TOP_LEFT);

      node = stack;
    }
    return node;
  }

  public void setOnGoBack(Runnable action) {
    onGoBack = action;
  }
}

Here is some basic code that should be easy for a beginner to understand.

  1. The main layout and the potential views to be displayed within it are created upfront.

  2. A menu bar is placed at the top of the border pane in the main layout.

  3. Menu items can be used to switch between views by setting the node to be currently displayed in the center of the border pane.

  4. The existing views are reused rather than being recreated on each navigation.

    • You could not store references to existing views and create new views on each navigation if preferred.

The views themselves are just nodes, so the example could easily be adapted to use FXML because the output of the FXML loader is also a node. The same goes for anything else which may generate a node to be used as a view in this fashion.

Operation

The scene is initially displayed and the user clicks on the "View" menu to show the list of available views.

view one

The user selects the "View Two" menu item and the second view is displayed. The application menu remains visible and can be used for future operations.

view two

Sample Code

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class ViewSwitcherApp extends Application {
    private BorderPane layout;
    private final Node viewOne = new ViewOne();
    private final Node viewTwo = new ViewTwo();

    @Override
    public void start(Stage stage) throws Exception {
        // View menu
        MenuItem viewOneMenuItem = new MenuItem("View One");
        viewOneMenuItem.setOnAction(e -> setView(viewOne));
        MenuItem viewTwoMenuItem = new MenuItem("View Two");
        viewTwoMenuItem.setOnAction(e -> setView(viewTwo));
        Menu viewMenu = new Menu(
                "View", null,
                viewOneMenuItem, viewTwoMenuItem
        );

        // File menu
        MenuItem exitMenuItem = new MenuItem("Exit");
        exitMenuItem.setOnAction(e -> Platform.exit());
        Menu fileMenu = new Menu(
                "File", null,
                exitMenuItem
        );

        MenuBar menuBar = new MenuBar(
                fileMenu, viewMenu
        );
        menuBar.setMinSize(MenuBar.USE_PREF_SIZE, MenuBar.USE_PREF_SIZE);

        // Layout scene
        layout = new BorderPane();
        layout.setTop(menuBar);
        setView(viewOne);

        stage.setScene(
                new Scene(layout, 300, 200)
        );
        stage.show();
    }

    private void setView(Node view) {
        layout.setCenter(view);
    }

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

class ViewOne extends StackPane {
    public ViewOne() {
        setStyle("-fx-background-color: lightblue; -fx-font-size: 30px;");
        getChildren().add(new Label("View One"));
    }
}

class ViewTwo extends StackPane {
    public ViewTwo() {
        setStyle("-fx-background-color: cornsilk; -fx-font-size: 30px;");
        getChildren().add(new Label("View Two"));
    }
}
Related