John Is No
John Is No

Reputation: 144

Use addKeyListener in a class to listen keys from another class?

I am trying to have a thread to listen to keys - which is a different class to where the JPanel is created. Below is the class where the JPanel is.

public class Game extends JPanel {

public static final int WIDTH = 600,
                        HEIGHT = 650;

private static boolean running;

private BufferedImage image;
private Graphics2D g;   

public String str;
private static Thread MyThread;


public Game() {
    super();
    setPreferredSize(new Dimension(WIDTH, HEIGHT));
    setFocusable(true);
    requestFocus();
}



public void addNotify(){
    super.addNotify();
    MyThread myThread = new MyThread(this);

    if(myThread == null){
        myThread = new Thread(myThread);
        myThread.start();
    }
}


public void run() {
    image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
    g = (Graphics2D) image.getGraphics();

    while(running){
        update();
        render();
        draw();
        System.out.println(str);                                    
    }
    stop();
}
private void update(){  }

private synchronized void render(){
    g.setColor(Color.BLACK);
    g.fillRect(0, 0, WIDTH, HEIGHT);

}

private synchronized void draw(){
    Graphics g2 = this.getGraphics();
    g2.drawImage(image, 0, 0, null);
    g2.dispose();
}
public void start(){
    if(running) return;
    running = true;
    run();
}

private void stop() {
    if(!running) return;
    running = false;

    try{
        myThread.join();
    } catch (InterruptedException e){
        e.printStackTrace();
    }
}

}

Below is the code where the thread that listens to the Game class:

public class MyThread  implements Runnable, KeyListener{

private static Game game;
private static boolean left, right;

public MyThread(Game game) {
    this.game = game;       
}

@Override
public void run() {
    game.addKeyListener(this);
    if(left){game.str = "left";}
    if(right){game.str = "right";}
}


@Override
public void keyPressed(KeyEvent e) {
    int key = e.getKeyCode();
    if(key == KeyEvent.VK_LEFT){left = true;}
    if(key == KeyEvent.VK_RIGHT){right = true;}
}

@Override
public void keyReleased(KeyEvent e) {
    int key = e.getKeyCode();
    if(key == KeyEvent.VK_LEFT){left = false;}
    if(key == KeyEvent.VK_RIGHT){right = false;}
}

@Override
public void keyTyped(KeyEvent e) {

}
}

Currently when I run the code, it only prints out null even when I try to click left and right arrow keys. I tried inserting game.str = "string"; in the run() method and the console prints out string, so the thread works. I think the problem is that MyThread is not listening to the JPanel. Can you please tell me how do I fix this -- having a separate thread to listen to the Game class?

PS. game.addKeyListener(this) is in the run() method of the MyThread class.

Upvotes: 0

Views: 1696

Answers (1)

MadProgrammer
MadProgrammer

Reputation: 347314

You need some way to model the user input, personally, I'd start with a simple enum...

public enum Input {
    LEFT, RIGHT;
}

Next, we need some way to notify interested parties that some kind of input event has occurred...

public interface InputHandler {
    public void add(Input input);
    public void remove(Input input);
}

Next, your key listener needs some way to communicate the events back to the observer...

Disclaimer: I don't recommend the user of KeyListener, see the end for more details

public class KeyHandler extends KeyAdapter {
    
    private InputHandler inputHandler;
    
    public KeyHandler(InputHandler handler) {
        this.inputHandler = handler;
    }

    @Override
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_LEFT) {
            inputHandler.add(Input.LEFT);
        } else if (key == KeyEvent.VK_RIGHT) {
            inputHandler.add(Input.RIGHT);
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_LEFT) {
            inputHandler.remove(Input.LEFT);
        } else if (key == KeyEvent.VK_RIGHT) {
            inputHandler.remove(Input.RIGHT);
        }
    }
}

Now, your Game needs some way to manage the input events...

public class Game extends JPanel implements InputHandler {

    private Set<Input> inputSet = new HashSet<>();
    //...
    public Game() {
        //...
        addKeyListener(new KeyHandler(this));
        //...
    }

    @Override
    public synchronized void add(Input input) {
        inputSet.add(input);
    }

    @Override
    public synchronized void remove(Input input) {
        inputSet.remove(input);
    }

All of this means that when some input event occurs, your Game is notified, it can update the current state model based on the event. The Game doesn't care that you've used a KeyListener, or JButton or some other input method, de-coupling it and making it more flexible.

Okay, you say, but how do I check which is pressed?

inputSet.contains(Input.LEFT)

will return true when LEFT was pressed and false when it is released.

Observations

Based on what I can deduce from your code, this...

public void run() {
    image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
    g = (Graphics2D) image.getGraphics();

    while(running){
        update();
        render();
        draw();
        System.out.println(str);                                    
    }
    stop();
}

will never be called. You're executing a Thread using the KeyListener, whose run method just adds itself to the panel and the exits.

As you're using Swing, I would strongly recommend using a Swing Timer instead, it reduces the risk of race conditions between the painting thread and the update thread, as the Timer is triggered with the context of the EDT, making it safer to update the state of the UI from within

Recommendations

Now, having said all that, I highly recommend you don't use KeyListener, as it suffers from focus related issues, is difficult to maintain, provide customisation and inject other input methods should you want to.

Instead, I'd suggest using the Key Bindings API instead, which will solve these short comming and provide a more robust and re-usable solution

The above solution will still work with key bindings

Upvotes: 1

Related Questions