I am trying to render a complicated set of objects, and instead of trying to render each object individually, I thought it would be faster to render them as a BufferedImage. The only way I could figure out how to do that was to turn the BufferedImage into an ImageIcon, and set that to a JLabel, and add the JLabel to the JFrame. To update the image, I remove the JLabel, set it with the new BufferedImage, and re-add it to the JFrame. This makes the screen flash rapidly as you can see an empty frame in between each rendering. If I don't remove the label, the program runs extremely slowly. How do I fix this? Should I even be using JLabels or is there a better way?
public static void createWindow() {
frame = new JFrame("PlanetSim");
frame.setPreferredSize(new Dimension(WINDOW_X, WINDOW_Y));
frame.setMaximumSize(new Dimension(WINDOW_X, WINDOW_Y));
frame.setMinimumSize(new Dimension(WINDOW_X, WINDOW_Y));
foregroundLabel = new JLabel(new ImageIcon(foreground));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(foregroundLabel);
frame.pack();
frame.setVisible(true);
}
public static void setForeground() {
frame.getContentPane().remove(foregroundLabel);
foregroundLabel = new JLabel(new ImageIcon(foreground));
frame.getContentPane().add(foregroundLabel);
frame.pack();
}
I doubt the problem has to do with the loop itself, but I'm including it anyway just in case.
public void run() {
long lastTime = System.nanoTime(), now; //keeps track of time on computer in nanoseconds
double amountOfTicks = 60.0; //number of ticks per rendering
double delta = 0; //time step
long timer = System.currentTimeMillis(); //timer to display FPS every 1000 ms
int frames = 0;
while (running) {
now = System.nanoTime();
delta += (now - lastTime) * amountOfTicks / 1000000000;
lastTime = now;
while(delta >= 1) {
tick();
delta--;
}
if (running)
render();
frames++;
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("FPS: " + frames);
frames = 0;
}
}
}
public void render() {
populateWindow();
Window.setForeground();
}
frame.getContentPane().remove(foregroundLabel);
foregroundLabel = new JLabel(new ImageIcon(foreground));
frame.getContentPane().add(foregroundLabel);
frame.pack();
I would suggest there is no need to remove/add the label or pack the frame since I would assume the Icon will be the same size every time.
All you need to do is replace the Icon of the label.
//frame.getContentPane().remove(foregroundLabel);
foregroundLabel.setIcon( new ImageIcon(foreground) );
//frame.getContentPane().add(foregroundLabel);
//frame.pack();
I am trying to render a complicated set of objects, and instead of trying to render each object individually, I thought it would be faster to render them as a BufferedImage
Otherwise just do the rendering in your paintComponent() method. Swing is double buffered by default so it essentially removes the need to create the BufferedImage.
See: get width and height of JPanel outside of the class for an example of custom painting. Just update the ball count from 5 to 100 to make it more complex.
Related
The animation isn't showing on the Pane background and it slows down the other animation already present. I pass on the Pane background as a parameter so I don't know maybe that is the cause but I have other methods doing that and it's not causing any problems.
public static void fireAnimation(Pane animatedBackground) {
AnimationTimer animationTimer = new AnimationTimer(){
#Override
public void handle (long now){
listFireParticles.addAll(addFireParticles(200,200));
for(Iterator<FireParticles> iteratorFirePart = listFireParticles.iterator(); iteratorFirePart.hasNext();){
FireParticles fireParticle = iteratorFirePart.next();
fireParticle.particlesUpdate();
if(!fireParticle.isAlive()){
iteratorFirePart.remove();
continue;
}
fireParticle.render(graphicsContext);
}
Canvas canvas = new Canvas (400, 400);
graphicsContext = canvas.getGraphicsContext2D();
animatedBackground.getChildren().add(canvas);
}
};
animationTimer.start();
System.out.println("ANIMATION");
}
You are creating a new canvas for each animation frame, rather than reusing an existing one.
You take a Pane and you add a new Canvas to it.
You do this an AnimationTimer handle method.
The timer method is called each pulse.
Be default, JavaFX generates a pulse sixty times a second.
In one second, your pane will contain sixty canvases.
In a minute there will be 3600 canvases.
In a day there will be five million one hundred and eighty four thousand canvases.
At some point before that, something is going to slow down and break.
FAQ
How would I make the canvas stop being created so many times? I can't figure it out.
Remove the lines which create a canvas and add it to the scene graph from the animation timer handle method.
Create the canvas and add it to the scene graph (e.g. a pane) only once, outside of the handle method.
Store a reference to the canvas and access the reference in the handle method.
You likely have other issues in code not shown, so don't expect a simple fix as outlined above to just make your application work as you expect.
Perhaps something roughly like this:
final int W = 200;
final in H = 200;
List<FireParticle> fireParticles = createFireParticles(W, H);
Canvas canvas = new Canvas (W, H);
animatedBackground.getChildren().add(canvas);
AnimationTimer animationTimer = new AnimationTimer() {
#Override
public void handle (long now){
GraphicsContext2D graphicsContext = canvas.getGraphicsContext2D();
graphicsContext.clearRect(0, 0, W, H);
if (fireParticles.isEmpty()) {
this.stop();
animatedBackground.getChildren().remove(canvas);
return;
}
for (Iterator<FireParticle> iteratorFirePart = fireParticles.iterator(); iteratorFirePart.hasNext();) {
FireParticle fireParticle = iteratorFirePart.next();
fireParticle.particlesUpdate();
if (!fireParticle.isAlive()){
iteratorFirePart.remove();
continue;
}
fireParticle.render(graphicsContext);
}
}
};
animationTimer.start();
Important: the above code is indicative only. I make no assertion it will exhibit the behaviour you wish. It is untested and won't work stand-alone, I didn't even try to compile it. Without the requested minimal example, that is the best that can be provided.
Im trying to add a Score and Elapsed Time label (scoreAndTimer) to my already working snake game code. The problem is when I use scoreAndTimer.setText(); it stacks with previous text.
I tried to setText(); then setText(String); to clear previous one but it doesnt work also.
private JLabel scoreAndTimer;
private int sec, min;
private Game game;
public Frame() {
JFrame frame = new JFrame();
game = new Game();
frame.add(game);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("Snake");
frame.setResizable(false);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
scoreAndTimer = new JLabel();
scoreAndTimer.setVerticalAlignment(SwingConstants.TOP);
scoreAndTimer.setHorizontalAlignment(SwingConstants.CENTER);
frame.add(scoreAndTimer);
timer();
}
private void timer(){
while(game.isRunning()){
scoreAndTimer.setText("SCORE: "+(game.getSnakeSize()-3)+" Elapsed Time: "+timeFormatter());
try{
if(sec == 60){
sec = 0;
min++;
}
sec++;
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
if(!game.isRunning())
scoreAndTimer.setText("Game Over");
}
private String timeFormatter(){
if(sec < 10 && min < 10)
return "0"+min+":0"+sec;
else if(sec >= 10 && min < 10)
return "0"+min+":"+sec;
else if(sec < 10 && min >= 10)
return min+"0:"+sec;
else
return min+":"+sec;
}
public static void main(String[] args) {
new Frame();
}
}
Program is working well but could'nt prevent overlap. There is no error. Im using totally 3 Threads in my program, im not sure if threads are making a problem about this. Code is a bit long thats why i dont share the rest for now, if needed i can share other parts also but i dont think the problem occurs at other classes.
JFrame, or more precisely it contentpane uses BorderLayout by default.
When you add components to a JFrame:
frame.add(game);
You implicitly add it to the BorderLayout.CENTER position, which is the default position. So frame.add(game); is equivalent to frame.add(game, BorderLayout.CENTER);
The BorderLayout.CENTER position (as well as other BorderLayout positions) can hold one component.
The problem is that you add another component to the same BorderLayout.CENTER position by:
frame.add(scoreAndTimer);
The solution is to add scoreAndTimer to a different position:
frame.add(scoreAndTimer, BorderLayout.PAGE_END);
and have
frame.pack();
frame.setVisible(true);
at the end, after you have added all components.
Important side note:
timer() as written is not going to work. Think of Swing Application as an application that runs on a single thread. When this thread is
busy with running the long while loop (like the one you have in timer(), it does not update the gui. The gui becomes unresponsive(freezes).
Use Swing timer.
I have a class that creates a JFrame and needs to have one image displayed in the JFrame. Images must rotate every few seconds.
I've tried using repaint(), revalidate(), and validate() on both the JFrame and JPanel and none seem to work. Following is my current code to update the JFrame/JPanel. This code will wait 4 seconds and then display the fourth image, but I would like it to update every second with a new image.
public void startSlideshow(){
int i = 0;
Long oldTime = System.currentTimeMillis();
do {
Long currentTime = System.currentTimeMillis();
if( currentTime - oldTime >= 1000 ) {
img = new JLabel(new ImageIcon("Albums/" + album + "/" + imageNames.get(i)));
panel.removeAll();
panel.setBackground(Color.BLACK);
panel.add(img);
panel.revalidate();
panel.repaint();
i++;
oldTime = System.currentTimeMillis(); // reset reference time
}
} while(i<imageNames.size());
panel.removeAll();
panel.setBackground(Color.BLACK);
panel.add(img);
panel.getRootPane().revalidate();
panel.repaint();
}
Any help is greatly appreciated as I've been banging my head for a while now. Thanks.
You're blocking the Event Dispatching Thread, preventing it from process new repaint requests.
Instead of using a loop and Thread.sleep, try using a javax.swing.Timer
Take a look at How to Use Swing Timers and Concurrency in Swing
You may also find it simpler to change the JLabel's icon instead of creating a new JLabel on each time through...
I need a certain image to be redrawn at different locations constantly as the program runs. So I set up a while loop that should move an image across the screen, but it just redraws the image on top of itself over and over again. What am I doing wrong? Is there a way to delete the old image before drawing it in a new location?
JFrame frame = buildFrame();
final BufferedImage image = ImageIO.read(new File("BeachRoad_double_size.png"));
JPanel pane = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int num = 0;
boolean fluff = true;
while (fluff == true) {
num = num + 1;
g.drawImage(image, num, 0, null);
if (num == 105) {
fluff = false;
}
}
}
};
frame.add(pane);
You can't code a loop in the paintComponent() method. The code will execute so fast that the image will only be painted in the final position, which in your case should be with an x position of 105.
Instead you need to use a Swing Timer to schedule the animation every 100 milliseconds or so. Then when the timer fires you update the x position and invoke repaint() on the panel. Read the Swing tutorial on Using Swing Timers for more information.
Putting a while loop inside a paintComponent method is not the way to do it. Instead, there should be some setup like the following:
...
final int num = 0;
final JPanel pane;
Timer timer = new Timer(10, new ActionListener() {
public void actionPerformed(ActionEvent e) {
num++;
pane.repaint();
}
});
pane = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, num, 0, null);
}
});
timer.start();
This will move the image ever 10 milliseconds, as specified in the Timer constructor.
This is a common issue people starting out in animation have, as I did. You can't 'remove an image' from the screen. However, you can repaint the entire screen, then redraw your image at a new location.
In psuedocode:
while (condition)
background(white); //or whatever color your background is
drawImage(x,y);
The code above clears the screen so it's safe for you to redraw your image. This effectively 'deletes' your image.
Edit: I didn't read your code, I just addressed your question. So other answers that fix your code are probably better than mine.
I wish to place a small Jframe right above the Button, on ActionPerformed
I directly tried to get the X (getX()) and Y(getY()) co-ordinates of the JScrollPane in which the button is added, but it always seems to return wrong co-coordinates
values returned by jScrollPane1.getLocation()
java.awt.Point[x=10,y=170]
The above values are same independent on where I place the JScrollPane on the screen.
This works if I remove the JScrollPane and directly try to get the Jpanels co-ordinates!!
for example
private void showDialog() {
if (canShow) {
location = myButton.getLocationOnScreen();
int x = location.x;
int y = location.y;
dialog.setLocation(x - 466, y - 514);
if (!(dialog.isVisible())) {
Runnable doRun = new Runnable() {
#Override
public void run() {
dialog.setVisible(true);
//setFocusButton();
//another method that moving Focus to the desired JComponent
}
};
SwingUtilities.invokeLater(doRun);
}
}
}
This nice method will help you:
// Convert a coordinate relative to a component's bounds to screen coordinates
Point pt = new Point(component.getLocation());
SwingUtilities.convertPointToScreen(pt, component);
// pt is now the absolute screen coordinate of the component
Add: I didn't realise, but like mKorbel wrote, you can simply call
Point pt = component.getLocationOnScreen();
Since you want to spawn a new frame right above a given component, you want to get the screen coordinates of your component.
For this, you need to use the getLocationOnScreen() method of your component.
Here is a useful code snippet :
public void showFrameAboveCmp(Frame frame, Component cmp) {
Dimension size = cmp.getSize();
Point loc = cmp.getLocationOnScreen();
Dimension frameSize = frame.getSize();
loc.x += (size.width - frameSize.width)/2;
loc.y += (size.height - frameSize.height)/2;
frame.setBounds(loc.x, loc.y, frameSize.width, frameSize.height);
frame.setVisible(true);
}