I'm writing a simple Game of Life program in Java and am having a bit of trouble getting it to animate. I've got a JComponent class called LifeDraw, which displays a grid of pixels, with the following paint method:
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int y = 0; y < lGrid.getHeight(); y++) {
for (int x = 0; x < lGrid.getWidth(); x++) {
if (lGrid.getCell(x,y) == 1) {
g.setColor(Color.red);
g.fillRect(x * lGrid.getSqSize(), y * lGrid.getSqSize(), lGrid.getSqSize(), lGrid.getSqSize());
} else {
g.setColor(Color.white);
g.fillRect(x * lGrid.getSqSize(), y * lGrid.getSqSize(), lGrid.getSqSize(), lGrid.getSqSize());
}
}
}
}
And then another class LifeGrid that has a method run(), which when called will update the grid of pixels for one generation and then call LifeDraw.repaint(). However, if I try to call run() in a loop, the JComponent doesn't repaint until the loop is finished so all that is ever displayed is the first generation and the last one. I figured it was probably just updating too quickly to repaint, so I tried using Thread.sleep() between iterations but still had the same problem. Theoretically (or at least I was hoping it would), it should repaint the component between each iteration and display an animation of the pixels changing.
I'm not that well versed in Java GUI, so any help would be really appreciated. Hopefully I've explained it clearly enough, let me know otherwise!
I don't know if it would solve your problem, but you could try calling run() from a Timer instead of a simple for or while loop.
From the JavaDocs for repaint():
Adds the specified region to the dirty region list if the component is
showing. The component will be repainted after all of the currently
pending events have been dispatched.
All repaint does is signal that the area needs repainting.
Try paintImmediately or update.
Repaint has to be fired in the event loop, not in another thread.
Try replacing your call to repaint() with following code:
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
repaint();
}
});
Related
I've searched for similar threads like this but couldn't find any.
I am wondering if there is a more efficient way of rendering a tile, which only uses 1 image and draws the same image in a grid to cover an area.
I'm using the code below:
public void render(Graphics g) {
for(int r = 0; r <= tilePieces.length; r++) {
for(int c = 0; c <= tilePieces[0].length; c++) {
try {
tilePieces[r][c].render(g);
}
catch(Exception e) {
}
}
}
}
When I create a tile, I also create as many tile pieces (squares) it needs and I store them in an array.
Every time the render() method of the object is called, it loops through the array and calls their render() methods, which draw the image.
There is no lag in the game, but I find this way of doing this inefficient, since I have to create a bunch of objects for one tile and call their render methods.
That's why, I tried drawing the same one image in the method above, instead of calling the render() method of each tile piece, and that caused the game to run like 1 frame per 5 seconds. Which is weird, what is the difference between calling a method to draw your image and drawing your image directly in the loop?
I have done a program that numerically solves a set of differential equations which describes how an "arbitrary" illness move in an isolated and constant population, it was a programming assignment from a class I took a while ago. What I've done to extend it is to add some graphical components that can pause, reset and "play" the simulation, as well as some components that allows me to change some constants in the equations.
All this was an exercise in programming as I find it to be fun and exciting and want to become better.
However, at the moment I'm stuck, what I want to do now is to make a very simple form of animation of it. I want to visualize the data I get for the number of infected, susceptibles and resistants in a grid as points. I managed to create the grid and have an idea of how to place the dots.
The problem I have is how to draw the dots as the program is working, I can draw one dot in the grid but only as the grid is created, that's it. I need to be able to create a dot at a specific place in the grid, this goes on until the number of dots reaches a finite number, say 30. At that points I want to have the first dot, the one the left, removed, all the dots shifted to the left and place the new dot at the furthest right of the grid, the whole thing is then repeated.
I think I will be able to figure it out with some help/hints about the paintComponent() method and whether I need to use repaint() method at all, I can't get my head around these for some reason. I've read through my course literature on Java, but despite the extensive sections where he explains most of the different graphical components he does not say that much about those methods, only that you don't call for the paintComponent() method, it is done automatically.
If there is something unclear let me know and I'll try to clarify it.
Thanks in advance.
//
Fox Mulder
I think I will be able to figure it out with some help/hints about the paintComponent() method and whether I need to use repaint() method at all, I can't get my head around these for some reason.
Basically, say you create a custom component by extending JPanel. When you #Override the paintComponent() method, it get implicitly called for you, so you never have to call it. So what ever you paint inside the method, gets drawn on your surface. For example
public class DrawingPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(x, y, 10, 10);
}
}
When you call repaint() you are basically causing the paintComponent method to be call implicitly. So to answer your question, Yes you will need to call it if you want to animate, as you will need to update some kind of variable (like the x and y) in the paintComponent() method, to see any change in the drawing.
You can see more at Performing Custom Painting
Not to handle the actual animation, you'll want to use a javax.swing.Timer. You can see more at How to use Swing Timers. Here's the basic construct
Timer ( int delayInMillis, ActionListener listener )
where delayInMillis is the time to delay between ticks(in this case animations) and the ActionListener listens for "ticks". Each tick, the actionPerformed of the ActionListener is called. There, you can put the code to update any variables you use for animation.
So for example you update the x and y, in the actionPerformed, then call repaint()
public class DrawingPanel extends JPanel {
int x = 50;
int y = 50;
public DrawingPanel() {
Timer timer = new Timer(40, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
x += 5;
y += 5;
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(x, y, 10, 10);
}
}
Now this was just a simple example. But in your case you want to animate a scatter plot. So what you can do is have a list of Points and in the actionPerformed you can add pull points from that list and push them into another list that is to be drawn. So say you have this
List<Point> originalPoints;
List<Point> pointsToDraw;
...
#Override
protected void paintComponent(Grapchics g) {
super.paintComponent(g);
for (Point point : pointsToDraw) {
g.fillOval(point.x - 5, point.y - 5, 10, 10);
}
}
Basically all the points in pointsToDraw list will be drawn. Initially it will be empty. And in the timer, you can add to the list, until the originalPoints list is exhausted. For example.
List<Point> originalPoints;
List<point> pointsToDraw;
private int currentIndex = 0;
public DrawingPanel(List<Point> originalPoints) {
this.originalPoints = originalPoints;
pointsToDraw = new ArrayList<>();
Timer timer = new Timer(40, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
if (currentIndex == originalPoints.size() - 1) {
((Timer)e.getSource()).stop();
} else {
pointsToDraw.add(originalPoints.get(currentIndex));
currentIndex++;
}
repaint();
}
});
timer.start();
}
So basicall you just keep a current index. When the index reaches the size of the original list, you stop the timer. Otherwise you just pop from the originalPoints and push to the pointsToDraw. For every point you add the pointsToDraw, a repaint() is called, and there will be another point for the paintComponent to draw a circle with.
The END
UDPATE
I just reread your question, and I think I have have misunderstood it. If you want all the points drawn, then basically just have one list. And draw all the points initially. with each tick, just remove the first index, advance all the rest up an index, and add a new one to the end. Though this is the implementation of a LinkedList so you may just want to use that
Heres part of my simple code.I want to achieve moving oval to cursors X axis location after clicking left button.Problem is that I can see only last position of oval (when it already stops).I think repaint method in while block doesnt work as I would like.I would like to see each move of oval as its getting into position of cursor.Thank you for suggestions.
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.*;
public class Testfile extends JPanel implements Runnable,MouseListener{
public static JFrame frame;
public int x;
public int y;
public int pointX;
public int pointY;
public void paintComponent(Graphics g){
super.paintComponent(g);
g.fillOval(x, y, 20, 20);
}
public static void main(String args[])throws InterruptedException{
Testfile z=new Testfile();
z.setBackground(Color.cyan);
frame=new JFrame("Test");
frame.setSize(500,500);
frame.add(z);
frame.addMouseListener(z);
frame.setVisible(true);
}
public void mouseClicked(MouseEvent e){
pointX=(int)MouseInfo.getPointerInfo().getLocation().getX();
pointY=(int)MouseInfo.getPointerInfo().getLocation().getY();
try{
while(x!=pointX){
x=x+1;
Thread.sleep(10);
repaint();
}
}
catch(InterruptedException v){System.out.println(v);}
}
.I think repaint method in while block doesnt work as I would like
Your problem has nothing to do with repaint "not working" and all to do with your tying up the Swing event thread. If you run a long-running process on the Swing Event Dispatch Thread (or EDT), the thread responsible for painting the GUI and interacting with the user, your GUI freezes and won't paint itself or respond until the EDT is released.
Solution: don't use a while (true) loop or Thread.sleep(...) on the Swing event thread. Also:
Use a Swing Timer instead to act as your animation "loop".
Another possible solution is to use a background thread to do the Thread.sleep(...), but in my opinion this is not worth the trouble since a Swing Timer will work so well and is easier to implement correctly.
Also:
Don't add your MouseListener to the JFrame but rather to your drawing JPanel. Otherwise you'll find that you'll be off in the y direction by the height of the title bar.
Use the mousePressed(...) method not mouseClicked(...) since the former is more forgiving.
Get the deltaX and deltaY on mousePressed, the direction that the circle should go by subtracting x from pointX and y from pointY.
I've gotten your code to work by checking the Manhattan distance between x and y and pointX (manHattanDistance = Math.abs(x - pointX) + Math.abs(y - pointY);) and pointY, and stopping the timer if it gets below a minimum. I've also saved the prior Manhattan distance and have checked the differences between the old and new one to make sure that the oval doesn't over-shoot, kind of as a fail-safe.
Use doubles to hold your x, y, pointX, pointY, etc, and cast to int when drawing.
Don't forget to cast your Graphics object to a Graphics2D and use RenderingHints to turn antialiasing on. This will make for prettier graphics.
Avoid "magic" numbers. Use constants instead.
Consider using x and y for the center of your circle rather than the left upper corner.
For example, my paintComponent(...) method could look like:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// RADIUS is an int const and = 10
g.fillOval((int) x - RADIUS, (int) y - RADIUS, 2 * RADIUS, 2 * RADIUS);
}
As above. you need to do these things on separate threads.
Sometimes you will need to decrement x so check if it's already bigger than the the point
clicked, or the program will keep incrementing it indefinitely. Also you'll probably want to do the same with y
I have a program selection tool that i made. it opens a JFrame with 17 buttons, 15 of which are customizable, and they get their text from a .txt document located in the C: drive. when i click the assign button, it opens a JFileChooser to select a file to open when the button is clicked. You then select a button to change, and then type the text you want displayed by the button. After that the program rewrites the .txt file and updates the buttons. here is the code for updating:
public static void restart() {
start.assignButtonActions();
start.assignButtonText();
start.paint(graphics);
}
public void assignButtonActions() {
/**
* assign button actions
*/
for (int i = 0; i < buttonAction.length; i++) {
buttonAction[i] = io.readSpecificFromHD("C:\\ButtonActions.txt", i
+ 1 + actionButton.length);
}
}
public void assignButtonText() {
for (int i = 0; i < actionButton.length; i++) {
/**
* set button text
*/
actionButton[i].setText(io.readSpecificFromHD(
"C:\\ButtonActions.txt", i + 1));
}
}
public void paint(Graphics g) {
g.drawImage(getImage("files/background.png"), 0, 0, FRAMEWIDTH,
FRAMEHEIGHT, null);
refresh();
}
public void refresh() {
graphics.drawImage(getImage("files/background.png"), 0, 0, FRAMEWIDTH,
FRAMEHEIGHT, null);
for (int i = 0; i < actionButton.length; i++) {
actionButton[i].repaint();
}
assignButton.repaint();
helpButton.repaint();
}
Thats all the code that is required for this question i believe. The problem is, after the method restart() is called, the background is there, with a white square around the buttons, with it being white inside the square. not really a major problem, but really incredibly annoying and pretty unprofessional. At first i thought it was that the buttons were resizing after the background is painted, so i made it so that the refresh runs twice each time its called. didnt help one bit.
EDIT:
I fixed the problem. I took hovercraft's answer and modified what i learned a little bit. all i had to do was modify the restart() method to:
public static void restart() {
start.assignButtonActions();
start.assignButtonText();
start.repaint();
}
because the repaint(); repaint the whole component which was what hovercraft said. Thank you a ton everyone! hope this helps future questions.
You appear to be handling your Swing graphics incorrectly by calling paint(...) directly and trying to use a Graphics object outside of a JComponent's paintComponent(...) method. Don't do this, as all the Swing graphics tutorials will tell you (if you've not gone through some of them yet, you will want to do this soon). Instead do all graphics within a JComponent's (such as a JPanel's) paintComponent(...), call the super's method first, and use the Graphics object provided by the JVM in the paintComponent's method parameter.
Edit
Tutorial links:
The introductory tutorial is here: Lesson: Performing Custom Painting.
The advanced tutorial is here: Painting in AWT and Swing.
I'm thinking that you'll have to re-write most of your graphics code. Changes you should make:
Draw only in a JPanel or other JComponent-derived class, not in a JFrame or other top-level window.
Draw in your class's paintComponent(...) method.
Place an #Override annotation just above your paintComponent(...) method to be sure that you are in fact overriding the super method.
Call the super's paintComponent(...) as the first line (usually) of your paintComponent(...) override method.
Use the Graphics object passed into this method by the JVM.
Do not use a Graphics object obtained by calling getGraphics() on a component (with rare exceptions).
Do not give your class a Graphics field and try to store the Graphics object in it. The Graphics objects given by the JVM do not persist and will quickly become null or non-usable.
Do not call paint(...) or paintComponent(...) directly yourself (with rare exceptions -- and your current code does not qualify as one of the exceptions, trust me).
You will likely not need to call repaint() on your JButtons
Hi i want to know how to call the paint method...
I am a novice programmer and am really just experimenting with things like paint.
The program i am trying to make is the game where there are 3 rungs and the aim of the game is to move different sized disks from the left/right rung to the right/left rung.
here is my code(no where near finished give me a break):
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int amount = 0;
// get the amount of rectangles to draw to represent disks
while (amount <= 1 && amount >= 5) {
System.out.print("Please enter amount of discs to use(less than 7 more than one).");
amount = scan.nextInt();
}
}
public void paint(Graphics g) {
// draw a certain amount of rectangles in ascending order
if (amount <= 1 && amount >= 5) {
for (int i = 0; i < amount; i++) {
g.fillRect(220 - (20 * i), 200 + (10 * i), 100 - (20 * i), 20);
}
}
}
The paint method will be called for the first time upon creation of the object.
To force the paint() method to be called again you can either call update(Graphics g) if you want to pass in a new Graphics object, but normally I would suggest using repaint() method, since this way it will be scheduled to be called asap.
You don't need to call it. Instead, you should use the main loop which Java creates for you.
The usual approach is to extend JPanel (see this question: How to make canvas with Swing?) and override the paint() method.
Now create a JFrame, add the new UI component to it and open the frame. Java will then make sure it gets rendered.
I am not expert or something else to teach others but you need to put your code to be painted to paintComponent(Graphics g) method instead of paint and then call repaint method.