Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Saturday, November 7, 2015

Mac Menus in a JavaFX App

On the Mac, a top-level menubar stays fixed at the top of the screen.  This is in contrast to Windows where an application's menubar is at the top of the window and moves with the window.  To get your Java program to behave like a native Mac application, you only have to specify a Java System property "apple.laf.useScreenMenuBar".  Unfortunately for FX developers, this only works with Swing (and then under certain conditions).

UPDATE 9/9/2017

For JavaFX, set the useSystemMenuBar property.  This will take the MenuBar and move it off of the top of the Stage and put it in the Finder.

I'll keep the rest of the post as-is in case someone has been using it as a reference.  -Carl
----

All source code in this post is available at GitHub under examples-javafx-macmenu.

This is a JavaFX app running on a Mac.  Notice that the top-level menubar denotes the FX executable process "java".  Also, notice that the menubar belonging to the app stays with the window.

Running an FX Application on a Mac

This is a Swing app running on a Mac.  We get the name of the main class in the top-level menubar.  This example also has the app menubar staying with the window.

Running a Swing Application on a Mac
With a simple setProperty() call to set apple.laf.useScreenMenuBar to true, the Swing application automatically becomes closer to the Mac experience.

Mac LAF MenuBar for the same Swing app
Note that the Swing app is using the setJMenuBar() call on the JFrame.  If you're adding the JMenuBar any other way, say as the topmost Component in a BorderLayout, the apple.laf.useScreenMenuBar property will not work.

JFXPanel

What this means for the FX developer, is that a small amount of Swing code is needed to best support the Mac.  If you're heavily vested in the Application class, like if you're using an FX preloader from JNLP, this may not be the right approach.  Technically, I'd prefer the whole thing to be in FX, but the Mac experience is at stake.

This screenshot shows an FX / Swing hybrid.  JFXPanel is used to hold the main view of my FX application.  In both examples -- before and after involving Swing -- I have an FXML file loaded in a Scene.  The Swing version replaces a Stage with a JFXPanel.

Swing Providing Mac MenuBar to JavaFX App
The first thing I do in the app is set the system property.

    System.setProperty("apple.laf.useScreenMenuBar", "true");


Next, I create the Swing JFrame and use an FXMLLoader to load the main FX view into a Scene.  The Scene is added to a JFXPanel which is then added to the JFrame contentPane.
    
   try {

      JFXPanel mainFXWindow = createMainFXWindow();  
  
      this.getContentPane().add( mainFXWindow );
 
    } catch(Exception exc) {
        exc.printStackTrace();
        System.exit(1);
    }

This is the code to load the .fxml file.

 private JFXPanel createMainFXWindow() throws Exception {
  JFXPanel jfxPanel = new JFXPanel();  //  initializes the toolkit
  FXMLLoader fxmlLoader = 
    new FXMLLoader( this.getClass().getResource("/macmenu-fxml/MacMenu.fxml") );
  fxmlLoader.load();
  Parent p = fxmlLoader.getRoot();
  Scene scene = new Scene(p);
  jfxPanel.setScene( scene );
  return jfxPanel;
 }

The FXML code is the same for the pure and hybrid approaches except for a block of code in the @FXML initialize() method of the Controller.  This checks the property and will remove the menubar which is being displayed by Swing.  If the system property is not set, then we won't have a menubar at the top of the screen, and the VBox can retain what was defined and wired in the FXML.

 @FXML
 VBox vbox;
 
 @FXML
 MenuBar menubar;
 
 @FXML
 public void initialize() {
     String macMenu = System.getProperty("apple.laf.useScreenMenuBar");
     if( macMenu != null && macMenu == "true" ) {
        vbox.getChildren().remove(menubar);
     }
 }

At some point, maybe a pure JavaFX integration with the Mac will be available.  However, if you're like me, you can't wait for that.  If you're building an FX app, I'd continue doing almost all of it in FX, but you might find a few examples like Mac integration or the System Tray where you may need a little Swing.

This post is based on information from the Apple website.

All source code in this post is available at GitHub under examples-javafx-macmenu.


No comments:

Post a Comment