Search code examples
javaswingpaintcomponentkeylistenerkey-bindings

How can I get multiple key bindings to work?


As of now, I can only get one of my key bindings to work one at a time. It's usually the first one created. They each have their own class to go to when I press a key so I don't understand why all of my keys would not work. Is it because they all share the same panel? Currently I have three action classes, UpAction, PlusAction, and MinusAction. UpAction moves the rectangle up and the other two increase/decrease the size of the rectangle. Another problem I ran into was that when I could move the rectangle upward, when I would click my increase/decrease buttons, my up command would not work anymore. Would I have to write something for my code to regain foccus on the up arrow key again?

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.geom.Rectangle2D;

import javax.swing.Icon;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;


public class Rectangle extends JPanel {

private int height;
private int width;
private int x;
private int y;
private Graphics2D g2;
private Action plusAction;
private Action minusAction;
private Action up;
private static JButton buttonIncrease;
private static JButton buttonDecrease;



public Rectangle(int width, int height,int x, int y)
{
    this.height = height;
    this.width = width;
    this.x = x;
    this.y = y;




    JLabel label = new JLabel();

    buttonIncrease = new JButton(" Increase Size ");
    buttonDecrease = new JButton(" Decrease Size ");

    buttonIncrease.addActionListener(new ButtonListener());
    buttonDecrease.addActionListener(new ButtonListener());
    buttonIncrease.setActionCommand("1");
    buttonDecrease.setActionCommand("2");


    up = new UpAction();
    getInputMap().put(KeyStroke.getKeyStroke("UP"), "doUpAction");
    getActionMap().put("doUpAction", up);

    plusAction = new PlusAction();
    minusAction = new MinusAction();

    buttonIncrease.getInputMap().put(KeyStroke.getKeyStroke(" released Q"), "doPlusAction");
    buttonIncrease.getActionMap().put("doPlusAction", plusAction);


    buttonDecrease.getInputMap().put(KeyStroke.getKeyStroke(" released A"), "doMinusAction");
    buttonDecrease.getActionMap().put("doMinusAction", minusAction);

    add(buttonIncrease);
    add(buttonDecrease);


    label.addKeyListener(new LabelListener());
    label.setFocusable(true);
    label.setOpaque(true);
    this.add(label);
    //label.requestFocusInWindow();
    setVisible(true);





}


public Dimension getPreferredSize() {
    return new Dimension(500,500);
}

static class PlusAction extends AbstractAction
{
    //When the plus key is pressed, the increase button
    //will be pressed
    public void actionPerformed(ActionEvent arg0) {
        buttonIncrease.doClick();

    }

}

static class MinusAction extends AbstractAction
{
    //When the minus key is pressed, the decrease button
    //will be pressed
    public void actionPerformed(ActionEvent arg0) {
        buttonDecrease.doClick();

    }

}
 class UpAction extends AbstractAction
{

    public void actionPerformed(ActionEvent e) {
        y = y - 5;
        repaint();
    }

}
// Listener for increasing/decreasing size buttons
 class ButtonListener implements ActionListener{

    public void actionPerformed(ActionEvent e) {

        int action = Integer.parseInt(e.getActionCommand());

        switch(action){
        case 1:
            height = height + 5;
            width = width + 5;
            repaint();
            break;
        case 2:
            height = height - 5;
            width = width - 5;
            repaint();
            break;
        }
    }
 }





class LabelListener implements KeyListener {
public void keyPressed(KeyEvent e)
{   

    if (e.getKeyCode() == KeyEvent.VK_DOWN)
    {
        y = y + 5;
        repaint();
    }
    if (e.getKeyCode() == KeyEvent.VK_LEFT)
    {
        x = x - 5;
        repaint();
    }
    if (e.getKeyCode() == KeyEvent.VK_UP)
    {
        y = y - 5;
        repaint();
    }

    if (e.getKeyCode() == KeyEvent.VK_RIGHT)
    {
        x = x + 5;
        repaint();
    }

}


public void keyReleased(KeyEvent e) {}

public void keyTyped(KeyEvent e) {}


 }

public void paintComponent(Graphics g) {

super.paintComponent(g);
this.g2 = (Graphics2D) g;
g2.drawString("X: " + x,10,20);
g2.drawString("Y: " + y,10,35);
g2.drawString("Width: " + width,10,55);
g2.drawString("Height: " + height,10,70);
g2.drawRect(x, y, width, height);

}

}

Test class:

import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;



public class Test {

 public static void main(String[] args) {


        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI(); 
            }
        });
    }

    private static void createAndShowGUI() {

        JFrame f = new JFrame("Rectangle");


        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(new Rectangle(40,20,250,250));
        f.pack();
        f.setVisible(true);



    }
}

Solution

  • You're not setting the input map's condition correctly:

       private static final int INPUT_CONDITION = JComponent.WHEN_IN_FOCUSED_WINDOW;
       private int height;
       private int width;
       //...
    
       public Rectangle(int width, int height, int x, int y) {
          // .....
    
          up = new UpAction();
    
          // ******** note difference to the getInputMap method below
          getInputMap(INPUT_CONDITION).put(KeyStroke.getKeyStroke("UP"),
                "doUpAction");
          getActionMap().put("doUpAction", up);
          plusAction = new PlusAction();
          minusAction = new MinusAction();
          buttonIncrease.getInputMap(INPUT_CONDITION).put(
                KeyStroke.getKeyStroke("released Q"), "doPlusAction");
          buttonIncrease.getActionMap().put("doPlusAction", plusAction);
          buttonDecrease.getInputMap(INPUT_CONDITION).put(
                KeyStroke.getKeyStroke("released A"), "doMinusAction");
    

    The default InputMap condition is JComponent.WHEN_FOCUSED, and the key binding will only work when the listened to component has focus. Better in your case to set it explicitly to JComponent.WHEN_IN_FOCUSED_WINDOW, and now it will work as long as the component is in a window that has focus.

    You set it when you call getInputMap. Rather than call it in a default way, getInputMap(), I suggest that you call it like, getInputMap(INPUT_CONDITION), setting up the appropriate constant.

    You've also got KeyListeners in that code -- why? And having a Graphics2D or Graphics class field is a recipe for possible disaster. I would get rid of the Graphics2D field and just use it locally in the paintComponent method or methods called off of paintComponent.


    Edit
    You ask:

    Why would using Graphics2D globally be a bad thing? –

    Because having the variable would make it tempting to use it, and if you used it outside of paintComponent, you're liable to run into a NullPointerException, because a Graphics object obtained from the JVM this way is a very short lived thing. Trust me, get rid of that variable.