Java Graphics 2D and KeyListener - java

I am teaching myself 2D graphics in Java right now. I am trying to create a block-stacking game. So far, I am able to get the first row moving and stopping, but my aim is to generate a new row below the first while the first row stays in place. I feel like the fix for this is a simple concept I haven't learned yet. However, any advice will be appreciated. If you want to point me in the right direction to where I can teach myself 2D Graphics in general, I will appreciate that also.
My code for the JFrame is as follows:
public class BlockStacker extends JFrame {
public class AL extends KeyAdapter {
stack2 s2 = new stack2();
public void keyPressed(KeyEvent e){
int keyCode = e.getKeyCode();
}
}
public static void main(String[] args){
stack2 s = new stack2();
JFrame f = new JFrame();
f.add(s);
f.setVisible(true);
f.setSize(1920, 1080);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setTitle("Block Stacker");
}
}
The code with the graphics and the keylistener is this:
public class stack2 extends JPanel implements ActionListener, KeyListener{
Timer t = new Timer(250, this);
double x, y, velX = 253;
public stack2() {
t.start();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
setBackground(Color.BLACK);
Color blue = new Color(0,0,255);
g2.setColor(blue);
Rectangle2D.Double rectangle = new Rectangle.Double(x + 210, y, 200, 100);
g2.fill(rectangle);
Rectangle2D.Double rectangle2 = new Rectangle.Double(x, y, 200, 100);
g2.fill(rectangle2);
Rectangle2D.Double rectangle3 = new Rectangle.Double(x + 420, y, 200, 100);
g2.fill(rectangle3);
}
public void actionPerformed(ActionEvent e) {
if (x < 0) {
velX = -velX;
}
x += velX;
repaint();
if (x < 10 || x > 1200) {
velX = -velX;
repaint();
}
}
public void space() {
velX = 0;
repaint();
}
public void space2() {
}
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if (code == KeyEvent.VK_SPACE) {
space();
}
}
public void keyTyped(KeyEvent e) {}
public void keyReleased(KeyEvent e) {
int code = e.getKeyCode();
if (code == KeyEvent.VK_SPACE) {
space2();
}
}
}

There are multiple problems...
You attach the KeyListener to JFrame, but a JFrame has a JRootPane, content pane and it's content between it and the user, any of those components could have keyboard focus, preventing the frame from receiving key events
You create a instance of Stack in both the KeyAdapter and then the frame, so the instance of Stack you are trying to change isn't the version that's on the screen
KeyListener only responds to key events when the component that they are registed to are focus able and have focus...
Use key bindings instead? See How to use key bindings

Related

Difference between implementing JFrame and JPanel

I was trying to understand the difference between JFrame and JPanel. I tend to use subclasses of JFrame instead of JPanel, but people always tell me that it's better to use a subclass of JPanel instead. Here is an example of me using JFrame:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
public class Game extends JFrame implements Runnable {
int x, y, xCoord, yCoord;
private Image dbImage;
private Graphics dbg;
public void move() {
x += xCoord;
y += yCoord;
if (x <= 20) {
x = 20;
}
if (x >= 480) {
x = 480;
}
if (y <= 40) {
y = 40;
}
if (y >= 480) {
y = 480;
}
}
public void setXCoord(int xcoord) {
xCoord = xcoord;
}
public void setYCoord(int ycoord) {
yCoord = ycoord;
}
public class AL extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == e.VK_LEFT) {
setXCoord(-1);
}
if (keyCode == e.VK_RIGHT) {
setXCoord(+1);
}
if (keyCode == e.VK_UP) {
setYCoord(-1);
}
if (keyCode == e.VK_DOWN) {
setYCoord(+1);
}
Game.this.repaint();
}
#Override
public void keyReleased(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == e.VK_LEFT) {
setXCoord(0);
}
if (keyCode == e.VK_RIGHT) {
setXCoord(0);
}
if (keyCode == e.VK_UP) {
setYCoord(0);
}
if (keyCode == e.VK_DOWN) {
setYCoord(0);
}
Game.this.repaint();
}
}
public static void main(String[] args) {
Game game = new Game();
Thread t = new Thread(game);
t.start();
}
public Game() {
addKeyListener(new AL());
setTitle("Game");
setSize(500, 500);
setResizable(true);
setVisible(true);
setBackground(Color.BLACK);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
x = 250;
y = 250;
}
public void paintComponent(Graphics g) {
g.setColor(Color.GREEN);
g.fillOval(x, y, 15, 15);
}
#Override
public void paint(Graphics g) {
dbImage = createImage(getWidth(), getHeight());
dbg = dbImage.getGraphics();
paintComponent(dbg);
g.drawImage(dbImage, 0, 0, this);
}
#Override
public void run() {
try {
while (true) {
move();
Thread.sleep(30);
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
This works fine (except for there being a small delay when I hold down one of the buttons), but when I try to change my code by implementing JPanel instead of JFrame, nothing shows up... Here's the code for the JPanel subclass:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Game extends JPanel implements Runnable {
int x, y, xCoord, yCoord;
private Image dbImage;
private Graphics dbg;
JFrame frame;
public void move() {
x += xCoord;
y += yCoord;
if (x <= 20) {
x = 20;
}
if (x >= 480) {
x = 480;
}
if (y <= 40) {
y = 40;
}
if (y >= 480) {
y = 480;
}
}
public void setXCoord(int xcoord) {
xCoord = xcoord;
}
public void setYCoord(int ycoord) {
yCoord = ycoord;
}
public class AL extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == e.VK_LEFT) {
setXCoord(-1);
}
if (keyCode == e.VK_RIGHT) {
setXCoord(+1);
}
if (keyCode == e.VK_UP) {
setYCoord(-1);
}
if (keyCode == e.VK_DOWN) {
setYCoord(+1);
}
Game.this.repaint();
}
#Override
public void keyReleased(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == e.VK_LEFT) {
setXCoord(0);
}
if (keyCode == e.VK_RIGHT) {
setXCoord(0);
}
if (keyCode == e.VK_UP) {
setYCoord(0);
}
if (keyCode == e.VK_DOWN) {
setYCoord(0);
}
Game.this.repaint();
}
}
public static void main(String[] args) {
Game game = new Game();
Thread t = new Thread(game);
t.start();
}
public Game() {
frame = new JFrame();
frame.addKeyListener(new AL());
frame.setTitle("Game");
frame.setSize(500, 500);
frame.setResizable(true);
frame.setVisible(true);
frame.setBackground(Color.BLACK);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
x = 250;
y = 250;
}
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.GREEN);
g.fillOval(x, y, 15, 15);
}
#Override
public void paint(Graphics g) {
dbImage = createImage(getWidth(), getHeight());
dbg = dbImage.getGraphics();
paintComponent(dbg);
g.drawImage(dbImage, 0, 0, this);
}
#Override
public void run() {
try {
while (true) {
move();
Thread.sleep(30);
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
As the code shows, you need a custom JPanel, because you want to change the behavior of some methods like paintComponent.
However, you don't need a custom JFrame, so no need to create a class extending it.
Finally, your main class has no need to be your panel class.
Here is an example class, I moved the frame stuff from Game's constructor to this main class.
public class MainClass {
public static void main(String[] args) {
Game game = new Game();
JFrame frame = new JFrame();
frame.addKeyListener(new AL());
frame.setTitle("Game");
frame.setSize(500, 500);
frame.setResizable(true);
frame.getContentPane().add(game);
frame.setBackground(Color.BLACK);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Thread t = new Thread(game);
t.start();
}
}
A JFrame is a far more complex component then you might think, for starters, it has a JRootPane as it's primary container, which contains the contentPane, JMenuBar and controls the glassPane
see How to Use Root Panes for more details.
The JFrame also has decorations (borders), these borders are painted within the confines of the window itself, the contents are then laid out within these, so the borders don't paint over then.
When you override paint of a top level container, like JFrame, there are a number of issues which you run into:
It's not double buffered, which can cause flickering as the window is updated
The other components can be painted independently of the frame (so the frame's paint method is not called), which can cause no end of issues
You can now paint beneath the frame's decorations, see Java graphic image, How to get the EXACT middle of a screen, even when re-sized and How can I set in the midst? for more details.
You're locking your self into a single use case, you can't add windows to other containers, which reduces your components re-use value
Generally speaking, from a OOP point of view, you're not actually adding any new functionality to the class (or least none which can't be generated through better approaches).
When you use something like JPanel, all other the above are no longer of concern:
They are double buffered by default
If you use a layout manager (on the content pane), the component will be laid out within the frame's decorations
The width and height of the component represent the whole viewable area
You can add this component to what ever container you want
You should also override the JPanel's getPreferredSize method and return the preferred size you want your panel to generally be, then you can use JFrame#pack to "pack" the window around it, this will make the window larger then the content area, but means you're not scratching your head wondering why you set the window to a certain size, but your component is smaller

Using Action Listeners for buttons

Code: Java Sphere class
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Ellipse2D;
public class Sphere extends JPanel {
private boolean flashinglights = false;
private int x = 168;
private int y = 75;
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (flashinglights) { //This is the flash option. Here it should change between grey and orange
g2.setColor(Color.ORANGE);
Ellipse2D.Double ball = new Ellipse2D.Double(x, y, 50, 50);
g2.draw(ball);
g2.fill(ball);
} else {
g2.setColor(Color.gray); //This should stay grey as it does now.
Ellipse2D.Double ball = new Ellipse2D.Double(x, y, 50, 50);
g2.draw(ball);
g2.fill(ball);
}
}
public void chooseflashinglights(){ //Ignore these methods
flashinglights = false;
}
public void choosesteady(){
flashinglights = true;
}
public void flickerorange(int d) { y = y + d; }
public void flickergrey(int d) { y = y + d; }
public static void main(String[] args) {
JFrame scFrame = new AnimationViewer();
scFrame.setTitle("Circle");
scFrame.setSize(400, 400);
scFrame.setDefaultCloseOperation((JFrame.EXIT_ON_CLOSE));
scFrame.setVisible(true);
}
}
Animation Viewer Class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class AnimationViewer extends JFrame {
JButton jbtFlash = new JButton("Flash");
JButton jbtSteady = new JButton("Steady");
JPanel bPanel = new JPanel();
Sphere sphPanel = new Sphere();
Timer timer;
public AnimationViewer() {
this.add(bPanel, BorderLayout.SOUTH);
bPanel.add(jbtFlash);
bPanel.setLayout(new GridLayout(1,2));
bPanel.add(jbtSteady);
this.add(sphPanel, BorderLayout.CENTER);
jbtSteady.addActionListener(new SteadyLights());
jbtFlash.addActionListener(new FlashingLights());
timer = new Timer(100, new TimerListener());
timer.start();
}
class TimerListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
sphPanel.flickerorange(0);
sphPanel.flickergrey(0);
repaint();
}
}
class FlashingLights implements ActionListener{
public void actionPerformed(ActionEvent e){
sphPanel.chooseflashinglights();
}
}
class SteadyLights implements ActionListener{
public void actionPerformed(ActionEvent e){
sphPanel.choosesteady();
}
}
}
So right now there is a sphere that appears on the screen. There are two buttons showed below. Flash and Steady. On the steady button it has to stay one colour (orange) which it does not.
Now on Flash it has to change from Orange to grey every 100 milli seconds.
I know it has to be something to do with Action listeners but how exactly do I implement this?
You have a lot of extra code. I would do it like this. Write flashing logic in paintComponent method. Then create just one timer. On tick call the paintComponent method for every 100ms. On flash button click start the timer, on steady button click stop the timer and call paintComponent once.
Sphere class:
public class Sphere extends JPanel {
private boolean flashinglights = false;
private int x = 168;
private int y = 75;
private Color[] colors = new Color[] {Color.ORANGE, Color.GRAY };
private int colorIndex = 0;
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (!flashinglights) {
g2.setColor(Color.ORANGE);
Ellipse2D.Double ball = new Ellipse2D.Double(x, y, 50, 50);
g2.draw(ball);
g2.fill(ball);
} else {
if(colorIndex > colors.length - 1)
colorIndex = 0;
g2.setColor(colors[colorIndex++]);
Ellipse2D.Double ball = new Ellipse2D.Double(x, y, 50, 50);
g2.draw(ball);
g2.fill(ball);
}
}
public void chooseflashinglights(){ //Ignore these methods
flashinglights = true;
}
public void choosesteady(){
flashinglights = false;
}
public static void main(String[] args) {
JFrame scFrame = new AnimationViewer();
scFrame.setTitle("Circle");
scFrame.setSize(400, 400);
scFrame.setDefaultCloseOperation((JFrame.EXIT_ON_CLOSE));
scFrame.setVisible(true);
}
}
AnimationViewer class:
public class AnimationViewer extends JFrame {
JButton jbtFlash = new JButton("Flash");
JButton jbtSteady = new JButton("Steady");
JPanel bPanel = new JPanel();
Sphere sphPanel = new Sphere();
Timer timer;
public AnimationViewer() {
this.add(bPanel, BorderLayout.SOUTH);
bPanel.add(jbtFlash);
bPanel.setLayout(new GridLayout(1,2));
bPanel.add(jbtSteady);
this.add(sphPanel, BorderLayout.CENTER);
timer = new Timer(100, new TimerListener());
jbtSteady.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
sphPanel.choosesteady();
timer.stop();
sphPanel.repaint();
}
});
jbtFlash.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
sphPanel.chooseflashinglights();
timer.start();
}
});
}
class TimerListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
sphPanel.repaint();
}
}
}
I would do things a bit differently.
I'd have a field in my drawing JPanel class that indicates what color should be shown.
For instance, if there are only two colors that are swapped, I'd use a boolean field, and would swap it's value in my timer, and then base the color draw on its value.
The paintComponent method would use that field, the boolean, to decide which Color to use for g.setColor(...).
If many colors are drawn, the field could be a combination of an array of Color together with an int index into the array. Then I'd increment the index as needed, and use the index in paintComponent to decide which color to draw with.
I'd change that field in my Timer's ActionListener -- just a single call to change it, not your strange call to flicker orange and flicker gray.
I'd start the Timer with one button
I'd stop it with the other button. That's all the button's action listener would do, simply start or stop the timer.

My repaint in JFrame is not working

I have a problem with repaint in JFrame i used repaint() earlier when i was making an animation and everything worked
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Draw extends JComponent implements KeyListener {
int x = 0;
int y = 0;
public void paint(Graphics g){
g.setColor(Color.BLUE);
g.fillRect(x, y, 50, 50);
}
public void keyPressed(KeyEvent k) {
if(k.getKeyCode() == KeyEvent.VK_UP){
y -= 2;
} else if(k.getKeyCode() == KeyEvent.VK_DOWN){
y += 2;
} else if(k.getKeyCode() == KeyEvent.VK_LEFT){
x -= 2;
} else if(k.getKeyCode() == KeyEvent.VK_RIGHT){
x += 2;
}
repaint();
}
public void keyReleased(KeyEvent k) {}
public void keyTyped(KeyEvent k) {}
}
This is my draw class which if i run as an applet everything works but i don't want an applet
My frame class
import javax.swing.*;
public class Frame {
Draw d = new Draw();
public Frame(){
JFrame f = new JFrame("Game");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(800, 600);
f.setVisible(true);
f.add(d);
f.addKeyListener(new Draw());
}
}
and my Main class
public class Main {
public static void main(String[] args) {
Frame f = new Frame();
}
}
The repaint() is the one not working i tested the key listener it works so why isn't the repaint() working?
The KeyListener shouldn't be working since a JComponent by default cannot get program focus, a necessary requirement for a KeyListener to work. One solution is to make it focusable via setFocusable(true) and then call requestFocusInWindow() on it. Better to use Key Bindings (tutorial link). Note that you should be overriding paintComponent, not paint and you should not forget to call the super's method within your override.
For example
Edit: I'm wrong since a JFrame can get focus and you're adding the KeyListener to the JFrame. But your problem is that you are creating a new Draw object to do this, not to the original displayed Draw object. Your code would actually work if you use the same Draw object for both displaying the image and for KeyListener:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Draw extends JComponent implements KeyListener {
int x = 0;
int y = 0;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillRect(x, y, 50, 50);
}
public void keyPressed(KeyEvent k) {
if (k.getKeyCode() == KeyEvent.VK_UP) {
y -= 2;
} else if (k.getKeyCode() == KeyEvent.VK_DOWN) {
y += 2;
} else if (k.getKeyCode() == KeyEvent.VK_LEFT) {
x -= 2;
} else if (k.getKeyCode() == KeyEvent.VK_RIGHT) {
x += 2;
}
repaint();
}
public void keyReleased(KeyEvent k) {
}
public void keyTyped(KeyEvent k) {
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Draw d = new Draw();
JFrame f = new JFrame("Game");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setSize(800, 600);
f.setVisible(true);
f.add(d);
f.addKeyListener(d);
}
});
}
}
Safer though is to use Key Bindings.

Using KeyListener with Java

For a homework assignment I need to create a program that essentially displays a ball and the user should be able to move it using the left and right keys. However, the program isn't responding to the keys. I don't know where the bug is, and I'd appreciate it very much if someone could help! This is the code:
public class GraphicsComponent extends JComponent
{
Ellipse2D.Double ball = new Ellipse2D.Double(200, 400, 80, 80);
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.RED);
g2.fill(ball);
g2.draw(ball);
}
}
public class BallViewer
{
public static void main(String[] args)
{
JFrame frame = new JFrame(); //creates a new JFrame called frame
frame.setSize(600,600); //invokes the method setSize on the implicit parameter frame
frame.setTitle("Move this Ball"); //sets the title of the fram
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final GraphicsComponent g = new GraphicsComponent(); //creates a new GraphicsComponent called g, is final so that the inner class can access it
frame.add(g);//adds component g to the frame
frame.setVisible(true); //sets the visibility of the frame
class PressListener implements KeyListener //creates an inner class that implements MouseListener interface
{
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_LEFT)
{
System.out.println("Left key pressed");
}
if (e.getKeyCode() == KeyEvent.VK_RIGHT)
{
System.out.println("Right key pressed");
}
}
public void keyReleased(KeyEvent e)
{
}
public void keyTyped(KeyEvent e)
{
}
}
PressListener listener = new PressListener();
g.addKeyListener(listener);
}
}
KeyListener will only respond when the component it's registered is focusable AND has focusable, JComponent is not focusable by default.
Instead, use key bindings, they save you all the hassel of messing with focus issues related to KeyListener
You're also going to have problems with making the Ellipse2D move, start by setting it's position as 0x0 and translating the Graphics context to the position you want to paint the ball
As an example...
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "up");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "down");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
am.put("up", new DeltaAction(0, -10));
am.put("down", new DeltaAction(0, 10));
am.put("left", new DeltaAction(-10, 0));
am.put("right", new DeltaAction(10, 0));
And the DeltaAction...point is the location at which the Ellipse is painted...
public class DeltaAction extends AbstractAction {
private int deltaX;
private int deltaY;
public DeltaAction(int deltaX, int deltaY) {
this.deltaX = deltaX;
this.deltaY = deltaY;
}
#Override
public void actionPerformed(ActionEvent e) {
point.x += deltaX;
point.y += deltaY;
if (point.x < 0) {
point.x = 0;
} else if (point.x + DIAMETER >= getWidth()) {
point.x = getWidth() - DIAMETER;
}
if (point.y < 0) {
point.y = 0;
} else if (point.y + DIAMETER >= getHeight()) {
point.y = getHeight() - DIAMETER;
}
repaint();
}
}

Prevent User from holding key down in Eclipse

I am creating a track and field game and I need the player to tap the button to fill the meter up. When the player releases the key the meter goes down. Unfortunately, when I hold the button the meter fills up.
Code:
public class TapGame extends JPanel implements ActionListener, KeyListener{
Timer tm = new Timer(5,this);
int barHeight = 0, bar = 0;
boolean canPress = true;
public TapGame(){
tm.start();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillRect(0, 355, bar, 10);
g.setColor(Color.WHITE);
g.drawRect(0, 355, 100, 10);
}
public void actionPerformed(ActionEvent e){
bar+= barHeight;
bar -= 2;
if (bar < 0) bar = 0;
if (bar > 98) bar = 98;
repaint();
}
public void keyPressed(KeyEvent e){
int c = e.getKeyCode();
if (c == KeyEvent.VK_A){
if (canPress) {
barHeight = 4;
canPress = false;
}
else barHeight=0;
}
}
public void keyTyped(KeyEvent e){}
public void keyReleased(KeyEvent e){
canPress = true;
barHeight =0;
}
public static void main(String[] args){
TapGame t = new TapGame();
JFrame jf = new JFrame("Tap Mini Game Test");
jf.setSize(600,400);
jf.setLocationRelativeTo(null);
jf.setVisible(true);
jf.add(t);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
It appears java calls those button presses multiple times. See here:
https://stackoverflow.com/questions/5199581/java-keylistener-keypressed-method-fires-too-fast

Categories