I am learning java. This is my first animation. I want a ball to move up and down continuously when start button is pressed, and it should STOP when stop button is pressed. The code I have written moves the ball 5 times(3 times down and 2 times up). But the panel displays only start and final positions, it does not display intermediate positions. How to display intermediate positions as well?
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class dabble
{
private boolean z = true;
private int x=10;
private int y=10;
private JFrame frame;
private JLabel label;
private mypanel panel;
private JButton b1;
private JButton b2;
public static void main (String[] args)
{
dabble dab = new dabble();
dab.start();
}
void start()
{
frame = new JFrame();
label = new JLabel();
panel = new mypanel();
b1= new JButton("Start");
b2= new JButton("Stop");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
b1.addActionListener(new al1());
b2.addActionListener(new al2());
frame.getContentPane().add(BorderLayout.NORTH,b1);
frame.getContentPane().add(BorderLayout.SOUTH,b2);
frame.getContentPane().add(BorderLayout.CENTER,panel);
frame.getContentPane().add(BorderLayout.EAST,label);
frame.setSize(600,600);
frame.setVisible(true);
}
void go()
{
for(int i=0;i<5;i++)
{
if(z==false)
break;
//label.setText("Hi");
y=510-y;
panel.repaint();
try{
Thread.sleep(500);
//label.setText("sleep");
}catch(Exception Ex)
{
//label.setText("exp");
}
}
}
class al1 implements ActionListener{
public void actionPerformed(ActionEvent event){
go();
}
}
class al2 implements ActionListener{
public void actionPerformed(ActionEvent event){
z=false;
}
}
class mypanel extends JPanel
{
public void paintComponent ( Graphics g)
{
g.setColor(Color.white);
g.fillRect(0,0,this.getWidth(),this.getHeight());
int red = (int) (Math.random()*255);
int green = (int) (Math.random()*255);
int blue = (int) (Math.random()*255);
Color c1 = new Color(red,green,blue);
g.setColor(c1);
g.fillOval(x,y,20,20);
}
}
}
Calling repaint() does not actually paint the panel - it just marks it to be painted later. And painting always happens on the event dispatch thread, as do event listener notifications.
Since go() is being called on the event dispatch thread (by a button action listener), the panel cannot be repainted while go() is running. You simply queue up a single repaint that happens as soon as go() is done.
What you probably want to do is to use a javax.swing.Timer that fires once every 500 ms, and have its action be to move the ball one step and then call repaint().
Well you perform your loop 5 times (instead of an infinite loop). Moreover you change the vertical position from 10 to 500 and then back to 10 etc... If you want to see intermediate position, you should consider asking to repaint the intermediate positions as well providing the intermediate values of y.
OK, doing the following solves your issues but it is definitely a poor design:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class dabble {
private static final long ANIMATION_DURATION = 5000; // 5 seconds
private static final int REFRESH_RATE = 10;// 10 times per second
private boolean incrementing = true;
private volatile boolean z = true;
private int x = 10;
private volatile int y = 10;
private JFrame frame;
private JLabel label;
private mypanel panel;
private JButton b1;
private JButton b2;
public static void main(String[] args) {
dabble dab = new dabble();
dab.start();
}
void start() {
frame = new JFrame();
label = new JLabel();
panel = new mypanel();
b1 = new JButton("Start");
b2 = new JButton("Stop");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
b1.addActionListener(new al1());
b2.addActionListener(new al2());
frame.getContentPane().add(BorderLayout.NORTH, b1);
frame.getContentPane().add(BorderLayout.SOUTH, b2);
frame.getContentPane().add(BorderLayout.CENTER, panel);
frame.getContentPane().add(BorderLayout.EAST, label);
frame.setSize(600, 600);
frame.setVisible(true);
}
void go() {
new Animation().start();
}
class al1 implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
go();
}
}
class al2 implements ActionListener {
#Override
public void actionPerformed(ActionEvent event) {
z = false;
}
}
class mypanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
int red = (int) (Math.random() * 255);
int green = (int) (Math.random() * 255);
int blue = (int) (Math.random() * 255);
Color c1 = new Color(red, green, blue);
g.setColor(c1);
g.fillOval(x, y, 20, 20);
}
}
class Animation extends Thread {
private long start;
#Override
public void run() {
start = System.currentTimeMillis();
while (true) {
if (!z) {
return;
}
double progress = (double) (System.currentTimeMillis() - start) / ANIMATION_DURATION;
System.err.println(progress);
if (incrementing) {
y = (int) (10 + 500 * progress);
} else {
y = (int) (510 - 500 * progress);
}
if (progress > 1.0) {
start = System.currentTimeMillis();
incrementing = !incrementing;
}
panel.repaint();
try {
Thread.sleep(1000 / REFRESH_RATE);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
Related
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 was given this homework assignment by my professor to implement two buttons and control a fan. I am very new to GUI's and I have not the slightest clue how to call upon my timer in order to increment it using timer.setDelay(); Any help would be greatly appreciated. My code is below:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Fan extends JFrame implements ActionListener{
JButton speedup;
JButton slowdown;
JPanel testPanel = new MyPanel();//trying to inherit properties of MyPanel
public Fan() {
add(testPanel);
GridLayout f = new GridLayout(1,2);
setLayout(f);
JButton speedup = new JButton("Speed Up");
speedup.addActionListener(this);
add(speedup);
JButton slowdown = new JButton("Slow Down");
slowdown.addActionListener(this);
add(slowdown);
}
/* public void actionPerformed(ActionEvent event){
int delay;
String cmd = event.getActionCommand();
if(cmd == "Speed Up" ){
delay = testPanel.getTimer().getDelay();
delay++;
testPanel.getTimer().setDelay(delay);
}
else{
delay = testPanel.getTimer().getDelay();
delay--;
testPanel.getTimer().setDelay(delay);
*/
//My attempt at getting timer to work commented out
}
public class MyPanel extends JPanel {
private static final long serialVersionUID = 1L;
public Timer timer = new Timer(10, new TimerListener());
private int alpha = 0; //angle
public Timer getTimer(){
return timer; //getter method for timer
}
public MyPanel() {
timer.start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
alpha = alpha + 1;
int xc = getWidth()/2;
int yc = getHeight()/2;
int rad = (int)(Math.min(getWidth(), getHeight())*0.4);
int x = xc - rad;
int y = yc - rad;
g.fillArc(x, y, 2*rad, 2*rad, 0+alpha, 30);
g.fillArc(x, y, 2*rad, 2*rad, 90+alpha, 30);
g.fillArc(x, y, 2*rad, 2*rad, 180+alpha, 30);
g.fillArc(x, y, 2*rad, 2*rad, 270+alpha, 30);
}
class TimerListener implements ActionListener {
public void actionPerformed(ActionEvent e){
repaint();
}
}
public static void main(String[] args) {
JFrame fan = new Fan();
fan.setSize(700, 700);
fan.setLocationRelativeTo(null);
fan.setDefaultCloseOperation(EXIT_ON_CLOSE);
fan.setTitle("Spinning Fan");
fan.setVisible(true);
}
}
JPanel testPanel = new MyPanel(); will limit testPanel to the methods of JPanel (so you can't use testPanel.getTimer()). Instead use MyPanel testPanel = new MyPanel();, then you will be able to use testTimer.getTimer();
if(cmd == "Speed Up" ){. Don't compare Strings with ==. Instead use equals. So if ("Speed Up".equals(cmd)) {}
If you want to "Speed up" an animation, you should decrease the delay, not increase it. And vice verse.
Side Notes
Run Swings apps on the Event Dispatch Thread. You can achieve this by wrapping the code in your main in a SwingUtilities.invokeLater(...). See more at Initial Threads
Here's a fixed example
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Fan extends JFrame implements ActionListener {
JButton speedup;
JButton slowdown;
MyPanel testPanel = new MyPanel();// trying to inherit properties of MyPanel
public Fan() {
add(testPanel);
JButton speedup = new JButton("Speed Up");
speedup.addActionListener(this);
JButton slowdown = new JButton("Slow Down");
slowdown.addActionListener(this);
JPanel panel = new JPanel();
panel.add(speedup);
panel.add(slowdown);
add(panel, BorderLayout.SOUTH);
pack();
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("Spinning Fan");
setVisible(true);
}
public void actionPerformed(ActionEvent event) {
int delay;
String cmd = event.getActionCommand();
if ("Speed Up".equals(cmd)) {
delay = testPanel.getTimer().getDelay();
delay--;
testPanel.getTimer().setDelay(delay);
} else {
delay = testPanel.getTimer().getDelay();
delay++;
testPanel.getTimer().setDelay(delay);
}
}
// My attempt at getting timer to work commented out
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Fan();
}
});
}
}
class MyPanel extends JPanel {
private static final long serialVersionUID = 1L;
public Timer timer = new Timer(10, new TimerListener());
private int alpha = 0; // angle
public Timer getTimer() {
return timer; // getter method for timer
}
public MyPanel() {
timer.start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
alpha = alpha + 1;
int xc = getWidth() / 2;
int yc = getHeight() / 2;
int rad = (int) (Math.min(getWidth(), getHeight()) * 0.4);
int x = xc - rad;
int y = yc - rad;
g.fillArc(x, y, 2 * rad, 2 * rad, 0 + alpha, 30);
g.fillArc(x, y, 2 * rad, 2 * rad, 90 + alpha, 30);
g.fillArc(x, y, 2 * rad, 2 * rad, 180 + alpha, 30);
g.fillArc(x, y, 2 * rad, 2 * rad, 270 + alpha, 30);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(600, 600);
}
class TimerListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
repaint();
}
}
}
I'm wondering why my game objects are not showing. I am able to draw to the JPanel if I use g.drawRect outside of the for-each loop, but calling PipeObject inside of the loop doesn't seem to work for me. Am I doing something wrong here? Thanks for any help or solutions.
This is an updated version of my old question, which can be found here.
UPDATE - The pipes are drawing correctly, but not moving to the left by calling pipe.move().
Game
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
//import javax.swing.border.EmptyBorder;
import javax.swing.SwingUtilities;
public class Game {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
final CardLayout cl = new CardLayout();
final JPanel gui = new JPanel(cl);
// remove if no border is needed
//gui.setBorder(new EmptyBorder(10,10,10,10));
JPanel menu = new JPanel(new GridBagLayout());
JButton playGame = new JButton("Play!");
ActionListener playGameListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cl.show(gui, "game");
}
};
playGame.addActionListener(playGameListener);
Insets margin = new Insets(20, 50, 20, 50);
playGame.setMargin(margin);
menu.add(playGame);
gui.add(menu);
cl.addLayoutComponent(menu, "menu");
final JPanel pipes = new Pipes();
gui.add(pipes);
cl.addLayoutComponent(pipes, "game");
JFrame f = new JFrame("PipeGame");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
Pipes
import java.util.*;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Pipes extends JPanel {
boolean gameNotOver = true;
int x1 = 754;
int y1 = setHeightVal();
int y2 = setHeightVal();
int y3 = setHeightVal();
List<PipeObject> pipes = new ArrayList<PipeObject>();
public Pipes() {
pipes.add(new PipeObject(x1, y1));
pipes.add(new PipeObject(x1 + 300, y2));
pipes.add(new PipeObject(x1 + 600, y3));
}
public void drawEndlessPipes() {
if (gameNotOver) {
Timer pipeSpeed = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (PipeObject pipe : pipes) {
pipe.move();
}
}
});
pipeSpeed.start();
}
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (PipeObject pipe : pipes) {
pipe.drawPipe(g);
}
}
public int setHeightVal() { //Get a random number and select a preset height
int num = (int)(9*Math.random() + 1);
int val = 0;
if (num == 9)
{
val = 295;
}
else if (num == 8)
{
val = 246;
}
else if (num == 7)
{
val = 216;
}
else if (num == 6)
{
val = 185;
}
else if (num == 5)
{
val = 156;
}
else if (num == 4)
{
val = 125;
}
else if (num == 3)
{
val = 96;
}
else if (num == 2)
{
val = 66;
}
else
{
val = 25;
}
return val;
}
public Dimension getPreferredSize() {
return new Dimension(751,501);
}
}
PipeObject
import java.awt.Graphics;
public class PipeObject {
//Declare and initialiaze variables
int x1;
int x2 = 75; //pipe width, total is 83
int y1 = -1; // Y should be -1
int y2;
int gap = 130; //gap height
public PipeObject(int x, int y) {
this.x1 = x;
this.y2 = y;
}
public void drawPipe(Graphics g/*, int x1, int y2*/) {
g.drawRect(x1,y1,x2,y2); //Draw part 1
g.drawRect(x1-3,y2-1,x2+6,25); //Draw part 2
g.drawRect(x1-3,y2+25+gap,x2+6,25); //Draw part 3
g.drawRect(x1,y2+25+gap+25,x2,500-y2-49-gap); //Draw part 4
}
public void move() {
x1--;
}
public int getMyX() { //To determine where the pipe is horizontally
return x1-3;
}
public int getMyY() { //To determine where the pipe is vertically
return y2+25;
}
}
You are painting the pipes beyond the visual bounds of the panel. The dimension of Pipes panel is 751x501, but the pipes begin at x1 = 754. Try changing x1 to 1 in Pipes and you should see the three pipes. Or, you can maximize the frame, and you should see the missing pipes far on the right side.
As CyberStorm said, you never called drawEndlessPipes() to start the swing timer at all.
Also I don't see a repaint() call to Pipes panel inside your timer, just changing the x value won't magically move anything, you need to call repaint() in Pipes.java to make it repaint the screen and show animation.
So here it goes:
Modified Runnable in Game.java
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
final CardLayout cl = new CardLayout();
final JPanel gui = new JPanel(cl);
// remove if no border is needed
//gui.setBorder(new EmptyBorder(10,10,10,10));
JPanel menu = new JPanel(new GridBagLayout());
JButton playGame = new JButton("Play!");
ActionListener playGameListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cl.show(gui, "game");
}
};
playGame.addActionListener(playGameListener);
Insets margin = new Insets(20, 50, 20, 50);
playGame.setMargin(margin);
menu.add(playGame);
gui.add(menu);
cl.addLayoutComponent(menu, "menu");
final JPanel pipes = new Pipes();
gui.add(pipes);
cl.addLayoutComponent(pipes, "game");
JFrame f = new JFrame("PipeGame");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See http://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
((Pipes) pipes).drawEndlessPipes();
}
};
Modified drawEndlessPipes() in Pipes.java
public void drawEndlessPipes() {
if (gameNotOver) {
Timer pipeSpeed = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (PipeObject pipe : pipes) {
pipe.move();
Pipes.this.repaint();
}
}
});
pipeSpeed.start();
}
}
I am working on a project which is a Checkout Simulation. I have the code to make it work run but i am struggling to understand and implement how to add graphics(in my case a square) once a certain condition is true. For example i have made my code so that it goes through random numbers and if 2,4,6 or 8 has been randomly generated, someone will be added to the queue and the same goes for if they are even numbers 1 or 3, someone is removed from the queue. I basically just want to know how to add a square to the screen once i have met my condition (for example, generating a 4 should add a square to the screen but it doesn't)
ANY help would really be appreciated!
public class MainPanel extends JPanel {
private Queue<String> tillQueue;
private int rndNumber;
private int currentLength;
private ArrayList<Integer> lengthList;
private double mean;
private Random rand;
private int MAXLENGTH;
private static Random r = new Random();
private static Random r2 = new Random();
Color colour;
private static final int IMAGE_SIZE = 600;
private Timer timer;
private int delay;
private JButton startButton;
private JButton stopButton;
private BufferedImage buffer;
JToolBar toolbar;
public MainPanel() {
startButton = new JButton("START");
stopButton = new JButton("STOP");
toolbar = new JToolBar();
toolbar.add(startButton);
toolbar.add(stopButton);
this.buffer = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB);
setDoubleBuffered(false);
StartActionHandler start = new StartActionHandler();
StopActionHandler stop = new StopActionHandler();
TimerEvent timerEvt = new TimerEvent();
startButton.addActionListener(start);
stopButton.addActionListener(stop);
delay = 50;
timer = new Timer(delay, timerEvt);
}
public class TimerEvent implements ActionListener {
public void actionPerformed(ActionEvent e) {
//drawNext(buffer.getGraphics());
for (int time = 1; time < 9; time++) {
rndNumber = rand.nextInt(6) + 1; //generates random number
if (rndNumber == 2 || rndNumber == 4 || rndNumber == 6 || rndNumber == 8) {
//time is added to queue
tillQueue.add(String.valueOf(time));
drawNext(buffer.getGraphics());
repaint();
}
}
}
}
public class StartActionHandler implements ActionListener {
public void actionPerformed(ActionEvent e) {
timer.start();
}
}
private void drawNext(Graphics g) {
int x = r.nextInt(IMAGE_SIZE);
int y = r.nextInt(IMAGE_SIZE);
int red = r2.nextInt(255);
int green = r2.nextInt(255);
int blue = r2.nextInt(255);
Color randomColour = new Color(red, green, blue);
g.setColor(randomColour);
g.fillRect(x, y, 10, 10);
repaint();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(buffer, 0, 0, this);
}
}
Note several changes to get rendering working:
For convenience, use the buffer's createGraphics() method and dispose() it when done.
Initialize the offscreen buffer to a known state.
One instance of Random is usually sufficient.
Limit variable scope to the extent possible, e.g. private class TimerEvent.
Override getPreferredSize() to establish the rendering area size.
As tested:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.Timer;
/**
* #see https://stackoverflow.com/a/21238669/230513
*/
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new MainPanel());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test().display();
}
});
}
private static class MainPanel extends JPanel {
private static final int SIZE = 500;
private static final int DELAY = 100;
private static final Random r = new Random();
private final Queue<String> tillQueue = new LinkedList<>();
private Timer timer;
private JButton startButton;
private JButton stopButton;
private BufferedImage buffer;
private JToolBar toolbar;
public MainPanel() {
super(new BorderLayout());
startButton = new JButton("START");
stopButton = new JButton("STOP");
toolbar = new JToolBar();
toolbar.add(startButton);
toolbar.add(stopButton);
buffer = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = buffer.createGraphics();
g.clearRect(0, 0, SIZE, SIZE);
g.dispose();
StartActionHandler start = new StartActionHandler();
TimerEvent timerEvt = new TimerEvent();
timer = new Timer(DELAY, timerEvt);
startButton.addActionListener(start);
add(new JLabel(new ImageIcon(buffer)));
add(toolbar, BorderLayout.SOUTH);
}
private class TimerEvent implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
for (int time = 1; time < 9; time++) {
if (r.nextInt(6) % 2 == 0) {
tillQueue.add(String.valueOf(time));
drawNext();
}
}
}
}
private class StartActionHandler implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
timer.start();
}
}
private void drawNext() {
Graphics2D g = buffer.createGraphics();
int x = r.nextInt(SIZE);
int y = r.nextInt(SIZE);
g.setColor(new Color(r.nextInt()));
g.fillRect(x, y, 10, 10);
g.dispose();
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(buffer, 0, 0, this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
}
}
How is suposed to work? when you met the condition an item is added to tillQueue, but tillQueue is never readed...
I you want to draw something you can draw it in the method paintComponent.
To draw a rectangle simply use:
http://docs.oracle.com/javase/7/docs/api/java/awt/Graphics.html#drawRect(int, int, int, int)
You can iterate the tillQueue in the paintComponent method and draw the corresponding rectangles.
I have the following program which has some very strange and unwanted behavior when it runs. Its supposed to have two buttons, "Start" and "Stop, but when I click "Start" another button shows up right below "Start". Here's a print screen of what I'm talking about:
What am I doing wrong and how do I fix this ugly problem?
Here's the code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
public class TwoButtonsTest {
JFrame frame;
Timer timer;
boolean isClicked;
public static void main(String[] args) {
TwoButtonsTest test = new TwoButtonsTest();
test.go();
}
public void go() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
JButton startButton = new JButton("Start");
startButton.addActionListener(new StartListener());
JButton stopButton = new JButton("Stop");
stopButton.addActionListener(new StopListener());
final DrawPanel myDraw = new DrawPanel();
frame.getContentPane().add(BorderLayout.CENTER, myDraw);
frame.getContentPane().add(BorderLayout.NORTH, startButton);
frame.getContentPane().add(BorderLayout.SOUTH, stopButton);
frame.setVisible(true);
timer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
myDraw.repaint();
}
});
}
class StartListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//needs to be implemented
if(!isClicked) {
}
isClicked = true;
timer.start();
}
}
class StopListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//needs to be implemented
timer.stop();
isClicked = false;
}
}
class DrawPanel extends JPanel {
public void paintComponent(Graphics g) {
int red = (int)(Math.random()*256);
int blue = (int)(Math.random()*256);
int green = (int)(Math.random()*256);
g.setColor(new Color(red, blue, green));
Random rand = new Random();
// following 4 lines make sure the rect stays within the frame
int ht = rand.nextInt(getHeight());
int wd = rand.nextInt(getWidth());
int x = rand.nextInt(getWidth()-wd);
int y = rand.nextInt(getHeight()-ht);
g.fillRect(x,y,wd,ht);
}
} // close inner class
}
Also I'm trying to get the Start button to do two things. One is to of course start the animation but when the Stop button is pressed and I press Start again, I want it to clean the screen so to speak and start the animation again a new. Any tips on that?
You do not call super.paintComponent(Graphics g) in overriden paintComponent(..) method which you should in order to honor the paint chain and thus the painting of other components.
This call should also be the first call within the method:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//do painting here
}
A probem might arise that drawings are not persistent. You must than have a way to store drawings and redraw every time. The most common is an ArrayList which will hold objects to be drawn (thus you cann add to the list remove etc), you would than iterate over the list and redraw each object in paintComponent. See my answer here for an example.
Also please remember to create and manipulate Swing components on Event Dispatch Thread :
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
//create UI and components here
}
});
Dont call setSize(..) on JFrame rather override getPreferredSize() of JPanel and return an appropriate height which fits all components, than call JFrame#pack() before setting JFrame visible (but after adding all components).
No need for getContentPane().add(..) as of Java 6+ add(..) defaults to contentPane
Do not re declare Random i.e Random r=new Random() each time paintComponent is called as this will make the distributions of the values less random rather initiate it once when class is created and call methods on the instance
Here is the fixed code (with above fixes implemented):
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;
public class TwoButtonsTest {
JFrame frame;
Timer timer;
boolean isClicked;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
TwoButtonsTest test = new TwoButtonsTest();
test.go();
}
});
}
final DrawPanel myDraw = new DrawPanel();
public void go() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton startButton = new JButton("Start");
startButton.addActionListener(new StartListener());
JButton stopButton = new JButton("Stop");
stopButton.addActionListener(new StopListener());
frame.add(myDraw, BorderLayout.CENTER);
frame.add(startButton, BorderLayout.NORTH);
frame.add(stopButton, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
timer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
myDraw.repaint();
}
});
}
class StartListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
//needs to be implemented
if (!isClicked) {
}
myDraw.clearRects();
isClicked = true;
timer.start();
}
}
class StopListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
//needs to be implemented
timer.stop();
isClicked = false;
}
}
class DrawPanel extends JPanel {
private ArrayList<MyRectangle> rects = new ArrayList<>();
private Random rand = new Random();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
addRect();
for (MyRectangle r : rects) {
g.setColor(r.getColor());
g.fillRect(r.x, r.y, r.width, r.height);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
public void clearRects() {
rects.clear();
}
public void addRect() {
// following 4 lines make sure the rect stays within the frame
int ht = rand.nextInt(getHeight());
int wd = rand.nextInt(getWidth());
int x = rand.nextInt(getWidth() - wd);
int y = rand.nextInt(getHeight() - ht);
int red = (int) (Math.random() * 256);
int blue = (int) (Math.random() * 256);
int green = (int) (Math.random() * 256);
rects.add(new MyRectangle(x, y, wd, ht, new Color(red, blue, green)));
}
} // close inner class
}
class MyRectangle extends Rectangle {
Color color;
public MyRectangle(int x, int y, int w, int h, Color c) {
super(x, y, w, h);
this.color = c;
}
public Color getColor() {
return color;
}
}
I wish I could offer a solution, but as of yet I haven't found one. I can tell you the root of the "problem" here lies in the way you are drawing the Center section of your BorderLayout. You are overriding the whole paintComponent() function for this program and having whatever it creates put into the Center of your BoarderLayout. In this case, each time you click a button, the program calls the repaint to draw the image of a clicked button, but since you have also added ANY of the drawn objects to the Center panel, it also is drawn there. Since this specific repaint doesn't specify a location, it goes in the upper left corner.
I fixed your button problem on my Windows XP computer by invoking SwingUtilities.
I formatted your Java code.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TwoButtonsTest implements Runnable {
JFrame frame;
Timer timer;
boolean isClicked;
public static void main(String[] args) {
SwingUtilities.invokeLater(new TwoButtonsTest());
}
#Override
public void run() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(500, 500);
JButton startButton = new JButton("Start");
startButton.addActionListener(new StartListener());
JButton stopButton = new JButton("Stop");
stopButton.addActionListener(new StopListener());
final DrawPanel myDraw = new DrawPanel();
frame.getContentPane().add(BorderLayout.CENTER, myDraw);
frame.getContentPane().add(BorderLayout.NORTH, startButton);
frame.getContentPane().add(BorderLayout.SOUTH, stopButton);
frame.setVisible(true);
timer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
myDraw.repaint();
}
});
}
class StartListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
// needs to be implemented
if (!isClicked) {
}
isClicked = true;
timer.start();
}
}
class StopListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
// needs to be implemented
timer.stop();
isClicked = false;
}
}
class DrawPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
int red = (int) (Math.random() * 256);
int blue = (int) (Math.random() * 256);
int green = (int) (Math.random() * 256);
g.setColor(new Color(red, blue, green));
Random rand = new Random();
// following 4 lines make sure the rect stays within the frame
int ht = rand.nextInt(getHeight());
int wd = rand.nextInt(getWidth());
int x = rand.nextInt(getWidth() - wd);
int y = rand.nextInt(getHeight() - ht);
g.fillRect(x, y, wd, ht);
}
} // close inner class
}
To clean the screen when you press the Start button, you're going to have to add some methods to your DrawPanel class.
Here's one way to do it.
class DrawPanel extends JPanel {
protected boolean eraseCanvas;
public void setEraseCanvas(boolean eraseCanvas) {
this.eraseCanvas = eraseCanvas;
}
#Override
public void paintComponent(Graphics g) {
if (eraseCanvas) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
} else {
int red = (int) (Math.random() * 256);
int blue = (int) (Math.random() * 256);
int green = (int) (Math.random() * 256);
g.setColor(new Color(red, blue, green));
Random rand = new Random();
// following 4 lines make sure the rect stays within the frame
int ht = rand.nextInt(getHeight());
int wd = rand.nextInt(getWidth());
int x = rand.nextInt(getWidth() - wd);
int y = rand.nextInt(getHeight() - ht);
g.fillRect(x, y, wd, ht);
}
}
} // close inner class