Make text appear briefly in a JPanel - java

I am trying to make text appear briefly before it disappears. It would be along the lines of
1) Set color to black
2) wait x amount of seconds
3) set color to background color
The method I call is repaint(), which then calls paintComponent(Graphics painter). repaint() is called only if I press the space-bar.
I thought of trying repaint();Thread.sleep(1000);repaint(); (I do catch the Interrupt exception, just not shown), but it only calls paintComponent once per space-bar .
Is there an easy way to do this or is this something that is a bit challenging?

I would use a Swing Timer to schedule the repainting of the text.
Also, I would just use a JLabel to display the text. Then you just use the setText(...) method to change the text as you wish and the component will repaint itself.

You need to override the paint method in your panel and make it implement Runnable so that you can turn off the text after a few seconds. Here is some sample code:
import java.awt.Color;
import java.awt.Graphics;
import java.io.Exception;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
class MyPanel extends JPanel implements Runnable{
private final static String TEXT = "HELLO WORLD";
private boolean on = true;
#Override
public void paint(Graphics g) {
super.paint(g);
if(on){
g.drawString(TEXT, 20, 20);
}
}
#Override
public void run() {
for(int i = 0 ; i< 2 ; i++){
paintImmediately(0, 0, getWidth(), getHeight());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
on = false;
}
}
}
public class App {
public static void main(String[] args) throws Exception {
JFrame f = new JFrame();
final MyPanel p = new MyPanel();
f.add(p);
f.setSize(100,100);
f.setVisible(true);
Thread t = new Thread(p);
t.start();
}
}

Related

Timer in for loop java swing [duplicate]

Ok, so the program's purpose is to just draw and oval and move it across the screen. The code compiles on Eclipse without an error, but when run, no oval is drawn or moved across the screen. I have been researching, and it seems that threads have to do a lot with this, but do I need one for this simple program? I am obviously new to GUI programming with Swing so I would appreciate an explanation or link to one for any additions to the program regarding threads or such related concepts.
public class Game extends JPanel
{
int x =0;
int y =0;
private void moveBall()
{
x+=1;
y+=1;
}
public void paint (Graphics g)
{
super.paint(g);
g.fillOval(x, y, 30, 30);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Animation");
Game game = new Game();
frame.add(game);
frame.setVisible(true);
frame.setSize(300,400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
while (true)
{
game.moveBall();
game.repaint();
}
}
}
The likely problem is, that thread is running too fast for the UI, the UI is been shown well after the "ball" has left the visible area.
You need to do a couple of things...
First, you need to make sure that the updates are scheduled properly within the Event Dispatching Thread and secondly, that there is a short delay between updates. For example, 25fps is about a 40 millisecond delay between updates, 60fps is about 16 milliseconds
There are a number of ways to achieve this, depending what it is you hope to achieve, for example, you could simply use Thread.sleep to cause the thread to pause for a small amount of time between updates. The problem with this is Swing is not thread safe and all updates to the UI should be made within the context of the Event Dispatching Thread.
While you program is only simply, it's possible that a paint cycle could run while you updating it's state, resulting in a dirty update.
Another solution might be to use a Swing Timer which will allow you schedule updates at a regular interval which are triggered within the context of the Event Dispatching Thread, making it safer to use.
Have a look at Concurrency in Swing and How to use Swing Timers for more details.
As an example...
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 javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class BallAnimation {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
new BallAnimation();
}
public BallAnimation() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int x = 0;
private int y = 0;
public TestPane() {
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
moveBall();
repaint();
}
});
timer.start();
}
protected void moveBall() {
x++;
y++;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.fillOval(x, y, 30, 30);
g2d.dispose();
}
}
}
As a side note, unless you really have reason to do so, you should avoid overriding paint and instead use paintComponent
try your loop with sleep as simplest way to fit your code. main is actually a thread. and JFrame creates its own thread.
while (true)
{
game.moveBall();
game.repaint();
try { Thread.sleep(50); } catch (Exception e){}
}
and I just realized, you dont paint your whole screen with a default color.
change your paint method to this
public void paint (Graphics g)
{
super.paint(g);
g.setColor(Color.white); //default color
g.fillRect(0, 0, getWidth(), getHeight()); // fill whole canvas
g.setColor(Color.black); //change color
g.fillOval(x, y, 30, 30); // draw oval
}

Simple Java Animation using JFrame and JPanel

Ok, so the program's purpose is to just draw and oval and move it across the screen. The code compiles on Eclipse without an error, but when run, no oval is drawn or moved across the screen. I have been researching, and it seems that threads have to do a lot with this, but do I need one for this simple program? I am obviously new to GUI programming with Swing so I would appreciate an explanation or link to one for any additions to the program regarding threads or such related concepts.
public class Game extends JPanel
{
int x =0;
int y =0;
private void moveBall()
{
x+=1;
y+=1;
}
public void paint (Graphics g)
{
super.paint(g);
g.fillOval(x, y, 30, 30);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Animation");
Game game = new Game();
frame.add(game);
frame.setVisible(true);
frame.setSize(300,400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
while (true)
{
game.moveBall();
game.repaint();
}
}
}
The likely problem is, that thread is running too fast for the UI, the UI is been shown well after the "ball" has left the visible area.
You need to do a couple of things...
First, you need to make sure that the updates are scheduled properly within the Event Dispatching Thread and secondly, that there is a short delay between updates. For example, 25fps is about a 40 millisecond delay between updates, 60fps is about 16 milliseconds
There are a number of ways to achieve this, depending what it is you hope to achieve, for example, you could simply use Thread.sleep to cause the thread to pause for a small amount of time between updates. The problem with this is Swing is not thread safe and all updates to the UI should be made within the context of the Event Dispatching Thread.
While you program is only simply, it's possible that a paint cycle could run while you updating it's state, resulting in a dirty update.
Another solution might be to use a Swing Timer which will allow you schedule updates at a regular interval which are triggered within the context of the Event Dispatching Thread, making it safer to use.
Have a look at Concurrency in Swing and How to use Swing Timers for more details.
As an example...
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 javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class BallAnimation {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
new BallAnimation();
}
public BallAnimation() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int x = 0;
private int y = 0;
public TestPane() {
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
moveBall();
repaint();
}
});
timer.start();
}
protected void moveBall() {
x++;
y++;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.fillOval(x, y, 30, 30);
g2d.dispose();
}
}
}
As a side note, unless you really have reason to do so, you should avoid overriding paint and instead use paintComponent
try your loop with sleep as simplest way to fit your code. main is actually a thread. and JFrame creates its own thread.
while (true)
{
game.moveBall();
game.repaint();
try { Thread.sleep(50); } catch (Exception e){}
}
and I just realized, you dont paint your whole screen with a default color.
change your paint method to this
public void paint (Graphics g)
{
super.paint(g);
g.setColor(Color.white); //default color
g.fillRect(0, 0, getWidth(), getHeight()); // fill whole canvas
g.setColor(Color.black); //change color
g.fillOval(x, y, 30, 30); // draw oval
}

dynamically change color of custom graphic

Problem: graphics aren't repainted until after methods are run.
When button clicked two methods are called. Inside each method is code that is supposed to change the color of the graphic associated with this method (in the UI); when the method starts the graphic is changed from black to green; when the method finishes the color is changed from green to red. Then the next method is called and its graphic should turn green (method is running) and when the method finishes its graphic should be filled with red (method finished).
I created a simple status circle graphic (a 30 px circle with fill color) with 3 color states: black for ready; green for running; red for finished.
I believe the problem has to do with repaint() being on a separate thread and scheduled to run when able? I tried putting the code that updates the graphic inside its own thread-runnable and then using thread.join() to make sure the code had finished running but that didn't work.
EDIT
Edit: removing the code I had used for demonstration and replacing with a single, runnable code sample as per comments. What you'll see if you run the code is after you click the button the graphics don't update when each method is started and stopped, it waits until both methods have run and then repaints the graphics.
package graphicsUpdateDemo;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.beans.Transient;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* Application entry
*/
public class App{
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new MainFrame();
}
});
}
}
/**
* Main frame
*/
class MainFrame extends JFrame implements SomeListener{
private AddedPanel addedPanel;
// Constructor
public MainFrame(){
// Set frame properties
setSize(500, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
setLayout(new FlowLayout(FlowLayout.CENTER, 5, 20));
// Create AddedPanel.
addedPanel = new AddedPanel();
add(addedPanel);
// Set AddedPanel listener to this JFrame.
addedPanel.setSomeListener(this);
}
// AddedPanel listener method
#Override
public void doStuff() {
// run simulated sort methods
sort1();
sort2();
}
// Simulated sort
// .......graphic should turn green as soon as method starts
// .......graphic should turn red as soon as method finishes.
private void sort1() {
// repaint graphic to show method is starting
addedPanel.statusOne.setStatus(SortStatus.running);
// EDIT: Make panel repaint itself.
addedPanel.paintImmediately(0, 0, getWidth(), getHeight());
// Simulate work being done.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// repaint graphic to show methid is finished
addedPanel.statusOne.setStatus(SortStatus.finished);
// EDIT: Make panel repaint itself.
addedPanel.paintImmediately(0, 0, getWidth(), getHeight());
}
// Simulated sort
// .......graphic should turn green as soon as method starts
// .......graphic should turn red as soon as method finishes.
private void sort2() {
// repaint graphic to show method is starting (green)
addedPanel.statusTwo.setStatus(SortStatus.running);
// EDIT: Make panel repaint itself.
addedPanel.paintImmediately(0, 0, getWidth(), getHeight());
// Simulate work being done.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// repaint graphic to show method is finished.
addedPanel.statusTwo.setStatus(SortStatus.finished);
// EDIT: Make panel repaint itself.
addedPanel.paintImmediately(0, 0, getWidth(), getHeight());
}
}
/**
* Panel to add to MainFrame
*/
class AddedPanel extends JPanel{
// Button listener
SomeListener listener;
// Button
private JButton aButton = new JButton("Click Me");
// Create Status Circles for showing method state.
public StatusCircles statusOne = new StatusCircles();
public StatusCircles statusTwo = new StatusCircles();
// Constructor.
public AddedPanel(){
setLayout(new BorderLayout(0, 15));
// Add button to panel.
add(aButton, BorderLayout.NORTH);
// Make panel for holding graphics and labels.
JPanel resultsPanel = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(5, 5, 5, 5);
resultsPanel.add(statusOne, c);
c.gridx = 1;
resultsPanel.add(new JLabel("Method A"), c);
c.gridx = 0; c.gridy = 1;
resultsPanel.add(statusTwo, c);
c.gridx = 1;
resultsPanel.add(new JLabel("Method B"), c);
add(resultsPanel, BorderLayout.CENTER);
aButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
if(listener != null){
listener.doStuff();
}
}
});
}
public void setSomeListener(SomeListener listener){
this.listener = listener;
}
}
/**
* Graphic for showing user state of method:
* black for ready
* green for running
* red for finished
*/
class StatusCircles extends JPanel{
private SortStatus sortStatus;
private Ellipse2D.Double statusCircle = new Ellipse2D.Double(2, 2, 25, 25);
// Constructor
public StatusCircles(){
sortStatus = SortStatus.ready;
}
#Override
protected void paintComponent(Graphics g) {
// Cast Graphics to Graphics2D
Graphics2D g2 = (Graphics2D)g;
// Turn on anti aliasing
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Set background
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, getWidth(), getHeight());
// Fill status circle with color based on status field
switch(sortStatus){
case ready:
g2.setColor(Color.BLACK);
g2.fill(statusCircle);
break;
case running:
g2.setColor(Color.GREEN);
g2.fill(statusCircle);
break;
case finished:
g2.setColor(Color.RED);
g2.fill(statusCircle);
break;
}
}
#Override
#Transient
public Dimension getPreferredSize() {
return new Dimension(30, 30);
}
// Set state method is in.
public void setStatus(SortStatus status) {
this.sortStatus = status;
repaint();
}
}
/**
* Interface
*/
interface SomeListener{
public void doStuff();
}
/**
* Enum for depicting status of graphic.
*/
enum SortStatus {
ready,
running,
finished
}
EDIT
"The repaint method lodges a request to update the viewing area and returns immediately. Its effect is asynchronous, meaning that it is up to the JVM to execute the paintComponent method on a separate thread." - Introduction to Java programming by Liang.
I think the problem is either A) in my ignorance my program design is doing something no sane programmer would do, and/or B) I don't know how to make the program change graphics colors then after that happens, then continue doing work on whatever thread the work is being done on (EDT, main thread?).
I did run into an answer that suggested never to slow down the "main thread" to wait for things to be drawn; and to instead make icons for each status circle and then swap icons - which I guess would force an immediate redraw of whatever is holding the icon? Wouldn't this, though, suggest there is a way to force an immediate repaint?
Thought experiment: you have a loop that runs 100 times, each iteration takes a second. You want to show the user each iteration by changing the color of a circle to one of a hundred different colors. Would you have to make 100 different icons for this? Or, and what I want to do, is change the fill color of the circle each iteration. ...but how to force the repainting of the circle with each iteration?
EDIT
Don't know if it is the "right" solution but the program now functions as I want it to. I placed these addedPanel.paintImmediately(0, 0, getWidth(), getHeight()); directly after the method calls asking for the graphic color to change. I updated the working example code above, the edits are depicted by "//EDIT: Make panel repaint itself".
EDIT
Now I have more confidence that I am on the right track. I believe I have implemented the things recommended to me. Understanding SwingWorker came really fast once I understood it was basically like Android's asynTask() (that's where I learned it first, that's why I say it like that). And the simulated work via sleeping is occurring in its own thread, off the EDT, so's okay now (?) ((not that I need my program to take a nap)) Here, now, is the full working code:
package graphicsUpdateDemo;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.beans.Transient;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
/**
* Application entry
*/
public class App{
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new MainFrame();
}
});
}
}
/**
* Main frame
*/
class MainFrame extends JFrame implements SomeListener{
private AddedPanel addedPanel;
// Constructor
public MainFrame(){
// Set frame properties
setSize(500, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout(FlowLayout.CENTER, 5, 20));
// Create AddedPanel.
addedPanel = new AddedPanel();
add(addedPanel);
// Set AddedPanel listener to this JFrame.
addedPanel.setSomeListener(this);
// Call setVisible last
setVisible(true);
}
// AddedPanel listener method
#Override
public void doStuff() {
// Call sort1(), when that finishes have it call sort2().
sort1();
}
// Simulated sort
// .......graphic should turn green as soon as method starts
// .......graphic should turn red as soon as method finishes.
private void sort1() {
// repaint graphic to show method is starting
addedPanel.statusOne.setStatus(SortStatus.running);
// Run sort in its own thread.
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){
#Override
protected Void doInBackground() throws Exception {
// Simulate work being done.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void done() {
// repaint graphic to show methid is finished
addedPanel.statusOne.setStatus(SortStatus.finished);
// Call sort2
sort2();
}
};
worker.execute();
}
// Simulated sort
// .......graphic should turn green as soon as method starts
// .......graphic should turn red as soon as method finishes.
private void sort2() {
// repaint graphic to show method is starting (green)
addedPanel.statusTwo.setStatus(SortStatus.running);
// Run sort in its own thread
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>(){
#Override
protected Void doInBackground() throws Exception {
// Simulate work being done.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void done() {
// repaint graphic to show method is finished.
addedPanel.statusTwo.setStatus(SortStatus.finished);
}
};
worker.execute();
}
}
/**
* Panel to add to MainFrame
*/
class AddedPanel extends JPanel{
// Button listener
SomeListener listener;
// Button
private JButton aButton = new JButton("Click Me");
// Create Status Circles for showing method state.
public StatusCircles statusOne = new StatusCircles();
public StatusCircles statusTwo = new StatusCircles();
// Constructor.
public AddedPanel(){
setLayout(new BorderLayout(0, 15));
// Add button to panel.
add(aButton, BorderLayout.NORTH);
// Make panel for holding graphics and labels.
JPanel resultsPanel = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(5, 5, 5, 5);
resultsPanel.add(statusOne, c);
c.gridx = 1;
resultsPanel.add(new JLabel("Method A"), c);
c.gridx = 0; c.gridy = 1;
resultsPanel.add(statusTwo, c);
c.gridx = 1;
resultsPanel.add(new JLabel("Method B"), c);
add(resultsPanel, BorderLayout.CENTER);
aButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
if(listener != null){
listener.doStuff();
}
}
});
}
public void setSomeListener(SomeListener listener){
this.listener = listener;
}
}
/**
* Graphic for showing user state of method:
* black for ready
* green for running
* red for finished
*/
class StatusCircles extends JPanel{
private SortStatus sortStatus;
private Ellipse2D.Double statusCircle = new Ellipse2D.Double(2, 2, 25, 25);
// Constructor
public StatusCircles(){
sortStatus = SortStatus.ready;
}
#Override
protected void paintComponent(Graphics g) {
// Cast Graphics to Graphics2D
Graphics2D g2 = (Graphics2D)g;
// Turn on anti aliasing
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Set background
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, getWidth(), getHeight());
// Fill status circle with color based on status field
switch(sortStatus){
case ready:
g2.setColor(Color.BLACK);
g2.fill(statusCircle);
break;
case running:
g2.setColor(Color.GREEN);
g2.fill(statusCircle);
break;
case finished:
g2.setColor(Color.RED);
g2.fill(statusCircle);
break;
}
}
#Override
#Transient
public Dimension getPreferredSize() {
return new Dimension(30, 30);
}
// Set state method is in.
public void setStatus(SortStatus status) {
this.sortStatus = status;
repaint();
}
}
/**
* Interface
*/
interface SomeListener{
public void doStuff();
}
/**
* Enum for depicting status of graphic.
*/
enum SortStatus {
ready,
running,
finished
}
Using the approach shown here, let each sort update its display from a separate SwingWorker, while a Supervisor worker monitors a CountDownLatch to determine when all sorts are done.
Addendum: I have never seen an Applet before, nor a SwingWorker…I don't understand why I would need to determine when all the sorts are done…I have edited the question.
The example is also a hybrid.
SwingWorker helps avoid blocking the EDT.
Determining done tells you when to (re-)enable the start button.
Try adding a PropertyChangeListener, shown here, to a ColorIcon, seen here, in your label. Each time you setProgress() in the worker, you'll see a corresponding PropertyChangeEvent.

Animated drawing of a successively complex image in Java

I am trying to draw an image on a JPanel that requires thousands of calculations, and I want to animate the progression of the drawing. I.e., instead of doing all 100K iterations of drawing in one go, and then repainting the JPanel, I want to repaint after each iteration, then pause for a fraction of a second, so the user sees the image gradually appearing. However, each refresh of the JPanel erases previous drawings, so my method doesn't work. How can I do this without replicating all (1..N-1) calculations on the Nth iteration?
Consider this example: I want "snow" to gradually appear on the screen. However, this code will only show the 100,000th "snowflake" as all previous ones get erased each time repaint() is called.
import javax.swing.*;
import java.awt.*;
import java.util.Random;
class spanel extends JPanel{
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawLine(snow.x, snow.y, snow.x, snow.y);
}
}
class snow extends Thread {
static int x,y;
Random r = new Random();
public void run(){
JFrame sboard = new JFrame();
sboard.setSize(600,600);
sboard.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
spanel mypanel = new spanel();
sboard.add(mypanel);
sboard.setVisible(true);
for (int i=0;i<100000;i++){
x=r.nextInt(600);
y=r.nextInt(600);
sboard.repaint();
try {
snow.sleep((long)10);
} catch (InterruptedException e) {};
}
}
}
public class SnowAnim {
public static void main(String[] args) {
(new snow()).start();
}
}
Custom Painting Approaches shows how to draw on a BufferedImage. There are also plenty of other examples in the forum.
Also, when doing animation you should use a Swing Timer to schedule the animation.
How can I do this without replicating all (1..N-1) calculations on the Nth iteration?
Add them to a BufferedImage as seen in this example.
You should probably do your painting on a buffer, then draw the current state of the buffer in paintComponent();
You could also skip the call to super.paintComponent(g);, but then you would have to worry about other elements getting visually "stuck" in your panel.
Thanks all! I figured it out. BufferedImage solves it. For the record, here is my updated code. It also implements camickr's suggestion to use a Swing timer instead of a thread to schedule the animation. I also made some aesthetic changes to make the output look more like snow :-) Thanks again!
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.util.Random;
import java.awt.event.*;
class spanel extends JPanel{
int x,y,rad,i;
static Random r = new Random();
BufferedImage image;
Graphics2D g2d;
Timer timer;
spanel(){
image = new BufferedImage(600, 600, BufferedImage.TYPE_INT_ARGB);
g2d = (Graphics2D)image.getGraphics();
setBackground(Color.black);
g2d.setColor(Color.white);
i=0;
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
iterate();
}
};
timer = new Timer(10, listener);
timer.start();
}
public void iterate(){
x=r.nextInt(600);
y=r.nextInt(600);
rad=r.nextInt(5)+5;
g2d.fillOval(x, y, rad, rad);
repaint();
i++;
if (i==1000){timer.stop();}
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(image,0,0,null);
}
}
public class SnowAnim {
public static void main(String[] args) {
JFrame sboard = new JFrame();
sboard.setSize(600,600);
sboard.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
spanel mypanel = new spanel();
sboard.add(mypanel);
sboard.setVisible(true);
}
}

Time Delay using Thread.sleep() for paintComponent(Graphics g) not working as expected

I am making an Animated ProgressBar, in which i used multiple fillRect() method of class javax.swing.Graphics.
To put a delay after each rectangle is painted, I am using Thread.sleep(500) method for making a delay, (Suggested by many Forums, for making a delay).
The Problem is, instead of making a delay of 0.5sec after each Rectangle box is displayed, it takes the whole delay required by all the rectangles, in the start, and then displays the final image, thats the Progress Bar.
Question 1
TO make a delay for every single bar, i put the delay "Thread.sleep(500)" along with the bars "fillRect()" in a single for() loop, i would like to know, Why does it takes all the delay in the beginning and then dislplays the completed ProgressBar.
Question 2
How can i change my code, so that the delay can occur simultaneously with each rectangle bar, so when i run the program it should generate an Animated Progress Bar.
Code:
import javax.swing.JOptionPane;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
class DrawPanel extends JPanel
{
public paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(new Color(71,12,3));
g.fillRect(35,30,410,90);
for ( int i=1; i<=40; i+=2)
{
Color c = new Color (12*i/2,8*i/2,2*i/2);
g.setColor(c);
g.fillRect( 30+10*i,35,20,80);
try
{ Thread.sleep(500); }
catch(InterruptedException ex)
{ Thread.currentThread().interrupt(); }
}
}
}
class ProgressBar
{
public static void main (String []args)
{
DrawPanel panel = new DrawPanel();
JFrame app = new JFrame();
app.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
app.add(panel);
app.setSize(500,200);
app.setVisible(true);
}
}
Your help is highly appreciated, Thankyou.
Don't block the EDT (Event Dispatch Thread) - the GUI will 'freeze' when that happens. Instead of calling Thread.sleep(n) implement a Swing Timer for repeated tasks. See Concurrency in Swing for more details. Also be sure to check the progress bar tutorial linked by #Brian. It contains working examples.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class DrawPanel extends JPanel
{
int i = 0;
public DrawPanel() {
ActionListener animate = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
repaint();
}
};
Timer timer = new Timer(50,animate);
timer.start();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(new Color(71,12,3));
g.fillRect(35,30,410,90);
Color c = new Color (12*i/2,8*i/2,2*i/2);
g.setColor(c);
g.fillRect( 30+10*i,35,20,80);
i+=2;
if (i>40) i = 0;
}
}
class ProgressBar
{
public static void main (String []args)
{
DrawPanel panel = new DrawPanel();
JFrame app = new JFrame();
app.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
app.add(panel);
app.setSize(500,200);
app.setVisible(true);
}
}
I really wouldn't do this. The Swing refresh thread isn't supposed to be used like this. You're much better off using another thread (perhaps using a TimerTask) and redrawing rectangles upon demand.
Check out the Oracle ProgressBar tutorial for more info, code etc.

Categories

Resources