I'm following along in a (dated) Java book, and this project is supposed to "animate" a circle moving across the screen. However, when the program is run, the circle remains in one spot. My code looks identical to the book's. Am I forgetting something? Am I calling repaint() at the wrong time?
public class Animation
{
JFrame f;
int x, y;
public static void main(String [] args)
{
Animation a = new Animation();
a.go();
}
public void go()
{
f=new JFrame();
myPanel p=new myPanel();
f.getContentPane().add(p);
f.setSize(300, 300);
f.setVisible(true);
for(int i=0; i<=50; i++)
{
p.repaint();
x++;
y++;
}
}
class myPanel extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(x, y, 40, 40);
}
}
}
So, immediately, two things jump out at me.
It's possible that the loop which is updating the values is NOT running on the Event Dispatching Thread, which could be leading to dirty read/writes
repaint can consolidate requests to reduce the amount of work placed on the Event Queue/Event Dispatching Thread. Since there is not "artificial" delay in the loop, it's possible that all your requests are been reduced to a single update pass by the RepaintManager
The first thing I would do is isolate the responsibility for the management of the position of the oval, because in your current code, it could be updated from anywhere, which is just a mess
class MyPanel extends JPanel {
private Point posy = new Point(0, 0);
public Point getPosy() {
return posy;
}
public void move() {
Point posy = getPosy();
posy.x++;
posy.y++;
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.GREEN);
g.fillOval(posy.x, posy.y, 40, 40);
}
}
Next, I'd ensure that the context of the UI is been modified from within the EDT...
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Animation a = new Animation();
a.go();
}
});
}
and finally, I'd make use of Swing Timer to act as a pseudo loop for the animation...
public void go() {
f = new JFrame();
MyPanel p = new MyPanel();
f.getContentPane().add(p);
f.setSize(300, 300);
f.setVisible(true);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
p.move();
}
});
timer.start();
}
There are a number of reasons for using a Swing Timer in this context. First, the "ticks" are executed ON the EDT, making it safe to update the UI from within and it won't block the UI while it's "waiting" between ticks
I would recommend having a look at:
Painting in AWT and Swing which will give you a better understanding into how painting actually works
Concurrency in Swing as it will give you a heads up into some of the pitfalls in trying to do more then one thing within the GUI
How to Use Swing Timers because they are really useful for this kind of thing
Related
Hi i wanna ask about Swing timer and Canvas. Im doing simple animation for changing color of objects. I made it with Thread.sleep but JFrame was unresponsive while repainting so i change it to Swing Timer. But now when i start the animation its doing nothing timer is working but objects on canvas dont change color.
Here is my function to animate change of colors im using it in overide paint function of canvas
private void paintSearch(Vector<NodeGraph2D> vector,Graphics graphics2D) {
if (!vector.isEmpty()) {
final int[] k = {0};
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (repaint) {
if (k[0] == vector.size())
return;
if (vector == pruferTreeNodes) {
vector.elementAt(k[0]).draw((Graphics2D) graphics2D);
} else {
graphics2D.setColor(Color.GREEN);
((Graphics2D) graphics2D).fill(vector.elementAt(k[0]));
graphics2D.setColor(Color.BLACK);
((Graphics2D) graphics2D).drawString(vector.elementAt(k[0]).getNodeGraph().getName(), vector.elementAt(k[0]).getX1() + 15, vector.elementAt(k[0]).getY1() + 25);
}
k[0] += 1;
}
}
});
timer.start();
}
}
Do you think my usage of timers is bad ? Thanks for response. :)
When doing custom painting in Swing it is a good idea to subclass JPanel (it could be an anonymous class) and to store painting-related data in attributes somewhere accessible to the panel.
Your timer would not do any painting but rather manipulate the painting-related data. You should never attempt to do any painting on a graphics object outside of the EventDispatcherThread of Swing or outside of the paintComponent methods of JComponents. (refer to the documentation of these methods for further information)
Here is an example of how custom painting with a timer manipulating color could look like:
public static void main(String[] args) {
EventQueue.invokeLater(Example::new);
}
// this is the painting-related data that is being manipulated by the timer
private int currentColorIndex;
public Example() {
JFrame frame = new JFrame("Custom Painting");
frame.setSize(640, 480);
frame.setLocationRelativeTo(null);
Color[] allColors = {Color.RED, Color.BLUE, Color.GREEN,
Color.YELLOW, Color.ORANGE, Color.MAGENTA};
JPanel myCustomPanel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
// here the painting related data is being used by the custom JPanel implementation
g.setColor(allColors[currentColorIndex]);
g.fillRect(0, 0, getWidth(), getHeight());
}
};
frame.setContentPane(myCustomPanel);
Timer timer = new Timer(100, e -> {
// the timer does not use any graphics objects, etc, but rather manipulates our painting-related data
currentColorIndex = (currentColorIndex + 1) % allColors.length;
// whenever the painting-related data has changed we need to call repaint() on our custom JPanel implementation
myCustomPanel.repaint();
});
timer.setRepeats(true);
timer.start();
frame.setVisible(true);
}
I'm trying to draw an animation in a JPanel by displaying all the frames as BufferedImage objects and using a Thread to invoke g.drawImage in the JPanels paintComponent(Graphics g) method for each frame, with sleeping in between. My understanding is that invoking g.drawImage from anywhere, as long as g is the Graphics object from the paintComponent, should cause the pixels in the JPanel to be updated, but there is no change in the JPanel. Is that not how Graphics.drawImage works, or is it an issue with using another Thread, or something else all together? An abbreviated version of my code is below, with unnecessary bits removed
class Example extends JPanel{
public Dimension getPreferredSize(){
return new Dimension(500, 500);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g){
BufferedImage temp1;
BufferedImage temp2;
try{
temp1 = ImageIO.read(new File("C:\\Users\\Owner\\Desktop\\test1.png"));
temp2 = ImageIO.read(new File("C:\\Users\\Owner\\Desktop\\test2.png"));
}catch(IOException e){
temp1 = null;
temp2 = null;
}
final BufferedImage image1 = temp1;
final BufferedImage image2 = temp2;
Thread drawThread = new Thread(new Runnable(){
public void run(){
g.drawImage(image1, 0, 0, null);
try{
Thread.sleep(100);
}catch(InterruptedException e){
// omitted
}
g.drawImage(image2, 0, 0, null);
try{
Thread.sleep(100);
}catch(InterruptedException e){
// omitted
}
}
});
drawThread.start();
}
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
public void run(){
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new B());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Your current painting code is wrong. A painting method is for painting only. You would NEVER start a Thread from a painting method. You can't control when the painting method is invoked and every time the method is invoked you would start another Thread.
The Graphics object should only be used as a short duration object that exists for the duration of the painting method. You should not attempt to keep a reference to the object indefinitely.
I'm trying to draw an animation
If you want to do animation then you use a Swing Timer to schedule the animation.
So you should have the image as a property of the class. Then when the Timer fires you change the image property and invoke repaint() on the panel.
I want do draw a rectangle in my JFrame window, but I'am always getting a nullpointer error..
Why is it happening? what is the best (correct) way to draw graphics like rectangles, gradients, etc or something like falling snow in swing?
This is the Exception:
Exception in thread "Thread-0" java.lang.NullPointerException
at gui.Window.run(Window.java:24)
at gui.Window$1.run(Window.java:34)
at java.lang.Thread.run(Unknown Source)
And source:
public class Window extends JFrame implements Runnable {
private boolean run = true;
public Window() {
super.setSize(500, 500);
super.setTitle("MY GUI");
super.setDefaultCloseOperation(EXIT_ON_CLOSE);
super.setContentPane(new Container());
}
#Override
public void run() {
Graphics g = super.getContentPane().getGraphics();
while (this.run) {
g.setColor(new Color(0, 0, 0, 255));
g.fillRect(0, 0, 200, 200);
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
#Override
public void run() {
Window window = new Window();
window.run();
}
}).start();
}
}
Error line 24: g.setColor(new Color(0, 0, 0, 255));
Why is it doing that?
The code you posted makes no sense.
First of all, every interaction with Swing components (except calls to repaint()) must be done in the event dispatch thread.
Second, there is no point in running an infinite loop that would constantly paint the same thing on a Graphics.
Third, that's not how it works. You can't get the Graphics associated to a component and paint on it. Instead, you must override the paintComponent(Graphics) method of a Swing component, wait for swing to call this method, and use the provided Graphics argument to paint whatever you want. If you want to change what is being painted, then you need to call repaint() on this element. Don't do that with JFrame. Create a subclass of JComponent or JPanel, and add an instance of the subclass to a JFrame, and then make this JFrame visible:
public class CustomComponent extends JComponent {
#Override
public void paintComponent(Graphics g) {
// paint here
}
#Override
public Dimension getPreferredSize() {
// return preferred size here
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.add(new CustomComponent());
f.pack();
f.setVisible(true);
}
});
}
}
getGraphics will return null if the component is not visible.
To make your Window visible you have to call setVisible(bool).
You also have to be careful using threads with Swing.
I'm trying to make a pong game in Java but it doesn't work.
I've done some testing and it seems that the variables are updating but that when I do
repaint(); in the timers actionPerformed(ActionEvent e) doesn't call the paintComponent() method
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class PongGame extends JComponent implements ActionListener, MouseMotionListener{
public int state = 1;
public int paddleX;
public String buttonColor = "blue";
public int mouseX, mouseY;
private int ballX = 400;
private int ballY = 150;
public static void main(String[] args){
JFrame window = new JFrame("Pong");
PongGame game = new PongGame();
window.add(new PongGame());
window.pack();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setLocationRelativeTo(null);
window.setResizable(false);
window.setVisible(true);
Timer t = new Timer(20, game);
t.start();
}
public Dimension getPreferredSize(){
return new Dimension(800, 600);
}
public void paintComponent(Graphics g){
paddleX = mouseX;
g.setColor(Color.WHITE);
g.fillRect(0,0, 800, 600);
g.setColor(Color.BLACK);
g.fillRect(paddleX, 550, 150, 15);
g.fillOval(ballX, ballY, 30, 30);
}
#Override
public void actionPerformed(ActionEvent e) {
ballX = ballX + 10;
ballY = ballY + 10;
System.out.println(ballX + " " + ballY);
}
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
mouseX = e.getX();
repaint();
}
}
you haven't registered the implemented MouseMotionListener to any component:
game.addMouseMotionListener(game);
you are not adding your first created instance of PongGame to the frame rather added a new one producing bug:
PongGame game = new PongGame();
window.add(new PongGame()); // <<--- why creating the new instance ?
// it should be window.add(game);
As a good programming practice: try putting the add listener code in the component's own creating context i.e., in their constructor to make your code better readable.
The problem is:
PongGame game = new PongGame();
window.add(new PongGame());
You have two instances of PongGame. One added to the frame (new PongGame()) and the other (game) that actually reacts to the timer. Change this row to:
window.add(game);
To correct the actual problem. Add a constructor (tested locally):
PongGame() {
addMouseMotionListener(this);
}
repaint() does not invoke paint() directly. It schedules a call to an intermediate method, update(). Finally, update() calls paint() (unless you override update).
The reason for this complexity is Java's support for concurrent programming. It does this using threads.
Using repaint() can be tricky for at least three reasons.
the interaction between repaint() and the spontaneous painting done by the GUI thread
the fact that repaint() just asks the thread system to schedule a call to update()/paint() and then exits. The repaint() method is asynchronous.
the problem of preventing your drawing from being erased when being updated.
I suggest you to try the same with update().
Useful link : http://www.scs.ryerson.ca/~mes/courses/cps530/programs/threads/Repaint/index.html
My question is about how to get repaint() to work properly when executed from a method, or more specifically from an actionlistener. To illustrate my point, the moveIt() method when executed from the initial go(), invokes repaint() as expected and you see the circle slide. When moveIt() is called from the button ActionListener, the circle jumps from start position to end position. I have included a println statement before the call to repaint() and inside the repaint() and you can see that repaint() is called 10 times at startup and only once when the button is pressed. - Thanks in advance for your assistance.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class SimpleAnimation {
int x=70;
int y=70;
MyDrawPanel drawPanel;
public static void main(String[] args) {
SimpleAnimation gui = new SimpleAnimation();
gui.go();
}
public void go(){
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawPanel = new MyDrawPanel();
JButton button = new JButton("Move It");
frame.getContentPane().add(BorderLayout.NORTH, button);
frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
button.addActionListener(new ButtonListener());
//frame.getContentPane().add(drawPanel);
frame.setSize(300,300);
frame.setVisible(true);
// frame.pack(); Tried it with frame.pack with same result.
moveIt();
}//close go()
public void moveIt(){
for (int i=0; i<10; i++){
x++;
y++;
drawPanel.revalidate();
System.out.println("before repaint"); //for debugging
drawPanel.repaint();
try{
Thread.sleep(50);
}catch (Exception e){}
}
}//close moveIt()
class ButtonListener implements ActionListener{
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
moveIt();
}
}//close inner class
class MyDrawPanel extends JPanel{
public void paintComponent(Graphics g){
System.out.println("in repaint"); //for debugging
g.setColor(Color.white);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.blue);
g.fillOval(x, y, 100, 100);
}
}//close inner class
}
Calling Thread.sleep in the ActionListener blocks the EDT and causes the GUI to "freeze". You could use a Swing timer here instead.
You are performing a long running task (sleeping) on the EDT(Event Dispatch Thread). As a result when the EDT is sleeping it cannot update the UI and repaints do not work as expected.
To correct the situation always sleep on a seperate thread.
Use SwingWorker or Timer for such situations
Look at this post for more info about how to access swing components in a thread-safe way.
UPDATE: This page explains it better.