在跨平台 Java Swing 应用程序中设置 Mac 菜单栏和 Dock





5.00/5 (3投票s)
一个类,可以在应用程序启动时轻松地以编程方式设置 Mac 菜单栏和 Dock 图标。
引言
Java 应用程序开发的一个吸引人的特性是“一次编写,随处运行”的概念。由于我目前使用 Java 进行应用程序开发,所以我决定看看我的应用程序在 Mac 上的外观如何。
网上有很多资源详细介绍了让 Java 应用程序在 Apple 的操作系统上运行时看起来和行为都像原生 Mac 应用程序应该采取的步骤。甚至在很多年前,可以通过在源代码中轻松配置一个应用程序,使其名称出现在菜单栏中。 可惜的是,今天的情况并非如此。
确保应用程序名称出现在菜单栏和 Dock 上的图标的一种可靠方法是打开终端并使用以下命令启动 jar
java -Xdock:name= <applicationName> -Xdock:icon= <iconPath> -jar <jar file path>
然而,这样做违背了首先制作可执行 jar 的目的,并且我想为我的用户以及我懒惰的自己保持简单。 如果我的应用程序在 Mac 上启动时,在完全加载之前使用这些参数重新启动自己会怎么样? 这真是太好了!
OSXHelper 类
我编写了一个小类,可以非常容易地确保 Java 应用程序名称出现在 Mac 菜单栏中,并且其图标出现在 Dock 中。 只需将该类添加到您的项目中并按如下方式使用它
private static final String APPLICATION_NAME = "Menu bar & dock demo";
private static final String APPLICATION_ICON = "/resources/app-icon.png";
/**
* Launch the application.
*/
public static void main(String[] args) {
if(OSXHelper.IS_MAC){
// These calls must come before any AWT or Swing code is called,
// otherwise the Mac menu bar will use the class name as the application name.
System.setProperty("apple.laf.useScreenMenuBar", "true");
OSXHelper.setMacMenuAboutNameAndDockIcon(args, APPLICATION_NAME, APPLICATION_ICON);
}
// Ok proceed with launch
System.setProperty("java.net.preferIPv4Stack", "true");
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Demo window = new Demo();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace()
}
}
});
}
工作原理
该类包含两个静态字段和三个静态方法。 就本文而言,我将只关注 setMacMenuAboutNameAndDockIcon()
,这是奇迹发生的地方。
static protected void setMacMenuAboutNameAndDockIcon(final String[] applicationArgs,
final String applicationName, final String applicationIcon){
try {
String iconPath = null;
String[] launch = {""};
boolean XdockSet = false;
for(String arg : applicationArgs) if(XdockSet = arg.equals("-XdockSet")) break;
if(!XdockSet){
if(null != applicationIcon){
iconPath = exportResource(new Object(){}.getClass().getEnclosingClass(),
applicationIcon);
}
if(null != iconPath){
launch = new String[] { "java", "-Xdock:name=" + applicationName,
"-Xdock:icon=" + iconPath, "-jar",
THIS_JAR_FILE.getAbsolutePath(), "-XdockSet" };
} else {
launch = new String[] { "java","-Xdock:name=" + applicationName,
"-jar", THIS_JAR_FILE.getAbsolutePath(), "-XdockSet" };
}
String[] command = new String[launch.length + applicationArgs.length];
System.arraycopy(launch, 0, command, 0, launch.length);
System.arraycopy(applicationArgs, 0, command, launch.length, applicationArgs.length);
Runtime.getRuntime().exec(command);
Runtime.getRuntime().exit(0);
} else { // XdocSet so remove temporary copy of icon.
// We land here after the Jar has been relaunched
// with our new command line arguments.
// One would think that an attempt to delete the temporary icon should be safe here,
// however the call to "Runtime.getRuntime().exec(commands)" above
// does not return until this section of code (run in another process) is completed.
// Therefore, the temporary icon is deleted before it can be loaded.
// The call to invokeLater() allows the JVM to finish loading this instance before
// the deletion of the temporary icon.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if(null != applicationIcon){
try {
// exportResource checks to see if the resource
// (in this case the temporary icon) exists and will
// not overwrite it if present. It will however
// return the path to the existing resource.
String iconPath = exportResource(new Object(){}
.getClass().getEnclosingClass(), applicationIcon);
Thread.sleep(1000);
new File(iconPath).delete();
} catch (Exception e) { e.printStackTrace(); }
}
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
在查看这段代码时; 首先要注意的是,此方法的行为就像它是递归方法一样,虽然它不直接调用自身,但它执行一种进程间递归,因此需要一个停止条件。 这是通过在方法代码的前半部分完成后附加到 jar 文件参数列表的标志来实现的。 因此,检查此标志是该过程的第一步。
第一次调用此方法时,将找不到 -XdockSet
标志(更确切地说是不应该找到。 如果您使用此类,请不要将其用作应用程序的参数。)因此执行会继续到方法的上半部分,在该部分构建并执行重新启动命令。
为了具有一定的灵活性; 设置 Dock 图标是可选的。 如果为 applicationIcon
参数提供 null,则 Java 启动命令字符串将不包含 -Xdock:icon=
参数和参数。 因此,Dock 将显示默认的 Java 咖啡杯。 但是,如果您想在 Dock 中显示应用程序的图标; 将资源从 jar 中导出是必要的,以便在重新启动期间 Java 运行时可以读取它。 对 exportResource()
的调用从可执行 jar 中提取资源到 jar 所在的文件夹,并返回提取资源的路径,在本例中为我们应用程序的图标。 然后将其包含在 Java 启动命令字符串中。 最后,我们将 -XdockSet
标志作为传递给 jar 文件的第一个参数。
然后,必须将最初传递给 jar 文件的任何参数附加到启动命令,这样我们在重新启动应用程序时就不会丢失它们。 然后,生成的启动命令用于在新进程中启动我们的应用程序,然后再退出当前进程。
第二次调用此方法时,将找到 -XdockSet
标志,因此执行会移动到方法的后半部分,在该部分进行清理。 在这种情况下,我想在 JVM 将临时图标资源加载到 Dock 之后,删除从 jar 中复制出来的临时图标资源。 代码中的注释解释了在方法完成后调用 File.delete()
的必要性。
最终评论
演示和源代码是在 Windows PC 上创建的,并在 Mac 上进行了测试。
历史
- 2020 年 2 月 16 日:版本 1.0.0.0