KeyListener on JPanel randomly unresponsive

2019-02-19 17:31发布

I'm having trouble with the default Java KeyListener in my project. I noticed that the KeyListener doesn't seem to get KeyEvents forwarded sometimes when I start.

Symptoms of the problem: When starting the application key input isn't processed. This only happens sometimes. Sometimes I have to close and start the app 7-8 times until this shows up. Sometimes it's the first try. When it happens it won't work until I restart the app again.

What I'm using: Window 7 x64 and the newest Eclipse and JDK versions.

What I found out already: I've put a breakpoint in debug mode and checked the JPanel instance out. The KeyListener is always successfully added to it, it seems. Also, MouseListener and MouseMotionListener work just fine, all the time.

Minimal code:

public class Player implements KeyListener
{
    public void keyTyped(KeyEvent e){}
    public void keyReleased(KeyEvent e){ }

    public void keyPressed(KeyEvent e){
        System.out.println("Key Pressed!");
    }

}

public class Game {

    public static void main(String[] args) {
        new Game();
    } 

    public Game(){
        JFrame window = new JFrame();
        window.setVisible(true);

        //Now set the window size correctly
        window.setSize(800, 600);  
        //Set-up the rest of the window
        window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        window.setResizable(true);


        //Create our panel
        JPanel canvas = new JPanel();
        canvas.setFocusable(true);
        window.add( canvas ); //Add it to our window

        Player k = new Player();
        canvas.addKeyListener(k);
    }
}

Thank you for your time!

PS: Ok, answer to my own question:

It seems that I have to call setVisible(true) after setting the window's size:

    JFrame window = new JFrame();


    Now set the window size correctly
    window.setSize(800, 600);  
    window.setVisible(true);

Switching out the setSize() and setVisible() like this seems to make it work. Tried it about a dozen times without a problem.

I guess setVisible might not like giving Focus to the window if it's of size 0x0. The question is: Why does this only cause a problem in one out of some cases?

2条回答
Root(大扎)
2楼-- · 2019-02-19 17:48

Try adding a JButton to your "canvas" JPanel, then pressing the button and seeing what happens to your KeyListener -- it fails because the JPanel lost the focus. To prevent this from happening, use Key Bindings instead (see the link in my comment above for the tutorial). For e.g.,

import java.awt.event.*;
import java.awt.*;
import javax.swing.*;

@SuppressWarnings("serial")
public class Game2 {

   private static final String UP = "up";

   public static void main(String[] args) {
      new Game2();
   }

   public Game2() {
      JFrame window = new JFrame("Press up-arrow key");
      window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      JPanel canvas = new JPanel();
      canvas.setPreferredSize(new Dimension(400, 300));
      window.add(canvas);

      canvas.add(new JButton(new AbstractAction("Press space-bar") {
         public void actionPerformed(ActionEvent e) {
            System.out.println("Button or space-bar pressed");
         }
      }));
      ActionMap actionMap = canvas.getActionMap();
      int condition = JComponent.WHEN_IN_FOCUSED_WINDOW;
      InputMap inputMap = canvas.getInputMap(condition);

      inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), UP);
      actionMap.put(UP, new UpAction());

      window.pack();
      window.setLocationRelativeTo(null);
      window.setVisible(true);
   }
}

@SuppressWarnings("serial")
class UpAction extends AbstractAction {
   @Override
   public void actionPerformed(ActionEvent arg0) {
      System.out.println("Up Arrow pressed!");
   }
}
查看更多
贪生不怕死
3楼-- · 2019-02-19 17:51

Don't know if this is related to your problems, but due to the intermittent nature of it perhaps it is...You should execute setVisible() last and in the swing thread. You could call setSize after setVisible if you want to, but the user might see a flicker and it likewise should be done in the swing thread. Do this as your last step:

SwingUtilities.invokeLater( new Runnable() {
   public void run() {
      window.setVisible( true );
   }
} );

To do this, you will also need to make window declaration final:

...
final JFrame window = new JFrame();
...
查看更多
登录 后发表回答