javaswingkeylistenerfocusable

keyListener not applying to JPanel (yes, it is focused)


I am currently working on a project and when I try to add my own key listener that is in its own class it doesn't work and when you press the keys nothing happens, and I've been at it for a while now. I can't use Keybindings so please don't offer for me to change to those because they don't work with what I am doing since they don't support multiple key presses at 1 time (trust me ive tried). It is focused using panel.setFocusable(true); and panel.requestFocusInWindow();, and I even did it to the frame with frame.setFocusable(true); and frame.requestFocusInWindow(); but still nothing. these are all my files:

EDIT: I added a SwingUtilities.invokeLater(new Runnable() {, but still nothing, I am currently doing what MadProgrammer said about KeyBindings but as of now I'm getting some weird errors I'm trying to fix.

Game.Java:

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import javax.swing.JPanel;
import com.PK.character.MainCharacter;

public class Game extends JPanel{
    private static final long serialVersionUID = -2398443377427441196L;
    public static Image gamemainmenu = Toolkit.getDefaultToolkit().createImage("src/resources/homerscared.jpg");
    public static boolean menu;
    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        g.drawImage(gamemainmenu, 10, 10, null);
        menu = true;
        if (menu = true){
            g.drawImage(MainCharacter.MainCharacterImage, 100, 100, null);
        }
    }
}

ButtonListener.Java:

import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JPanel;

import com.PK.PK;
import com.PK.character.MainCharacter;

public class ButtonListener implements KeyListener{
private static JPanel gamepanel = PK.panel;
@Override
public void keyPressed(KeyEvent arg0) {
    /**N=0
     * NE=1
     * E=2
     * SE=3
     * S=4
     * SW=5
     * W=6
     * NW=7
     */
    if (arg0.getKeyChar() == KeyEvent.VK_DOWN){
        MainCharacter.move(4, MainCharacter.CharacterS);
        System.out.println("down pressed");
        gamepanel.setForeground(Color.BLUE);
    }
    if (arg0.getKeyChar() == KeyEvent.VK_UP){
        MainCharacter.move(0, MainCharacter.CharacterN);
        System.out.println("up pressed");
    }
    if (arg0.getKeyChar() == KeyEvent.VK_LEFT){
        MainCharacter.move(6, MainCharacter.CharacterW);
        System.out.println("left pressed");
    }
    if (arg0.getKeyChar() == KeyEvent.VK_RIGHT){
        MainCharacter.move(2, MainCharacter.CharacterE);
        System.out.println("right pressed");

    }
    if (arg0.getKeyChar() == KeyEvent.VK_RIGHT && arg0.getKeyChar() == KeyEvent.VK_UP){
        MainCharacter.move(1, MainCharacter.CharacterNE);
        System.out.println("right and up pressed");
    }
    if (arg0.getKeyChar() == KeyEvent.VK_RIGHT && arg0.getKeyChar() == KeyEvent.VK_DOWN){
        MainCharacter.move(3, MainCharacter.CharacterSE);
        System.out.println("up and right pressed");
    }
    if (arg0.getKeyChar() == KeyEvent.VK_LEFT && arg0.getKeyChar() == KeyEvent.VK_UP){
        MainCharacter.move(7, MainCharacter.CharacterNW);
        System.out.println("up and left pressed");
    }
    if (arg0.getKeyChar() == KeyEvent.VK_LEFT && arg0.getKeyChar() == KeyEvent.VK_DOWN){
        MainCharacter.move(5, MainCharacter.CharacterSW);
        System.out.println("left and down pressed");
    }
}

@Override
public void keyReleased(KeyEvent arg0) {

}

@Override
public void keyTyped(KeyEvent arg0) {

}

}

PK.Java (Main Class):

package com.PK;

import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyListener;

import javax.swing.JFrame;
import javax.swing.JPanel;
import com.PK.movement.ButtonListener;

public class PK {
    public static short CharacterX, CharacterY;
    public static final int width = 800;
    public static final int height = 600;
    public static Date date = new Date();
    public static String dString = date.toString();
    public static String dFormat = "[" + dString + "]: ";
    public static JFrame frame = new JFrame();
    public static JPanel panel = new Game();
    public static KeyListener bt = new ButtonListener();
    public static Image logobasic = Toolkit.getDefaultToolkit().createImage("src/resources/logo-basic.png");
public static void main(String[] args){
    frame.setContentPane(panel);
    System.out.println(dFormat + "Panel added to frame");
    frame.setSize(width, height);
    frame.setTitle("PK");
    frame.setIconImage(logobasic);
    frame.setVisible(true);
    frame.setJMenuBar(null);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    System.out.println(dFormat + "Frame settings set");
    System.out.println(dFormat + "Launching...");   
}

public PokemonUniverse(){
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            panel.setFocusable(true);
            panel.requestFocusInWindow();
            System.out.println(dFormat + "Panel focused");
            panel.addKeyListener(bt);
            System.out.println(dFormat + "KeyListener added to panel");
            frame.setFocusable(true);
            frame.requestFocusInWindow();
            System.out.println(dFormat + "Frame focused");
            frame.addKeyListener(bt);
            System.out.println(dFormat + "KeyListener added to frame");
        }
    });
}

MainCharacter.Java

package com.PK;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;

import javax.swing.JPanel;

import com.PK.Game;
import com.PK.PK;

@SuppressWarnings("unused")
public class MainCharacter {
static Toolkit tk = Toolkit.getDefaultToolkit();
public static Image MainCharacterImage = PK.logobasic, CharacterS, CharacterN, CharacterW, CharacterE, CharacterSE, CharacterNE, CharacterSW, CharacterNW;
private static JPanel gamepanel = PK.panel;
private static short Y = PK.CharacterY;
private static short X = PK.CharacterX;

/**N=0
 * NE=1
 * E=2
 * SE=3
 * S=4
 * SW=5
 * W=6
 * NW=7
 */
public static void move(int direction, Image FacingDirection) {
    if (direction == 0){
        Y++;
        MainCharacterImage = FacingDirection;
        gamepanel.repaint();
    }
    else if (direction == 1){
        Y++;
        X++;
        MainCharacterImage = FacingDirection;
        gamepanel.repaint();
    }
    else if (direction == 2){
        X++;
        MainCharacterImage = FacingDirection;
        gamepanel.repaint();
    }
    else if (direction == 3){
        Y--;
        X++;
        MainCharacterImage = FacingDirection;
        gamepanel.repaint();
    }
    else if (direction == 4){
        Y--;
        MainCharacterImage = FacingDirection;
        gamepanel.repaint();
    }
    else if (direction == 5){
        Y--;
        X--;
        MainCharacterImage = FacingDirection;
        gamepanel.repaint();

    }
    else if (direction == 6){
        X--;
        MainCharacterImage = FacingDirection;
        gamepanel.repaint();
    }
    else if (direction == 7){
        X--;
        Y++;
        MainCharacterImage = FacingDirection;
        gamepanel.repaint();
    }
    else{
        MainCharacterImage = PK.logobasic;
        gamepanel.repaint();
    }
}
}

Solution

  • The short answer is, don't use KeyListener, use the Key Bindings API which will allow you to overcome these shortcomings and configure the level of focus required in order for them to be triggered.

    See How to Use Key Bindings for more details

    Updated

    First, you need to stop think in such absolute terms and start thinking in more abstract concepts. Your game shouldn't care HOW something is done, only when something IS down.

    The problem is not with the key bindings API but how you see the problem.

    For example, you shouldn't care HOW an "up" event is triggered, only that it was. This means that trigger could have come from a joystick, game controller, network server, mind control through the force and even the keyboard. The game would then react to this change in state accordingly.

    Key bindings

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.KeyEvent;
    import javax.swing.AbstractAction;
    import javax.swing.ActionMap;
    import javax.swing.InputMap;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.KeyStroke;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.border.EmptyBorder;
    import javax.swing.border.LineBorder;
    
    public class KeyBindingsTest {
    
        public static void main(String[] args) {
            new KeyBindingsTest();
        }
    
        public KeyBindingsTest() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public interface InputManager {
    
            public void upWasPerformed(boolean active);
            public void downWasPerformed(boolean active);
            public void leftWasPerformed(boolean active);
            public void rightWasPerformed(boolean active);
    
        }
    
        public static class TestPane extends JPanel implements InputManager {
    
            public static final LineBorder LINE_BORDER = new LineBorder(Color.RED);
            public static final EmptyBorder EMPTY_BORDER = new EmptyBorder(1, 1, 1, 1);
    
            private JLabel up;
            private JLabel down;
            private JLabel left;
            private JLabel right;
    
            public TestPane() {
                setLayout(new GridBagLayout());
                up = new JLabel("UP");
                up.setBorder(EMPTY_BORDER);
    
                down = new JLabel("DOWN");
                down.setBorder(EMPTY_BORDER);
    
                left = new JLabel("LEFT");
                left.setBorder(EMPTY_BORDER);
    
                right = new JLabel("RIGHT");
                right.setBorder(EMPTY_BORDER);
    
                setLayout(new GridBagLayout());
                GridBagConstraints gbc = new GridBagConstraints();
                gbc.gridx = 0;
                gbc.gridy = 1;
                add(left, gbc);
                gbc.gridx++;
                gbc.gridy = 0;
                add(up, gbc);
                gbc.gridy = 2;
                add(down, gbc);
                gbc.gridx++;
                gbc.gridy = 1;
                add(right, gbc);
    
                InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up.pressed");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "up.released");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down.pressed");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "down.released");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "left.pressed");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "left.released");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right.pressed");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "right.released");
    
                ActionMap am = getActionMap();
                am.put("up.pressed", new UpAction(this, true));
                am.put("up.released", new UpAction(this, false));
                am.put("down.pressed", new DownAction(this, true));
                am.put("down.released", new DownAction(this, false));
                am.put("left.pressed", new LeftAction(this, true));
                am.put("left.released", new LeftAction(this, false));
                am.put("right.pressed", new RightAction(this, true));
                am.put("right.released", new RightAction(this, false));
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(200, 200);
            }
    
            @Override
            public void upWasPerformed(boolean active) {
                up.setBorder(active ? LINE_BORDER : EMPTY_BORDER);
            }
    
            @Override
            public void downWasPerformed(boolean active) {
                down.setBorder(active ? LINE_BORDER : EMPTY_BORDER);
            }
    
            @Override
            public void leftWasPerformed(boolean active) {
                left.setBorder(active ? LINE_BORDER : EMPTY_BORDER);
            }
    
            @Override
            public void rightWasPerformed(boolean active) {
                right.setBorder(active ? LINE_BORDER : EMPTY_BORDER);
            }
    
        }
    
        public static abstract class InputManagerAction extends AbstractAction {
    
            private InputManager manager;
            private boolean activate;
    
            public InputManagerAction(InputManager manager, boolean activate) {
                this.manager = manager;
                this.activate = activate;
            }
    
            public InputManager getManager() {
                return manager;
            }
    
            public boolean shouldActivate() {
                return activate;
            }
    
        }
    
        public static class UpAction extends InputManagerAction {
    
            public UpAction(InputManager manager, boolean activate) {
                super(manager, activate);
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                getManager().upWasPerformed(shouldActivate());
            }
    
        }
    
        public static class DownAction extends InputManagerAction {
    
            public DownAction(InputManager manager, boolean activate) {
                super(manager, activate);
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                getManager().downWasPerformed(shouldActivate());
            }
    
        }
    
        public static class LeftAction extends InputManagerAction {
    
            public LeftAction(InputManager manager, boolean activate) {
                super(manager, activate);
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                getManager().leftWasPerformed(shouldActivate());
            }
    
        }
    
        public static class RightAction extends InputManagerAction {
    
            public RightAction(InputManager manager, boolean activate) {
                super(manager, activate);
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                getManager().rightWasPerformed(shouldActivate());
            }
    
        }
    }
    

    Now, you have two choices to make, you can change the way you are thinking and take advantage of an API which will solve your problem and make your program more flexible and configurable (as the user might like to change the keys that trigger the events) or your can continue trying to use to an API which is failing to meet your needs and which the community at large will encourage you not to continue to use...