How to use swing timer? - java

I'm trying to write a program that draws a new square every second. This is my code for my JPannel class. I have two other classes but I beleive they are irrelivant to my question. One has the main method where it creates an object of the other class that contains the JFrame. I just cant figure out how to get the timer to work and how it works.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Drawing extends JPanel implements ActionListener{
Drawing(){
super();
setBackground(Color.WHITE);
startTimer();
}
public void startTimer() {
Timer timer = new Timer(1000, this);
timer.start();
}
public void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
super.paintComponent(g);
g.setColor(Color.BLUE);
for(int x = 0; x < 999; x ++) {
for(int y = 0; y < 999; y ++) {
g.drawRect(0,0, x, y);
}
}
}
public void actionPerformed(ActionEvent e) {
repaint();
}
}

Get rid of the for loops in your paintComponent method. Note that painting methods should not change object state. Instead the timer's actionPerformed method gets repeatedly called -- advance your counter or x/y variables there, and then call repaint().
e.g.,
private int x;
private int y;
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
// RECT_WIDTH etc are constants
g.drawRect(x, y, RECT_WIDTH, RECT_HEIGHT);
}
public void actionPerformed(ActionEvent e) {
int width = getWidth();
int height = getHeight();
// code here to change the object's state -- here to change
// location of the rectangle
x++;
y++;
// TODO: check that x and y are not beyond bounds and if so,
// then re-position them
repaint();
}

Related

How to draw objects from an ArrayList in Java

Okay, so I'm trying to make a program that draws a bunch of rectangles that move around the screen randomly. I have a Dot class where each dot holds its x and y values, and in my paint class I randomly change the x and y values and then repaint(). What I have right now doesn't load any thing other than a blank JFrame. I suspect that I'm drawing each dot wrong. The following is my code:
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Movement extends JFrame {
public ArrayList<Dot> dots = new ArrayList<Dot>();
Random rn = new Random();
DrawPanel drawPanel = new DrawPanel();
public Movement() {
for(int i = 0; i < 100; i ++) {
Dot dot = new Dot(5, 5);
dots.add(dot);
}
ActionListener listener = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < dots.size();i++) {
dots.get(i).setX(dots.get(i).getX() + rn.nextInt(20)-10);
dots.get(i).setY(dots.get(i).getY() + rn.nextInt(20)-10);
}
drawPanel.repaint();
}
};
Timer timer = new Timer(100, listener);
timer.start();
add(drawPanel);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
setBounds(100, 100, 500, 500);
}
private class DrawPanel extends JPanel {
protected void paintComponent(Graphics g) {
for(int i = 0; i < dots.size(); i ++) {
g.fillRect(dots.get(i).getX(), dots.get(i).getY(), 5, 5);;
super.paintComponent(g);
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new Movement();
}
});
}
}
Dot class:
public class Dot {
private int x, y;
public Dot(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
Any and all help is appreciated.
If you call super.paintComponent(g); after you paint your own components you have wiped out your own painting. So,
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
for(int i = 0; i < dots.size(); i ++) {
g2d.fillRect(dots.get(i).x, dots.get(i).y, 5, 5);
}
}
Also,
// don't repeat type in constructor
// use built in point instead of custom class
public ArrayList<Point> dots = new ArrayList<>();
and
ActionListener listener = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < dots.size();i++) {
dots.get(i).x = dots.get(i).x + rn.nextInt(20)-10;
dots.get(i).y = dots.get(i).y + rn.nextInt(20)-10;
}
drawPanel.repaint();
}
};
and it probably doesn't make any difference, but
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Movement mf = new Movement();
}
});
You appear to be calling super.paintComponent after you paint dots. Not only that, you appear to be calling it inside the loop, calling it every time you paint a dot, after you paint it.
So you keep painting a dot, letting super.paintComponent paint a clear panel, then painting another dot, then undoing it again, paint another, undo... and so on.
Call super.paintComponent one time, and do it before you do your custom painting.

Draw a car when mouse clicked JAVA

I have 2D-Graphic class to draw the car and I really need help to make the car appear when I click on the mouse. This is what I have so far.
public class CarMove extends JComponent
{
private int lastX = 0;
private int x = 1;
//create the car from draw class
Car car1 = new Car(x,320);
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
int carSpeed = 1;
int w = getWidth();
x = lastX + carSpeed;
if (x == w - 60)
{
x = x - carSpeed;
}
FrameMouseListener listener = new FrameMouseListener();
super.addMouseListener(listener);
lastX = x;
}
public class FrameMouseListener implements MouseListener
{
#Override
public void mouseClicked(MouseEvent e)
{
Graphics2D g2 = (Graphics2D) g;
car1.draw(g2);
}
}
}
I add the mouselistener to the frame when I click on the frame the car will appear but I cant make it work in mouseClicked event to draw the car
Your code should look more like:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
public class CarMove extends JComponent
{
private volatile boolean drawCar = false;
private int lastX = 0;
private int x = 1;
// create the car from draw class
Car car1 = new Car(x, 320);
{
FrameMouseListener listener = new FrameMouseListener();
super.addMouseListener(listener);
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
int carSpeed = 1;
int w = getWidth();
x = lastX + carSpeed;
if (x == w - 60)
{
x = x - carSpeed;
}
if (drawCar)
{
car1.draw(g2);
}
lastX = x;
}
public class FrameMouseListener extends MouseAdapter
{
#Override
public void mouseClicked(MouseEvent e)
{
drawCar = true;
repaint();
}
}
}
First of all, you need to add your listener only once at startup.
Second, all drawing operations must be performed in painting mathods, you are not allowed to store the graphics object and draw to it from any place in your code. There is a special drawing thread, that may only touch this object. SO the rexcipe is, set up what should be painted (by some variables), prepare your painting method to properly use those variables, then call repaint() whern you would like to refresh the view.

Why isn't my JFrame object animating as I would expect?

I'm trying to animate a circle as per a Head First Java exercise. The original exercise calls for using an internal "MyDrawPanel" class within the "AnimatedBall" class, but I'm trying to do an external MyDrawPanel class. I have no errors, but there also is no animation. I'm assuming it has something to do with me passing values to the MyDrawPanel constructor that don't update when I run my for loop, but I'd like to know why this is.
AnimatedBall2.java:
import javax.swing.JFrame;
public class AnimatedBall2 {
JFrame frame = new JFrame();
int height = 300;
int width = 300;
int x = 70;
int y = 70;
public static void main(String[] args){
AnimatedBall2 gui = new AnimatedBall2();
gui.go();
}
public void go(){
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyDrawPanel panel = new MyDrawPanel(x, y, height, width);
frame.getContentPane().add(panel);
frame.setSize(height,width);
frame.setVisible(true);
for(int i = 0; i < 100; i++){
x++;
y++;
panel.repaint();
try{
Thread.sleep(50);
} catch(Exception ex){}
}
}
}
MyDrawPanel.java:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class MyDrawPanel extends JPanel{
int x;
int y;
int height;
int width;
public MyDrawPanel(int x1, int y1, int z1, int z2){
x = x1;
y = y1;
height = z1;
width = z2;
}
public void paintComponent(Graphics g){
g.setColor(Color.white);
g.fillRect(0, 0, height, width);
g.setColor(Color.orange);
g.fillOval(x, y, 100, 100);
}
}
Swing is a single threaded framework, that is, there is a single thread which is responsible for processing all events within the system and scheduling painting.
Anything that blocks this thread, will prevent it from processing new events or paint requests.
This is a very bad idea...
public void go(){
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyDrawPanel panel = new MyDrawPanel(x, y, height, width);
frame.getContentPane().add(panel);
frame.setSize(height,width);
frame.setVisible(true);
for(int i = 0; i < 100; i++){
x++;
y++;
panel.repaint();
try{
Thread.sleep(50);
} catch(Exception ex){}
}
}
Add could potentially lock up your application...
Take a look at Concurrency in Swing for more details. You should also have a look at Initial Threads
In this case, I would recommended using a javax.swing.Timer, see How to use Swing Timers for more details.
The main problem is, you've decarled x and y in your AnimatedBall2 class...
public class AnimatedBall2 {
//...
int x = 70;
int y = 70
And in your MyDrawPanel class...
public class MyDrawPanel extends JPanel{
int x;
int y;
But your loop is updating the values in the AnimatedBall2 class, meaning that the values in MyDrawPanel never change
You will need to add an "update" method of some kind which can tell the MyDrawPanel that it should update it's x/y values accordingly...
Which brings us to...Swing is not thread safe, all modifications or interactions with the UI should be done within the context of the Event Dispatching Thread...This is why I'd recommend using a Swing Timer, as it triggers it's notifications within the context of the EDT.
When performing custom painting, you should call super.paintComponent before performing any custom painting of your own. You should also not relay on "magic" numbers
g.fillRect(0, 0, height, width);
The size of the component can be changed by the layout manager depending on it's needs, instead, you should be using getWidth and getHeight which will tell you exactly what the size of the current component is.
For example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class AnimatedBall2 {
public static void main(String[] args) {
new AnimatedBall2();
}
public AnimatedBall2() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
final MyDrawPanel panel = new MyDrawPanel();
JFrame frame = new JFrame("I'm a banana");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
Timer timer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
panel.update();
}
});
timer.start();
}
});
}
public class MyDrawPanel extends JPanel {
int x;
int y;
int deltaX = 0;
int deltaY = 1;
public MyDrawPanel() {
x = 150;
y = 150;
setBackground(Color.WHITE);
}
public void update() {
x += deltaX;
y += deltaY;
if (x < 0) {
x = 0;
deltaX *= 1;
} else if (x > getWidth()) {
x = getWidth();
deltaX *= 1;
}
if (y < 0) {
y = 0;
deltaY *= -1;
} else if (y > getHeight()) {
y = getHeight();
deltaY *= -1;
}
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.orange);
g.fillOval(x - 50, y - 50, 100, 100);
}
}
}
for(int i = 0; i < 100; i++){
x++;
y++;
panel.repaint();
try{
Thread.sleep(50);
} catch(Exception ex){}
}
x and y are int types, not pointers. so when you do x++ and y++. it is actually not updating the x, y values which you passed in in the constructor

animation using repaint()

I am trying to create a simple animation which draws random rectangles when a button is pressed. So far I managed to create rectangle on the press of a button. I want to further develop the code so that when I press the button, more than multiple random rectangles are created. I tried to create a for loop which asks the inner class to repaint itself but it still didn't work. can anyone help me please.
public class TwoButtonsRandomRec {
JFrame frame;
private int width = 500;
private int height = 500;
private DrawPanel dp;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public static void main (String[] args)
{
TwoButtonsRandomRec test = new TwoButtonsRandomRec();
test.go();
}
public void go()
{
dp = new DrawPanel();
JButton start = new JButton("Start");
start.addActionListener(new startListener());
JButton stop = new JButton("Stop");
stop.addActionListener(new stopListener());
frame = new JFrame();
frame.setSize(getWidth(), getHeight());
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(BorderLayout.NORTH, start);
frame.getContentPane().add(BorderLayout.SOUTH, stop);
}
class startListener implements ActionListener{
public void actionPerformed(ActionEvent event){
frame.getContentPane().add(BorderLayout.CENTER, dp);
frame.repaint();
frame.getRootPane().revalidate();
for(int i=0; i<10; i++){
dp.repaint();
}
}
}
class stopListener implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("stop");
}
}
class DrawPanel extends JPanel{
public void paintComponent(Graphics g){
int w = 5+(int)(Math.random() * width-5);
int h = 5+(int)(Math.random() * height-5);
int maxX = width-w; // diffX & diffY are used to ensure that rectangle is
int maxY = width-h; // draw completely inside the window
int x = (int)(Math.random() * maxX);
int y = (int)(Math.random() * maxY);
Color color = new Color((int) (Math.random()*256), // random red
(int) (Math.random()*256), // random green
(int) (Math.random()*256));// random blue
g.setColor(color);
g.fillRect(x,y,w,h);
}
}
}
repaint() simply tells Swing "when you'll have time, please repaint this area". So if you add rectangles in a loop and call repaint at each iteration, all the rectangles will only appear after the loop has finished, and the action event has been handled.
To have an animation, you need to loop in a separate thread. The easiest way to do that is to use a Swing Timer. When the Start button is started, start a timer which adds a random rectangle and calls repaint() every X milliseconds. When the Stop button is pressed, stop the timer.
What you should do is to put the loop inside paintComponent method and not call repaint in the loop.
So your paintComponent method should look like this:
public void paintComponent(Graphics g){
for (int i = 0; i < 10; i++) {
int w = 5+(int)(Math.random() * width-5);
int h = 5+(int)(Math.random() * height-5);
int maxX = width-w; // diffX & diffY are used to ensure that rectangle is
int maxY = width-h; // draw completely inside the window
int x = (int)(Math.random() * maxX);
int y = (int)(Math.random() * maxY);
Color color = new Color((int) (Math.random()*256), // random red
(int) (Math.random()*256), // random green
(int) (Math.random()*256));// random blue
g.setColor(color);
g.fillRect(x,y,w,h);
}
}
And your action performed should look like this:
public void actionPerformed(ActionEvent event){
frame.getContentPane().add(BorderLayout.CENTER, dp);
frame.repaint();
frame.getRootPane().revalidate();
dp.repaint();
}
Well,here I have done a short EG for you.It displays random rectangles, random times on random screen location.
(You can set your own value of randomization, and the screen location max bound,as per your requirements.)
And also note
int i=(int)(Math.random()*10);
int j=(int)(Math.random()*10);
for(;i<j;i++)
Where at times i may be > than j.So,loop may not work on one or two cliks.Change as per your need.
Here is the working code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SimpleStamper extends JApplet {
public void init() {
Display display = new Display();
setContentPane(display);
}
class Display extends JPanel implements MouseListener {
Display() {
setBackground(Color.black);
addMouseListener(this);
}
public void mousePressed(MouseEvent evt) {
if ( evt.isShiftDown() ) {
repaint();
return;
}
int x = evt.getX();
int y = evt.getY();
Graphics g = getGraphics();
//***MODIFY THE FOLLOWING LINES****//
int i=(int)(Math.random()*10);
int j=(int)(Math.random()*10);
for(;i<j;i++)
{ g.setColor(Color.red);
x=(int)(Math.random()*100);
y=(int)(Math.random()*100);
g.fillRect( x , y , 60, 30 );
g.setColor(Color.black);
g.drawRect(x , y , 60, 30 );}
g.dispose();
}
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
public void mouseClicked(MouseEvent evt) { }
public void mouseReleased(MouseEvent evt) { }
}
}

Java:: Use a mouse listener object in a different class

Hello people,
I am trying to write an animated character for a multitouch screen. I want my object to have 5 eyes and each of whose pupil to be dragged and dropped differently, within the eye of course.
I have tried to do it all in a single class and the problem seems to be assigning mouse handlers to each of the five pupils! In other words, if I move one pupil, all the pupils are moving.
Then, I resorted to using a bespoke class just to the pupil. When I use it by itself, the pupil is draggable. However, when I use it as an object in the eyes class, the pupil is static! No clicks registered, no mouse activity tracked.
I have looked at tutorials, other related issues with mouse handlers and could not make any progress. I changed the code a dozen times from various tutorials and suggestions before finally posting here. Any ideas where I am missing the cue? Any pointers would be greatly appreciated.
Thanks in advance.
PS: I know I am yet to put the constraints on the pupil movement within the eye.
Code for main eyes class:
package rollEyes;
import java.awt.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class FiveEyes extends JPanel
{
private static final long serialVersionUID = 1L;
private static final int SIZE = 512;
private int a = SIZE / 2;
private int b = a;
private int r = 4 * SIZE / 5;
private int n;
int circleSize=30;
Pupil dc = new Pupil(1);
public FiveEyes(int n)
{
super(true);
this.setPreferredSize(new Dimension(SIZE, SIZE));
this.n = n;
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.black);
a = getWidth() / 2;
b = getHeight() / 2;
int m = Math.min(a, b);
r = 4 * m / 5;
int r2 = Math.abs(m - r) / 2;
int numOfEyes = 5;
for (int i = 0; i < numOfEyes ; i++)
{
Graphics2D g2d2 = (Graphics2D) g;
double t = 2 * Math.PI * i / n;
int x = (int) Math.round(a + r * Math.cos(t));
int y = (int) Math.round(b + r * Math.sin(t));
drawEyeSockets(g2d2, x,y, 2*r2,2*r2);
}
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
create();
}
});
}
public void drawEyeSockets(final Graphics2D g2, int x, int y, int w, int h)
{
g2.drawOval(x,y,w,h);
dc.drawCircle(g2, x+12, y+12);
}
private static void create()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
FiveEyes fivey = new FiveEyes(5);
f.add(fivey);
f.pack();
f.setVisible(true);
}
}
Code for the Pupil class:
package rollEyes;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Pupil extends JPanel
{
private static final long serialVersionUID = 1L;
int radius=50;
int x_after = 50;
int y_after = 50;
MouseHandler mh ;
private static int n =1;
public Pupil(int n)
{
super(true);
}
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
drawCircle(g2d,x_after,y_after);
}
public void drawCircle(final Graphics2D g2d, int x, int y)
{
g2d.setColor(Color.BLUE);
g2d.fillOval(x, y, radius/2, radius/2);
mh = new MouseHandler();
this.addMouseListener(mh);
this.addMouseMotionListener(mh);
}
private static void create()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Pupil dc = new Pupil(n);
f.add(dc);
f.pack();
f.setVisible(true);
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
create();
}
});
}
private class MouseHandler extends MouseAdapter
{
boolean circleClicked=false;
public void mouseReleased(MouseEvent e)
{
circleClicked = false;
}
public void mousePressed(MouseEvent me)
{
circleClicked = true;
}
public void mouseDragged(MouseEvent me)
{
if (circleClicked)
{
x_after = me.getX();
y_after = me.getY();
repaint();
}
}
}
}
You have Pupil extend JPanel, but really shouldn't be doing that. Instead, use the concepts that you've learned in your current Pupil class -- how to draw a movable circle, and extend it in the larger FiveEyes class, only this time create a List<Pupil> and draw them. My suggestions:
Make Pupil not extend JPanel. Instead give it the machinery to draw circles in certain locations and to have that location changed.
Also you will need to give it a way to recognize if its circle has been clicked by giving it a contains(Point p) method. One way to do this is to use a Shape object, or you can roll your own method.
Give FiveEyes a List<Pupil> that in reality is an ArrayList<Pupil> and fill it with Pupil objects.
In FiveEyes paintComponent(...) method, iterate through this List telling each Pupil to draw itself.
In your FiveEyes MouseAdapter's mousePressed(...) method, iterate through your Pupil List to see if a Pupil has been clicked on. If so, move it.
Alternatively, you could create a Pupil BufferedImage, put it into an ImageIcon, and put that into a JLabel, and then allow your FiveEyes class's MouseAdapter to drag the labels around.
First of all, this code
mh = new MouseHandler();
this.addMouseListener(mh);
this.addMouseMotionListener(mh);
must only be called once. You're adding millions of mouse handlers to the component!
And you need five instances of Pupil, one for each eye. Right now, you have only one, so of course the rendering will only yield one result.
Lastly, you must make sure that only one eye is "active" at a time (i.e. not all of them should receive mouse events or not all of them should process them).

Categories