I have been trying to find out how to move a rectangle with the arrow keys but there seems to be a problem.
I am using a KeyListener to detect all of the key inputs.
I do not know how to use a KeyBinding therefore I do not wish for the solution to have it.
I am planning to learn it right after mastering KeyListener. Please give me suggestions on how to fix it.
package expo;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Expo extends JPanel implements KeyListener{
int x = 0;
int y = 0;
#Override
public void keyTyped(KeyEvent e) {
//System.out.println("Key Typed");
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_DOWN){
System.out.println("Key Pressed" + e.getKeyCode());
y = y + 2;
}
}
#Override
public void keyReleased(KeyEvent e) {
//System.out.println("Key Released");
}
public void paint(Graphics g){
g.setColor(Color.BLUE);
g.drawRect(x ,y,100,100);
repaint();
}
public static void main(String[] args) throws InterruptedException {
Expo expo = new Expo();
JFrame f = new JFrame();
f.setVisible(true);
f.setSize(500,500);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.addKeyListener(expo);
f.add(expo);
f.repaint();
}
}
Use the key bindings API over KeyListener, it solves the focus related issues that KeyListener suffers from. How to Use Key Bindings
Don't break the paint chain. If you override one of the paint methods, you must call it's super implementation.
Avoid overriding paint, it's normally to high in the paint process and because of the way painting works, doesn't always get called when a child component is painted, which can cause some interesting issues. Convention recommends using paintComponent instead. See Painting in AWT and Swing and Performing Custom Painting for more details
Try calling JFrame#setVisible last, after you have established the UI, you will find it causes less issues
As an example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Expo extends JPanel {
int x = 0;
int y = 0;
public Expo() {
bindKeyStrokeTo(WHEN_IN_FOCUSED_WINDOW, "down", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
y += 2;
if (y + 100 > getHeight()) {
y = getHeight() - 100;
}
repaint();
}
});
bindKeyStrokeTo(WHEN_IN_FOCUSED_WINDOW, "up", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
y -= 2;
if (y < 0) {
y = 0;
}
repaint();
}
});
}
public void bindKeyStrokeTo(int condition, String name, KeyStroke keyStroke, Action action) {
InputMap im = getInputMap(condition);
ActionMap am = getActionMap();
im.put(keyStroke, name);
am.put(name, action);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.drawRect(x, y, 100, 100);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
public static void main(String[] args) throws InterruptedException {
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 Expo());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
That's all cool and everything, but now, when you hold down the key, the rectangle moves, pauses and then begins to move steadily!?
This is actually quite normal. Instead, what you can do, is establish an "update loop" which constantly monitors the state of a set of flags, makes decisions about what to do when those flags are set and updates the UI.
So, what this does, is sets up a Swing Timer which ticks every 40 milliseconds, checks the state of the current "vertical key state", updates the y position accordingly and schedules a repaint, this allows for a much more smoother movement as we're not reliant on the repeating key stroke.
This also demonstrates the power of the key bindings API, as it establishes a single Action to handle both the up and down movements and the associated key releases...neat
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Expo extends JPanel {
int x = 0;
int y = 0;
public enum VerticalKey {
UP, DOWN, NONE;
}
public enum HorizontalKey {
LEFT, RIGHT, NONE;
}
private VerticalKey verticalKeyState = VerticalKey.NONE;
private HorizontalKey horizontalKeyState = HorizontalKey.NONE;
public Expo() {
bindKeyStrokeTo(WHEN_IN_FOCUSED_WINDOW, "pressed.down", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), new VerticalAction(VerticalKey.DOWN));
bindKeyStrokeTo(WHEN_IN_FOCUSED_WINDOW, "released.down", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), new VerticalAction(VerticalKey.NONE));
bindKeyStrokeTo(WHEN_IN_FOCUSED_WINDOW, "pressed.up", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), new VerticalAction(VerticalKey.UP));
bindKeyStrokeTo(WHEN_IN_FOCUSED_WINDOW, "released.up", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), new VerticalAction(VerticalKey.NONE));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
switch (verticalKeyState) {
case UP:
y -= 2;
break;
case DOWN:
y += 2;
break;
}
if (y + 100 > getHeight()) {
y = getHeight() - 100;
} else if (y < 0) {
y = 0;
}
repaint();
}
});
timer.start();
}
public void bindKeyStrokeTo(int condition, String name, KeyStroke keyStroke, Action action) {
InputMap im = getInputMap(condition);
ActionMap am = getActionMap();
im.put(keyStroke, name);
am.put(name, action);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.drawRect(x, y, 100, 100);
}
public void setVerticalKeyState(VerticalKey verticalKeyState) {
this.verticalKeyState = verticalKeyState;
System.out.println(verticalKeyState);
}
public void setHorizontalKeyState(HorizontalKey horizontalKeyState) {
this.horizontalKeyState = horizontalKeyState;
}
public class VerticalAction extends AbstractAction {
private VerticalKey verticalKey;
public VerticalAction(VerticalKey verticalKeys) {
this.verticalKey = verticalKeys;
}
#Override
public void actionPerformed(ActionEvent e) {
setVerticalKeyState(verticalKey);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
public static void main(String[] args) throws InterruptedException {
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 Expo());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Just change these two pieces of code:
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_DOWN){
System.out.println("Key Pressed" + e.getKeyCode());
y = y + 2;
repaint();//add this line to update the UI
}
}
and
public void paint(Graphics g){
super.paint(g);//you should always call the super-method first
g.setColor(Color.BLUE);
g.drawRect(x ,y,100,100);
}
This would fix the issue. Though i'd recommend to override paintComponent instead of paint (read the section about "The Paint Methods" in this article: Painting in AWT and Swing). Basically the changes are i made change the following:
In keyPressed this line: repaint(); will update the UI after the square moved. Actually the square was moved before aswell, but the changes won't show until the UI is updated. In paint this line: super.paint(g); makes the panel do it's default painting first, which includes clearing the complete panel. And i've removed the repaint(); call, since it's completely useless.
NOTE:
if you use paintComponent instead of paint, you'll have to change the first call from super.paint(g) to super.paintComponent(g) to avoid a stackoverflow.
Related
Hello and thanks in advance,
i am working with Graphics2D for a casino game (Roulette), so i am trying to add motion to the chips of the casino (The money), so for that i am using MouseDragged events and as a test i am working with only 1 ellipse.
code below
package roulette;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.swing.JPanel;
import javax.swing.Timer;
public class RouletteInterface extends JPanel{
private List<Shape> money = new ArrayList<>();
public RouletteInterface() {
createEllipseGrap();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g4d = (Graphics2D) g.create();
paintEllipseGrap(g4d, g);
g4d.dispose();
}
protected void createEllipseGrap() {
Ellipse2D elipse = new Ellipse2D.Double(100, 100, 30, 30);
money.add(elipse);
addMouseListener(new moneyMouseListener());
addMouseMotionListener(new moneyMouseListener());
}
protected void paintEllipseGrap(Graphics2D g3d, Graphics g) {
g3d.setColor(Color.BLUE);
g3d.fill(money.get(0));
}
private class moneyMouseListener extends MouseAdapter {
int dragging;
private int x;
private int y;
#Override
public void mousePressed(MouseEvent e) {
if(money.get(0).contains(e.getPoint())) {
x = e.getX();
y = e.getY();
dragging = 0;
} else {
return ;
}
}
#Override
public void mouseDragged(MouseEvent e) {
if(dragging == 0) {
x = e.getX();
y = e.getY();
Ellipse2D elipse = new Ellipse2D.Double(x, y, 30, 30);
money.set(0, elipse);
repaint();
} else {
}
}
#Override
public void mouseReleased(MouseEvent m) {
dragging = 1;
repaint();
}
}
}
public class principal{
public static void main(String[] args) {
new principal();
}
public principal() {
JFrame frame = new JFrame();
frame.add(new RouletteInterface());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
What's the problem?
The MouseDragged event is still firing even after i release the mouseclick so the circle is still moving with my cursor when i click and drag on another side of the window
Your problem is that you are adding two different instances of your moneyMouseListeners as MouseListener and as MouseMotionListener:
addMouseListener(new moneyMouseListener());
addMouseMotionListener(new moneyMouseListener());
You would have to do it like that:
moneyMouseListener mListener = new moneyMouseListener();
addMouseListener(mListener);
addMouseMotionListener(mListener);
PS.: When using a variable like your "dragging" variable that is only used to assign a "1" or a "0" you should use a boolean variable with "true" and "false" ;)
Also, you might want to consider drawing/undrawing the chip using XOR mode of the Graphics, rather than calling repaint() all the time.
Press: undraw the chip in normal mode, then xor draw it.
Drag: XOR draw the chip, reset the position, XOR draw again.
Release: XOR draw the chip, then draw in normal mode.
That way if you move the chip over something in the background (or over another chip) you don't damage the other chip or the background.
i need some help here with an exercise of my Java class. What i'm trying to do is that when i click on any part of the JFrame, an image moves closer to the pointer. Right now i have done the part that once i click on the JFrame the image is in the same position as the pointer but it does like it's "teleporting" and i'm trying to make it more like a constant movement to the pointer position.
So far this is my code i have atm:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ControlaRaton extends MouseAdapter {
JLabel label;
public ControlaRaton(JLabel label){
this.label = label;
}
public void mouseClicked(MouseEvent evt){
Point pos = evt.getPoint();
System.out.println(pos.x+" "+pos.y);
//System.out.println("Boton: "+evt.getButton());
label.setLocation(pos.x-20,pos.y-50);
}
}
Any ideas on how to do it that way? I was thinking maybe using a Thread but i don't know exactly how to implement it here :s
This is pretty simple approach, basically all this does is uses a Swing Timer to move an entity towards the last known click point
Have a look at
Concurrency in Swing
How to use Swing Timers
How to Write a Mouse Listener
for more details
Swing is NOT thread safe, when performing these types of operations, it's important to take this into consideration. The Swing API provides several ways to work threads, in this case, I've used a simple Timer as it generates updates at a regular interval and triggers updates from within the EDT, making it safe to update the UI from within
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class MoveTowards {
public static void main(String[] args) {
new MoveTowards();
}
public MoveTowards() {
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 MoveTowardsPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
class MoveTowardsPane extends JPanel {
private final BufferedImage image;
private Point imagePosition = new Point(150, 150);
private Point mousePoint;
private double imageAngleRad = 0;
public MoveTowardsPane() {
BufferedImage i = null;
try {
i = ImageIO.read(getClass().getResource("/sprite.png"));
} catch (IOException e) {
e.printStackTrace();
}
image = i;
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
mousePoint = e.getPoint();
double dx = e.getX() - imagePosition.getX();
double dy = e.getY() - imagePosition.getY();
imageAngleRad = Math.atan2(dy, dx);
repaint();
}
});
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (mousePoint != null) {
int centerX = imagePosition.x + (image.getWidth() / 2);
int centerY = imagePosition.y + (image.getHeight() / 2);
if (mousePoint.x != centerX) {
imagePosition.x += mousePoint.x < centerX ? -1 : 1;
}
if (mousePoint.y != centerY) {
imagePosition.y += mousePoint.y < centerY ? -1 : 1;
}
repaint();
}
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics gr) {
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr.create();
g.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
int cx = image.getWidth() / 2;
int cy = image.getHeight() / 2;
AffineTransform oldAT = g.getTransform();
g.translate(cx + imagePosition.x, cy + imagePosition.y);
g.rotate(imageAngleRad);
g.translate(-cx, -cy);
g.drawImage(image, 0, 0, null);
g.setTransform(oldAT);
g.dispose();
}
}
}
Why doesn't this use a JLabel? A lot of reasons, JLabel isn't really well fitted for the task, as it needs to take into account a lot of other information. This example also "turns" the sprite towards the click point, something which isn't easily achieved with a JLabel.
In principle, the theory is still the same for moving a component.
See
Painting in AWT and Swing
Performing Custom Painting
for more details about how this approach works
Redraw your circle on mouse move
void mouseMoved(MouseEvent evt){
Point pos = evt.getPoint();
System.out.println(pos.x+" "+pos.y);
//System.out.println("Boton: "+evt.getButton());
label.setLocation(pos.x-20,pos.y-50);
}
});
If you want to move the label just a bit with each click you could do the following:
public void mouseClicked(MouseEvent evt){
Point clickedPos = evt.getPoint();
Point newPosForLabel = calculateNewPos(clickedPos, labelPos);
label.setLocation(newPosForLabel);
}
private Point calculateNewPos(Point clickedPos, Point labelPos) {
// calculate newPos based on labelPos and clickedPos
return newPos;
}
else use the timer from Hannes or from the link given by MadProgrammer:
I've created a JFrame with a rectangle in the center of it that moves when I press certain keys. It's all nice and dandy, but the rectangle keeps going when I release the keys. In fact, if I press a key multiple times, the rectangle accelerates. This is probably (definitely) because I'm using a timer to get around that pesky 0.5-second input delay when holding a key down.
I think I have to put something in the keyReleased() method, but I'm at a loss for what to put there. Any tips? Thanks.
PS: Please don't yell at me for not using key bindings. I know: they're better and stuff. But I'm focusing on key listeners at the moment.
Program:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.Timer;
#SuppressWarnings ("serial")
public class GameFrame extends JComponent implements KeyListener
{
static GameFrame gameFrame = new GameFrame();
public int x = 350;
public int y = 250;
public int keyCode;
public static void main (String[] args)
{
JFrame frame = new JFrame ("Java Game");
frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
frame.setSize (800, 600);
frame.setResizable (false);
frame.getContentPane().setBackground (Color.WHITE);
frame.getContentPane().add (gameFrame);
frame.addKeyListener (gameFrame);
frame.setVisible (true);
}
#Override
public void paintComponent (Graphics graphics)
{
super.paintComponent (graphics);
graphics.setColor (Color.BLACK);
graphics.fillRect (x, y, 100, 100);
}
public void keyPressed (KeyEvent event)
{
keyCode = event.getKeyCode();
new Timer (100, new ActionListener()
{
public void actionPerformed (ActionEvent event)
{
if (keyCode == KeyEvent.VK_LEFT)
{
x--;
repaint();
}
if (keyCode == KeyEvent.VK_RIGHT)
{
x++;
repaint();
}
if (keyCode == KeyEvent.VK_UP)
{
y--;
repaint();
}
if (keyCode == KeyEvent.VK_DOWN)
{
y++;
repaint();
}
}
}).start();
}
public void keyReleased (KeyEvent event) {}
public void keyTyped (KeyEvent event) {}
}
Avoid KeyListener, seriously, they are more trouble than they are worth, use the key bindings API instead. How to Use Key Bindings
There are a number of ways you might achieve this. One of the better way is to use a indirect approach. That is, the user presses a key and you raise a flag to indicate which is pressed, they release the key, you reset the flag, indicating that the key is no longer pressed.
You then use some kind of update loop to change the position of the object based which keys are currently active.
But why go to so much hassle I hear you ask. When a user presses a key, they is a short delay between the first key press and repeated key notification (while the key is down, the OS will send you key events until it is released), this makes the movement look a little "staggered".
Instead, we raise flag and use a constant update loop to make changes to the state of the object based on the state of the flags, which smooths out the key events, for example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test{
public static void main(String[] args) {
new Test();
}
public Test() {
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 static class TestPane extends JPanel {
public enum HorizontalMovement {
NONE,
LEFT,
RIGHT
}
private HorizontalMovement horizontalMovement = HorizontalMovement.NONE;
private int xPos = 0;
public TestPane() {
addKeyPressedBinding("left.pressed", KeyEvent.VK_LEFT, new MoveHorizontialAction(HorizontalMovement.LEFT));
addKeyPressedBinding("right.pressed", KeyEvent.VK_RIGHT, new MoveHorizontialAction(HorizontalMovement.RIGHT));
addKeyReleasedBinding("left.relesed", KeyEvent.VK_LEFT, new MoveHorizontialAction(HorizontalMovement.NONE));
addKeyReleasedBinding("right.relesed", KeyEvent.VK_RIGHT, new MoveHorizontialAction(HorizontalMovement.NONE));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
switch (horizontalMovement) {
case LEFT:
xPos--;
break;
case RIGHT:
xPos++;
break;
}
if (xPos < 0) {
xPos = 0;
} else if (xPos + 50 > getWidth()) {
xPos = getWidth() - 50;
}
repaint();
}
});
timer.start();
}
protected void addKeyPressedBinding(String name, int keyCode, Action action) {
KeyStroke ks = KeyStroke.getKeyStroke(keyCode, 0, false);
addKeyBinding(name, ks, action);
}
protected void addKeyReleasedBinding(String name, int keyCode, Action action) {
KeyStroke ks = KeyStroke.getKeyStroke(keyCode, 0, true);
addKeyBinding(name, ks, action);
}
protected void addKeyBinding(String name, KeyStroke ks, Action action) {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(ks, name);
am.put(name, action);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Rectangle box = new Rectangle(xPos, (getHeight() - 50) / 2, 50, 50);
g2d.setColor(Color.BLUE);
g2d.fill(box);
g2d.dispose();
}
protected void addKeyBinding(String left, int VK_LEFT, MoveHorizontialAction moveHorizontialAction) {
throw new UnsupportedOperationException("Not supported yet.");
}
protected class MoveHorizontialAction extends AbstractAction {
private HorizontalMovement movement;
public MoveHorizontialAction(HorizontalMovement movement) {
this.movement = movement;
}
#Override
public void actionPerformed(ActionEvent e) {
horizontalMovement = movement;
}
}
}
}
So I've set this up as a test for KeyEvents and timers. The first time the right arrow key is pressed the event will wait 5 seconds like the timer is setup to, then print KeyPressed. However, after the first println, KeyPressed will be printed in rapid succession like a long queue of KeyEvents it was collecting up while I held the key down.I don't want all the extra key presses that holding the right arrow key causes. I want to hold the right arrow key down and only receive a println every 5 seconds. Any help is greatly appreciated.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class GameBoard extends JPanel
{
public Ninja ninja;
public GameBoard()
{
addKeyListener(new TAdapter());
setFocusable(true);
setBackground(Color.BLACK);
setDoubleBuffered(true);
ninja = new Ninja();
}
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(ninja.getImage(), 20,20,null);
}
private class TAdapter extends KeyAdapter
{
private Timer timer;
#Override
public void keyPressed(KeyEvent e)
{
timer = new Timer(5000, new ActionListener(){
public void actionPerformed(ActionEvent ae)
{
System.out.println("KeyPressed");
}
});
timer.start();
}
#Override
public void keyReleased(KeyEvent e)
{
ninja.keyReleased(e);
repaint();
}
}
}
When the key is held down, the OS will generate a repeating event for the stroke.
Normally, you would need some kind of flag that would indicate that the keyPressed event has already been handled or not.
Based on your example, you could use the Timer. For example, when keyPressed is triggered, you would check to see of the Timer is null or is running...
if (timer == null || !timer.isRunning()) {...
Now, in your keyReleased event, you could need to stop the timer, so that the next time keyPressed is triggered, you can restart the timer.
This assumes that you only want the timer to run only while the key is pressed.
As a general suggestion, you should be using Key Bindings instead of KeyListener as it will provide you better control over the focus level which triggers the key events
Updated with Key Bindings Example
This is based on what your code appears to be doing...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class WalkCycle {
public static void main(String[] args) {
new WalkCycle();
}
public WalkCycle() {
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 class TestPane extends JPanel {
private List<BufferedImage> walkCycle;
private int frame;
private Timer timer;
public TestPane() {
setBackground(Color.WHITE);
walkCycle = new ArrayList<>(10);
try {
walkCycle.add(ImageIO.read(getClass().getResource("/Walk01.png")));
walkCycle.add(ImageIO.read(getClass().getResource("/Walk02.png")));
walkCycle.add(ImageIO.read(getClass().getResource("/Walk03.png")));
walkCycle.add(ImageIO.read(getClass().getResource("/Walk04.png")));
walkCycle.add(ImageIO.read(getClass().getResource("/Walk05.png")));
walkCycle.add(ImageIO.read(getClass().getResource("/Walk06.png")));
walkCycle.add(ImageIO.read(getClass().getResource("/Walk07.png")));
walkCycle.add(ImageIO.read(getClass().getResource("/Walk08.png")));
walkCycle.add(ImageIO.read(getClass().getResource("/Walk09.png")));
walkCycle.add(ImageIO.read(getClass().getResource("/Walk10.png")));
Timer timer = new Timer(80, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
frame++;
if (frame >= walkCycle.size()) {
frame = 0;
}
System.out.println(frame);
repaint();
}
});
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "right-down");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "right-up");
am.put("right-down", new TimerAction(timer, true));
am.put("right-up", new TimerAction(timer, false));
} catch (IOException exp) {
exp.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics g2d = (Graphics2D) g.create();
BufferedImage img = walkCycle.get(frame);
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
g2d.dispose();
}
}
public class TimerAction extends AbstractAction {
private Timer timer;
private boolean start;
public TimerAction(Timer timer, boolean start) {
this.timer = timer;
this.start = start;
}
#Override
public void actionPerformed(ActionEvent e) {
if (start && !timer.isRunning()) {
System.out.println("Start");
timer.start();
} else if (!start && timer.isRunning()) {
System.out.println("stop");
timer.stop();
}
}
}
}
Personally, I would have a single Timer which was always ticking, which updated the view. The view would then check with the model about what should be updated and rendered and the key bindings would update the state of the model, but that's just me.
When you hold down a key, keyPressed events will happen in rapid succession. What you have done is added a 5 second delay to this wave of events.
To fix this depends on what you want to do. If you want to only allow an event to happen every 5 seconds, you can move the timer outside of the event, then when the event gets called, check if 5 seconds has passed based on a boolean toggled by the timer every 5 seconds.
This will solve your problem.
No timer is required
Its a simple use to system current timing...
private long startTime;
private class TAdapter extends KeyAdapter {
public void keyPressed(final KeyEvent e) {
if (System.currentTimeMillis() - startTime > 2000) {
startTime = System.currentTimeMillis();
ninja.keyPressed(e, 1);
repaint();
} else if (System.currentTimeMillis() - startTime > 1000) {
ninja.keyPressed(e, 2);
repaint();
}
}
public void keyReleased(KeyEvent e) {
ninja.keyReleased(e);
repaint();
startTime = 0;
}
}
I am trying to get a circle to move through the input of a keyboard. I am not able to move the object at all. Can someone help me figure out what is wrong? Here is my code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
public class AlienInvader extends JPanel implements KeyListener{
Constants constant = new Constants();
public void update() {
constant.x += constant.xvel;
addKeyListener(this);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.MAGENTA);
g.fillOval(constant.x, constant.y, 30, 30);
repaint();
}
#Override
public void keyPressed(KeyEvent e) {
System.out.println(constant.x);
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT:
constant.xvel = -1;
break;
case KeyEvent.VK_RIGHT:
constant.xvel = 1;
break;
}
}
#Override
public void keyReleased(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT:
constant.xvel = -1;
break;
case KeyEvent.VK_RIGHT:
constant.xvel = 1;
break;
}
}
#Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
}
}
I am not sure what I am doing wrong. I thought it was because I wasn't calling the update method, but when I added a if statement in paintComponent (so it only calls itself once) and tried it, I had no luck.
To start with, don't call repaint within any paintXxx method. Paint methods are typically called in response to a call to repaint, therefore you are creating a nasty, never ending, ever consuming loop of resource hell.
Secondly, KeyListeners only respond to key events when 1- The component the are registered to are focusable 2- When the component they are registered to have focus.
They are a poor choice in this case. Use Key bindings instead
Thirdly, you are not providing a preferredSize hint for layout managers to use. This may or may not be a bad thing in your case, but it's possible that you component will be laid out with a size of 0x0
Example
Something like....
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class MoveCircle {
public static void main(String[] args) {
new MoveCircle();
}
public MoveCircle() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int xDelta = 0;
private int keyPressCount = 0;
private Timer repaintTimer;
private int xPos = 0;
private int radius = 10;
public TestPane() {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "pressed.left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "pressed.right");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "released.left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "released.right");
am.put("pressed.left", new MoveAction(-2, true));
am.put("pressed.right", new MoveAction(2, true));
am.put("released.left", new MoveAction(0, false));
am.put("released.right", new MoveAction(0, false));
repaintTimer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
xPos += xDelta;
if (xPos < 0) {
xPos = 0;
} else if (xPos + radius > getWidth()) {
xPos = getWidth() - radius;
}
repaint();
}
});
repaintTimer.setInitialDelay(0);
repaintTimer.setRepeats(true);
repaintTimer.setCoalesce(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.drawOval(xPos, 0, radius, radius);
g2d.dispose();
}
public class MoveAction extends AbstractAction {
private int direction;
private boolean keyDown;
public MoveAction(int direction, boolean down) {
this.direction = direction;
keyDown = down;
}
#Override
public void actionPerformed(ActionEvent e) {
xDelta = direction;
if (keyDown) {
if (!repaintTimer.isRunning()) {
repaintTimer.start();
}
} else {
repaintTimer.stop();
}
}
}
}
}
For example...