Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

//

Tweaking the Menu Bar of JavaFX 8 Applications on OS X

9.4.2015 | 6 minutes of reading time

JavaFX provides a simple means to create platform independent applications with a graphical UI. This platform independence, however, usually comes at a price. As the compiled code is supposed to run on all supported operating systems, JavaFX does not support all unique operating system specific UI elements. This is usually not a big problem, but it can be rather annoying in certain cases.

On OS X, the application’s menu bar is usually detached from the application’s main window and shown at the very top of the desktop instead. In contrast to the regular menu of a JavaFX application, this menu bar always contains the Apple menu and something that is called application menu. This application menu is created for every application regardless of whether it uses an own menu bar or not and contains items to show, hide and quit the application. Native Mac OS applications also use the application menu for example for preferences, the about menu and other application related menu items. Unfortunately, JavaFX does not provide any means to access this menu let alone adding new custom items to it. This can be particularly annoying when menu items are labeled “Java” or contain the entire package name of your main class.

In this blog post, I describe how to access the application menu using Cocoa native bindings from Eclipse SWT to modify default application menu items and adding new custom items. With the resulting code, JavaFX applications are able to use the default Mac OS menu bar just like any native application.

The following example shows how to add a menu bar to a JavaFX application. It also sets the useSystemMenuBarProperty to true in order to put the menu bar at the very top of the screen instead of adding it to the application window.

1@Override
2public void start(Stage primaryStage) throws Exception {
3  MenuBar menuBar = new MenuBar();
4  menuBar.useSystemMenuBarProperty().set(true);
5 
6  Menu menu = new Menu("java");
7  MenuItem item = new MenuItem("Test");
8 
9  menu.getItems().add(item);
10  menuBar.getMenus().add(menu);
11 
12  primaryStage.setScene(new Scene(new Pane(menuBar)));
13  primaryStage.show();
14}

The problem with that code is that it does not affect the automatically generated application menu in any way. The menu items are just added to the right of the already existing application menu. Therefore, I created a small tool called NSMenuFX on Github that provides access to the generated menu bar using Eclipse SWT. For convenience, it allows for converting the auto generated Mac OS menu bar into a JavaFX MenuBar. This menu bar can then be modified just like any JavaFX menu bar and converted back to a Mac OS native NSMenuBar. In the following, I outline how to use this tool and how it uses Cocoa native bindings to provide the described functionality.

Accessing the auto generated menu bar with NSMenuFX is fairly simple, as outlined in the following example:

1NSMenuBarAdapter adapter = new NSMenuBarAdapter();
2MenuBar menuBar = adapter.getMenuBar();

The first Menu of menuBar now contains the application menu with all its default items. To access this menu, NSMenuBarAdapter uses the NSApplication class of Eclipse SWT. This is pretty much straight forward as NSApplication provides direct access to the application’s menu bar by calling

1NSMenu mainMenu = NSApplication.sharedApplication().mainMenu()

The slightly more tricky part starts by converting the NSMenu to a JavaFX MenuBar, since the two concepts are slightly different. In JavaFX, there is one MenuBar object containing several Menus whereas each Menu can contain MenuItems or further Menus. As depicted in the example above, Cocoa does not have the concept of a menu bar object but uses a NSMenu object instead. This menu solely consists of NSMenuItems, whereas each of this items has a title and an optional submenu. The title of these items is what is shown in the OS X menu bar whereas the submenus are the menus that open when you click on them. The submenus also contain NSMenuItems with, again, optional submenus. To shine a bit more light on the Cocoa Menu structure, consider the following code snippet that loops over the items of the OS X menu bar. It uses the ToJavaFXConverter to convert submenus to the respective JavaFX Menu classes and adds them to a MenuBar.

1NSArray itemArray = mainMenu.itemArray();
2for (long i = 0; i < itemArray.count(); i++) {
3  NSMenuItem item = new NSMenuItem(itemArray.objectAtIndex(i));
4  bar.getMenus().add(toJavaFX.convert(item.submenu()));
5}

The most interesting part of this code snippet is that the itemArray does not contain the actual NSMenuItems but only a sequence of Cocoa native object IDs. To bind a Java object to the respective native object, this object id must be passed in to the constructor. The convert method that is called subsequently then recurses through the submenu and converts all elements to their JavaFX counterparts.

Most aspects of the menu item conversion, like the title or the accelerator are pretty much straight forward and can be, more or less, directly translated. The menu item’s click action is, however, a bit more complicated as Cocoa uses selectors to call a given method whereas JavaFX uses EventHandler objects that are assigned to the menu item and called in case they are clicked. If you are not familiar with Objective-C, selectors can be loosely thought of as reflections defining method names that should be called on a given object. For the conversion from Cocoa to JavaFX, NSMenuBarAdapter creates a dummy EventHandler containing the selector address as well as a reference to the object on which the selector should be executed.

A bit more challenging is the conversion from JavaFX to Cocoa, as it requires the conversion from EventHandler objects to Objective-C selectors. Therefore, a new selector can be created at runtime by calling OS.sel_registerName(String name). By default, selectors are invoked on the delegate object of the application, which is an instance of SWTApplicationDelegate. To enable SWTApplicationDelegate to respond to the new selector, a respective method can be added at runtime as outlined in the following snippet taken from ToCocoaConverter. The implementation of the method is provided by a Callback object, which is called from Objective-C.

1Callback callback = new Callback(new MenuHookObject(handler), "actionProc", 3);
2long delegateClass = OS.objc_lookUpClass("SWTApplicationDelegate");
3OS.class_addMethod(delegateClass, selector, callback.getAddress(), "@:@");

The constructor of the callback object takes a Java object, the name of the method that should be called and the number of parameters. As depicted above, MenuHookObject.actionProc takes three arguments, i.e. a reference to its own object, a reference to the selector as well as a reference to the calling object. The same parameters are used for the method that is added to the delegate object as indicated by the signature @:@ (@ represents a NSObject reference, : represents a selector reference). When actionProc is called, MenuHookObject just calls the JavaFX EventHandler that was passed in to its constructor.

By being able to convert a JavaFX MenuBar back to a Objective-C Menu, the auto generated menu bar can simple be replaced with a modified or even entirely new version of the menu bar. The following example shows how to use NSMenuFX to add an “About” menu to the generated application menu.

1// Create a new menu item
2    MenuItem about = new MenuItem("About");
3    about.setOnAction(new EventHandler<ActionEvent>() {
4    @Override
5    public void handle(ActionEvent event) {
6        // Open a new JavaFX dialog
7    }
8    });
9 
10    // Add the menu item as first element to the application menu
11    menuBar.getMenus().get(0).getItems().add(0, about);
12 
13    // Update the menu bar
14    adapter.setMenuBar(menuBar);

I hope this article provides you with some interesting insights about Cocoa native bindings and how to use them to tweak the Mac OS menu bar items from JavaFX. Feel free to contact me directly if you have any further questions or leave a comment right below this post.

share post

Likes

0

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.