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

更新12/21:

7u10最近发布。 确认:

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

更新11/7:

我们有一个解决方法!

来自Oracle的Leonid Romanov在openjdk.java.net邮件列表中提供了一些有关正在发生的事情的见解 :

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

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

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

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

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


更新10/31:

示例代码的主要更新:

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

还与同事一起进行了更多挖掘,试图找出问题:

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

另一方面,很明显即使是本机方也没有接收键盘事件。 同事将此归因于7月6日从AWTViewNSWindow的转换。

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

  • 8000276:[macosx] graphicsDevice.setFullScreenWindow(frame)崩溃JVM
  • 8000430:[macosx] java.awt.FileDialog在macosx上发布
  • 7175707:[macosx] PIT:8 b43不再在AppKit线程问题上运行

更新10/26:

感谢@maslovalex下面关于工作于7u5的Applet的评论,我回过头来仔细研究了与OSX的JDK版本的兼容性:

  • 10.7.1 with 7u4:Fullscreen Works!
  • 10.7.1 with 7u5:Fullscreen Works!
  • 10.7.5与7u5:全屏工作!
  • 10.7.5与7u6:全屏rest:(

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

7u6是一个重要的里程碑版本,它将JRE和JDK全面支持Mac OS X,并将Java FX作为发布的一部分。 有关详细信息,请参阅发行说明和路线图 。 这种问题可能会随着支持转向Java FX而出现并不令人惊讶。

问题变成:

  1. Oracle会在近期发布的JDK中修复此问题吗? (如果您有现有错误的链接,请在此处添加。)
  2. 过渡期是否可行?

今天的其他更新:

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

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

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

  • 我以前对JAlbum修复的乐观态度已经破灭。 开发人员在他们的论坛上发布了一个响应 ,解释了他们在运行Java 7时如何在OS X上删除正确的全屏支持。他们在Oracle中有一个错误(我希望得到错误号)。


更新10/25:

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

核心问题是,您是否可以嵌入全屏窗口核心Swing组件,该组件具有键盘输入的默认处理( JTextField/JTextArea或甚至可编辑的combobox)并期望它们正常运行(无需求助于重建其基本键绑定手动地)。 还有一个问题是,窗口布局的其他坚持者,例如使用制表符进行焦点遍历,是否应该有效。

理想的目标是让能够使用带有所有按钮,标签,字段等的窗口Swing应用程序,并以全屏独占/自助服务终端模式运行,大多数function完好无损。 (以前,我已经看到在OS X上的Java 6上,Dialog弹出窗口或ComboBox下拉列表无法全屏显示,但其他组件表现良好。)

我将研究eawt的FullScreenfunction,如果它们支持kiosk锁定选项,例如消除Command-Tab应用程序切换,那将会很有趣。


原始问题:

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

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

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

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

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

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

这可能是之前报道的: Java图形全屏模式未注册键盘输入 。 但是,该问题并不是针对Java版本或平台的具体问题。

其他搜索已经在Lion中引入了一个单独的全屏选项: OSX Lion上的Java应用程序的全屏function 。 我还没有尝试使用这种方法,因为键盘输入似乎是FullScreen Exclusive模式的目标用例不可或缺的部分,例如游戏。

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); } }); } } 

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

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

要么

  • 或者使用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); 

参考:

  • 如何使用键绑定

而是使用键绑定 ,如此FullScreenTest所示。 另外,请考虑一个DocumentListener ,如此处所示,用于文本组件。

我想我终于找到了一个解决方案,将点击监听器注册到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); } };