I need the loop() method to be able to run repaint() but it does nothing when it's called.
Moreover, the loop() method also doesn't update() when executing the code. I've tried many different variations to try and get it to work but nothing has worked. In addition to that, I use the IDE Eclipse because my teacher said we shouldn't use game engines but write the code from scratch.
class:GamePanel
package main;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class GamePanel extends JPanel implements Runnable {
private static final long serialVersionUID = 1L;
final int originalTilesize=16;
final int scale=3;
final int tileSize=originalTilesize *scale;
final int maxScreenRow=12;
final int maxScreenCol=16;
final int screenWidth=tileSize*maxScreenCol;
final int screenHeight=tileSize*maxScreenRow;
int FPS=60;
KeyHandler keyH = new KeyHandler(); // implements KeyHandler in GamePanel
Thread gameThread;
int playerX=100;
int playerY=100;
int playerSpeed=4;
public GamePanel()
{
this.setPreferredSize(new Dimension(screenWidth,screenHeight));
this.setBackground(Color.black);
this.setDoubleBuffered(true);
this.addKeyListener(keyH);
this.setFocusable(true);
}
public void startGameThread()
{
gameThread = new Thread(this);
gameThread.start();
}
public void run() {
double drawInterval=1000000000/FPS;
double nextDrawTime=System.nanoTime()+drawInterval;// TODO Auto-generated method stub
while (gameThread !=null) {
update();
repaint();
try {
double remainingTime=nextDrawTime-System.nanoTime();
remainingTime=remainingTime/1000000;
if (remainingTime<0)
{
remainingTime=0;
}
Thread.sleep((long)remainingTime);
nextDrawTime+=drawInterval;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}
public void update()
{
if (keyH.upPressed == true)
{
playerY-=playerSpeed;
}
else if(keyH.downPressed==true)
{
playerY +=playerSpeed;
}
else if(keyH.leftPressed==true)
{
playerX -=playerSpeed;
}
else if(keyH.rightPressed==true)
{
playerX +=playerSpeed;
}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2=(Graphics2D)g;
g2.setColor(Color.white);
g2.fillRect(playerX,playerY,tileSize,tileSize);
g2.dispose();
}
}
class:KeyListener
package main;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class KeyHandler implements KeyListener {
public boolean upPressed,downPressed,leftPressed,rightPressed;
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
int code =e.getKeyCode();
if (code==KeyEvent.VK_W) {
upPressed = true;
}
if (code==KeyEvent.VK_S) {
downPressed=true;
}
if (code==KeyEvent.VK_A) {
leftPressed=true;
}
if (code==KeyEvent.VK_D) {
rightPressed=true;
}
}
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
int code=e.getKeyCode();
if (code==KeyEvent.VK_W) {
upPressed = false;
}
if (code==KeyEvent.VK_S) {
downPressed=false;
}
if (code==KeyEvent.VK_A) {
leftPressed=false;
}
if (code==KeyEvent.VK_D) {
rightPressed=false;
}
}
}
You don't need a "game loop" that redraws the GUI every few seconds. You just need to call method repaint. Also, it is not recommended to call method repaint from a thread which is not the Event Dispatch Thread (EDT). Lastly, you should use key bindings rather than a KeyListener.
The below code is not a complete solution. It just sets up a key binding for the down-arrow key which moves the rectangle down when the user presses the down-arrow key. Note that you also need to handle the situation when the rectangle hits the edge of the GamePanel – unless you want to draw the rectangle at such a location that it doesn't appear in the GamePanel.
(More notes after the code.)
package main;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class GamePanel extends JPanel {
private static final long serialVersionUID = 1L;
final int originalTilesize = 16;
final int scale = 3;
final int tileSize = originalTilesize * scale;
final int maxScreenRow = 12;
final int maxScreenCol = 16;
final int screenWidth = tileSize * maxScreenCol;
final int screenHeight = tileSize * maxScreenRow;
int playerX = 100;
int playerY = 100;
int playerSpeed = 4;
public GamePanel() {
setPreferredSize(new Dimension(screenWidth, screenHeight));
setBackground(Color.black);
setFocusable(true);
InputMap im = getInputMap();
im.put(KeyStroke.getKeyStroke("DOWN"), "DOWN");
ActionMap am = getActionMap();
am.put("DOWN", new DownAction());
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.white);
g.fillRect(playerX, playerY, tileSize, tileSize);
g.dispose();
}
class DownAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
playerY += playerSpeed;
repaint();
}
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new JFrame();
frame.add(new GamePanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
This line is not required as it is the default.
this.setDoubleBuffered(true);
Method startGameThread is not required (as explained above) and should therefore be removed. Consequently, method run should also be removed which means that class GamePanel does not need to implement interface Runnable. Also no need for variables FPS and gameThread.
As shown in above code, you need to write an Action for each key binding which therefore replaces method update so that method can also be removed. In the above code, I have created one Action, named DownAction. You need to create an Action for the other arrow keys. The keystroke names are:
UP
RIGHT
LEFT
Since we are using key bindings, no need for class KeyHandler nor for variable keyH.
Method paintComponent has protected access so no need to make it public.
In method paintComponent you are only calling methods of class Graphics so no need for the cast.
I added a main method so that the above code is a runnable application.
Related
Thanks in advance for help
I created a program that makes multiple bouncing balls When user clicks on the screen a new ball should appear and move around screen. But when i click on the screen a ball appears and doesn't moving at all. When another click happens, the ball created previously jumped to another position instantly.
this is the ball class: used to create balls
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.util.Random;
import javax.swing.JComponent;
public class Ball extends JComponent implements Runnable{
private final int DIAMETER = 25;
private final int v1 = 5;
private final int v2 = -5;
private final Random rnd = new Random();
private int posX;
private int posY;
private Color color;
private int xVelocity;
private int yVelocity;
public Ball(int posX, int posY) {
this.posX = posX;
this.posY = posY;
this.color = randomColor();
this.xVelocity = rnd.nextBoolean()?v1:v2;
this.yVelocity = rnd.nextBoolean()?v1:v2;
}
public void move() {
if (posX < 15) {
xVelocity = -xVelocity;
} else if(posX > 475) {
xVelocity = -xVelocity;
}
if (posY < 0) {
yVelocity = -yVelocity;
} else if(posY > 475) {
yVelocity = -yVelocity;
}
posX +=xVelocity;
posY +=yVelocity;
}
public void draw(Graphics2D g2) {
g2.setColor(color);
g2.fill(new Ellipse2D.Double(posX,posY,DIAMETER,DIAMETER));
}
private static Color randomColor() {
int r = (int)(Math.random()*255);
int g = (int)(Math.random()*255);
int b = (int)(Math.random()*255);
Color rColor = new Color(r,g,b);
return rColor;
}
#Override
public void run() {
while(!Thread.interrupted()) {
move();
repaint();
try {
Thread.sleep(60);
} catch (InterruptedException ex) {}
}
}
}
this is the ballcomponent class: used to create the panel & display the balls
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BallComponent extends JPanel{
private ArrayList<Ball> bList;
public BallComponent() {
bList = new ArrayList<Ball>();
this.setPreferredSize(new Dimension(500,500));
this.setBackground(Color.BLACK);
this.addMouseListener(new ClickListener());
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
super.paintComponent(g2);
for (Ball a : bList) {
a.draw(g2);
}
}
private class ClickListener extends MouseAdapter{
public void mouseClicked(MouseEvent e) {
Ball a = new Ball(e.getX(),e.getY());
bList.add(a);
repaint();
Thread gameThread = new Thread(a);
gameThread.start();
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setTitle("Bouncing Balls");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new BallComponent());
frame.pack();
frame.setVisible(true);
}
}
Since you have a bunch of threads doing random stuff. You might as well have your JPanel update on it's own.
At the end of your main method.
new Thread( ()->{
while(true){
frame.repaint();
try{
Thread.sleep(60);
} catch(Exception e){
break;
}
}
}).start();
Usually you would do this with a timer task, but since you said you couldn't use a Timer, I just wrote a thread version.
I think repaint() is safe to call from off of the EDT since it just schedules a repaint. The paintComponent method and click method will only be called on the EDT. So you shouldn't get CCME. There are a bunch of race conditions with the multiple threads, but it seems like the only problem would be balls drawn out of position.
I'm trying to draw over a vlcj (java binding of the VLC library) panel so that I can play a video and draw over it. And I have encounter some issues. Here is the full base code:
Code-listing 1: AppOverlay.java
package app;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import com.sun.jna.platform.WindowUtils;
#SuppressWarnings("serial")
public class AppOverlay extends Window implements Runnable {
private final boolean isRunning;
private final int fps;
private BufferedImage graphics;
private BufferedImage img;
private int x, y;
private boolean ltr;
public AppOverlay(Window owner) {
super(owner, WindowUtils.getAlphaCompatibleGraphicsConfiguration());
setBackground(new Color(0,0,0,0));
graphics = new BufferedImage(1280,800, BufferedImage.TYPE_INT_ARGB);
isRunning = true;
img = null;
ltr = true;
fps = 60;
x = 0;
y = 0;
}
#Override
public void run(){
while(isRunning){
try{
Thread.sleep(1000/fps);
} catch(InterruptedException e){
e.printStackTrace();
}
if(ltr) {
if(x < 1280) x++;
else ltr = false;
} else {
if(x < 0) ltr = true;
else x--;
}
repaint();
}
}
public void createAndShowGUI() {
setVisible(true);
Thread thread = new Thread(this);
thread.start();
String path = "Drive:\\path\\to\\image.png";
try {
img = ImageIO.read(new java.io.FileInputStream(path));
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
Graphics2D gfx = graphics.createGraphics();
gfx.setColor(new Color(255,255,255,0));
gfx.clearRect(0, 0, 1280, 800);
if(img != null) gfx.drawImage(img, x, y, null);
gfx.dispose();
g2d.drawImage(graphics, 0, 0, null);
}
}
Code-listing 2: AppPlayer.java
package app;
import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
#SuppressWarnings("serial")
public class AppPlayer extends EmbeddedMediaPlayerComponent {
}
Code-listing 3: AppFrame.java
package app;
import java.awt.Dimension;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class AppFrame extends JFrame {
private AppPlayer appPlayer;
private AppOverlay overlay;
public AppFrame(){
super();
}
public void createAndShowGUI() {
appPlayer = new AppPlayer();
appPlayer.setPreferredSize(new Dimension(1280,800));
getContentPane().add(appPlayer);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("App");
setVisible(true);
pack();
overlay = new AppOverlay(this);
appPlayer.mediaPlayer().overlay().set(overlay);
appPlayer.mediaPlayer().overlay().enable(true);
overlay.createAndShowGUI();
}
}
Code-listing 4: Main.java
package main;
import javax.swing.SwingUtilities;
import app.AppFrame;
public class Main {
public static void main(String[] args) {
final AppFrame app = new AppFrame();
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
app.createAndShowGUI();
}
});
}
}
with that and the vlcj-4 library you should be able to test my code yourself. My issue is that the Overlay (AppOverlay class that extends the Window class) doesn't display or refresh the animation unless I deselect the window (I click on another window or on the desktop or the OS toolbar) so that the window (application) is inactive then select the window (the application) again. It will only load one frame and that's it. I have to deselect and reselect the window again for it to load another frame (this is only the case for the Overlay i.e. if I play a video in the AppPlayer class the video will be playing just fine.
What I want is to be able to draw some animated graphics on the overlay. I know that with the JPanel class there is the paintComponent() method but the Window class doesn't have that method (only the paint() and repaint() methods are available).
What should I do to fix this?
EDIT:
I tried adding a JPanel on which I draw instead of drawing directly on the AppOverlay
Code-listing 5: AppPanel.java
package app;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class AppPanel extends JPanel implements Runnable {
private int x, y;
private boolean ltr;
public AppPanel() {
x = 0;
y = 0;
ltr = true;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(0,0,0,0));
g.clearRect(0, 0, 1280, 800);
g.setColor(Color.RED);
g.fillRect(x, y, 100, 100);
}
#Override
public void run() {
while(true){
try{
Thread.sleep(1000/60);
} catch(InterruptedException e){
e.printStackTrace();
}
if(ltr) {
if(x < 1280) x++;
else ltr = false;
} else {
if(x < 0) ltr = true;
else x--;
}
repaint();
}
}
}
then adding it to the AppOverlay.
Code-listing 6: AppOverlay.java with partial modification
public class AppOverlay extends Window implements Runnable {
//previous field declaration above ...
AppPanel panel;
AppPlayer player = null;
public AppOverlay(Window owner) {
//previous constructor instructions above...
panel = new AppPanel();
add(panel);
}
public void createAndShowGUI(AppPlayer player) {
setVisible(true);
/*
Thread thread = new Thread(this);
thread.start();
String path = "Drive:\\path\\to\\image.png";
try {
img = ImageIO.read(new java.io.FileInputStream(path));
} catch (IOException e) {
e.printStackTrace();
}
*/
Thread panelThread = new Thread(panel);
panelThread.start();
}
}
Doing this will display the graphics of the JPanel and animate them as needed.
If you know a way to make the JPanel background transparent (so that we can see through it) while still letting it display its graphics. That would solve the issue for sure.
I played around a bit with your example and came up with something working, but I wouldn't call it a nice solution.
The main issue seems to be that there is no way to tell the overlay to refresh (or I just have not found it). Just repainting the overlay does not update it on screen, so the workaround I used is to hide and show it again.
For the timeing of the update interval I used a javax.swing.Timer.
(In a real version you probably want to start and stop the timer via the MediaPlayerEventListener).
As a side effect the repaint method is called and the x coordinate is adjusted to move the image around the screen.
In the simplified example below (use your main to run it), I moved a red rectangle with the x coordinate instead of some unknown image.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.Timer;
import com.sun.jna.platform.WindowUtils;
import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.player.embedded.OverlayApi;
public class AppFrame extends JFrame {
private static final long serialVersionUID = -1569823648323129877L;
public class Overlay extends Window {
private static final long serialVersionUID = 8337750467830040964L;
private int x, y;
private boolean ltr = true;
public Overlay(Window owner) throws HeadlessException {
super(owner, WindowUtils.getAlphaCompatibleGraphicsConfiguration());
setBackground(new Color(0,0,0,0));
}
#Override
public void paint(Graphics g) {
super.paint(g);
if (ltr) {
if (x < 1180)
x += 1;
else
ltr = false;
} else {
if (x < 0)
ltr = true;
else
x -= 1;
}
g.setColor(Color.RED);
g.fillRect(x, y, 100, 100);
String s = Integer.toString(x);
g.setColor(Color.WHITE);
g.drawChars(s.toCharArray(), 0, s.length(), x+10, y+50);
}
}
private EmbeddedMediaPlayerComponent appPlayer;
public void createAndShowGUI() {
appPlayer = new EmbeddedMediaPlayerComponent();
appPlayer.setPreferredSize(new Dimension(1280, 800));
getContentPane().add(appPlayer);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("App");
setVisible(true);
pack();
Overlay overlay = new Overlay(this);
OverlayApi api = appPlayer.mediaPlayer().overlay();
api.set(overlay);
api.enable(true);
//appPlayer.mediaPlayer().media().play(" ... ");
Timer timer = new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
api.enable(false);
api.enable(true);
}
});
timer.setRepeats(true);
timer.setDelay(200);
timer.start();
}
}
If that is an option for you, it might be far easier to use an animated gif instead. At least that is working on its own (no need for the Timer).
Update:
As you figured out using a JPanel seems to work better.
Just use setOpaque(false) to make it transparent.
Here an adjusted example.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent;
import uk.co.caprica.vlcj.player.embedded.OverlayApi;
public class AppFrame2 extends JFrame {
private static final long serialVersionUID = -1569823648323129877L;
public class OverlayPanel extends JPanel {
private static final long serialVersionUID = 8070414617530302145L;
private int x, y;
private boolean ltr = true;
public OverlayPanel() {
this.setOpaque(false);
}
#Override
public void paint(Graphics g) {
super.paint(g);
if (ltr) {
if (x < 1180)
x += 1;
else
ltr = false;
} else {
if (x < 0)
ltr = true;
else
x -= 1;
}
g.setColor(Color.RED);
g.fillRect(x, y, 100, 100);
String s = Integer.toString(x);
g.setColor(Color.WHITE);
g.drawChars(s.toCharArray(), 0, s.length(), x+10, y+50);
}
}
public class Overlay extends Window {
private static final long serialVersionUID = 8337750467830040964L;
OverlayPanel panel;
public Overlay(Window owner) throws HeadlessException {
super(owner);
setBackground(new Color(0,0,0,0));
panel = new OverlayPanel();
this.add(panel);
}
}
private EmbeddedMediaPlayerComponent appPlayer;
public void createAndShowGUI() {
appPlayer = new EmbeddedMediaPlayerComponent();
appPlayer.setPreferredSize(new Dimension(1280, 800));
getContentPane().add(appPlayer);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("App");
setVisible(true);
pack();
Overlay overlay = new Overlay(this);
OverlayApi api = appPlayer.mediaPlayer().overlay();
api.set(overlay);
api.enable(true);
//appPlayer.mediaPlayer().media().play(" ... ");
Timer timer = new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
overlay.panel.repaint();
}
});
timer.setRepeats(true);
timer.setDelay(17);
timer.start();
}
}
You have already done the bulk of the work. Simply repaint the frame every time you draw over it by calling app.repaint();
You can use the following methods from JComponent: ( http://download.oracle.com/javase/6/docs/api/javax/swing/JComponent.html )
void repaint(long tm, int x, int y, int width, int height)
//**Adds the specified region to the dirty region list if the component is showing.*//
void repaint(Rectangle r)
/**Adds the specified region to the dirty region list if the component is showing.*//
You can call those before redraw()
While using Swing in java, I am trying to move a circle slowly from a starting position to an end position when clicking a button. However, I can't see the circle moving. It just moves from start to end in an instant.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class MyApp {
private int x = 10;
private int y = 10;
private JFrame f;
private MyDraw m;
private JButton b;
public void go() {
f = new JFrame("Moving circle");
b = new JButton("click me to move circle");
m = new MyDraw();
f.add(BorderLayout.SOUTH, b);
f.add(BorderLayout.CENTER, m);
f.setSize(500, 500);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
b.addActionListener(new Bute());
}
public static void main(String[] args) {
MyApp m = new MyApp();
m.go();
}
private class Bute implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < 150; i++) {
++x;
++y;
m.repaint();
Thread.sleep(50);
}
}
}
private class MyDraw extends JPanel {
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, 500, 500);
g.setColor(Color.red);
g.fillOval(x, y, 40, 40);
}
}
}
I think the problem is with the action listener because when I'm doing it without using button it is working. Any suggestions?
As Andrew Thompson said, calling Thread.sleep() without defining a second thread freezes everything, so the solution is to define and run another thread like so:
class Bute implements ActionListener, Runnable {
//let class implement Runnable interface
Thread t; // define 2nd thread
public void actionPerformed(ActionEvent e) {
t = new Thread(this); //start a new thread
t.start();
}
#Override //override our thread's run() method to do what we want
public void run() { //this is after some java-internal init stuff called by start()
//b.setEnabled(false);
for (int i = 0; i < 150; i++) {
x++;
y++;
m.repaint();
try {
Thread.sleep(50); //let the 2nd thread sleep
} catch (InterruptedException iEx) {
iEx.printStackTrace();
}
}
//b.setEnabled(true);
}
}
The only problem with this solution is that pressing the button multiple times will speed up the circle, but this can be fixed by making the button unclickable during the animation via b.setEnabled(true/false). Not the best solution but it works.
As said in the comments and another answer, don't block the EDT. Thead.sleep(...) will block it, so you have two options:
Create and manage your own (new) thread.
Use a Swing Timer
In this answer I'll be using a Swing Timer, since it's easier to use. I also changed the paintComponent method to use the Shape API and change the button text to start and stop accordingly as well as reusing the same ActionListener for the button and the timer:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class MovingCircle {
private JFrame frame;
private CustomCircle circle;
private Timer timer;
private JButton button;
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingCircle()::createAndShowGui);
}
private void createAndShowGui() {
frame = new JFrame(this.getClass().getSimpleName());
circle = new CustomCircle(Color.RED);
timer = new Timer(100, listener);
button = new JButton("Start");
button.addActionListener(listener);
circle.setBackground(Color.WHITE);
frame.add(circle);
frame.add(button, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private ActionListener listener = (e -> {
if (!timer.isRunning()) {
timer.start();
button.setText("Stop");
} else {
if (e.getSource().equals(button)) {
timer.stop();
button.setText("Start");
}
}
circle.move(1, 1);
});
#SuppressWarnings("serial")
class CustomCircle extends JPanel {
private Color color;
private int circleX;
private int circleY;
public CustomCircle(Color color) {
this.color = color;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(color);
g2d.fill(new Ellipse2D.Double(circleX, circleY, 50, 50));
}
#Override
public Dimension preferredSize() {
return new Dimension(100, 100);
}
public void move(int xGap, int yGap) {
circleX += xGap;
circleY += yGap;
revalidate();
repaint();
}
public int getCircleX() {
return circleX;
}
public void setCircleX(int circleX) {
this.circleX = circleX;
}
public int getCircleY() {
return circleY;
}
public void setCircleY(int circleY) {
this.circleY = circleY;
}
}
}
I'm sorry, I can't post a GIF as I wanted but this example runs as expected.
I completed my Java Homework, but it was all mushed into one main class (with a couple of private classes). I'm trying to make my code more elegant by having separate classes to handle things. I'm having trouble having the listener interact with the existing panels.
The main parts I'm looking at are in the Listener.java. The mouse click and action listener. I am able to get the name of the button that was clicked to trigger the listener, which is helpful, but I can't get it to interact with the "DrawBoard" panel that was added to the Panel panel.
I'm wondering what the best way to work this interaction is:
Window.java:
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Window extends JFrame {
/**
*
*/
private static final long serialVersionUID = -8255319694373975038L;
public static void main(String[] args){
new Window();
}
public Window(){
// Adds the custom panel
Panel Panel = new Panel();
this.add(Panel);
// Basic Window Features
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(800,800);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
Panel.java:
public class Panel extends JPanel{
public Panel(){
Button testButton = new Button("Test");
DrawBoard drawBoard = new DrawBoard();
Listener listener = new Listener();
this.add(testButton);
this.add(drawBoard);
}
}
Button.java
import java.awt.Dimension;
import javax.swing.JButton;
public class Button extends JButton{
public Button(String name) {
this.setText(name);
this.setName((String) name);
buttonSettings();
}
private void buttonSettings(){
Listener listener = new Listener();
this.addActionListener(listener);
int width = 200;
int height = 50;
Dimension dim = new Dimension(width,height);
this.setPreferredSize(dim);
}
}
DrawBoard.java
import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JPanel;
public class DrawBoard extends JPanel{
public DrawBoard(){
DrawBoardSettings();
}
private void DrawBoardSettings(){
int width = 600;
int height = 600;
Dimension dim = new Dimension(width,height);
this.setPreferredSize(dim);
this.setBackground(Color.WHITE);
}
}
Listener.java
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JComponent;
public class Listener implements ActionListener, MouseListener {
#Override
public void mouseClicked(MouseEvent e) {
**// Draw a dot at the mouse location of the DrawBoard JPanel that was added by the Panel JPanel that was added by the Window JFrame**
}
#Override
public void mousePressed(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void actionPerformed(ActionEvent e) {
String name = ((JComponent) e.getSource()).getName();
**// Draw a rectangle on the DrawBoard JPanel that was added by the Panel JPanel that was added by the Window JFrame**
}
}
Some thoughts here:
Currently you have several instances of that Listener class. That is probably not what you want.
Related to that: your code indicates that your Listener class should draw on some panel. If so ... that Listener somehow needs access to that panel.
One way to get there: you change your constructor to
private final Panel drawingBoard;
public Listener(Panel drawingBoard) {
this.drawingBoard = drawingBoard;
)
and then your actionPerformed() method has something to draw on.
The core thing to understand: you have to create a clear model (in your mind) about which components you have; and how they depend on each other. Because that drives your implementation; for example regarding the order in which you have to create your different objects. Given my example, you would need to first create a Panel object; so that you can then create a Listener for that Panel.
Here is the code for my completed project. I think I made it much cleaner and only called "listener" and "drawBoard" once, which cleared up my problems:
Window.java:
import javax.swing.JFrame;
public class Window extends JFrame {
/**
*
*/
private static final long serialVersionUID = -8255319694373975038L;
// initiates the program
public static void main(String[] args){
new Window();
}
public Window(){
// Adds the custom panel
Panel Panel = new Panel();
this.add(Panel);
// Basic Window Features
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(800,800);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
Panel.java:
import java.awt.FlowLayout;
import java.util.ArrayList;
import javax.swing.JPanel;
public class Panel extends JPanel{
/**
*
*/
private static final long serialVersionUID = 5509155261502497671L;
public Panel(){
// This is the area where stuff will be drawn
DrawBoard drawBoard = new DrawBoard();
// This is the only instance of listener. Buttons now call this variable
Listener listener = new Listener(drawBoard);
// Invisible Overlay allows me to get mouse listener coordinates for drawing
JPanel overlay = new JPanel();
//gets rid of the padding on the invisible overlay layer;
FlowLayout layout = (FlowLayout)overlay.getLayout();
layout.setVgap(0);
layout.setHgap(0);
// The mouse listener is added to the overlay layer rather than the panel to ensure that X,Y coords match the drawboard panel underneath
// I could not add the listener to the drawboard panel because the listener requires the drawboard panel to perform.
overlay.addMouseListener(listener);
overlay.addMouseMotionListener(listener);
overlay.add(drawBoard);
// Buttons that will appear on the top of the panel
ArrayList<Button> TopButtons = new ArrayList<Button>();
TopButtons.add(new Button("Blue", listener));
TopButtons.add(new Button("Green", listener));
TopButtons.add(new Button("Red", listener));
TopButtons.add(new Button("Black", listener));
// Buttons that will appear on the bottom of the pannel
ArrayList<Button> BotButtons = new ArrayList<Button>();
BotButtons.add(new Button("Small", listener));
BotButtons.add(new Button("Medium", listener));
BotButtons.add(new Button("Large", listener));
BotButtons.add(new Button("Clear", listener));
// Using for loops to clean up code a bit.
for (int i = 0; i < TopButtons.size(); i++){
this.add(TopButtons.get(i));
}
// add the overlay rather than the drawboard, to ensure that mouse functions work
this.add(overlay);
for (int i = 0; i < BotButtons.size(); i++){
this.add(BotButtons.get(i));
}
}
}
Button.java
import java.awt.Dimension;
import javax.swing.JButton;
public class Button extends JButton{
/**
*
*/
private static final long serialVersionUID = -819700115106662958L;
private final Listener listener;
public Button(String name, Listener listener) {
this.listener = listener;
this.setText(name);
this.setName((String) name);
buttonSettings();
}
private void buttonSettings(){
this.addActionListener(listener);
int width = 150;
int height = 50;
Dimension dim = new Dimension(width,height);
this.setPreferredSize(dim);
}
}
DrawBoard.java:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.JPanel;
public class DrawBoard extends JPanel{
/**
*
*/
private static final long serialVersionUID = 1368365222404381200L;
// ArrayList will store all points drawn on the canvas
public ArrayList<Point> points = new ArrayList<Point>();
// Constructor initiates the settings for the board
public DrawBoard(){
DrawBoardSettings();
}
// Painting of the points happens here
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
// draws a point for every point in the "points" array.
for (int i = 0; i < points.size(); i++){
Point thisPoint = points.get(i);
g.setColor(thisPoint.color);
g.fillOval(thisPoint.x,thisPoint.y,thisPoint.size,thisPoint.size);
}
}
private void DrawBoardSettings(){
// Sets the preferred window size
int width = 700;
int height = 600;
Dimension dim = new Dimension(width,height);
this.setPreferredSize(dim);
// background color and visibility
this.setBackground(Color.WHITE);
this.setVisible(true);
this.setOpaque(true);
}
}
Listener.java:
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JComponent;
public class Listener implements ActionListener, MouseListener, MouseMotionListener {
private final DrawBoard drawBoard;
//sets default color and size
private Color color;
private int size;
public Listener(DrawBoard drawBoard) {
this.drawBoard = drawBoard;
this.color = Color.BLACK;
this.size = 10;
}
// Contains settings for creating a new point
private void drawPoint(MouseEvent e){
Point point = new Point(e.getX(), e.getY(), this.color, this.size);
drawBoard.points.add(point);
drawBoard.repaint();
}
// Contains settings for all of the buttons
private void setButton(String name){
// Colors
if (name == "Blue"){
this.color = Color.BLUE;
}
if (name == "Red"){
this.color = Color.RED;
}
if (name == "Green"){
this.color = Color.GREEN;
}
if (name == "Black"){
this.color = Color.BLACK;
}
// Sizes
if (name == "Large"){
this.size = 15;
}
if (name == "Medium"){
this.size = 10;
}
if (name == "Small"){
this.size = 5;
}
// Clear
if (name == "Clear"){
drawBoard.points.clear();
drawBoard.repaint();
}
}
// Draws a point when the mouse is pressed down
#Override
public void mousePressed(MouseEvent e) {
drawPoint(e);
}
// Draws a point when the mouse is dragged (at every location it is dragged at)
#Override
public void mouseDragged(MouseEvent e) {
drawPoint(e);
}
// Changes the settings of buttons
#Override
public void actionPerformed(ActionEvent e) {
String name = ((JComponent) e.getSource()).getName();
setButton(name);
}
//Unused Listeners
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseMoved(MouseEvent e) {
// TODO Auto-generated method stub
}
}
Point.java
import java.awt.Color;
public class Point {
protected int x, y, size;
protected Color color;
public Point(int x, int y, Color color, int size){
this.x = x;
this.y = y;
this.color = color;
this.size = size;
}
}
I can draw static things to the screen, but I want to make them move with user key input. I don't know what to do, I've been searching and searching and haven't come up with an answer yet. Please help!
package com.Game.game;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Game extends JFrame
{
final static int width = 500;
final static int height = 500;
public int x = 250;
public int y = 250;
public int changeX = 10;
public int changeY = 10;
public static void main(String[] args)
{
new Game();
}
public Game()
{
KeyListener listener = new KeyListening();
addKeyListener(listener);
setFocusable(true);
DrawingStuff drawingstuff = new DrawingStuff();
add(drawingstuff);
setSize(width, height);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public class DrawingStuff extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawString("Hey there!", 300, 300);
g.setColor(Color.RED);
g.fillRect(x, y, 50, 50);
}
}
public class KeyListening implements KeyListener
{
DrawingStuff drawingstuff = new DrawingStuff();
#Override
public void keyPressed(KeyEvent e)
{
if(e.getKeyCode() == KeyEvent.VK_UP)
{
y = y + changeY;
System.out.println("Hey");
drawingstuff.repaint();
}
}
#Override
public void keyReleased(KeyEvent e)
{
}
#Override
public void keyTyped(KeyEvent e)
{
}
}
public void update()
{
}
}
EDIT: Fixed it. I took away the key listener stuff in the constructor method, added a command to focus on "drawingstuff" in the constructor method, and, most importantly, added this bit of code to the end of the constructor method:
while(true)
{
drawingstuff.repaint();
}
The problem is that your KeyListening object has a reference to a different DrawingStuff object than the one you added to your UI inside the Game constructor.
public class KeyListening implements KeyListener
{
DrawingStuff drawingstuff = new DrawingStuff();
...
You should pass a DrawingStuff reference to the KeyListening instance so that it can tell the right object to repaint itself.