I am trying to create a small program with Windowbuilder that simply paints a red rectangle (called car1) in a JPanel and move it around by pressing the arrow keys; to do that I associated to the arrows a method to change the x position call the repaint method but the rectangle doesn't move at all - therefore I am probably messing up something with the KeyEvent and/or with repaint.
What should I do to make the rectangle each time I press the proper arrow key move and refresh the Panel ?
public class Car extends JPanel {
int x;
int y;
public Car(int x,int y){
this.x=x;
this.y=y;
}
public void paint(Graphics g) {
g.setColor(Color.RED);
g.fillRect(x, y, 20, 20);
}
public void move_right(){
x=x+20;
}
public void move_left(){
x=x-20;
}
}
public class Form extends JFrame {
//private JPanel contentPane;
Car car1;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Form frame = new Form();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Form() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 727, 550);
getContentPane().setLayout(null);
car1 = new Car(350, 480);
car1.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_KP_LEFT) {
car1.move_left();
car1.repaint();
}
if (key == KeyEvent.VK_KP_RIGHT) {
car1.move_right();
car1.repaint();
}
}
});
car1.setBounds(0, 0, 700, 500);
car1.setBackground(new Color(255, 255, 255));
getContentPane().add(car1);
}
}
There are at least 3 problems:
Using KeyListener. KeyListener is well known for only responding to key events which occur on components which are focusable AND have keyboard focus. A JPanel by default is not focusable, therefore it can't recieve keyboard focus. A better solution is to use the key bindings API, which allows to define the level of focus a component must have before the bindings are triggered and allows you to re-use a Action for multiple keys, reducing code duplication
Overriding paint. It's highly recommended to override paintComponent instead of paint when performing custom painting. You've also failed to maintain the paint chain, which is going to cause no end of weird and wonderful paint artifacts. Painting in AWT and Swing and Performing Custom Painting for more details
Using null layouts. Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify
For example...
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.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 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 (Exception 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 int xPos;
public TestPane() {
Action leftAction = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
xPos -= 2;
if (xPos < 0) {
xPos = 0;
}
repaint();
}
};
Action rightAction = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
xPos += 2;
if (xPos + 10 > getWidth()) {
xPos = getWidth() - 10;
}
repaint();
}
};
bindKeyStroke(WHEN_IN_FOCUSED_WINDOW, "move.left", KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), leftAction);
bindKeyStroke(WHEN_IN_FOCUSED_WINDOW, "move.left", KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, 0), leftAction);
bindKeyStroke(WHEN_IN_FOCUSED_WINDOW, "move.left", KeyStroke.getKeyStroke(KeyEvent.VK_4, 0), leftAction);
bindKeyStroke(WHEN_IN_FOCUSED_WINDOW, "move.left", KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), leftAction);
bindKeyStroke(WHEN_IN_FOCUSED_WINDOW, "move.right", KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), rightAction);
bindKeyStroke(WHEN_IN_FOCUSED_WINDOW, "move.right", KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, 0), rightAction);
bindKeyStroke(WHEN_IN_FOCUSED_WINDOW, "move.right", KeyStroke.getKeyStroke(KeyEvent.VK_6, 0), rightAction);
bindKeyStroke(WHEN_IN_FOCUSED_WINDOW, "move.right", KeyStroke.getKeyStroke(KeyEvent.VK_D, 0), rightAction);
}
protected void bindKeyStroke(int condition, String name, KeyStroke keyStroke, Action action) {
InputMap im = getInputMap(condition);
ActionMap am = getActionMap();
im.put(keyStroke, name);
am.put(name, action);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int yPos = (getHeight() - 10) / 2;
g2d.drawRect(xPos, yPos, 10, 10);
g2d.dispose();
}
}
}
This is a solution:
addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_LEFT) {
car1.move_left();
car1.repaint();
}
if (key == KeyEvent.VK_RIGHT) {
car1.move_right();
car1.repaint();
}
}
});
Where are the mistakes:
addKeyListener: add key listener to frame, not to panel
VK_KP_: use VK_ prefix instead
keyTyped: use keyPressed instead
Next step you will have to solve is removing the previous rectangles
Related
KeyListener doesn't work at all it's like it's not there, it shows the frame with the paddle but it doesn't move when arrowkey pressed , BUT my code worked properly on my friend's computer , i deleted and installed the last version of JDK and eclipse and nothing changed , i even compilated it with cmd and it doesn't work
edit : one in a 100 tries it works properly then the next time it returns to not working
the code is about a paddle that moves with arrowkeys
the Paddle class :
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
public class Paddle extends JPanel implements KeyListener{
private int x=900,y=280 ;
private int sx=20,sy=20 ;
private int valY=0;
public Paddle() {
setFocusable(true);
requestFocus();
addKeyListener(this);
}
public void paintComponent(Graphics g) {
draw(g);
update();
repaint();
}
public void draw(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, 1000, 1000);
g.setColor(Color.ORANGE);
g.fillRect(x, y, sx, sy);
}
public void update() {
this.y+=this.valY;
}
public void keyTyped(KeyEvent e) {
System.out.println("typed");
}
public void keyPressed(KeyEvent e) {
System.out.println("typed");
int c=e.getKeyCode();
if (c==KeyEvent.VK_UP){
this.valY=-1;
}
if(c==KeyEvent.VK_DOWN){
this.valY=1;
}
}
public void keyReleased(KeyEvent e) {
valY=0;
}
}
You are violating some rules of custom painting. The fact that it worked at all was mostly luck.
First, remove your call to repaint(). It is causing paintComponent to be called again, which calls repaint, which calls paintComponent, which calls repaint, etc. You are creating a very fast infinite loop which uses up most of Swing’s resources. Instead, call repaint() whenever you change the data on which your drawing relies.
Second, you are not accounting for the fact that painting occurs for many reasons. You don’t control most of them. paintComponent may be called when you move or resize the window, or even when the mouse moves over the window, or for a number of other reasons.
Since it is impossible to predict when the system will request that your class paint its contents, you must not change the state of your object in paintComponent. You must not call your update() method from paintComponent, either directly or indirectly.
The correct way to regularly update your state is to call your updating method from a Timer. For instance:
// Update every 250 milliseconds, that is, 4 times per second.
Timer timer = new Timer(250, e -> update());
timer.start();
Finally, whenever you override paintComponent, the first line of code needs to be a call to super.paintComponent(g);. If you don’t do that, you will eventually see strange painting artifacts. This is discussed in the Performing Custom Painting tutorial.
Your issue appears to have to do with the KeyListener and Focus. I am going to suggest an alternative technique. Use the InputMap/ActionMap for the panel.
KeyStroke us = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false);
panel.getInputMap().put(us, "UP");
panel.getActionMap().put("UP", new AbstractAction(){
#Override
public void actionPerformed(ActionEvent evt){
//call what ever
}
});
When I do this, I don't have to request focus, I have a JFrame and a JPanel, as long as the JFrame is focus, then the respective actions get triggered by the keys.
Here is a complete example that moves a circle around a JPanel.
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.EventQueue;
import java.awt.Color;
import javax.swing.KeyStroke;
import javax.swing.AbstractAction;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
public class BallGame{
int x;
int y;
JPanel panel = new JPanel(){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.RED);
g.drawOval(x-5, y-5, 10, 10);
}
};
public void start(){
JFrame frame = new JFrame();
frame.add(panel);
frame.setSize(640, 480);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
KeyStroke us = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false);
KeyStroke ds = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false);
KeyStroke ls = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false);
KeyStroke rs = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false);
panel.getInputMap().put(us, "UP");
panel.getInputMap().put(ds, "DOWN");
panel.getInputMap().put(ls, "LEFT");
panel.getInputMap().put(rs, "RIGHT");
panel.getActionMap().put("UP", new AbstractAction(){
#Override
public void actionPerformed(ActionEvent evt){
up();
}
});
panel.getActionMap().put("DOWN", new AbstractAction(){
#Override
public void actionPerformed(ActionEvent evt){
down();
}
});
panel.getActionMap().put("LEFT", new AbstractAction(){
#Override
public void actionPerformed(ActionEvent evt){
left();
}
});
panel.getActionMap().put("RIGHT", new AbstractAction(){
#Override
public void actionPerformed(ActionEvent evt){
right();
}
});
}
public void up(){
y = y - 5;
y = y<0? panel.getHeight() : y;
panel.repaint();
}
public void down(){
y = y + 5;
y = y>panel.getHeight() ? 0 : y;
panel.repaint();
}
public void left(){
x = x - 5;
x = x<0 ? panel.getWidth() : x;
panel.repaint();
}
public void right(){
x = x + 5;
x = x>panel.getWidth() ? 0 : x;
panel.repaint();
}
public static void main(String[] args){
BallGame bg = new BallGame();
EventQueue.invokeLater( bg::start );
}
}
Check out the api though because you can tune it to catch modifiers and when to trigger the action. In case you want to monitor when the key is pressed and when it is released etc.
I'm trying to move a dot by pressing the right-left keys.
Here is my main:
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(null);
frame.setSize(500,500);
Graphic graphic=new Graphic();
frame.add(graphic);
}}
This is the graphic class where i created the dot and I implemented KeyListener and ActionListener:
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.JPanel;
import javax.swing.Timer;
public class Graphic extends JPanel implements ActionListener, KeyListener {
private int posX = 220;
private int posY = 300;
private Timer timer;
private int delay = 8;
private int width = 500;
private int height = 500;
public Graphic() {
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
timer = new Timer(delay, this);
timer.start();
this.setSize(width, height);
}
public void paint(Graphics g) {
g.setColor(Color.black);
g.fillRect(0, 0, width, height);
g.setColor(Color.GREEN);
g.fillOval(posX, posY, 20, 20);
}
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
if (posX <= 20) {
posX = 20;
} else {
moveLeft();
}
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
if (posX >= 460) {
posX = 460;
} else {
moveRight();
}
}
repaint();
}
private void moveRight() {
posX += 20;
}
private void moveLeft() {
posX -= 20;
}
#Override
public void actionPerformed(ActionEvent e) {
timer.start();
repaint();
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
}
In my opinion, this should work...but it doesn't. When I press the left-right keys nothing happens and it looks like it's not "listening" my commands. So, what is wrong with my code?
KeyListener works if the component which has the listener has the focus. JFrame has by default the focus when you display it but not a JPanel.
In the Graphic constructor add simply grabFocus() :
public Graphic() {
addKeyListener(this);
setFocusTraversalKeysEnabled(true);
setFocusable(true);
grabFocus();
timer = new Timer(delay, this);
timer.start();
this.setSize(width, height);
}
EDIT
I have tested on my machine. The problem is that it works randomly as the JFrame needs to be visible if we want that the JPanel grab the focus. Sometimes it is, other times it is not.
SwingUtilities.invokeLater() may solve the problem.
After adding the panel to the JFrame, invoke the code which grabs the focus in a invokeLater() method.
frame.add(graphic);
...
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
graphic.grabFocus();
}
});
You call repaint method of Component class instead of your paint method.
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.
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 have this login form and I have a "user photo." I'm trying to make it so that when you hover the mouse over the photo area, a transparent label with a colored background will appear (to give the effect of "selecting the photo"). It looks like this:
And once you move your mouse off it, it goes back to being "deselected."
Now my problem is, if you hover your mouse over the login button first then move your mouse over the photo, a "ghost login button" appears. It looks like this:
I don't know why this is happening. Can someone help? Here is the relevant code:
package com.stats;
public class Stats extends JFrame implements Serializable {
private JLabel fader;
public Stats() {
try {
Image image = ImageIO.read(new File(System.getenv("APPDATA")
+ "\\Stats\\Renekton_Cleave.png"));
JLabel labelUserPhoto = new JLabel(new ImageIcon(image));
fader = new JLabel();
fader.setBounds(97, 44, 100, 100);
fader.setOpaque(true);
fader.setBackground(new Color(0, 0, 0, 0));
labelUserPhoto.setBounds(97, 44, 100, 100);
PicHandler ph = new PicHandler();
contentPane.add(fader);
contentPane.add(labelUserPhoto);
fader.addMouseMotionListener(ph);
} catch(Exception e) {
e.printStackTrace();
}
}
private class PicHandler implements MouseMotionListener {
public void mouseDragged(MouseEvent e) { }
public void mouseMoved(MouseEvent e) {
int x = e.getX();
int y = e.getY();
System.out.println("x: " + x + ", y: " + y);
if ((x > 16 && x < 80) && (y > 16 && y < 80)) {
if (!fader.isOpaque()) {
fader.setOpaque(true);
fader.setBackground(new Color(0, 0, 0, 40));
fader.repaint();
}
} else {
if (fader.isOpaque()) {
fader.setOpaque(false);
fader.repaint();
}
}
}
}
I can see a number of issues with your example, but the most significant is the use of a color with an alpha value.
fader.setBackground(new Color(0, 0, 0, 40));
Swing doesn't render components with alpha based colors well (within this context). By making component opaque and then setting the background color to use an alpha value, you are telling Swing that it doesn't need to worry about painting what's underneath your component, which isn't true...
The Graphics context is also a shared resource, meaning that anything that was painted before your component is still "painted", you need to clear the Graphics context before painting.
This example uses a rather nasty trick to get it's work done. Because all the painting occurs within the UI delegate, if we were simply to allow the default paint chain to continue, we wouldn't be able to render underneath the icon. Instead, we take over control of the "dirty" details and paint the background on the behalf the parent.
This would be simpler to achieve if we simple extended from something like JPanel and painted the image ourselves
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FadingIcon {
public static void main(String[] args) {
new FadingIcon();
}
public FadingIcon() {
startUI();
}
public void startUI() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
BufferedImage img = null;
try {
img = ImageIO.read(new File("C:\\Users\\swhitehead\\Documents\\My Dropbox\\Ponies\\SmallPony.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setLayout(new GridBagLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new FadingLabel(new ImageIcon(img)));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class FadingLabel extends JLabel {
private boolean mouseIn = false;
private MouseHandler mouseHandler;
public FadingLabel(Icon icon) {
super(icon);
setBackground(Color.RED);
super.setOpaque(false)(
}
#Override
public void setOpaque(boolean opaque) {
}
#Override
public final boolean isOpaque() {
return false;
}
protected MouseHandler getMouseHandler() {
if (mouseHandler == null) {
mouseHandler = new MouseHandler();
}
return mouseHandler;
}
#Override
public void addNotify() {
super.addNotify();
addMouseListener(getMouseHandler());
}
#Override
public void removeNotify() {
removeMouseListener(getMouseHandler());
super.removeNotify();
}
#Override
protected void paintComponent(Graphics g) {
if (mouseIn) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
g2d.setColor(getBackground());
g2d.fillRect(0, 0, getWidth(), getHeight());
g2d.dispose();
}
getUI().paint(g, this);
}
public class MouseHandler extends MouseAdapter {
#Override
public void mouseEntered(MouseEvent e) {
mouseIn = true;
repaint();
}
#Override
public void mouseExited(MouseEvent e) {
mouseIn = false;
repaint();
}
}
}
}
I would also recommend that you take the time to learn how to use appropriate layout managers, they will save you a lot of hair pulling later
Check out A Visual Guide to Layout Managers and Laying Out Components Within a Container
Since I can't comment I have to put this as an answer:
As trashgod mentioned, this problem can be caused by a missing super.paintComponent(g) call however you don't seem to be overriding the paintComponent method at all (at least you didn't show it here). If you do override the paintComponent method of a JFrame or any JPanels, you need to have:
public void paintComponent(Graphics g) {
super.paintComponent(g);
//rest of your drawing code....
}
But if you haven't used one at all then the problem may be caused by something else.
Since jdk7 there a new mechanism to apply visual decorations (and listening to events for child components): that's the JLayer/LayerUI pair.
In your case a custom layerUI would
trigger a repaint on rollover
implement the paint to apply the transparent color
Below is an example, similar to the WallPaperUI in the tutorial:
// usage: create the component and decorate it with the custom ui
JLabel label = new JLabel(myIcon);
content.add(new JLayer(label, new RolloverUI()));
// custom layerUI
public static class RolloverUI extends LayerUI<JComponent> {
private Point lastMousePoint;
private JLayer layer;
/**
* Implemented to install the layer and enable mouse/motion events.
*/
#Override
public void installUI(JComponent c) {
super.installUI(c);
this.layer = (JLayer) c;
layer.setLayerEventMask(AWTEvent.MOUSE_MOTION_EVENT_MASK
| AWTEvent.MOUSE_EVENT_MASK);
}
#Override
protected void processMouseMotionEvent(MouseEvent e,
JLayer<? extends JComponent> l) {
updateLastMousePoint(e.getPoint());
}
#Override
protected void processMouseEvent(MouseEvent e,
JLayer<? extends JComponent> l) {
if (e.getID() == MouseEvent.MOUSE_EXITED) {
updateLastMousePoint(null);
} else if (e.getID() == MouseEvent.MOUSE_ENTERED) {
updateLastMousePoint(e.getPoint());
}
}
/**
* Updates the internals and calls repaint.
*/
protected void updateLastMousePoint(Point e) {
lastMousePoint = e;
layer.repaint();
}
/**
* Implemented to apply painting decoration below the component.
*/
#Override
public void paint(Graphics g, JComponent c) {
if (inside()) {
Graphics2D g2 = (Graphics2D) g.create();
int w = c.getWidth();
int h = c.getHeight();
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, .5f));
g2.setPaint(new GradientPaint(0, 0, Color.yellow, 0, h,
Color.red));
g2.fillRect(0, 0, w, h);
g2.dispose();
}
super.paint(g, c);
}
protected boolean inside() {
if (lastMousePoint == null || lastMousePoint.x < 0
|| lastMousePoint.y < 0)
return false;
Rectangle r = layer.getView().getBounds();
r.grow(-r.width / 10, -r.height / 10);
return r.contains(lastMousePoint);
}
}
I had a similar problem with ghosted images after something moved or was resized.
#MadProgrammer has some really good points, but in the end, they didn't work for me (possibly because I had multiple layers using 0.0 alpha value colors, some of which also had images in them, so I couldn't set a composite value for the whole component). In the end, what fixed it was a simple call to
<contentpane>.repaint()
after all the draw commands were executed.