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

时间:2012-10-25 08:30:50

标签: java macos swing fullscreen keyboard-events

更新12/21:

7u10最近发布。确认:

  1. 问题仍然存在
  2. 值得庆幸的是,解决方法仍然有效!
  3. 更新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)。理论上,这应该恢复正确的第一响应者。

    似乎有效的代码片段就是:

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

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

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


    更新10/31:

    示例代码的主要更新:

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

    还与同事一起尝试分离问题:

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

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

    已找到一系列现有的Oracle错误,您可以查看here


    更新10/26:

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

    • 10.7.1 with 7u4:Fullscreen Works!
    • 10.7.1 with 7u5:Fullscreen Works!
    • 10.7.5 with 7u5:Fullscreen Works!
    • 10.7.5 with 7u6:Fullscreen Breaks:(

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

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

    问题变成:

    1. Oracle会在近期发布的JDK中修复此问题吗? (如果您有现有错误的链接,请在此处加入。)
    2. 在过渡期间是否可以采取解决方法?
    3. 今天的其他更新:

      • 我将Apple extensions approach to fullscreen mode作为替代探索路径(更新的示例代码待定清除)。好消息:输入有效!坏消息:似乎没有任何信息亭/隔离选项。 我尝试杀死Dock - directlyApp - 据我所知,Dock负责Command-Tab app切换,Mission Control和Launch Pad,但却发现了它还负责处理全屏应用程序!因此,Java调用变得不起作用,永远不会返回 如果有disable Command-Tab(以及Mission Control和Launchpad和Spaces)的方式而不影响Dock的全屏处理,那将非常有用。或者,可以尝试重新映射某些键,例如Command,但这会影响在程序中的其他地方和系统本身使用该修饰符的能力(当您需要Command-C复制某些文本时,这并不完全理想)。 / p>

      • 我对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中有一个错误(我希望得到错误号)。

        < / LI>

      更新10/25:

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

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

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

      我正在研究eawt FullScreen功能,如果它们支持自助服务终端锁定选项,例如取消Command-Tab应用程序切换,那将会很有趣。


      原始问题:

      我有一个Swing应用程序多年来一直通过Java 6支持Mac OS X上的FullScreen (exclusive)模式。我一直在使用最新的Mountain Lion版本(10.8.2 Supplemental)进行兼容性测试,在该模式下,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。 我还没有尝试使用这种方法,因为键盘输入似乎是FullScreen Exclusive模式的目标用例不可或缺的部分,例如游戏。

      JavaDoc中提到此模式可能会禁用输入方法。我尝试拨打建议的Component.enableInputMethods(false),但似乎是have no effect

      我有点乐观地认为,根据我遇到的Java应用程序的发行说明(JAlbum)中的条目,可以找到解决此问题的方法。在Mac和Java 7上运行全屏幻灯片时,stated fix for 10.10.6:&#34;键盘支持不起作用&#34;

      我的测试用例如下。从本期的第二个例子中可以轻松修改它(未经修改,也表现出我的问题):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);
                  }
              });
          }
      }
      

3 个答案:

答案 0 :(得分:2)

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

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

  • 或者JComponent.WHEN_IN_FOCUSED_WINDOW使用KeyBinding

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

<强>参考:

答案 1 :(得分:2)

相反,请使用key bindings,如此FullScreenTest所示。另外,请考虑DocumentListener,显示here,用于文本组件。

答案 2 :(得分:0)

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