Multiple JavaFX Scenes Sharing One MenuBar

JavaFX allows primary stages to swap out scenes. However, JavaFX stages don’t handle MenuBars directly. So what if you want to swap JavaFX scenes, but need to share one MenuBar between them? The trick is using a BorderPane as a root pane of your scene to hold a top JavaFx MenuBar and a central pane that gets swapped, instead of swapping the primary stage’s scene.

Note: If you have never set up JavaFX in Eclipse, check out my JavaFX 8 with Eclipse Mars tutorial. Also, I have a JavaFX 8 Hello World tutorial, too.

This example creates a Main class, a menu controller class, and three FXML files. The FXML files are a MenuBar, and two AnchorPanes that we swap between. All the custom code is included below for quick reference.

JavaFX Menu Layout Diagram
When using one JavaFX MenuBar with multiple views, encapsulate everything in a BorderPane.

The first step is to create a base JavaFX project in Eclipse. Other IDEs will have a similar base project, so you should be able to use the following code with a little massaging. I called my project, fx8menu. You can call yours what ever you like. My custom files included Main.java, MyMenusControl.java, MyMenus.fxml, PaneOne.fxml, and PaneTwo.fxml.

Here’s a pic of my Package Explorer.

JavaFX Menu Project
I called my JavaFX MenuBar example project, fx8menu.

Replace the Main class with the following code. Main.java uses the FXMLLoader to load the MyMenus.fxml and the PaneOne.fxml to set up the initial display. It also provides access via a get method to the BorderPane that we use as the root of our primary stage’s scene. Note that we don’t load the PaneTwo.fxml here. We don’t actually need PaneTwo.fxml until we are planning to display it. The AnchorPane from PaneOne.fxml and the MenuBar from MyMenus.fxml are added to the BorderPane named root.

The code is commented, so it should be an easy read.

package application;

import java.io.IOException;
import java.net.URL;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.MenuBar;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;


public class Main extends Application {
  
  // Creating a static root to pass to the controller
  private static BorderPane root = new BorderPane();

  /**
   * Just a root getter for the controller to use
   */
  public static BorderPane getRoot() {
    return root;
  }

  @Override
  public void start(Stage primaryStage) throws IOException {
    
    // loading FXML resources
    // note that we don't need PaneTwo in this class
    
    URL menuBarUrl = getClass().getResource("MyMenus.fxml");
    MenuBar bar = FXMLLoader.load( menuBarUrl );

    URL paneOneUrl = getClass().getResource("PaneOne.fxml");
    AnchorPane paneOne = FXMLLoader.load( paneOneUrl );


    // constructing our scene using the static root

    root.setTop(bar);
    root.setCenter(paneOne);
    
    Scene scene = new Scene(root, 640, 480);
    scene
      .getStylesheets()
      .add(getClass()
      .getResource("application.css")
      .toExternalForm());
    
    primaryStage.setScene(scene);
    primaryStage.show();

  }

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

The code for MyMenusControl.java is below. We use JavaFX’s dependency injection to add access to the MenuItems in our menu. The two event handler methods are defined in MyMenus.fxml, and realized in this controller. The AnchorPanes in PaneOne.fxml and PaneTwo.fxml are loaded via the FXMLLoader. We add them to the center of the BoardPane named root that we exposed in Main.java.

/**
 * Sample Skeleton for 'MyMenus.fxml' Controller Class
 */

package application;

import java.io.IOException;
import java.net.URL;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;

/**
 * Note that we load the panes with the FXMLLoader
 * on every use. This allows us to manipulate the
 * CSS between loads and have it take affect. 
 * 
 * Also, the panes should not save state internally.
 * Reloading the FXML forces better programming
 * design, because it is impossible to get lazy
 * and expect the panes to save their own state.
 */
public class MyMenusControl {

  @FXML // fx:id="displayOne"
  private MenuItem displayOne; // Value injected by FXMLLoader

  @FXML // fx:id="displayTwo"
  private MenuItem displayTwo; // Value injected by FXMLLoader

  /**
   * Event handler for MenuItem one
   */
  @FXML
  void switchToOne(ActionEvent event) {
    
    try {
      
      URL paneOneUrl = getClass().getResource("PaneOne.fxml");
      AnchorPane paneOne = FXMLLoader.load( paneOneUrl );
      
      BorderPane border = Main.getRoot();
      border.setCenter(paneOne);
      
    } catch (IOException e) {
      e.printStackTrace();
    }


  }

  /**
   * Event handler for MenuItem two
   */
  @FXML
  void switchToTwo(ActionEvent event) {

    try {
      
      URL paneTwoUrl = getClass().getResource("PaneTwo.fxml");
      AnchorPane paneTwo = FXMLLoader.load( paneTwoUrl );
      
      BorderPane border = Main.getRoot();
      border.setCenter(paneTwo);
    
    } catch (IOException e) {
      e.printStackTrace();
    }

  }

}

To quickly view any of the *.fxml files, remember to open them in SceneBuilder. These FXML files were generated with SceneBuilder 8, so you’ll need a newer version of SceneBuilder to open them.

PaneOne.fxml isn’t very exciting. It is just a label in the center of an AnchorPane. Here’s the AnchorPane displayed in running the project.

JavaFX AnchorPane One
This is display number one for my JavaFX view swap.

Here’s the FXML for PaneOne.fxml.

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

<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane 
  prefHeight="480.0" 
  prefWidth="640.0"
  xmlns:fx="http://javafx.com/fxml/1" 
  xmlns="http://javafx.com/javafx/8.0.40">
  <children>
    <Label 
      layoutX="251.0" 
      layoutY="227.0" 
      text="Anchor Pane One">
      <font>
        <Font size="18.0" />
      </font>
    </Label>
  </children>
</AnchorPane>

PaneTwo.fxml is a little more exciting. Here’s the AnchorPane in the running FXML 8 project.

JavaFX AnchorPane Two
This is display number two for my JavaFX view swap.

This is the code for PaneTwo.fxml.

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

<?import javafx.scene.shape.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>


<AnchorPane 
  prefHeight="480.0" 
  prefWidth="640.0"
  xmlns:fx="http://javafx.com/fxml/1" 
  xmlns="http://javafx.com/javafx/8.0.40">
  <children>
    <Sphere 
      layoutX="306.0" 
      layoutY="132.0" 
      radius="20.0" />
    <Sphere 
      layoutX="260.0" 
      layoutY="164.0" 
      radius="20.0" />
    <Sphere 
      layoutX="346.0" 
      layoutY="164.0" 
      radius="20.0" />
    <Sphere 
      layoutX="346.0" 
      layoutY="220.0" 
      radius="20.0" />
    <Sphere 
      layoutX="306.0" 
      layoutY="251.0" 
      radius="20.0" />
    <Sphere 
      layoutX="256.0" 
      layoutY="336.0" 
      radius="20.0" />
    <Sphere 
      layoutX="302.0" 
      layoutY="336.0" 
      radius="20.0" />
    <Sphere 
      layoutX="356.0" 
      layoutY="336.0" 
      radius="20.0" />
    <Sphere 
      layoutX="276.0" 
      layoutY="291.0" 
      radius="20.0" />
  </children>
</AnchorPane>

MyMenus.fxml contains all the action. You have two MenuItems with event listeners attached to them. Selecting one swaps out the AnchorPane for PaneOne.fxml’s view. Selecting the other MenuItem swaps out the BorderPane’s center for the AnchorPane from PaneTwo.fxml.

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

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.MenuBar?>

<MenuBar 
  xmlns="http://javafx.com/javafx/8.0.40"
  xmlns:fx="http://javafx.com/fxml/1" 
  fx:controller="application.MyMenusControl">
  <menus>
    <Menu 
      mnemonicParsing="false" 
      text="First Menu">
      <items>
        <MenuItem 
          fx:id="displayOne" 
          mnemonicParsing="false"
          onAction="#switchToOne" 
          text="Display Anchor Pane One" />
      </items>
    </Menu>
    <Menu 
      mnemonicParsing="false" 
      text="Second Menu">
      <items>
        <MenuItem 
          fx:id="displayTwo" 
          mnemonicParsing="false"
          onAction="#switchToTwo" 
          text="Display Anchor Pane Two" />
      </items>
    </Menu>
  </menus>
</MenuBar>

Now, that wasn’t so bad, was it?