Is it possible to repaint a JPanel from within a loop in another object? I have a JFrame that consists of a JPanel (DrawPanel) and a SA object. I would like to update/repaint the JPanel during the while loop in this SA object. I started a new thread, but still panel.repaint() does not execute.
public class Mainform extends JFrame {
private DrawPanel DrawPanel;
public static void main(String[] args) {
DrawPanel panel = new DrawPanel();
SA sa = new SA(panel);
Thread t = new Thread(sa);
t.start();
//...
}
}
public class DrawPanel extends JPanel implements MouseMotionListener, MouseListener {
public DrawPanel() {
super();
setBackground(Color.WHITE);
addMouseWheelListener(this);
addMouseListener(this);
addMouseMotionListener(this);
}
//...
}
public class SA implements Runnable {
private DrawPanel panel;
public SA(DrawPanel p) {
this.panel = p;
init();
}
public void run() {
while (true) {
//...
panel.repaint();
}
}
}
EDIT: run is public
The basic answer is "yes".
This assumes that the component you are trying to repaint is
Added to a container
That container is attached to some kind of native peer (ie a window)
That window is visible.
The RepaintManager is generally smart enough to know not to waste time painting something that isn't displayable.
The following example is rather basic, but will increment a counter within the paintComponent of a JPanel each time it is called. The Runnable, which is attached to a Thread, will update every second...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class RepaintTest {
public static void main(String[] args) {
new RepaintTest();
}
public RepaintTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane tp = new TestPane();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(tp);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Thread thread = new Thread(new Repainter(tp));
thread.setDaemon(true);
thread.start();
}
});
}
public class Repainter implements Runnable {
private JPanel panel;
public Repainter(JPanel panel) {
this.panel = panel;
}
#Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
panel.repaint();
}
}
}
public class TestPane extends JPanel {
private int repaints = 0;
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics g2d = (Graphics2D) g.create();
repaints++;
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(repaints);
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(text, x, y);
g2d.dispose();
}
}
}
Related
I want to make a little rain program in swing, but for some reason I cannot repaint the panel from another class. I tried using an inner class for the panel this time, but it doesn't seem to work with repainting it from another class/thread. Does someone know why?
sscce:
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
public class UI extends JFrame {
public static void main(String[] args) {
UI myProgram = new UI();
myProgram.setVisible(true);
}
public UI() {
this.setSize(new Dimension(500,300));
this.setBackground(Color.WHITE);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
UserPanel p = new UserPanel(this);
}
public class UserPanel extends JPanel implements ActionListener {
private Timer time = new Timer(1, this);
private UI myFrame;
public UserPanel(UI myFrame) {
this.myFrame = myFrame;
this.setSize(myFrame.getSize());
time.start();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("painting");
g.setColor(Color.BLACK);
g.fillRect(this.getWidth()/2, this.getHeight()/2, 50,50);
}
}
}
UI Class (with inner class JPanel):
package Rain;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
public class UI extends JFrame {
public UI() {
this.setSize(new Dimension(500,300));
this.setBackground(Color.WHITE);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
UserPanel p = new UserPanel(this);
}
private class UserPanel extends JPanel implements ActionListener {
private Timer time = new Timer(1, this);
private UI myFrame;
private ArrayList<Raindrop> rain = new ArrayList<Raindrop>();
private static final int AMOUNT = 50;
private Random rand = new Random();
public UserPanel(UI myFrame) {
this.myFrame = myFrame;
this.setSize(myFrame.getSize());
for(int i = 0; i < AMOUNT; i++) {
createRain();
}
new Painter(this);
time.start();
}
public void createRain() {
float distance = rand.nextFloat() * 90 + 10;
int x = rand.nextInt(this.getWidth());
int y = 100;
rain.add(new Raindrop(distance,x,y));
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("tick");
for(Raindrop r : rain) {
r.fall();
}
}
public void paintComponent(Graphics g) {
System.out.println("painting");
g.setColor(this.getBackground());
g.fillRect(0,0,this.getWidth(),this.getHeight());
for(Raindrop r : rain) {
r.draw(g);
}
}
}
}
Painter:
package Rain;
import javax.swing.JPanel;
public class Painter extends Thread {
private JPanel p;
public Painter(JPanel p) {
this.p = p;
this.start();
}
public void run() {
while(true) {
System.out.println("trying to paint..");
p.repaint();
}
}
}
Console Output:
trying to paint..
tick
trying to paint..
tick
...
Expected Output:
trying to paint..
painting
tick
trying to paint..
...
The thread does work but it never calls the paintComponent(Graphics g) function in the panel
All Swing applications must run on their own thread, called EDT. (Hopefully, you start your application by calling SwingUtilities#invokelater method). So, repainting a component outside of Event Dispatch Thread is really bad bad (bad) idea. Instead of creating new Thread, repaint the component inside javax.swing.Timer's action listener since it will run in EDT.
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("tick");
for(Raindrop r : rain) {
r.fall();
}
repaint(); //repaint in EDT
}
Also, when you #Override paintComponent method, always start by calling super.paintComponent(g);
public void paintComponent(Graphics g) {
super.paintComponent(g);//let component get painted normally
System.out.println("painting");
g.setColor(this.getBackground());
g.fillRect(0,0,this.getWidth(),this.getHeight());
for(Raindrop r : rain) {
r.draw(g);
}
}
UPDATE after your SSCCE
In order a component to get painted, it must have a parent. You UserPanel p = new UserPanel(this); but you never add it to the frame:
UserPanel p = new UserPanel(this);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(p);
The complete SSCCE:
public class UI extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> { //Run in EDT
UI myProgram = new UI();
myProgram.setVisible(true);
});
}
public UI() {
super("title");//call super for frame
this.setSize(new Dimension(500, 300));
this.setBackground(Color.WHITE);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
UserPanel p = new UserPanel(this);
//Use border layout to make p fit the whole frame
getContentPane().setLayout(new BorderLayout());
getContentPane().add(p, BorderLayout.CENTER);
}
public class UserPanel extends JPanel implements ActionListener {
private Timer time = new Timer(1, this);
private UI myFrame;
public UserPanel(UI myFrame) {
this.myFrame = myFrame;
this.setSize(myFrame.getSize());
time.start();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("painting");
g.setColor(Color.BLACK);
g.fillRect(this.getWidth() / 2, this.getHeight() / 2, 50, 50);
}
}
}
Don't ignore the SwingUtilities.invokeLater.
I have tried a ton of different things to try to get the JLabel to show but I don't understand why it is not working. I have tried resizing it, though that is not what i want to do, I have tried other classes, but I would prefer to stick with this one, and it is starting to get really frustrating. If you have any ideas please help. But please try to keep them simple and explain very clearly as I am still quite new to java. I have only been going for about three or four months. Here is my code:
package com.thefallenpaladin;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
/**
* Created by darkp_000 on 11/4/2015.
*/
#SuppressWarnings("serial")
public class Game extends JPanel implements KeyListener,MouseListener {
public boolean mainMenu = true;
public int winWidth = 700; //Window Stats
public int winHeight = 600;
public int buttonOneX = 60; // Button Stats
public int buttonOneY = 240;
public int buttonOneW = 100;
public int buttonOneH = 75;
public boolean buttonOne = false;
public int mouseX; // not set because it is set in mouseClicked
public int mouseY;
public static void main(String[] args) {
Game game = new Game();
JFrame window = new JFrame("I hate this");
JLabel onePlayer = new JLabel();
onePlayer.setLocation(0,0/*game.buttonOneX + game.buttonOneX/2,game.buttonOneY + game.buttonOneY/2*/);
window.add(game);
window.setFocusable(true);
window.setResizable(false);
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
window.setSize(700,600); //TODO
window.setVisible(true);
game.requestFocusInWindow();
game.add(onePlayer);
game.addKeyListener(game);
game.addMouseListener(game);
window.setLocationRelativeTo(null);
while(true) { // Main Game loop
onePlayer.setText("One Player");
game.repaint();
game.customUpdate();
}
}
public void customUpdate() {
if(mouseX > buttonOneX && mouseX < buttonOneX+buttonOneX && mouseY > buttonOneY && mouseY < buttonOneY+buttonOneY && mainMenu) {
buttonOne = true;
System.out.print("Starting Game");
}
}
public void paint(Graphics g) {
if(mainMenu) {
g.setColor(Color.CYAN); // Set main menu
g.fillRect(0,0,winWidth,winHeight);
g.setColor(Color.GREEN);
g.fillRect(buttonOneX,buttonOneY,buttonOneW,buttonOneH);
}
if(buttonOne) {
mainMenu = false;
g.setColor(Color.GREEN);
g.fillRect(0,0,winWidth,winHeight);
}
}
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
System.out.println(e);
}
public void keyReleased(KeyEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
// System.out.println(e);
mouseX = e.getX();
mouseY = e.getY();
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
}
Okay so you've made a couple of basic mistakes...
First, JLabel onePlayer = new JLabel(); creates an empty label, with no size (0x0) and since labels are transparent by default, you'd not see it
Next, you've overridden paint of a top level container (JFrame), but failed to honor the paint chain effectively preventing any of the child components from ever getting painted
public void paint(Graphics g) {
if (mainMenu) {
g.setColor(Color.CYAN); // Set main menu
g.fillRect(0, 0, winWidth, winHeight);
g.setColor(Color.GREEN);
g.fillRect(buttonOneX, buttonOneY, buttonOneW, buttonOneH);
}
if (buttonOne) {
mainMenu = false;
g.setColor(Color.GREEN);
g.fillRect(0, 0, winWidth, winHeight);
}
}
So, if I remove your paint method and change JLabel onePlayer = new JLabel(); to JLabel onePlayer = new JLabel("I'm a label"); I get this output...
Also...
while (true) { // Main Game loop
onePlayer.setText("One Player");
game.repaint();
game.customUpdate();
}
has the potential to try screw up your program, you have no guarantee's in what thread your main method is been called and you should not make assumptions.
Start by creating a custom component, extending from something like JPanel and override it's paintComponent method, place your custom painting there. In fact, you should have a panel for each state of your game (menu, running, settings, etc).
Add these to your frame (probably using a CardLayout to enable you to easily switch between them)
Use either a Thread or Swing Timer as a main game loop, one which you create explicitly.
Have a look at Painting in AWT and Swing, Performing Custom Painting, How to Use CardLayout and How to use Swing Timers for some more details
As a "conceptual" example...
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class AwesomeGame {
public static void main(String[] args) {
new AwesomeGame();
}
public AwesomeGame() {
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 ContentPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface NavigationController {
public void letsPlay();
}
public class ContentPane extends JPanel implements NavigationController {
private CardLayout cardLayout;
private GamePane gamePane;
public ContentPane() {
cardLayout = new CardLayout();
setLayout(cardLayout);
add(new MenuPane(this), "TheMenu");
add((gamePane = new GamePane()), "TheGame");
cardLayout.show(this, "TheMenu");
}
#Override
public void letsPlay() {
cardLayout.show(this, "TheGame");
gamePane.play();
}
}
public class MenuPane extends JPanel {
public MenuPane(NavigationController navigationController) {
JLabel label = new JLabel("My Super Dupa Awesome Game!");
label.setFont(label.getFont().deriveFont(Font.BOLD, 48));
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(label, gbc);
JButton play = new JButton("Play Now!");
play.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
navigationController.letsPlay();
}
});
add(play, gbc);
setBackground(Color.GREEN);
}
}
public class GamePane extends JPanel {
public GamePane() {
setBackground(Color.BLUE);
}
public void play() {
Timer timer = new Timer(500, new ActionListener() {
int count;
#Override
public void actionPerformed(ActionEvent e) {
count++;
if (count % 2 == 0) {
setForeground(Color.BLACK);
} else {
setForeground(Color.RED);
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
String text = "I bet you're blowen away by it's awesomness!";
FontMetrics fm = g2d.getFontMetrics();
int x = (getWidth() - fm.stringWidth(text)) / 2;
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.drawString(text, x, y);
g2d.dispose();
}
}
}
i have an application containing a jframe, this jframe then adds a jpanel which constains an image. the jpanel is displayed for a given time, then removed from the jframe and another jpanel is added.
I want to fade in and out between the images, and ive done this using a timer
private void fadeOut() {
ActionListener fadeOutAc = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
opacity += 10;
if (opacity >= 255) {
opacity = 255;
fadeOutT.stop();
}
repaint();
}
};
fadeOutT = new Timer(20, fadeOutAc);
fadeOutT.start();
}
private void fadeIn() {
ActionListener fadeInAc = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
opacity -= 10;
if (opacity <= 0) {
opacity = 0;
fadeInT.stop();
}
repaint();
}
};
fadeInT = new Timer(10, fadeInAc);
fadeInT.setInitialDelay(200);
fadeInT.start();
}
public void paint(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(picColor.getRed(), picColor.getGreen(), picColor.getBlue(), opacity));
g.fillRect(0, 0, presWin.getWidth(), presWin.getHeight());
}
i recently moved the fading in/out from the jpanel to the jframe instead. The problem is, that in the jpanel, the repaint only had to draw an image, now it has to repaint the entire jpanel each time. Is there a way to call repaint without having the paint the components, only the rectangel?
To me, it seems a bit silly to put the functionality in the JFrame when what you seem to want is a container which can fade it's content in and out. This way you can isolate the responsibility to a single container/class which can be placed or used in what ever way you want in isolation to the rest of the UI.
Basically, this example uses a FadingPane (based on a JPanel) to control the fading process, but onto which I place JLabel which holds the actual images.
Fading is controlled through the use of a AlphaComposite, meaning that this panel will actually physically fade in and out, not just change fill color ;)
There is also a FadingListener which provides additional notifications about the fading process, really only interested in fadeOutDidComplete, so you can switch the images and fade the panel back in, but you never know...
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
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 JLabel label;
private FadingPane fadingPane;
private File[] pictures;
private int index;
public TestPane() {
// Just for show
setBackground(Color.RED);
fadingPane = new FadingPane(new FadeListener() {
#Override
public void fadeDidStart(FadingPane panel) {
}
#Override
public void fadeDidStop(FadingPane panel) {
}
#Override
public void fadeOutDidComplete(FadingPane panel) {
nextPicture();
fadingPane.fadeIn();
}
#Override
public void fadeInDidComplete(FadingPane panel) {
}
});
setLayout(new BorderLayout());
fadingPane.setLayout(new BorderLayout());
label = new JLabel();
fadingPane.add(label);
add(fadingPane);
JButton next = new JButton("Next");
add(next, BorderLayout.SOUTH);
next.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fadingPane.fadeOut();
}
});
pictures = new File("/Volumes/Disk02/Dropbox/MegaTokyo/thumnails").listFiles(new FileFilter() {
#Override
public boolean accept(File pathname) {
String name = pathname.getName().toLowerCase();
return name.endsWith(".jpg") || name.endsWith(".png");
}
});
nextPicture();
}
protected void nextPicture() {
index++;
if (index >= pictures.length) {
index = 0;
}
try {
BufferedImage img = ImageIO.read(pictures[index]);
label.setIcon(new ImageIcon(img));
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public interface FadeListener {
public void fadeDidStart(FadingPane panel);
public void fadeDidStop(FadingPane panel);
public void fadeOutDidComplete(FadingPane panel);
public void fadeInDidComplete(FadingPane panel);
}
public class FadingPane extends JPanel {
private float delta;
private float alpha = 1f;
private Timer timer;
private FadeListener fadeListener;
public FadingPane(FadeListener fadeListener) {
this.fadeListener = fadeListener;
// This is important, as we may not always be opaque
// and we don't want to stuff up the painting process
setOpaque(false);
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
float alpha = getAlpha() + delta;
if (alpha < 0.001f) {
alpha = 0f;
timer.stop();
fadeListener.fadeOutDidComplete(FadingPane.this);
} else if (alpha >= 1.0f) {
alpha = 1.0f;
timer.stop();
fadeListener.fadeInDidComplete(FadingPane.this);
}
setAlpha(alpha);
}
});
}
public float getAlpha() {
return alpha;
}
public void setAlpha(float value) {
if (alpha != value) {
this.alpha = Math.min(1.0f, Math.max(0.0f, value));
repaint();
}
}
#Override
public void paint(Graphics g) {
// I don't normally recomamned overriding paint, but in this case,
// I want to affect EVERYTHING that might be added to this panel
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.SrcOver.derive(getAlpha()));
super.paint(g2d);
g2d.dispose();
}
public void fadeIn() {
timer.stop();
fadeListener.fadeDidStop(FadingPane.this);
delta = 0.05f;
timer.restart();
fadeListener.fadeDidStart(FadingPane.this);
}
public void fadeOut() {
timer.stop();
fadeListener.fadeDidStop(FadingPane.this);
delta = -0.05f;
timer.restart();
fadeListener.fadeDidStart(FadingPane.this);
}
}
}
Thats totaly normal, moving your function to the JFrame and calling repaint function would actualy call repaint of your JFrame.
I think the best solution would be to pass panel as an argument to your fadeIn and fadeOut function and call its repaint methode for example fadeIn :
private void fadeIn(JPanel panelParam) {
ActionListener fadeInAc = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
opacity -= 10;
if (opacity <= 0) {
opacity = 0;
fadeInT.stop();
}
panelParam.repaint(); // here call repaint of the panel.
}
};
fadeInT = new Timer(10, fadeInAc);
fadeInT.setInitialDelay(200);
fadeInT.start();
}
With that you can apply your effect on any other panel.
Hope it helped.
I've written the following code
import java.awt.BorderLayout;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
final public class Test
{
JFrame frame;
DrawPanel drawPanel;
boolean up = false;
boolean down = true;
boolean left = false;
boolean right = true;
private int timeStep = 0;
private int ballYTravel = 100;
private int BALL_NUM = 24;
public static void main(String... args)
{
new Test().go();
}
private void go()
{
frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawPanel = new DrawPanel();
frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
frame.setResizable(false);
frame.setSize(800, 600);
frame.setLocationByPlatform(true);
frame.setVisible(true);
moveIt();
}
class DrawPanel extends JPanel
{
private static final long serialVersionUID = 1L;
public double getY(int i, int t) {
return 200 + ballYTravel / 2 * (Math.sin(timeStep * (i / 200 + 0.08)));
}
public void paintComponent(Graphics g)
{
for (int k = 0; k < BALL_NUM; k++ ) {
g.fillRect(100 + 20 *k , (int) getY(k, timeStep), 6, 6);
}
timeStep++;
}
}
private void moveIt()
{
while (true)
{
try
{
Thread.sleep(10);
}
catch (Exception e)
{
e.printStackTrace();
}
frame.repaint();
}
}
}
It runs and animates, however it is not animating in the same fashion as the Javascript code I referenced it from which can be found here http://codepen.io/anon/pen/ZYQoQZ
any help in understanding why is appreciated
There are (possibly) two basic problems...
In getY, you are ignoring the parameter t and using timeStep instead, while, technically, this probably isn't going to make a MASSIVE difference, it is an area of concern
You have an integer division issue. i/200 will result in int result, where you really want a double. Change it to i/200d
For example...
import java.awt.Dimension;
import java.awt.EventQueue;
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.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
final public class Test {
private int timeStep = 0;
private final int ballYTravel = 100;
private final int BALL_NUM = 24;
public static void main(String... args) {
new Test();
}
public Test() {
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 DrawPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
class DrawPanel extends JPanel {
private static final long serialVersionUID = 1L;
public DrawPanel() {
new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
timeStep++;
repaint();
}
}).start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 200);
}
public double getY(int i, int t) {
return 100 + ballYTravel / 2 * (Math.sin(t * (i / 200d + 0.08)));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int k = 0; k < BALL_NUM; k++) {
g.fillRect(10 + 20 * k, (int) getY(k, timeStep), 6, 6);
}
}
}
}
You're also breaking the paint chain, which is going to cause you issues in the long run, make sure you are calling super.paintComponent...
For more details see...
Performing Custom Painting
Painting in AWT and Swing
Concurrency in Swing
How to use Swing Timers
Initial Threads
Your transliteration reveals several problems:
Swing GUI objects should be constructed and manipulated only on the event dispatch thread.
Don't use setSize() when you really mean to override getPreferredSize().
Invoke pack() to let the container adopt its preferred size.
Use javax.swing.Timer to pace the animation.
Revised code, incorporating #Mad's fix and using drawOval():
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
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.Timer;
final public class Test {
JFrame frame;
DrawPanel drawPanel;
private int timeStep = 0;
private int ballYTravel = 100;
private int BALL_NUM = 24;
public static void main(String... args) {
new Test().go();
}
private void go() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawPanel = new DrawPanel();
frame.add(BorderLayout.CENTER, drawPanel);
frame.setResizable(false);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
Timer t = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
drawPanel.repaint();
}
});
t.start();
}
});
}
private class DrawPanel extends JPanel {
public double getY(int i, int t) {
return 200 + ballYTravel / 2 * (Math.sin(t * (i / 200d + 0.08)));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (int k = 0; k < BALL_NUM; k++) {
g.drawOval(100 + 20 * k, (int) getY(k, timeStep), 8, 8);
}
timeStep++;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(700, 500);
}
}
}
public class Ova extends JPanel implements ActionListener{
int x=0;
Timer timer=new Timer(100,this);
int y=0,x1=5,y1=5;
public static void main(String[] ds)
{
Ova ss=new Ova();
ss.nn();
}
private void nn() {
JFrame frame=new JFrame("fram");
frame.setSize(1000,600);
JPanel as=new JPanel();
frame.add(as);
frame.add(new Ova());
frame.setVisible(true);
timer.start();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawOval(x, y, 50, 50);
}
public void actionPerformed(ActionEvent e) {
if (x<0 || x>950){
x1=-x1;
}
if (y<0 || y>530)
{
y1=-y1;
}
x=x+x1;
y=y+y1;
repaint();
}
}
Whenever I put timer.start() inside paintComponent() the repaint() method works but if I start the timer outside the paint component method the repaint method does not work.Please explain the reasons. Thank you.
Problem
You have mis-matched reference issue
Start with this snippet...
public class Ova extends JPanel implements ActionListener {
int x = 0;
Timer timer = new Timer(100, this);
int y = 0, x1 = 5, y1 = 5;
public static void main(String[] ds) {
Ova ss = new Ova();
ss.nn();
}
private void nn() {
JFrame frame = new JFrame("fram");
frame.setSize(1000, 600);
JPanel as = new JPanel();
frame.add(as);
frame.add(new Ova());
frame.setVisible(true);
timer.start();
}
First, main creates an instance of Ova, then the method nn creates a new instance of Ova and then you start the timer...
Now the question you need to ask is, which Ovas timer did you start...the one on the screen or the one main created...
I can tell, it was the one that main created, which is not visible on the screen
(A possible) Solution
Start by never creating a JFrame inside a JPanel (or other component). The only time I would do something like that is via a static support method...
Remove the nn method and replace it with a begin (or something like that) method that starts the timer.
In the main method, create an instance of Ova, add it to an instance of JFrame and call Ova's begin method...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
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.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Ova extends JPanel implements ActionListener {
int x = 0;
Timer timer = new Timer(100, this);
int y = 0, x1 = 5, y1 = 5;
public static void main(String[] ds) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
Ova ova = new Ova();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(ova);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
ova.begin();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(600, 600);
}
private void begin() {
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(x, y, 50, 50);
}
#Override
public void actionPerformed(ActionEvent e) {
if (x < 0 || x > 950) {
x1 = -x1;
}
if (y < 0 || y > 530) {
y1 = -y1;
}
x = x + x1;
y = y + y1;
repaint();
}
}
Avoid calling setSize on anything, instead, override the getPreferredSize method of the component and return a preferred size and then use pack on the JFrame to wrap the frame around this. You will get far better and reliable results.