Wie lässt sich eine Programm-Menuleiste in das MacOS-Menu integrieren?

Ab der Java-Version 9 können mit Hilfe der Klasse java.awt.Desktop und Interfaces des package java.awt.desktop erreicht werden, dass das zugehörige Verhalten einer Swing-Anwendung bei Auswahl von About, Preferences und Quit über das MacOS-Menu gesteuert wird.
Bis zur Version 8 stellte Apple mit seinen Java-Extensions des Packages com.apple.eawt Schnittstellen und Klassen bereit, die die speziellen Anpassungen ermöglichen.

Die Klasse des Beispiels definiert einen kleinen leeren JFrame mit einer einfachen Menuleiste, die einen Menupunkt zum Beenden der Applikation enthält. Bei Betätigung von 'About' und 'Preferences' im Menu wird zur Demonstration jeweils eine Nachricht auf der Konsole ausgegeben.

Die eigentliche Integration des Programm-eigenen Menus in die MacOS-Menuleiste erfolgt über den folgenden Eintrag in die System-Eigenschaften (43). Der Aufruf sollte möglichst früh im Quelltext erfolgen.

System.getProperties().put("apple.laf.useScreenMenuBar", "true");

ab v.9Das im Folgenden notierte Beispiel demonstriert wie ein programmeigener 'Über'- und ein 'Optionen'-Dialog ab der Java-Version 9 in das Apple-Menu eingebunden werden können.

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;

import java.awt.Desktop;
import java.awt.desktop.AboutEvent;
import java.awt.desktop.AboutHandler;
import java.awt.desktop.PreferencesEvent;
import java.awt.desktop.PreferencesHandler;
import java.awt.desktop.QuitEvent;
import java.awt.desktop.QuitHandler;
import java.awt.desktop.QuitResponse;

public class MacMenuLeiste {

    public MacMenuLeiste() {
        init();
    }

    private void init() {
        if (getOS().equals("mac")) {
            new MacImpl();
        }

        JMenuBar mBar = new JMenuBar();
        JMenu fileMenu = new JMenu("Datei");
        JMenuItem quitItem = new JMenuItem("Beenden");
        quitItem.addActionListener(e -> System.exit(0));
        fileMenu.add(quitItem);
        mBar.add(fileMenu);
        JFrame frame = new JFrame("Apple Menuleiste");
        frame.setJMenuBar(mBar);
        frame.setSize(300, 300);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        if (getOS().equals("mac")) {
            System.getProperties().put("apple.laf.useScreenMenuBar", "true");
        }
        SwingUtilities.invokeLater(() -> new MacMenuLeiste());
    }

    public static String getOS() {
        String osname = System.getProperty("os.name");
        if (osname != null && osname.toLowerCase().indexOf("mac") != -1) {
            return "mac";
        }
        if (osname != null && osname.toLowerCase().indexOf("windows") != -1) {
            return "win";
        }
        return "noarch";
    }
}

class MacImpl implements AboutHandler,  PreferencesHandler,
        QuitHandler {

    public MacImpl() {
        handleOS();
    }

    public void handleOS() {
        try {
            final Desktop desktop = Desktop.getDesktop();
            desktop.setAboutHandler(this);
            desktop.setPreferencesHandler(this);
            desktop.setQuitHandler(this);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    @Override
    public void handleAbout(AboutEvent arg0) {
        // new AboutDialog();
        System.out.println("handleAbout()");
    }

    @Override
    public void handlePreferences(PreferencesEvent arg0) {
        // new OptionsDialog();
        System.out.println("handlePreferences()");
    }

    @Override
    public void handleQuitRequestWith(QuitEvent arg0, QuitResponse arg1) {
        System.exit(0);
    }
}

Im Rahmen eines Strategy-Pattern wird zur Laufzeit in der Methode getOS() (48) das Betriebssystem abgefragt. Handelt es sich um ein MacOSX, so wird eine Instanz der Klasse MacImpl erzeugt (60). Sie implementiert drei Schnittstellen, die jeweils eine Methode bereitstellen. Sie werden überschrieben, um die zugehörige Funktionalität zu erreichen (79, 85, 91). Aufgerufen werden sie auf einem Objekt der Klasse Desktop innerhalb von handleOS() (67).

bis v.8Bis zur Version 8 findet man alle identisch benannten Interfaces und die abweichend benötigte Klasse Application, deren diesbezügliche Funktion Desktop übernommen hat, im package com.apple.eawt.

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;

import com.apple.eawt.AboutHandler;
import com.apple.eawt.AppEvent.AboutEvent;
import com.apple.eawt.AppEvent.PreferencesEvent;
import com.apple.eawt.AppEvent.QuitEvent;
import com.apple.eawt.Application;
import com.apple.eawt.PreferencesHandler;
import com.apple.eawt.QuitHandler;
import com.apple.eawt.QuitResponse;

public class MacMenuLeiste {

    public MacMenuLeiste() {
        init();
    }

    private void init() {
        if (getOS().equals("mac")) {
            new MacImpl();
        }

        JMenuBar mBar = new JMenuBar();
        JMenu fileMenu = new JMenu("Datei");
        JMenuItem quitItem = new JMenuItem("Beenden");
        quitItem.addActionListener(e -> System.exit(0));
        fileMenu.add(quitItem);
        mBar.add(fileMenu);
        JFrame frame = new JFrame("Apple Menuleiste");
        frame.setJMenuBar(mBar);
        frame.setSize(300, 300);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        if (getOS().equals("mac")) {
            System.getProperties().put("apple.laf.useScreenMenuBar", "true");
        }
        SwingUtilities.invokeLater(() -> new MacMenuLeiste());
    }

    public static String getOS() {
        String osname = System.getProperty("os.name");
        if (osname != null && osname.toLowerCase().indexOf("mac") != -1) {
            return "mac";
        }
        if (osname != null && osname.toLowerCase().indexOf("windows") != -1) {
            return "win";
        }
        return "noarch";
    }
}

class MacImpl implements ClassSelector, AboutHandler,  PreferencesHandler,
        QuitHandler {

    public MacImpl() {
        handleOS();
    }

    public void handleOS() {
        try {
            final Application application = Application.getApplication();
            application.setAboutHandler(this);
            application.setPreferencesHandler(this);
            application.setQuitHandler(this);
        } catch (Throwable e) {
            System.err.println("setupMacOSXApplicationListener failed: "
                    + e.getMessage());
        }
    }

    public void handleAbout(AboutEvent arg0) {
        // new AboutDialog();
        System.out.println("handleAbout()");
    }

    public void handlePreferences(PreferencesEvent arg0) {
        // new OptionsDialog();
        System.out.println("handlePreferences()");
    }

    public void handleQuitRequestWith(QuitEvent arg0, QuitResponse arg1) {
        System.exit(0);
    }
}

interface ClassSelector {
    public void handleOS();
}

Sollte Eclipse beim Zugriff auf die Klassen des Package com.apple.eawt einen Fehler anzeigen, so muss eine Zugriffs-Regel erstellt werden. Hierzu werden als 'Resolution' 'Accessible' und als 'Rule Pattern' 'com/apple/eawt/**' eingetragen.

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.