在OSX上使用Swing:如何捕获Command-Q?

23

被说服("学校")后,Swing应用程序在Mac上看起来很本地化之后,我正在尝试使我的应用程序尽可能本地化。一切看起来都很好,但是当我按下command+Q或从菜单中退出时,我的windowStateChanged(WindowEvent e)不会在我的主JFrame上触发(如果我以其他任何方式退出,则会触发)。如何响应真正的苹果退出?


3
+1 是一个有趣的问题,它帮助我摆脱了“一次编写,无处不在运行”的幼稚观念。我猜这解释了为什么苹果要自己构建Java。 - Carl Smotricz
谢谢,卡尔。很少有问题不让我再次注意到乔尔的“泄漏抽象”概念。 - Dan Rosenstark
有点像戈德温定律。 - yeoman
6个回答

16
您可以实现com.apple.eawt.ApplicationListener并响应Quit事件。在Mac OS X Reference Library示例中,可以找到一个例子,OSXAdapter
附注:请参阅Java for Mac OS X 10.6 Update 3 and 10.5 Update 8 Release Notes,了解弃用、重新设计的com.apple.eawt.Application类以及苹果Java扩展的API文档位置。控制单击或右键单击.jdk文件以Show Package Contents。您可以在OpenJDK源代码中浏览com.apple.eawt类。
此完整示例所示,您可以指定所需的QuitStrategyWindowListener将响应⌘Q
Application.getApplication().setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS);

正如这里所指出的,你可以从命令行设置该属性。

java -Dapple.eawt.quitStrategy=CLOSE_ALL_WINDOWS -cp build/classes gui.QuitStrategyTest

或者在程序早期,在发布任何GUI事件之前:

System.setProperty("apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS");
EventQueue.invokeLater(new QuitStrategyTest()::display);

image

按下⌘Q后的控制台:

java.vendor: Oracle Corporation
java.version: 1.8.0_60
os.name: Mac OS X
os.version: 10.11
apple.eawt.quitStrategy: CLOSE_ALL_WINDOWS
java.awt.event.WindowEvent[WINDOW_CLOSING,opposite=null,oldState=0,newState=0] on frame0

代码:

package gui;

import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTextArea;

/**
 * @see https://dev59.com/yms05IYBdhLWcg3wFOC8#7457102
 */
public class QuitStrategyTest {

    private void display() {
        JFrame f = new JFrame("QuitStrategyTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                System.out.println(e);
            }
        });
        f.add(new JTextArea(getInfo()));
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private String getInfo() {
        String[] props = {
            "java.vendor",
            "java.version",
            "os.name",
            "os.version",
            "apple.eawt.quitStrategy"
        };
        StringBuilder sb = new StringBuilder();
        for (String prop : props) {
            sb.append(prop);
            sb.append(": ");
            sb.append(System.getProperty(prop));
            sb.append(System.getProperty("line.separator"));
        }
        System.out.print(sb);
        return sb.toString();
    }

    public static void main(String[] args) {
        System.setProperty("apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS");
        EventQueue.invokeLater(new QuitStrategyTest()::display);
    }
}

很好,我稍后找到了,但是如果我没有com.apple.eawt包怎么办?或者它在Windows上也可以使用吗? - Dan Rosenstark
1
太棒了,现在一切都清楚了。它动态调用所有类。我的代码最终只有一行(加上苹果的类):OSXAdapter.setQuitHandler(this, getClass().getDeclaredMethod("onClose", (Class[])null)); - Dan Rosenstark
你说得对,我添加了一些额外的链接到苹果的示例中,以备将来参考。 - trashgod
2
自从1.6版本发布以来,这个类被弃用了。请参见:https://developer.apple.com/library/mac/#releasenotes/Java/JavaSnowLeopardUpdate3LeopardUpdate8RN/NewandNoteworthy/NewandNoteworthy.html重新设计的eAWT。 - user502187

7

该问题最受欢迎的回答已经很好了,但是为了填补“最佳方法”的空白:

System.setProperty("apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS");

这将会触发标准窗口关闭回调事件,非常适用于可移植代码。
通过以下讨论得出结论,这在应用程序的早期执行非常关键。我在主类的静态初始化程序中很早就写了这个,任何UI代码之前都可以执行。

这不就是和 Application.getApplication().setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS); 一样的吗? - Dan Rosenstark
3
是的,但它不需要访问Application类,这对于可移植应用程序是不必要的。使用一行代码而非反射即可实现。 - Shai Almog
1
@Yar:遗憾的是,它们在command-Q方面并不相同;我已经更新了此处引用的示例,以展示一种可移植的方法;删除对enableOSXQuitStrategy()的调用以查看效果。 - trashgod
你说得对。这似乎没有一种可移植的方式来完成,除非使用反射!感谢提供代码! - Shai Almog
@ShaiAlmog:我在 Mac OS X 10.[9, 10, 11] 上测试了各种 Java 1.8.0_[45,60] 的组合;您的方法在单击红色关闭按钮时按文档发送“WINDOW_CLOSING”,但不能通过 command-Q 或应用程序菜单发送;我很高兴发现我错了! :-) - trashgod
显示剩余3条评论

3

在试图访问com.apple.eawt.Application和com.apple.eawt.*子类时,我最初遇到了“访问限制”违规。

(注意:我正在使用Eclipse,在MAC上编程,使用Java 1.6和Swing)

因此,我需要修改我的Java构建路径,通过添加“com/apple/eawt/**”访问规则来允许访问苹果子类。之后,下面的代码能够成功编译并运行:

//NOTE: This code only works for MAC OS.  If you run this on Windows
//the application never starts (so you literally need to remove this block of code)

import com.apple.eawt.*;
import com.apple.eawt.QuitHandler;

 Application a = Application.getApplication();
 a.setQuitHandler(new QuitHandler() {


@Override
public void handleQuitRequestWith(com.apple.eawt.AppEvent.QuitEvent qe, com.apple.eawt.QuitResponse qr) {
    // TODO Auto-generated method stub

    int res = JOptionPane.showConfirmDialog(frame, "Are you sure you want to exit the program?", "Quit ?", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);

    if (res == JOptionPane.YES_OPTION) 
        qr.performQuit();
    else
        qr.cancelQuit();

   }

});

OSX的版本?我认为访问限制问题是新的。 - Dan Rosenstark

3

这是一个非常好的问题,我必须承认我没有确切的答案。不过,几年前当我在开发一款Java应用程序时遇到了这个问题,我通过向运行时注册一个关机挂钩来解决它,以执行我想要应用程序在退出之前完成的操作。这是一个很笨重的解决方案,但它起作用了。你可以查看我的代码,看看是否有所帮助。


有趣的解决方案,因为我自己编写主方法,所以还不错。 - Dan Rosenstark

2
您尝试过在您的菜单中设置command-Q作为加速器吗?您的应用程序能够响应它吗?我不确定,但我认为在Linux和可能的Windows中可以使用相当于Alt-F4的功能。我的应用程序会响应“关闭”按键,我会处理一些清理代码,然后进行编程式的System.exit()。如果您只是想要优雅地退出处理,您也可以捕获WindowEvent WINDOW_CLOSING,在那里传统上会执行“您确定吗?”的操作。

无法工作,因为您也可以使用鼠标触发菜单项。 - Dan Rosenstark
1
那有什么问题呢?你的应用程序对按键和“退出”菜单项做出相同的响应是有道理的。 - Carl Smotricz
1
当然。红点关闭窗口但不关闭应用程序 - 这是苹果的方式。我真的建议您在拒绝我的答案之前尝试将cmd-Q放入菜单中。 - Carl Smotricz
1
太好了,我希望你能让它正常工作。但是我必须面对这样一个事实,我在苹果特定的Java经验太少了,无法再为你提供更多帮助,所以我要尽力优雅地放弃了。祝你好运! - Carl Smotricz
2
谢谢,这很周到。我的回答可能价值不高,但我还是留下来让讨论保持可见。是的,Apple 欺负开发者,但结果是非常流畅的用户体验。我想每件事都有利弊吧。我不确定在 Apple 商店里是否一样;据我所知,他们在那里做的大多数事情都是为了底线利润,而不是为了用户方便。 - Carl Smotricz
显示剩余5条评论

0

看了一下Java for Mac OS X 10.6 Update 3 and 10.5 Update 8 Release Notes的链接,我注意到有一个关于默认退出操作的部分。这描述了一个系统属性,请求在响应“退出”菜单项时关闭所有窗口,听起来正是所需的?我已经在我的应用程序中使用了这个(仅在OS X上使用Info.plist设置属性),它似乎按照描述的方式工作。这可能只适用于最近的Java/OS X版本,但对于这些平台来说,似乎是一个不错的解决方案,而且不需要任何代码更改。


1
谢谢。@trashgod 已经在他的答案附录中处理了这个问题。 - Dan Rosenstark
@Igor 我不知道,但所有常规事件都按预期调用。 - Dan Rosenstark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接