【问题标题】:FullScreen Swing Components Fail to Receive Keyboard Input on Java 7 on Mac OS X Mountain LionFullScreen Swing 组件无法在 Mac OS X Mountain Lion 上的 Java 7 上接收键盘输入
【发布时间】:2012-10-15 09:38:24
【问题描述】:

12/21 更新:

7u10 最近发布了。确认:

  1. 问题依然存在
  2. 谢天谢地,解决方法仍然有效!

11/7 更新:

我们有一个解决方法!

Leonid Romanov from Oracle on the openjdk.java.net mailing list provided some insight as to what's going on:

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

sn-p 似乎可以工作的代码很简单:

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

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

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


10/31 更新:

示例代码的主要更新:

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

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

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

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

已经找到了一些现有的Oracle bug,您可以查找here


10/26 更新:

感谢@maslovalex 下面关于在 7u5 上工作的 Applet 的评论,我回去仔细检查了与 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 作为发行版的一部分。更多信息可在Release NotesRoadmap 中获得。随着支持转移到 Java FX,这样的问题可能会突然出现,这并不奇怪。

问题变成了:

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

今天的其他更新:

  • 我合并了 Apple extensions approach to fullscreen mode 作为探索的替代路径(更新的示例代码等待清理)。好消息:输入有效!坏消息:似乎真的没有任何信息亭/隔离选项。
    我尝试杀死 Dock - directly 或使用 App - 据我了解 Dock 负责 Command-Tab 应用程序切换、任务控制和 Launch Pad,却发现它负责处理全屏应用程序!因此,Java 调用变得无功能并且永远不会返回。
    如果有一种方法可以disable Command-Tab(以及任务控制和启动板和空间)而不影响 Dock 的全屏处理,那将非常有用。或者,可以尝试重新映射某些键,例如 Command,但这会影响在程序和系统本身的其他地方使用该修饰符的能力(不完全理想,当您需要使用 Command-C 复制一些文本时)。

  • 我在使用 KeyListeners 时运气不佳(我没有收到任何回调),但我还有更多选择可以尝试。

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

  • 我之前对 JAlbum 修复的乐观态度已经破灭。开发人员 posted a response on their forum 解释了他们如何在运行 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 功能,如果它们支持 kiosk 锁定选项(例如消除 Command-Tab 应用程序切换),将会很有趣。


原问题:

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

(我在下面的测试用例中缩小了范围,使其在全屏模式下无法输入简单的 JTextField。)

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

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

我已经在三台不同的 Mac 上分别进行了测试:

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

这可能是以前报告过的: Java Graphics Full Screen Mode not Registering Keyboard Input。 但是,这个问题并不特定于 Java 版本或平台。

额外的搜索在 Lion 中引入了一个单独的全屏选项: Fullscreen feature for Java Apps on OSX Lion。 我还没有尝试使用这种方法,因为键盘输入似乎是全屏独占模式的目标用例不可或缺的一部分,例如游戏。

JavaDoc 中提到了这种模式可能会禁用输入法。我试图打电话给建议的Component.enableInputMethods(false),但似乎是have no effect

我有点乐观地认为,这个问题的解决方案是基于我遇到的一个 Java 应用程序 (JAlbum) 的发行说明中的​​一个条目。 A stated fix for 10.10.6:“在 Mac 和 Java 7 上运行全屏幻灯片时,键盘支持不起作用”

我的测试用例如下。它在本期的第二个示例中稍作修改(未修改,也显示了我的问题):How to handle events from keyboard and mouse in full screen exclusive mode in 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);
            }
        });
    }
}

【问题讨论】:

  • A 有完全相同的问题。几周前,我向 Oracle 发布了错误请求(使用简单的 Applet 代码进行复制,因为使用 7u7 我只能使用小程序来复制它) - 到目前为止没有答案。您也可以报告它(也许它会让事情发生得更快)。顺便提一句。 7u5 一切正常。
  • 实际上在 jAlbum 中,他们根本不为 osx 中的 java 7 使用 FullScreen(未装饰的大小正确的框架) - 检查幻灯片。如果我使用 java 6 幻灯片放映直接运行 FS Exclusive)
  • 感谢@maslovalex 的信息。非常有趣的是 7u5 为您工作。我将尝试在另一台机器上进行测试。
  • 还有@maslovalex,你碰巧有你提到的那个Oracle错误的链接吗?
  • 不,没有可见的错误 - 它在“内部审查”中

标签: java macos swing fullscreen keyboard-events


【解决方案1】:

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

  • 在您添加KeyBindings 的组件实例上调用requestFocus()

  • 或者将JComponent.WHEN_IN_FOCUSED_WINDOWKeyBindings 一起使用:

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

参考:

【讨论】:

  • 我会在早上试一试。我认为焦点不是问题的原因之一是 JTextField 在鼠标单击时获得焦点环。此外,您可以使用鼠标选择文本。我的理解是这些事件将为后续的 KeyEvents 提供组件焦点 - 并且 JTextField 将有自己的基本文本输入/标准输入绑定,但显然在 FullScreen 中默认情况下可能不是这种情况。
【解决方案2】:

改为使用key bindings,如FullScreenTest 所示。另外,考虑一个DocumentListener,显示为here,用于文本组件。

【讨论】:

  • 感谢您的更新。您引用的 FullScreenTest(最初是贡献的)正是我用作上述示例的起点的示例。我的第一个测试涉及直接使用您的 FullScreenTest 并且未更改,包括它对键绑定的使用。不幸的是,它表现出与上面提到的相同的问题,这就是为什么我认为键绑定不是问题。
  • 抱歉,我没有要测试的 10.8。是否可以通过setExtendedState() 最大化?
【解决方案3】:

我想我终于找到了一个解决方案,将点击侦听器注册到 JFrame 本身。 (这是一个扩展 JFrame 的类,因此所有的“this”引用。)

/**
 * Toggles full screen mode. Requires a lot of references to the JFrame.
 */
public void setFullScreen(boolean fullScreen){
    GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice dev = env.getDefaultScreenDevice();//Gets the main screen
    if(!fullScreen){//Checks if a full screen application isn't open
        this.dispose();//Restarts the JFrame
        this.setVisible(false);
        this.setResizable(true);//Re-enables resize-ability.
        this.setUndecorated(false);//Adds title bar back
        this.setVisible(true);//Shows restarted JFrame
        this.removeMouseListener(macWorkAround);
        this.pack();
        this.setExtendedState(this.getExtendedState()|JFrame.MAXIMIZED_BOTH);//Returns to maximized state
        this.fullScreen = false;
    }
    else{
        this.dispose();//Restarts the JFrame
        this.setResizable(false);//Disables resizing else causes bugs
        this.setUndecorated(true);//removes title bar
        this.setVisible(true);//Makes it visible again
        this.revalidate();
        this.setSize(Toolkit.getDefaultToolkit().getScreenSize());
        try{
            dev.setFullScreenWindow(this);//Makes it full screen
            if(System.getProperty("os.name").indexOf("Mac OS X") >= 0){
                this.setVisible(false);
                this.setVisible(true);
                this.addMouseListener(macWorkAround);
            }
            this.repaint();
            this.revalidate();
        }
        catch(Exception e){
            dev.setFullScreenWindow(null);//Fall back behavior
        }
        this.requestFocus();
        this.fullScreen = true;
    }
}

private MouseAdapter macWorkAround = new MouseAdapter(){
    public void mouseClicked(MouseEvent e){
        MainGUI.this.setVisible(false);
        MainGUI.this.setVisible(true);
    }
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-12
    • 1970-01-01
    • 2012-10-21
    • 2012-11-15
    • 2012-09-15
    • 2013-09-24
    • 1970-01-01
    相关资源
    最近更新 更多