On Head First Java book we're seeing some little animation and I'm trying to draw an animation that traces out a diagonal line. I'm doing so by using the paintComponent() method and drawing an oval at x,y (values which are updated each time through the loop). Why do I lose the previously drawn ovals? According to the book I should be getting a smear on the screen where the previously drawn ovals are not lost. This should need fixing by adding to the paintComponent() method a white background every time repaint() is called but instead i'm not getting the 'mistake'. Why is this and how do I get to KEEP the previously drawn ovals on the panel? Using JDK 13.0.2 and Mac OSX Catalina
import javax.swing.*;
import java.awt.*;
public class SimpleAnimation {
int x = 70;
int y = 70;
public static void main(String[] args) {
SimpleAnimation gui = new SimpleAnimation();
gui.go();
}
public void go() {
JFrame frame = new JFrame();
MyDrawPanel drawPanel = new MyDrawPanel();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(drawPanel);
frame.setSize(300,300);
frame.setVisible(true);
for (int i = 0; i < 130; i++) {
x++;
y++;
drawPanel.repaint();
try {
Thread.sleep(25);
} catch (Exception ex){};
}
}
class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.orange);
g.fillOval(x,y,50,50);
}
} // close inner class
} // close outer class
Is that what prevents the smear of ovals?
class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.orange);
g.fillOval(x,y,50,50);
}
The code should be:
class MyDrawPanel extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g); // added
g.setColor(Color.orange);
g.fillOval(x,y,50,50);
}
You need the super.paintComponent(g) to clear the background of the panel before doing the custom painting.
Yeah, great book. The output i'm getting with the code as is, is the "corrected version"
Edit:
The book is correct. You need to understand how the EDT works. When you start an application the code invoked in the main() method executes on a separate Thread. Swing events and painting is done on the Event Dispatch Thread (EDT). So invoking sleep() in the go() method should NOT affect the painting of the circle and you should see the smear. If you only see a single oval painted after the loop is finished, then this imples your IDE or platform starts the code in the main() method on the EDT, which is not normal.
You can verify my above statement by adding:
System.out.println( SwingUtilities.isEventDispatchThread() );
to your loop to see if it is executing on the EDT or not.
Related
So basically it won't leave a trail. I tried to remove super.paint and i've tried to make multiple but it either creates an error or it doesn't. I've gone throught it atleast 10 times which is why i went here. Thanks in advance!
import javax.swing.*;
import java.awt.*;
public class Grafik extends JPanel {
private int x = 0;
private void moveBall()
{
x += 1;
}
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillRect(x, 50, 20, 80);
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
Grafik grafik = new Grafik();
frame.setSize(700, 800);
frame.setLocation(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(grafik);
frame.setTitle("Mitt spel");
frame.setResizable(false);
frame.setVisible(true);
while(true)
{
grafik.repaint();
grafik.moveBall();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Problems/Solutions:
Swing painting is done in the paintComponent method override, not the paint method.
Be sure to call the `super's paintComponent method first thing in your own override method, so the JPanel can do house-keeping painting
Your animation loop should be a Swing Timer, not a while (true) loop as the latter risks running afoul of Swing threading rules
If you want to create an animation but leave persisting images within the drawing then
Either create an ArrayList of objects, perhaps Points, that represent the trail, and in your paintComponent method draw the trail using a for loop that iterates through the ArrayList, or
use a BufferedImage as a background image, one that is drawn within the paintComponent method immediately after calling super.paintComponent(g) but before drawing the moving sprint. Draw your trail into this BufferedImage by getting a Graphics object out of it, by calling getGraphics() on the BufferedImage.
package games;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class viza extends JPanel implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 1L;
int x=0, y=200;
Timer tm =new Timer(5,this);
public viza(){
tm.start();
}
public void paintComponent(Graphics g){
g.setColor(Color.red);
g.fillRect(x, y, 20, 20);
}
public void actionPerformed(ActionEvent e){
x=x+1;
y=y+1;
if(x>300)
x=0;
if(x<0)
x=0;
repaint(); //after x and y are changet then I use repaint();
} // the frame is created and the new object is added into the frame.
public static void main(String[] args){
viza a=new viza();
JFrame frame = new JFrame();
frame.setSize(500,500);
frame.add(a);
frame.setVisible(true);
}
}[1]
The code is used to draw a filled rectangle on the panel. However when I start the program the object moves but the panel is not repainted. If I try and resize the window while the program is runing it does load properly. As soon as i stop doing that the panel or frame (Not sure) is not repainted anymore. So i end up whith a line.
You should clear the underlying pane before you re-draw the rectangle in its new position.
To do that, let the super.paintComponent() do that for you, since that would be the correct approach to custom painting in any JComponent:
public void paintComponent(Graphics g){
super.paintComponent(g); // let it do the default paint
g.setColor(Color.red);
g.fillRect(x, y, 20, 20);
}
Also you may want to add a default close operation to your frame (in main method) to exit the program after closing the frame:
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
Another tip is to set a bigger timeout for your Timer, because 5 milliseconds is occurring very fast and your user may not see the movement. Try something bigger than 50 or 100.
Good Luck.
I am trying to draw a rectangle which its position is updated every second, for that I have a class which extends JPanel and in it I have overriden the paint ( or paintComponent) function_ I have tried both _ but apparanetly this function is called only once and as it is shown in the code below when I try to call it in an infinite loop with repaint function it doesnt get called, any ideas what I can do?
public class Board extends JPanel implements KeyListener{
public void setUpBoard(){
JFrame frame = new JFrame();
Board board = new Board();
frame.setVisible(true);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(600, 600);
frame.setLocation(350, 80);
frame.add(board);
}
public void paint(Graphics g){
g.setColor(Color.RED);
g.fillRect(food.getX(),200,20,20);
}
}
the code above is the graphic part, below is the main function, which is placed in another class :
public static void main(String[] args) throws InterruptedException {
Board board = new Board();
FoodGenerator food = new FoodGenerator();
board.setUpBoard();
while(true){
board.repaint();
food.adder();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
in the code above food.adder is where the position of the rectangle is updated, which I have checked and doesnt have any issues.
The problem is that you're creating a new Board object in setUpBoard and adding that to your JFrame:
Board board = new Board();
// ...
frame.add(board);
So when you use repaint(), you're repainting the instance of Board that you created in the main method, and not the instance you created in setUpBoard, which is the one you add to the frame.
This can be easily fixed by using Board board = this; in setUpBoard, or, even simpler in my opinion, just using frame.add(this). Subsequent calls to repaint will then schedule a call to paint for the same Board object that you created in the main method.
Also, since you're working with Swing, don't use paint, and instead use paintComponent, making sure that super.paintComponent(g) is the first statement in the method body.
Another problem is that the repaint calls are being done on the main thread, not on the event thread.
I am working on trying to make a GUI with a rectangular vehicle object in the middle of the page with regards to x-coordinate and two rectangular objects on either side of the vehicle.
I am extending a JPanel, so I call repaint in the run method to call the paintComponent method, but I am not even entering the paintComponent method. Additionally, do I have to do anything differently because I am working with Graphics2D?
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Display extends JPanel implements Runnable{
public static final int frameWidth=1300;
public static final int frameHeight=800;
public double score;
public double updateTimeInterval=25;
public double prevUpdatedTime=0;
public Display(){
JFrame frame = new JFrame();
frame.setSize(frameWidth, frameHeight);
frame.setTitle("You are playing HoverTeam!!!");
frame.setVisible(true);
JPanel panel = new JPanel();
frame.getContentPane().add(panel);
System.out.println("completed constructor.");
}
public void paintComponent(Graphics g){
System.out.println("currently painting.");
Graphics2D g2 = (Graphics2D) g;
/*
* Testing with random GameState
*/
double[] pos = {28,6,Math.PI/8};
double[] vel = {5,5,0};
int[] nearList = {4,8,7,5};
GameState gs = new GameState(pos,vel,2,2,nearList,3);
//GameState gs = GameClient.getGameState();
/*
* Drawing the vehicle in the center of the screen with regards to the x-coordinate and then referencing the walls to it.
*/
Path2D.Double vehic = gs.getVehicleShapePath(frameWidth/2, gs.getPosition()[1]);
g2.draw(vehic);
int[] nearObstHeights = gs.getNearObstList();
double vehiclePast = gs.getPosition()[0]%5; //distance that the vehicle is past the second obstacle--reference to where to draw obstacles
for (int i =0; i<nearObstHeights.length;i++){
Rectangle2D.Double obstacle = new Rectangle2D.Double(frameWidth/2 -vehiclePast+5*(i-1),nearObstHeights[i],1,nearObstHeights[i]);
g2.draw(obstacle);
}
score = gs.getPosition()[0]/5;
g.drawString("Score:"+score, frameWidth/2, frameHeight-10);
}
public void run(){
/*
* No maximum score, game goes on forever.
*/
System.out.println("entereed run method.");
while (true){
long currentTime = System.currentTimeMillis();
if (currentTime-prevUpdatedTime>updateTimeInterval){
System.out.println("entered if statement");
prevUpdatedTime = currentTime;
repaint();
System.out.println("should have just repainted.");
}
}
}
public static void main(String[] args){
(new Thread(new Display())).start();
}
}
Thanks
Your code is very confusing, each object has to take care of its own responsabilities and nothing more. In particular:
Display is a JPanel... and also a Runnable? Why is that? You can have a runnable doing work with the GUI (carefully) but it shouldn't be the GUI itself.
Display is a JPanel but its constructor is doing all sorts of things like creating a frame. The constructor of Display only has to take care of constructing the JPanel. Create your own class MyApplication that does all the work, or an static method createAndShowGUI() and call it from your main.
Why are you creating a thread in the main? Or rather, why the same statment that creates the
Then you have some problems in your code:
The first thing you almost certianly do in your paintComponent is calling super.paintComponent(g).
You should learn what the Event Dispatching Thread is. Look at how to setup the initial threads in Swing. In short you must call invokeLater in your main and get rid of that new Thread(). And however you organize your code, do not execute that runnable in the EDT, it has an infinite loop and the GUI would freeze.
You never put any Display object in your frame. The code creating the GUI should be something like this:
.
//we create the GUI here, this is called by main inside the runnable of `invokeLater`.
public static creteAndShowGUI(){
JFrame frame = new JFrame();
frame.setSize(frameWidth, frameHeight);
frame.setTitle("You are playing HoverTeam!!!");
//JPanel panel = new JPanel(); //this is an empty panel, you want the Display subclass that you created.
//we use the Display panel that will use the `paintComponent` to paint itself
Display panel = new Display();
frame.getContentPane().add(panel);
System.out.println("completed constructor.");
frame.setVisible(true);
}
You don't need that runnable at all, the GUI is repainted when needed by its own. You only have to call repaint() if you are making changes in it with your code.
Edit: As per your comments I'll clarify something: Whatrepaint() does is schedule the repainting of the object in the screen, that process includes, at some point, calling paintComponent. However you are calling repaint() on an object that it's not in the screen. Your call repaint() or rather this.repaint() is repainting the object new Display() that you create in the main (and is aregument of new Thread() however you don't add that object to any frame, it's not in the GUI so there's nothing to repaint on the screen.
I have been playing around with Java's 2d painting tools and have hit a snag. I am attempting to move the objects. Here is the code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Test extends JPanel{
private int[] location = new int[2];
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillArc(location[0], location[1], 100, 100, 45, 90);
g.setColor(Color.black);
g.fillArc((location[0]+50-10),(location[1]+50-10), 20, 20, 0, 360);
new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setLocation((location[0]+50),50);
repaint();
System.out.println("repainting");
}
}).start();
}
public void setLocation(int x, int y){
this.location[0] = x;
this.location[1] = y;
}
public static void main(String[] args){
JFrame jf=new JFrame();
jf.setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
jf.setPreferredSize(new Dimension(300,500));
jf.setLocation(100,100);
jf.add(new Test());
jf.pack();
jf.setVisible(true);
}
}
This only paints one of the two objects to the screen... it seems to be the second one as when I change the parameters of setLocation on [1] the one object it does paint moves. Any thoughts? Thanks
Edit: Edited above code to reflect what was said below.
You are adding two components to the JFrame in a default way. This will add the components BorderLayout.CENTER and so the second component will cover and obscure the first. You will want to read up on layout managers to fix this. Also read up on Swing Timers for simple animations, since your code, even if written correctly would do no animation.
If you want to move the drawing, then
Use only one Test JPanel
Override JPanel's paintComponent(...) method, not paint(...) method.
call the super.paintComponent(g) method first thing in your paintComponent method override.
Give the Test JPanel public methods to allow outside classes to change the location without having them directly futz with the field. Make the location field (name should begin with a lower-case letter) private just to be safe.
Use a Swing Timer to periodically call this method and change location, then call repaint() on the JPanel.