全屏 Swing 组件无法在 Mac OS X Mountain Lion 上的 Java 7 上接收键盘输入

2022-09-01 21:25:28

12/21年更新:

7u10 最近发布。确认:

  1. 问题仍然存在
  2. 值得庆幸的是,解决方法仍然有效!

11/7更新:

我们有一个解决方法!

来自甲骨文的列昂尼德·罗曼诺夫(Leonid Romanov)在 openjdk.java.net 邮件列表中提供了一些关于正在发生的事情的见解

好吧,虽然我还不是100%确定,但看起来当我们进入全屏时,其他窗口成为第一响应者,因此发出哔哔声。您可以尝试以下解决方法:在帧上调用setFullScreenWindow()后,调用setVisible(false),然后调用setVisible(true)。从理论上讲,这应该恢复正确的第一响应者。

似乎有效的代码片段很简单:

dev.setFullScreenWindow(f);
f.setVisible(false);
f.setVisible(true);

我已经更新了示例代码,能够打开和关闭此修复程序;每次窗口进入全屏时都需要它。

在我更复杂的应用程序的更大上下文中,我仍然在全屏窗口中的子组件上遇到键盘焦点问题,其中鼠标单击会导致窗口失去焦点。(我猜它会进入上面引用的不需要的第一响应者窗口。当我有关于此案例的更多信息时,我会报告回来 - 我还不能在较小的样本中重现它。


10/31更新:

对示例代码的重大更新:

  • 包括在全屏独占模式和狮子式全屏模式之间切换
  • 侦听 以显示当前焦点组件的层次结构KeyboardFocusManager
  • 同时使用输入映射和 s 来尝试捕获输入KeyListener

还与同事进行了更多的挖掘,以尝试隔离问题:

在一方面,我们尝试在RT.jar中覆盖一些方法,以查看屏幕设备的选择方式是否存在问题。还尝试了 Toolkit.beep() 功能的入口点,以查看警报声音是否来自 Java 端 - 似乎不是。

在另一个方面,很明显,即使是本机端也没有收到键盘事件。一位同事将此归因于在 7u6 中从 a 切换到 a。AWTViewNSWindow

已经发现了一些现有的Oracle错误,您可以在此处查找:


10/26更新:

感谢下面@maslovalex关于在7u5上工作的小程序的评论,我回去仔细检查了与OSX的JDK版本的兼容性:

  • 10.7.1 与 7u4: 全屏工作!
  • 10.7.1 与 7u5: 全屏工作!
  • 10.7.5 与 7u5: 全屏工作!
  • 10.7.5 与 7u6:全屏中断:(

结合其他地方提到的其他测试,很明显,7u6引入了一个问题,该问题仍然存在于7u7和7u9中,并且它会影响Lion 10.7和Mountain Lion 10.8。

7u6是一个重要的里程碑版本,为Mac OS X带来了对JRE和JDK的全面支持,并且还将Java FX作为发行版的一部分。更多信息请参阅发行说明路线图。随着支持转向Java FX,这样的问题可能会突然出现,这并不奇怪。

问题就变成了:

  1. Oracle是否会在JDK的近期版本中解决此问题?(如果您有指向现有错误的链接,请在此处包含它们。
  2. 在此期间是否可以找到解决方法?

今天开始的其他更新:

  • 我将 Apple 扩展方法合并到全屏模式中,作为探索的替代路径(更新的示例代码等待清理)。好消息是:输入有效!坏消息是:似乎真的没有任何信息亭/隔离选项。
    我尝试直接或使用应用程序杀死Dock,因为据我所知,Dock负责命令选项卡应用程序切换,任务控制和Launch Pad,结果发现它也负责处理全屏应用程序!因此,Java 调用变得不起作用,并且永远不会返回。
    如果有一种方法可以在不影响Dock的全屏处理的情况下禁用Command-Tab(以及任务控制,Launchpad和Space),那将是非常有用的。或者,可以尝试重新映射某些键,例如Command,但这会影响在程序和系统本身的其他位置使用该修饰符的能力(不完全理想,当您需要Command-C复制一些文本时)。

  • 我在KeyListeners上没有运气(我没有收到任何回调),但我还有一些选择可以尝试。

  • 根据同事的建议,我尝试了反思。这个想法是,it:
    是一个本机方法,带有注释“如果应用程序(其窗口之一)拥有键盘焦点,则返回 true”。对此方法的调用已添加到CPlatformWindow中.java在过去的几个月中与焦点逻辑相关。如果它在测试代码中返回 false,则可能是问题的一部分。
    不幸的是,无论我在哪里检查它,该方法都返回了true。因此,即使根据低级系统,我的窗口也应该具有键盘焦点。((sun.lwawt.macosx.LWCToolkit)Toolkit.getDefaultToolkit()).isApplicationActive()

  • 我之前对JAlbum修复的乐观情绪已经破灭。开发人员在他们的论坛上发布了一个回复,解释了他们在运行Java 7时如何简单地删除OS X上适当的全屏支持。他们在Oracle中有一个错误(我希望得到错误编号)。


10/25年更新:

我现在还在Lion 10.7.4上尝试了Java 7u9,并且看到了完全相同的问题,所以它是JDK - 不是特定于操作系统的。

核心问题已经变成了您是否可以在全屏窗口中嵌入具有键盘输入的默认处理方式(甚至是可编辑的组合框)的核心 Swing 组件,并期望它们正常运行(而不必手动重建其基本键绑定)。另一个问题是,窗口布局的其他中坚力量(例如使用选项卡进行焦点遍历)是否应该起作用。JTextField/JTextArea

理想的目标是能够将带有窗口的 Swing 应用及其所有按钮、选项卡、字段等,并在全屏独占/展台模式下运行它,并且大多数功能完好无损。(以前,我看到对话框弹出窗口或ComboBox下拉列表在OS X上的Java 6上无法全屏显示,但其他组件表现良好。

我将研究 eawt FullScreen 功能,如果它们支持展台锁定选项(例如消除 Command-Tab 应用程序切换),这将很有趣。


原始问题:

我有一个Swing应用程序,多年来一直支持Mac OS X上的FullScreen(独家)模式,直到Java 6。我一直在对最新的Mountain Lion版本(10.8.2 Supplemental)和Oracle的JDK 7进行兼容性测试,并在该模式下注意到一个明显的问题:鼠标移动和单击工作正常,但键盘输入未传递到组件。

(我在下面的测试用例中将其缩小到在全屏模式下无法键入简单的JTextField。

一个症状是每次按键都会导致系统发出蜂鸣音,就好像操作系统不允许将键盘事件传递到应用程序一样。

另外,我的应用程序安装了一个出口钩子,Command-Q组合将触发该钩子 - 很明显操作系统正在侦听标准键组合。

我已经在三台不同的Mac上分别进行了测试,并进行了各种安装:

  • 在 Apple Java 6u35 和 6u37 上:窗口模式和全屏模式都接收输入。
  • 在 Oracle Java 7u7 和 7u9 上:窗口模式按预期工作,而全屏模式具有上述症状。

以前可能报告过此问题:Java 图形全屏模式未注册键盘输入。但是,这个问题并不具体涉及Java版本或平台。

额外的搜索发现了一个单独的全屏选项,该选项在Lion:OSX Lion上的Java应用程序的全屏功能中引入。我还没有尝试使用这种方法,因为键盘输入似乎是全屏独占模式的目标用例(如游戏)不可或缺的一部分。

此模式的 JavaDoc 中提到了输入法可能会被禁用。我试图调用建议的,但它似乎没有效果Component.enableInputMethods(false)

我有点乐观地认为,根据我遇到的Java应用程序(JAlbum)的发行说明中的一个条目,这个问题有一个解决方案。针对 10.10.6 的既定修复:“在 Mac 和 Java 7 上运行全屏幻灯片放映时,键盘支持不起作用”

我的测试用例如下。它从本期的第二个示例中略微修改(未经修改,也显示了我的问题):如何在java中以全屏独占模式处理键盘和鼠标的事件?特别是,它添加了一个按钮来切换全屏。

import java.lang.reflect.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;

/** @see https://stackoverflow.com/questions/13064607/ */
public class FullScreenTest extends JPanel {
    private GraphicsDevice dev = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
    private JFrame f = new JFrame("FullScreenTest");

    private static final String EXIT = "Exit";
    private Action exit = new AbstractAction(EXIT) {
        @Override
        public void actionPerformed(ActionEvent e) {
            Object o = dev.getFullScreenWindow();
            if(o != null) {
                dev.setFullScreenWindow(null);
            }
            f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING));
        }
    };
    private JButton exitBTN = new JButton(exit);

    private JTextField jtf = new JTextField("Uneditable in FullScreen with Java7u6+ on Mac OS X 10.7.3+");

    private JLabel keystrokeLabel = new JLabel("(Last Modifier+Key Pressed in JTextField)");

    private JLabel jtfFocusLabel = new JLabel("(JTextField Focus State)");

    private JLabel focusLabel = new JLabel("(Focused Component Hierarchy)");

    private JCheckBox useOSXFullScreenCB = new JCheckBox("Use Lion-Style FullScreen Mode");

    private JCheckBox useWorkaroundCB = new JCheckBox("Use Visibility Workaround to Restore 1st Responder Window");

    private static final String TOGGLE = "Toggle FullScreen (Command-T or Enter)"; 
    private Action toggle = new AbstractAction(TOGGLE) {
        @Override
        public void actionPerformed(ActionEvent e) {
            Object o = dev.getFullScreenWindow();
            if(o == null) {
                f.pack();

                /** 
                 * !! Neither of these calls seem to have any later effect.  
                 * One exception: I have a report of a 
                 * Mini going into an unrecoverable black screen without setVisible(true);  
                 * May be only a Java 6 compatibility issue.  !!
                 */
                //f.setVisible(true);
                //f.setVisible(false);

                if(!useOSXFullScreenCB.isSelected()) {
                    // No keyboard input after this call unless workaround is used
                    dev.setFullScreenWindow(f);

                    /**
                     * Workaround provided by Leonid Romanov at Oracle.
                     */
                    if(useWorkaroundCB.isSelected()) {
                        f.setVisible(false);
                        f.setVisible(true);
                        //Not necessary to invoke later...
                        /*SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                                f.setVisible(false);
                                f.setVisible(true);
                            }
                        });*/
                    }
                }
                else {
                    toggleOSXFullscreen(f);
                }
            }
            else {
                dev.setFullScreenWindow(null);
                f.pack();
                f.setVisible(true);
            }

            isAppActive();
        }
    };
    private JButton toggleBTN = new JButton(toggle);

    public FullScreenTest() {            
        // -- Layout --
        this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));

        exitBTN.setAlignmentX(JComponent.CENTER_ALIGNMENT);
        exitBTN.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
        this.add(exitBTN);

        jtf.setAlignmentX(JComponent.CENTER_ALIGNMENT);
        jtf.setMaximumSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE));
        this.add(jtf);

        keystrokeLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT);
        keystrokeLabel.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
        keystrokeLabel.setHorizontalAlignment(SwingConstants.CENTER);
        keystrokeLabel.setForeground(Color.DARK_GRAY);
        this.add(keystrokeLabel);

        jtfFocusLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT);
        jtfFocusLabel.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
        jtfFocusLabel.setHorizontalAlignment(SwingConstants.CENTER);
        jtfFocusLabel.setForeground(Color.DARK_GRAY);
        this.add(jtfFocusLabel);

        focusLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT);
        focusLabel.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
        focusLabel.setHorizontalAlignment(SwingConstants.CENTER);
        focusLabel.setForeground(Color.DARK_GRAY);
        this.add(focusLabel);

        useOSXFullScreenCB.setAlignmentX(JComponent.CENTER_ALIGNMENT);
        useOSXFullScreenCB.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
        useOSXFullScreenCB.setHorizontalAlignment(SwingConstants.CENTER);
        this.add(useOSXFullScreenCB);

        useWorkaroundCB.setAlignmentX(JComponent.CENTER_ALIGNMENT);
        useWorkaroundCB.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
        useWorkaroundCB.setHorizontalAlignment(SwingConstants.CENTER);
        this.add(useWorkaroundCB);

        toggleBTN.setAlignmentX(JComponent.CENTER_ALIGNMENT);
        toggleBTN.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
        this.add(toggleBTN);

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setResizable(false);
        f.setUndecorated(true);
        f.add(this);
        f.pack();

        enableOSXFullscreen(f);

        // -- Listeners --

        // Default BTN set to see how input maps respond in fullscreen
        f.getRootPane().setDefaultButton(toggleBTN);

        // Explicit input map test with Command-T toggle action from anywhere in the window
        this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
                KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), 
                toggle.getValue(Action.NAME));
        this.getActionMap().put(toggle.getValue(Action.NAME), toggle);

        // KeyListener test
        jtf.addKeyListener(new KeyAdapter() {                                                                                                                                                                                                                                                    
            public void keyPressed(KeyEvent e) {                                                                                                                                                                                                                                                  
                String ktext = "KeyPressed: "+e.getKeyModifiersText(e.getModifiers()) + "_"+ e.getKeyText(e.getKeyCode());
                keystrokeLabel.setText(ktext);
                System.out.println(ktext);
            }
        });

        // FocusListener test
        jtf.addFocusListener(new FocusListener() {
            public void focusGained(FocusEvent fe) {
                focused(fe);
            }
            public void focusLost(FocusEvent fe) {
                focused(fe);
            }
            private void focused(FocusEvent fe) {
                boolean allGood = jtf.hasFocus() && jtf.isEditable() && jtf.isEnabled();
                jtfFocusLabel.setText("JTextField has focus (and is enabled/editable): " + allGood);
                isAppActive();
            }
        });

        // Keyboard Focus Manager
        KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        focusManager.addPropertyChangeListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent e) {
                if (!("focusOwner".equals(e.getPropertyName()))) return;
                Component comp = (Component)e.getNewValue();
                if(comp == null) {
                    focusLabel.setText("(No Component Focused)");
                    return;
                }
                String label = comp.getClass().getName();
                while(true) {
                    comp = comp.getParent();
                    if(comp == null) break;
                    label = comp.getClass().getSimpleName() + " -> " + label;
                }
                focusLabel.setText("Focus Hierarchy: " + label);
                isAppActive();
            }
        });
    }

    /**
     * Hint that this Window can enter fullscreen.  Only need to call this once per Window.
     * @param window
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static void enableOSXFullscreen(Window window) {
        try {
            Class util = Class.forName("com.apple.eawt.FullScreenUtilities");
            Class params[] = new Class[]{Window.class, Boolean.TYPE};
            Method method = util.getMethod("setWindowCanFullScreen", params);
            method.invoke(util, window, true);
        } catch (ClassNotFoundException e1) {
        } catch (Exception e) {
            System.out.println("Failed to enable Mac Fullscreen: "+e);
        }
    }

    /**
     * Toggle OSX fullscreen Window state. Must call enableOSXFullscreen first.
     * Reflection version of: com.apple.eawt.Application.getApplication().requestToggleFullScreen(f);
     * @param window
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static void toggleOSXFullscreen(Window window) {
        try {
            Class appClass = Class.forName("com.apple.eawt.Application");

            Method method = appClass.getMethod("getApplication");
            Object appInstance = method.invoke(appClass);

            Class params[] = new Class[]{Window.class};
            method = appClass.getMethod("requestToggleFullScreen", params);
            method.invoke(appInstance, window);
        } catch (ClassNotFoundException e1) {
        } catch (Exception e) {
            System.out.println("Failed to toggle Mac Fullscreen: "+e);
        }
    }

    /**
     * Quick check of the low-level window focus state based on Apple's Javadoc:
     *  "Returns true if the application (one of its windows) owns keyboard focus."
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public static void isAppActive() {
        try {
            Class util = Class.forName("sun.lwawt.macosx.LWCToolkit");
            Method method = util.getMethod("isApplicationActive");
            Object obj = method.invoke(Toolkit.getDefaultToolkit());
            System.out.println("AppActive: "+obj);
        } catch (ClassNotFoundException e1) {
        } catch (Exception e) {
            System.out.println("Failed to check App: "+e);
        }
    }

    public static void main(String[] args) {
        System.out.println("Java Version: " + System.getProperty("java.version"));
        System.out.println("OS Version: " + System.getProperty("os.version"));

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                FullScreenTest fst = new FullScreenTest();
                if(!fst.dev.isFullScreenSupported()) {
                    System.out.println("FullScreen not supported on this graphics device.  Exiting.");
                    System.exit(0);
                }
                fst.toggle.actionPerformed(null);
            }
        });
    }
}

答案 1

这是因为您向其添加另一个组件的组件现在已经失去了焦点,您可以通过以下任一方法解决此问题:

  • 调用要向其添加 的组件实例requestFocus()KeyBinding

  • 或者与 s 一起使用:JComponent.WHEN_IN_FOCUSED_WINDOWKeyBinding

    component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, 0),
                            "doSomething");
    component.getActionMap().put("doSomething",
                             anAction);
    

参考:


答案 2

请改用键绑定,如此 FullScreenTest 中所示。此外,对于文本组件,请考虑此处显示的 。DocumentListener