multithreading with java swing for a simple 2d animation - java

my final goal for this application is to animate several items in the same JPanel at a different speed using a thread for each item.the first part is done however the items move at the same speed and i have no idea on how to fix this problem.
package javagamestutos;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
public class Board extends JPanel implements Runnable {
private Star star;
private Thread animator;
ArrayList<Star> items=new ArrayList<Star>();
public Board() {
setBackground(Color.BLACK);
setDoubleBuffered(true);
star=new Star(25,0,0);
Star star2=new Star(50,20,25);
items.add(star2);
items.add(star);
}
public void addNotify() {
super.addNotify();
animator = new Thread(this);
animator.start();
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D)g;
for (Star s : this.items) {
g2d.drawImage(s.starImage, s.x, s.y, this);
}
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
public void run() {
while(true){
try {
for (Star s : this.items) {
s.move();
}
repaint();
Thread.sleep(star.delay);
} catch (InterruptedException ex) {
Logger.getLogger(Board.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
here is the star class wich is the moving item.
package javagamestutos;
import java.awt.Image;
import javax.swing.ImageIcon;
/**
*
* #author fenec
*/
public class Star {
Image starImage;
int x,y;
int destinationX=200,destinationY=226;
boolean lockY=true;
int delay;
public Star(int delay,int initialX,int initialY){
ImageIcon ii = new ImageIcon(this.getClass().getResource("star.png"));
starImage = ii.getImage();
x=initialX;
y=initialY;
this.delay=delay;
}
void moveToX(int destX){
this.x += 1;
}
boolean validDestinatonX(){
if(this.x==this.destinationX){
this.lockY=false;
return true;
}
else
return false;
}
void moveToY(int destY){
this.y += 1;
}
boolean validDestinatonY(){
if(this.y==this.destinationY)
return true;
else
return false;
}
void move(){
if(!this.validDestinatonX() )
x+=1;
if(!this.validDestinatonY() && !this.lockY)
y+=1;
/*if(!this.validDestinatonY())
y+=1;
*/
}
}
and here is the skeleton of the animation that extends a JFrame :
package javagamestutos;
import javax.swing.JFrame;
public class Skeleton extends JFrame {
public Skeleton() {
add(new Board());
setTitle("Stars");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300, 280);
setLocationRelativeTo(null);
setVisible(true);
setResizable(false);
}
public static void main(String[] args) {
new Skeleton();
}
}
do you have any idea how to achieve my goals?am i using threads proprely?
thank you in advance.

That's because you're invoking the "move" method at a fixed rate specified by the delay of the first "start"
Thread.sleep(star.delay);
So if you move them a little every "n" milliseconds, they will seems to move at the same peace.
If you want them to move at different speed, you have to move them in different thread ( you are using only one now ) Bear in mind the comment by omry,
EDIT
I did something similar just recently
I have two different things so animate so I have two timers ( timers use threads underneath, but they can repeat the execution code every fixed rate ).
The first apply the text to a JLabel every second ( 1000 ms )
final Timer timer = new Timer();
timer.scheduleAtFixedRate( new TimerTask() {
public void run(){
setText();
}
}, 0, 1000 );
And other change the displaying image every 10 seconds ( 10,000 ms )
final Timer imageTimer = new Timer();
imageTimer.scheduleAtFixedRate( new TimerTask() {
public void run() {
setImage();
}
}, 0, 10000 );
I have a video of the result here:
For more advanced ( and nice ) time management you MUST take a look at the "Timing Framework" project which adds additional capabilities to timers.

You should be painting in the AWTDispatchThread. To do that you will want to use something like SwingUtilities.invokeLater(Runnable); This applies not only to your animation, but to the creation and setting visible of your JFrame as well. Failing to do this could result in deadlocks with the painting thread. Also, when moving your painting operations into the SwingUtilites methods, you will not want to include any while(true) loops, as that will commandeer your painting thread.

Generally Swing components should be used from the AWT Event Dispatch Thread (EDT). repaint is one of the methods that is supposedly okay to use off EDT. However, your Star is not and should not be thread-safe.
The easiest approach is to go for EDT-only (at least to start with). Instead of using Thread use javax.swing.Timer which fires on the EDT.
Misc comments: There should be no need for your paint method to dispose of the graphics object sent to it, or for it to sync using Toolkit. The component need not be set to double-buffered, but should be set opaque (JPanel is not guaranteed to be opaque). You should just extend JComponent instead of JPanel, as this is not a panel. It's generally not a great idea for outer classes to implement Runnable. Prefer private variables.

I would suggest you take a look at the open source library trident which does just that, its author, Kirill Grouchnikov is well-known in the Swing world (he is the author of the famous Substance look & feel).
Trident should help you solve the problem of having different objects move at different speeds, without having to create one thread per object (which is a problem in the end).

if you are sure you want to paint in the threads, you can use :
update(getGraphics());
instead of repaint.
this is generally considered bad practice, as you normally paint stuff in the AWT thread.

Related

Can Java have text in the background of a graphics file?

I have this code and its supposed to have the text "Simple Animation" scroll across the screen in swirling colors. Right now, it does that, but even after the text moves along, the color still stays. I was wondering if there was a way to have text in the background. For example, I was thinking I could just print out the exact same "Simple Animation" but in the same color as the background and about 10 pixels behind the actual text. However, when I tried this, the white text (that's the background color) just covered the swirling colors. I tried googling if I could have background text, but from I read, the only thing that a background can do is set the color. So, is there a way to have text in the background in a Java Graphics file?
Here is my Code:
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import javax.swing.*;
public class Scrolling_Sign extends JApplet implements Runnable {
String mesag = "Simple Animation";
Font f = new Font("Bauhaus 93",Font.BOLD,72);
Color colors[] = new Color[100000];
Thread runThread;
int Xposition = 600;
public void init() {
setBackground(Color.white);
}
public void start() {
if (runThread == null) {
runThread = new Thread(this);
runThread.start();
}
}
public void stop() {
if (runThread != null) {
runThread.stop();
runThread = null;
}
}
public void run() {
float c = 0;
for (int i = 0; i < colors.length; i++) {
colors[i] = Color.getHSBColor(c, (float)1.0,(float)1.0);
c += .02;
}
int i = 0;
while (true) {
setForeground(colors[i]);
repaint();
i++;
try { Thread.sleep(100); }
catch (InterruptedException e) { }
if (i == colors.length ) i = 0;
}
}
public void paint(Graphics g) {
g.setFont(f);
g.drawString(mesag,Xposition,100);
Xposition--;
if (Xposition < -290) {
Xposition = 600;
}
}
}
Thank you!
Suggestions:
Never draw directly within a JApplet or other top-level window.
Instead draw in the paintComponent of a JPanel that is displayed within the applet. The Swing tutorials will show you how.
Be sure to call the super.paintComponent(g) method within your override, and again read the Swing tutorials to see why. For more tutorials see: Swing Info
This is Swing -- use a Swing Timer to drive your animation, not threads.
If you ever do use Threads, never call Thread#stop() or use any other deprecated methods. Please read Why is Thread.stop deprecated?.
Please look at this answer for an example of Swing animation using a Swing Timer.
Unless this is for a class assignment, don't create JApplets as this is a dead technology, something even Oracle will tell you.
To display text in the background use the java.awt.Graphics method for writing text: drawString(...). Either that or place a JLabel over your background image.

Understanding the EDT in a real model with frequent updates

I am writing the Sugarscape simulation in Java and need a working GUI. Sugarscape is a spatial landscape consisting of tiles (of sugar), and agents moving and consuming sugar. For simplicity, I have only one agent and no sugar- I just want to see the agent moving.
For the past 2 weeks I have read into painting in java, concurrency in java, concurrency in swing, I have read filthy rich clients and countless StackOverflow threads, but I must resort to asking a question here.
I need my model separate from the GUI. This presents a problem since 99% of tutorials suggest to call for repaint within other methods. My idea was to run one "tick" of the simulation: all agents move, and then send an Event (my GUI class extends Observer) which then triggers a repaint(); request and update the GUI. However the problem (the misunderstanding) lies with the SwingUtilities.InvokeLater method. My code is:
public void setupGUI()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run() {
System.out.println("GUI is being setup, on EDT now? " + SwingUtilities.isEventDispatchThread());
SugarFrame frame = new SugarFrame(simulation.getWorld());
frame.setVisible(true);
}
});
}
For understanding what is happening I have inserted println's everywhere. The order of events is what confuses me:
Console output:
1.Agent created. Starting Position: X= 19 Y= 46 // This is in the Agent constructor
2.Simulation start. Experiment number: 0
GUI is being setup, on EDT now? true // As you see above, this is WITHIN the SwingUtilities.InvokeLater section. But then the EDT pauses and the real model continues:
Tick number 0
Invoke Agent Actions, fire TickStart Event
TickStartEvent created
Invoke Agent Actions, for-loop starting now
Agent number 0 moving now:
Consuming Sugar now.
Moving now.
Sleeping now.
The Sugarframe has been created and Grid added. All on EDT? true // And there it is back again. The paint component follows and the window with the Agent visible appears.
paintComponent called, on EDT? true
Now, I have read that by putting the main thread to sleep, you give the EDT time to run the repaint. However, this only happens once. Repaint is never called again, and I only ever see one iteration of the model.
I simply do not understand what piece of information I am missing to work with the EDT properly. Swingworker and Swingtimer are suggested regularly, but for every suggestion there is a notion that they are not needed for a model such as mine. Either paintComponent is not called at all, or queued up until the end (and then still not repainting, even if I use thread.sleep).
I'd appreciate any help. Apologies for the long post.
//Edit: as per request some more code.
The entire main method:
public class SimulationController {
static Simulation simulation;
public static final int NUM_EXPERIMENTS = 1;
public SimulationController()
{
Random prng = new Random();
SimulationController.simulation = new Simulation(prng);
}
public void run() {
setupGUI();
for(int i=0; i<NUM_EXPERIMENTS; i++) {
System.out.println("Simulation start. Experiment number: " + i);
simulation.getWorld().addObserver(simulation);
simulation.addObserver(simulation.getWorld());
simulation.run();
}
}
public void setupGUI()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run() {
System.out.println("GUI is being setup, on EDT now? " + SwingUtilities.isEventDispatchThread());
SugarFrame frame = new SugarFrame(simulation.getWorld());
frame.setVisible(true);
}
});
}
public static void main(String[] args) {
SimulationController controller = new SimulationController();
controller.run();
}
}
The paint override in my JPanel class:
#Override
public void paintComponent(Graphics g) {
System.out.println(">>>>>>>>paintComponent called, on EDT? " + SwingUtilities.isEventDispatchThread()+"<<<<<<<<<<");
super.paintComponent(g);
//g.clearRect(0, 0, getWidth(), getHeight());
rectWidth = getWidth() / world.getSizeX();
rectHeight = getHeight() / world.getSizeY();
for (int i = 0; i < world.getSizeX(); i++)
{
for (int j = 0; j < world.getSizeY(); j++)
{
// Upper left corner of this terrain rect
x = i * rectWidth;
y = j * rectHeight;
Tile tile = world.getTile(new Position(i, j));
if (tile.hasAgent())
{
g.setColor(Color.red);
} else
{
g.setColor(Color.black);
}
g.fillRect(x, y, rectWidth, rectHeight);
}
}
}
JPanel class again, update methods:
public void update(Observable o, Object arg)
{
if (arg instanceof TickEnd)
{
TickEvent tickEndevent = new TickEvent();
this.addTickEvent(tickEndevent);
}
}
}
private final BlockingQueue<TickEvent> TICK_EVENTS = new LinkedBlockingQueue<TickEvent>();
/**Runnable object that updates the GUI (I think)**/
private final Runnable processEventsRunnable = new Runnable()
{
public void run()
{
TickEvent event = new TickEvent();
while ((event = TICK_EVENTS.poll()) != null)
{
System.out.println("This is within processEventsRunnable, inside the While loop. Repaint is called now.");
repaint();
}
}
};
/**Add Event to the processing-Events-queue**/
public void addTickEvent(TickEvent event)
{
//System.out.println("This is in the Add TickEvent method, but before the adding. "+TICK_EVENTS.toString());
TICK_EVENTS.add(event);
System.out.println("TickEvent has been added! "+TICK_EVENTS.toString() + "On EDT?" + SwingUtilities.isEventDispatchThread());
if (TICK_EVENTS.size() >= 1)
{
SwingUtilities.invokeLater(processEventsRunnable);
}
}
And last but not least, the JFrame constructor:
/** Sugarframe Constructor**/
public SugarFrame(World world)
{
super("Sugarscape"); // creates frame, the constructor uses a string argument for the frame title
grid = new Grid(world); // variable is declared in the class
add(grid);
setDefaultCloseOperation(EXIT_ON_CLOSE); // specifies what happens when user closes the frame. exit_on_close means the program will stop
this.setContentPane(grid);
this.getContentPane().setPreferredSize(new Dimension(500, 500));
this.pack(); // resizes frame to its content sizes (rather than fixed height/width)
System.out.println("The Sugarframe has been created and Grid added. All on EDT? "+ SwingUtilities.isEventDispatchThread());
this.setVisible(true); // makes the Frame appear on screen
}
The sentences,
I need my model separate from the GUI. This presents a problem since 99% of tutorials suggest to call for repaint within other methods.
and
Now, I have read that by putting the main thread to sleep, you give the EDT time to run the repaint.
don't sound quite right to me, so I'll try to clear things up a bit and maybe If you reevaluate the fundamental ideas you had behind those statements you can find the piece of information that you were missing.
First of all, always keep in mind this scheduling model that we were talking about. You can not say "EDT do this for me now!". It is always "EDT here's one more task you need to do, do it when you are done with whatever you are doing". So the EDT has a queue of "tasks" to do and goes through it consuming one by one.
These tasks are usually created by events: pressing a button gives the EDT a task to do, when the state of a component of the GUI changes some listeners may be notified and enqueue some work in the EDT. However, you can also straight up say "EDT execute this piece of code, later". This is what you do with invokeLater, you schedule a work to do in the EDT whenever it's free. Even if you call invokeLater from the EDT the task is scheduled, not executed at the moment.
The same happens with invokeAndWait yes, the code is executed sequentially as if it was executed at the moment, but it is still an scheduled work. So repaint() is no exception to this. repaint() doesn't repaint the GUI, but rather schedules the repainting of the GUI.
However repaint() is exceptional in the sense that it can be called from outside the EDT! This is not surprising now that we know that the only thing that does is scheduling a certain work, it does not actually mess with the GUI so you can call it wherever you want.
This means that the line
SwingUtilities.invokeLater(processEventsRunnable);
where processEventsRunnable basically executes a repaint() is meaningless and the whole tick system overly complex and unnecesary. You just have to call repaint() when you change something on the GUI or on the data that the GUI feeds on so the changes are reflected on the screen.
Furthermore, if you wanted to do something that needs to be executed in the EDT (like changing the text of a Label with the score) you can just put that code in an invokeLater block in your main thread. That will queue and execute the task properly, you don't need to do your own event queue system.
Keeping all this in mind the following makes no sense:
I have read that by putting the main thread to sleep, you give the EDT time to run the repaint
The GUI will be updated on its own shortly after you call repaint(). The main doing a lot of things and calling a lot of repaints does not prevent the GUI from being updated. However, if you want to "sleep" the main so the pace of the changes is slow so the user can appreciate it on the screen, you should use a timer.
So, as long as your main is not accessing GUI values and methods, feel free to call repaint whenever you are done changing the data, periodically or not.
Edit: Also it sounds a little bit weird that you have a main thread doing things. As you read in the concurrency chapter, usually you just create the GUI in the EDT and then the application is mostly event-driven when buttons are pressed and such. If you need to do changes periodically use a timer. You can use auxiliar threads to do specific non-GUI related heavy work, like reading a file. But you don't usually have an auxiliar thread permanently active as part of the design.
The following is a very simple program that moves an square periodically. I just use a timer to change the data and call repaint(). Note that I'm using a SwingTimer (it is executed in the EDT) since I wanted to check the panel width. Otherwise I could run the code of the timer in any thread.
In your case you probably have your "map" stored independently of the GUI, so you just need to check that data to properly move the coordinates of the agent whenever you want (on keyboard press, periodically...).
It looks like this:
Full code:
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.SwingUtilities;
import javax.swing.Timer;
public class MovingSquareTest
{
int x, y, size, step;
MyPanel panel;
Timer timer;
public static final void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
MovingSquareTest app = new MovingSquareTest();
app.createAndShowGUI();
app.timer.start();
}
});
}
public MovingSquareTest()
{
x = 0;
y = 150;
size = 50;
step = 50;
timer = new Timer(500, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
x += step;
if (x < 0) x = 0;
if (x + size > panel.getWidth()) x = panel.getWidth() - size;
if (x == 0 || x + size == panel.getWidth()) step *= -1;
panel.repaint();
}
});
}
public void createAndShowGUI()
{
JFrame frame = new JFrame("Dance, my square!");
panel = new MyPanel();
frame.add(panel);
frame.setSize(600, 400);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private class MyPanel extends JPanel
{
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawRect(x, y, size, size);
}
}
}

Moving a rectangle incredibly choppy

I've created a simple program that draws a rectangle which falls down the screen at a constant rate. I first run Main.java:
package highst;
public class Main {
public static void main(String args[]){
new GameFrame();
}
}
which creates a new instance of GameFrame.java:
package highst;
import javax.swing.JFrame;
public class GameFrame extends JFrame {
public GameFrame() {
super("Falling rectangle");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(800, 600);
GameLogic game = new GameLogic();
this.getContentPane().add(game);
this.setVisible(true);
game.run();
}
}
Which in turn creates a new instance of GameLogic.java:
package highst;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
public class GameLogic extends JPanel implements Runnable, KeyListener {
Marvin marvin;
private enum GameState{
Running, Dead
}
GameState state = GameState.Running;
public GameLogic(){
marvin = new Marvin(50, 50);
Thread thread = new Thread(this);
thread.start();
addKeyListener(this);
setFocusable(true);
this.setBackground(Color.black);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.white);
g.fillRect(marvin.getX(), marvin.getY(), 50, 50);
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_SPACE){
marvin.jump();
}
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void run() {
if(state == GameState.Running){
while(true){
marvin.update();
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Which finally makes use of what will be my playable character, Marvin.java who is now a white rectangle:
package highst;
public class Marvin {
private int x, y;
public Marvin(int x, int y){
this.x = y;
this.y = y;
}
public void update(){
y -= -1;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void jump() {
x += 1;
}
}
It runs fine but the rectangle is not painted smoothly as it falls down the page. It seems to jump a few pixels at a time. I thought sleeping the thread for 17 milliseconds would cause everything to render smoothly. What am I doing wrong?
To have smooth animation, you need to make updates to the screen at a constant rate.
Here, you're doing a graphic repaint, which could take any amount of time, then waiting 17ms no matter what. This leads to each frame taking a different amount of time. The first frame might be done in 2ms, the next might take 5ms, then 3ms and so on... your frames will be displayed for 19ms then 22ms then 20ms...
What you need is a dedicated thread whose only job is to wait the right amount of time and then signal the main thread to repaint. Then your frames (provided they don't take more than 17ms to paint) come out every 17ms, exactly on cue.
Here's an tutorial on animation in Java applets, you should find it relevant.
Try these:
1> Reducing the sleeping time of the thread to see the effects and get the optimum speed of falling.
2> Make use of Double Buffering (a concept in which the screen is painted first in memory and then painted onto the display monitor):
Double Buffering - Docs
Double Buffering - Google Search Results
3> Simple suggestion: Avoid using sleep(). Instead use Timers. It is a very interesting and powerful substitute for threads. Also it will not create problems in the later stages of your game development. Check these links out:
Concurrency in java
Timer in Java
4> Check out these interesting tutorials about animation in Java:
Java World
Clear Rice
There are several things wrong...
You're ignoring the Initial Threads of Swing and not starting your UI within the context of the EDT, which leads to...
You are calling run on the instance of GameLogic AND creating a Thread which will call run again, setting up to loops. It's actually dumb luck that this worked at all. What this does is calls marvin.update twice...and random intervals, meaning that the object is moved at inconsistent rates
Basically, you should remove the line game.run() and wrap new GameFrame(); within the context of a EventQueue.invokeLater call...
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
GameFrame frame = new GameFrame();
}
});
}
Personally, I'd recommend against extending from JFrame directly, you're not adding any functionality to the class, and simply create an instance within the main which you add your GameLogic panel to. This makes the game infinitely more flexible in terms of deployment as you're not locking yourself into a single container.
I'd also encourage you not use KeyListener, but instead, make use the Key Bindings API instead, as it solves the focus issues associated with KeyListener

Why is paint()/paintComponent() never called?

For the last two days I have tried to understand how Java handles graphics, but have failed miserably at just that. My main problem is understanding exactly how and when paint() (or the newer paintComponent() ) is/should be called.
In the following code I made to see when things are created, the paintComponent() is never called, unless I manually add a call to it myself or calls to JFrame.paintAll()/JFrame.paintComponents().
I renamed the paint() method to paintComponent() in hoping that would fix my problem of it never being called (even at repaint()), but no luck.
package jpanelpaint;
import java.awt.*;
import javax.imageio.*;
import javax.swing.*;
import java.io.*;
import java.util.ArrayList;
public class ImageLoadTest extends JComponent {
ArrayList<Image> list;
public ImageLoadTest() {
list = new ArrayList<Image>();
try { //create the images (a deck of 4 cards)
for(String name : createImageFileNames(4)){
System.err.println(name);
list.add(ImageIO.read(new File(name)));
}
} catch (IOException e) { }
}
protected void paintComponent(Graphics g) {
int yOffset=0;
System.err.println("ImageLoadTest.paintComponent()");
for(Image img : list) {
g.drawImage(img, 0, yOffset, null);
yOffset+=20;
}
}
public static void main(String args[]) throws InterruptedException {
JFrame frame = new JFrame("Empty JFrame");
frame.setSize(new Dimension(1000, 500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Thread.sleep(1000);
frame.setTitle("Loading images");
ImageLoadTest ilt = new ImageLoadTest();
frame.add(ilt);
//update the screen
//DOESN'T WORK. only works if I call frame.paintAll(frame.getGraphics())
ilt.repaint();
frame.repaint();
Thread.sleep(1000);
frame.setTitle("Setting background");
ilt.setBackground(Color.BLACK);
//update the screen - DOESN'T WORK even if I call paintAll ..
ilt.repaint();
frame.repaint();
//have to call one of these to get anything to display
// ilt.paintComponent(frame.getGraphics()); //works
frame.paintComponents(frame.getGraphics()); //works
}
//PRIVATE HELPER FUNCTIONS
private String[] createImageFileNames(int count){
String[] fileNames = new String[count];
for(int i=0; i < count; i++)
fileNames[i] = "Cards" + File.separator + (i+1) + ".bmp";
return fileNames;
}
}
One of the reasons the paintComponent() doesn't get invoked in the original code is because the component has a "zero size" and the RepaintManger is smart enough not to try and paint something with no size.
The reason the reordering of the code works is because when you add the component to the frame and then make the frame visible the layout manager is invoked to layout the component. By default a frame uses a BorderLayout and by default a component is added to the center of the BorderLayout which happens give all the space available to the component so it gets painted.
However, you change the layout manager of the content pane to be a FlowLayout, you would still have a problem because a FlowLayout respects the preferred size of the component which is zero.
So what you really need to do is assign a preferred size to you your component so layout managers can do their job.
One major issue here is you are not updating your swing components on the Event Dispatch Thread (EDT). Try wrapping all the code in your main method in the following:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// swing code here...
}
});
Also: add your ImageLoadTest to the frame before setting the frame visible. This is based on a quick cursory read of the code -- I will read it further and see what else I can find.
EDIT:
Follow my original advice above, and simplify your main method to look like the following and your paintComponent() will be called:
public static void main(String args[]) throws InterruptedException {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Empty JFrame");
frame.setSize(new Dimension(1000, 500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
PaintComponentTest ilt = new PaintComponentTest();
frame.add(ilt);
frame.setVisible(true);
ilt.setBackground(Color.BLACK);
}
});
}
Also I would read up on using timers to perform animation, as well as general Swing event dispatching and how/when to override various paint methods.
http://java.sun.com/products/jfc/tsc/articles/painting/
http://java.sun.com/docs/books/tutorial/uiswing/misc/timer.html
http://java.sun.com/docs/books/tutorial/uiswing/concurrency/dispatch.html
To make Tom Hawtin - tackline happy. I rewrote once again
There are several things I changed (check the lines with the //new comment)
Rewrote it completely
Split into a clean new component file (ImageLoadTest.java) and a file to test it (Tester.java)
Improvements on original posters code
call constructor of parent in ImageLoadTest constructor (super())
provided second constructor to set list of images which component should display
IMPORTANT: call to setPreferredSize() of component in constructor. If size isn't set swing of course won't paint your component. preferred size is based on max. width of all images and on sum of all image heights
call to super.paintComponent(g) in overriden paintComponent()
changed paintComponent to automatically base yOffset on height of images being drawn
GUI initialization done on EDT
as original code based on using sleep() to illustrate loading and loading of images could take a long time SwingWorker's are used
worker waits then sets new title and then loads images
on completion the worker in done() finally adds the component to the JFrame and displays it. Added component to content pane of JFrame as described in JFrame api. And as described in javadoc made necessary call to validate() on JFrame after calling add(), as the JFrame is an already visible container whichs children changed.
javdoc citation from validate()
The validate method is used to cause a
container to lay out its subcomponents
again. It should be invoked when this
container's subcomponents are modified
(added to or removed from the
container, or layout-related
information changed) after the
container has been displayed.
second worker just does some more waiting then sets background color to black
used JPanel as baseclass for ImageLoadTest to fix setBackground() which I couldn't get to work with JComponent.
So your main problems where that you didn't set the preferred size of the component and that you did not call validate() on the JFrame after adding something to the already visible container.
This should work
jpanelpaint/ImageLoadTest.java
package jpanelpaint;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;
import java.util.List;
public class ImageLoadTest extends JPanel {
private List<Image> list;
public ImageLoadTest() {
super();
}
public ImageLoadTest(List<Image> list) {
this();
this.list = list;
int height = 0;
int width = 0;
for (Image img : list) {
height += img.getHeight(this);
width = img.getWidth(this) > width ? img.getWidth(this) : width;
setPreferredSize(new Dimension(width, height));
}
}
#Override
protected void paintComponent(Graphics g) {
int yOffset=0;
super.paintComponent(g);
System.err.println("ImageLoadTest.paintComponent()");
for(Image img : list) {
g.drawImage(img, 0, yOffset, null);
yOffset+=img.getHeight(this);
}
}
}
Tester.java
import java.awt.Dimension;
import java.awt.Color;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
import javax.swing.SwingUtilities;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import jpanelpaint.ImageLoadTest;
public class Tester {
private JFrame frame;
private ImageLoadTest ilt;
private final int NUMBEROFFILES = 4;
private List<Image> list;
//will load the images
SwingWorker worker = new SwingWorker<List<Image>, Void>() {
#Override
public List<Image> doInBackground() throws InterruptedException {
//sleep at start so user is able to see empty jframe
Thread.sleep(1000);
//let Event-Dispatch-Thread (EDT) handle this
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.setTitle("Loading images");
}
});
//sleep again so user is able to see loading has started
Thread.sleep(1000);
//loads the images and returns list<image>
return loadImages();
}
#Override
public void done() {
//this is run on the EDT anyway
try {
//get result from doInBackground
list = get();
frame.setTitle("Done loading images");
ilt = new ImageLoadTest(list);
frame.getContentPane().add(ilt);
frame.getContentPane().validate();
//start second worker of background stuff
worker2.execute();
} catch (InterruptedException ignore) {}
catch (ExecutionException e) {
String why = null;
Throwable cause = e.getCause();
if (cause != null) {
why = cause.getMessage();
} else {
why = e.getMessage();
}
System.err.println("Error retrieving file: " + why);
}
}
};
//just delay a little then set background
SwingWorker worker2 = new SwingWorker<Object, Void>() {
#Override
public List<Image> doInBackground() throws InterruptedException {
Thread.sleep(1000);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.setTitle("Setting background");
}
});
Thread.sleep(1000);
return null;
}
#Override
public void done() {
ilt.setBackground(Color.BLACK);
frame.setTitle("Done!");
}
};
public static void main(String args[]) {
new Tester();
}
public Tester() {
//setupGUI
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame = new JFrame("Empty JFrame");
frame.setSize(new Dimension(1000, 500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
//start the swingworker which loads the images
worker.execute();
}
//create image names
private String[] createImageFileNames(int count){
String[] fileNames = new String[count];
for(int i=0; i < count; i++)
fileNames[i] = "Cards" + File.separator + (i+1) + ".bmp";
return fileNames;
}
//load images
private List<Image> loadImages() {
List<Image> tmpA = new ArrayList<Image>();
try {
for(String name : createImageFileNames(NUMBEROFFILES)){
System.err.println(name);
tmpA.add(ImageIO.read(new File(name)));
}
} catch (IOException e) { }
return tmpA;
}
}
These were the main problems with the original code that caused it not to work:
not calling validate() after an add() operation
not setting the preferred size of the component.
not calling super.paintComponent() when overriding it (this made the
setBackground() call not work)
I needed to inherit from JPanel in order for it to get painted. Neither Component nor JComponent was sufficient for the setBackground() call to work, even when fixing point 3.
Having done the above, it really didn't matter if calling the method paintComponent or paint, both seemed to work as long as I remembered to call the super constructor at the start.
This info was assembled from what #jitter, #tackline, and #camickr wrote, so big kudos!
P.S. No idea if answering your own question is considered bad form, but since the information I needed was assembled from several answers, I thought the best way was upmodding the other answers and writing a sum up like this.
I recommend reading the first couple of chapters of "Filthy Rich Clients". I had been using Swing for years, but only after reading this book did I finally fully understand exactly how Java's painting mechanism works.

How to get rid of the flicker that appears during my animation?

I'm learning Java by making a small game in a JApplet.
I got a little problem with my sprite's animation.
Here is the code :
this.sprite.setBounds(0,0,20,17);
this.sprite.setIcon(this.rangerDown);
for(int i = 0; i< 16;i++)
{
this.sprite.setBounds(this.sprite.getX(), this.sprite.getY()+1, 20, 17);
this.sprite.update(this.sprite.getGraphics());
try{
Thread.currentThread().sleep(100);
}catch(InterruptedException e){
}
}
It left some flicker during the animation. Once the animation end, the flicker disappears, but it's kind of ugly... I guess there is some step I missed.
I use this method because it gives the better result for now, but I would like to stay without AWT if possible, using Swing instead.
Any ideas how to get rid of the flicker?
Thanks for reading.
Screenshoot (Can't post images, sorry).
This is not a shadow. Its the border of your sprite. It just happens to be black and appears as a shadow. If you change the amount you shift your sprite (lets say by 50 pixels, not just 1) you will see what i mean.
To fix it what you need to do is to draw the background as well each time you update the location of your sprite. Although this will probably produce flickering.
The correct way to do it is to change the way you draw your objects. You need to override the paintComponent method of your panel and then simply call repaint each time you have updated the locations of your sprites.
EDIT:
See this code sample for basic usage. NOTE: This is NOT how you should write animation using Threads. I wrote that to show you what goes in the paintComponent method and wrote the animation Thread to show you that the "shadow" you mentioned is gone. NEVER have a non ending run loop in a thread :)
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
JFrame f = new JFrame("Test");
MyPanel c = new MyPanel();
f.getContentPane().add(c);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(350, 100);
f.setVisible(true);
}
}
class MyPanel extends JPanel {
int x = 0;
boolean toTheRight = true;
public MyPanel() {
new Thread(new Runnable() {
#Override
public void run() {
while (true) {
x = (toTheRight)?x+5:x-5;
if (x>300)
toTheRight = false;
if (x<0)
toTheRight = true;
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g.create();
g2.setPaint(Color.white);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setPaint(Color.red);
g2.fillOval(x-2, 50, 4, 4);
}
}
The problem is double buffering.
In Applets:
Double buffering is done almost automatically. Call repaint() instead of paint in your method.
In Swing, there are many ways to do it. I usually go for the BufferStrategy route. When you're initializing your frame, do this:
JFrame frame;
... code to init frame here
frame.createBufferStrategy(2);
Then in your draw methods:
Graphics g = getBufferStrategy().getDrawGraphics();
..code to do drawing here...
g.dispose();
getBufferStrategy().show();

Categories