Java Swing ignores repaint()? - java

Basically I need one of my panels to be repainted at least 60 times per second. However I noticed, that if I don't move my mouse, FPS drops to ~5. I wrote program to test it.
package test;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Test extends JFrame implements ActionListener {
Timer t;
JLabel l;
JPanel p;
long lastT;
public static void main(String[] args){
new Test();
}
public Test(){
add(p = new JPanel());
p.add(l = new JLabel("0000000000000000000000000000000"));
pack();
lastT = System.nanoTime();
t = new Timer(10, this);
t.setRepeats(true);
t.start();
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
#Override
public void actionPerformed(ActionEvent e) {
long time = System.nanoTime();
l.setText(String.valueOf((time - lastT)));
repaint();
lastT = time;
}
}
It shows the interval between frames. If my mouse is inside the window, it changes much faster, if it is outside of window, it changes much slower, similar to my original problem. But the thing is, the number itself is not much different no matter if mouse is inside or outside, which means that Timer is shooting event at same interval. So it means that the repaint() is ignored? How can it be fixed?

Please note that repaint is never guaranteed to work as the Swing repaint manager will ignore stacked repaint requests -- that is if repaint requests build up and are not able to be handled in a timely fashion due to code being run from the Swing event queue, only the last one is called. Please read Painting in AWT and Swing for more.
Note however that there is no need to call repaint() in your code above, since changing the state of the JLabel's model will trigger a repaint on its own, and this would be a repaint of just the label itself, something that should be run more efficiently than calling repaint on the entire GUI. Also note that 10 mSec is a very short time slice and a Swing Timer may not be accurately or reliably called at 10 msec.

Related

visualizing JPanel change within a loop

I am new to Java swing programming. I want to make a frame which will appear red and blue in turn one after another. So, I took 2 child JPanel, 1 for red and other for blue, and a for-loop. On each iteration I remove one panel from parent panel and add another. But, when I run the program it only shows the last state of the frame.
Can anyone explain why? And what's the intended approach to make a program work like that?
My code:
public class Test2 extends JFrame {
public Test2() {
JPanel Red = new JPanel(new BorderLayout());
JPanel Blue = new JPanel(new BorderLayout());
//...initialize Red and Blue
Red.setBackground(Color.red);
Blue.setBackground(Color.blue);
Red.setPreferredSize(new Dimension(200,200));
Blue.setPreferredSize(new Dimension(200,200));
JPanel panel = new JPanel(new BorderLayout());
panel.setPreferredSize(new Dimension(200,200));
add(panel);
pack();
setTitle("Border Example");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
int M = 1000000; //note that, I made a long iteration to not finish the program fast and visualize the effect
for(int i=0;i<M;i++)
{
if(i%(M/10)==0) System.out.println(i); //to detect whether the program is running
if(i%2==0)
{
panel.removeAll();
panel.repaint();
panel.revalidate();
panel.add(Red,BorderLayout.CENTER);
}
else
{
panel.removeAll();
panel.repaint();
panel.revalidate();
panel.add(Blue,BorderLayout.CENTER);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Test2 ex = new Test2();
ex.setVisible(true);
}
});
}}
Don't use a loop. Swing will only repaint the frame once the entire loop has finished executing.
Instead you need to use a Swing Timer. When the Timer fires you invoke your logic. Read the section from the Swing tutorial on How to Use Swing Timers.
Here is a simple example of a Timer that simply displays the time every second: Update a Label with a Swing Timer
Also, don't remove/add panels. Instead you can use a Card Layout and sway the visible panel. Again read the tutorial on How to Use CardLayout.
Basically you don't need to use a while (or any other) loop, Swing only paints once it has finished that loop then repaint the GUI.
As stated before by #camickr on his answer, you could try a Swing Timer; here's an example that does exactly what you want.
From your comment on another answer:
Could you please explain why "repaint" does not work in a loop? And why is the Timer working without a "repaint"?
Swing is smart enough to know it doesn't needs to repaint in a loop, instead it will repaint once it the loop finishes, if you read the tutorial on Swing Custom Paint on the step 3 it says:
"Swing is smart enough to take that information and repaint those sections of the screen all in one single paint operation. In other words, Swing will not repaint the component twice in a row, even if that is what the code appears to be doing."
And Timer will repaint it, because it's not running on the EDT but in it's own Thread
I would suggest to take in one step at a time.
First make it run without changing panels / colors.
Now it doesn't because this
public final void Test2() {
is a method (which is never used) and not a constructor.
Change to a constructor declaration like :
public Test2() {
to make the program do something. Then you can go to the next step.
Also use Java naming conventions (like blue instead of Blue).

Pause game and add flashing text Java

I have this loop
while (true) {
game.update();
view.repaint();
Thread.sleep(DELAY);
}
In the game.update various components of the game have their position changed and those updates are reflected when the repaint() method is called on the view. The view extends JComponent and loops through the game objects and calls their print methods.
What I want to do is have a boolean called nextLevel in the game and if it's true Flash text on the screen for the player to notify them that they're going onto the next level. Maybe flash 4-5 times. Then continue the game.
Is this possible? I have been playing around with Thead.Sleep() but this only seems to pause the displaying and in the background the game is still going on.
Any ideas on how to do this?
Maybe you want to avoid threading by using a Timer object.
an example like that could be
int flashTimer = 0;
if(nextLevel) {
Timer timer = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
//flash something method here
flashTimer++;
}
});
timer.start();
}
and then check your flashTimer if it reaches the number you want then just stop the timer by timer.stop();
Just an idea which seems to me a bit simpler. the 1000 value is milliseconds which is passed and executes the code inside the actionPerformed method every 1 sec.
Hope it helped

Java - JProgress bar not showing (threaded)

I am adding a feature to a program to save some content to file. The progress is shown by a progress bar (in its own JFrame), but the progress bar is only being displayed on the last value it reads. I have a global being updated by the main thread, that represents the % of work completed, and the other thread reads this global and updates the progress bar accordingly.
Right now when it runs, the JFrame is empty, then activity completes, then the progress bar shows itself with complete amount. How do i make it update the progress as it goes along (and show the JProgressbar from the start)? Here is my code:
public class GenomeAnnotator{
private JProgressBar csvProgressBar;
private JFrame csvSaveLoadFrame; //for the progress bar
private Container csvCon;
private double csvPercentSaved; //% of work completed
public JFrame m_frame; //main program frame
....
public static void main(String[] args){
...
showGUI();
...
}
public void showGUI(){
...
JMenu file = new JMenu("File");
JMenu exptann = new JMenu("Export annotation..);
JMenuItem exptcsv = newJMenuItem("CSV format");
exptcsv.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
..determine output file + checks...
System.out.println("Writing to .csv file......");
csvSaveLoadFrame = new JFrame("Saving to csv file..");
csvProgressBar =new JProgressBar(0,100);
csvSaveLoadFrame.setSize(300,100);
csvCon = csvSaveLoadFrame.getContentPane();
csvCon.setLayout(null);
csvProgressBar.setBounds(10,10,280,20);
csvCon.add(csvProgressBar);
csvSaveLoadFrame.setResizable(false);
csvSaveLoadFrame.setVisible(true);
ORF [] ora= orfPanel.getAcceptedOrfs();
int val;
double toload = blastData.size() + ora.length; //how much work
double loaded=0.0; //how much work completed
/*Thread that will read % value from global and update prog. bar*/
Thread progressBarMover = new Thread() {
#Override
public void run() {
int previous=0;
while(csvPercentSaved<100){
csvProgressBar.setValue((int)csvPercentSaved);
//tried putting a sleep() in here when testing
//values from global is read successfully
}
}
System.out.println("Thread done!");
csvPercentSaved = 0; //reset value when done
csvSaveLoadFrame.setVisible(false);
}
};
progressBarMover.start();
for (int k=0; k<blastData.size(); k++) {
..do output work...
loaded+=1; //update % values
csvPercentSaved = (loaded/toload)*100;
val = (int)csvPercentSaved;
System.out.println("main complete "+val+"%");
}
for (int k=0; k<ora.length; k++) {
...do more ouput work...
loaded+=1;
csvPercentSaved = (loaded/toload)*100; //update % value
val = (int)csvPercentSaved;
System.out.println("main complete "+val+"%");
}
System.out.println("Output file finished!");
csvPercentSaved = 100;
}
});
exptann.add(exptcsv);
file.add(exptann);
}
EDIT
found solution here:
https://weblogs.java.net/blog/mkarg/archive/2010/01/03/did-you-know-swingworker-can-send-progress-status
Several issues there:
Most most important (and I missed this initially!), you're not doing your long running code within the background thread but rather within the Swing event thread, the EDT. I am meaning these two for loops: A) for (int k=0; k<blastData.size(); k++) {...} and B) for (int k=0; k<ora.length; k++) {...} which looks to be the code where you're loading or saving information. This will freeze your GUI right up.
Also important, you're doing Swing calls from within a background thread, including setting the progress bar's value and setting a JFrame's visiblity, something that you never want to do, and that mostly negates the benefits of using the background thread in the first place.
In other words, you're doing all your Swing threading work exactly backwards -- making Swing calls from the background thread and running the long process in the event thread.
Instead, do the opposite -- do all the long-running work in a background thread and make all of the non-thread-safe Swing calls on the EDT.
One way to do this is to use a SwingWorker, do your loading and saving from within its doInBackground(...) method
and set its progress field as progress is being made..
You would then monitor the worker's progress field in a PropertyChangeListener, this being done on the EDT, and then use this to set your progress bar's value.
Or if you have to use your own background thread, then
Have the inner class implement Runnable, not extend Thread
If you make Swing calls from within your background thread, then wrap these calls in a Runnable and queue them onto the Swing event thread via SwingUtilities.invokeLater(yourRunnable)
More minor issues:
You should not be using null layouts and absolute positioning but rather use layout managers. While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.
Your secondary dialog window should be a JDialog, and probably a modal JDialog, not another JFrame. You're not creating and showing a new stand-alone program, but rather are displaying a dialog window off of the main GUI window. If you want the main GUI window non-functioning while the dialog is displayed, then the modal JDialog is the way to go, as it works just like a JOptionPane (which is a form of a modal JDialog), and makes the calling window non-functional while its visible.
For some of my code examples:
How do I make my SwingWorker example work properly?
For a lot more of my examples

Updating Swing components while blocking the GUI

I was programming a GUI today, which is doing longer calculations when pressing a button. While the calculations are running, I wanted to use intermediate results of the still running calculation and write them to a JLabel. The GUI however, should not be operable by the user before the calculation has finished.
At first I was doing something like this:
(1)
public class GUI extends JFrame {
JLabel label = new JLabel("Status: ");
public GUI(){...}
public void calculate() {
for(int i = 0; i < 10; i++) {
String one = calculationPartOne(i);
label.setText("Status: " + one);
label.repaint(); //*
calculationPartTwo(i);
}
}
}
This did not work, the JLabel would only update after the calculation has finished. I also tried to .repaint() and .validate() all components involved at the line commented *, but it did nothing.
So, after trying and searching Google/StackoOverflow the whole day I finally have a working solution, but I still do not understand why above does not work. I wanted the GUI to block, so naturally I ran the calculation in the same thread. However, calling any methods to repaint the GUI -inbetween- the calculation (making the calculation stop while the GUI is updated) did not work, and I do not understand why. Can someone explain?
In the end, I used the SwingWorker class to do my calculations, and use it's functions to update the JLabel while calculating. However, as I need the GUI to block, I now disable -all- the components before excuting the SwingWorker and have the SwingWorker re-enable all the components after finishing the calculation.
So, I use SwingWorker, to not block the EDT, but then "fake" to block the EDT by disabling everything? This seems really paradox to me.
Here is an outline of what I have now:
public class GUI extends JFrame {
JLabel label = new JLabel("Status: ");
//I didn't use a list, but it works to illustrate it here
List<Component> GUIComponents = ...;
public GUI() {...}
public void calculate() {
SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
protected Void doInBackground() throws Exception {
for(int i = 0; i < 10; i++) {
String one = calculationPartOne(i);
publish(one);
calculationPartTwo(i); //**
}
}
protected void done() {
setEnableGUI(true);
}
protected void process(List<String> chunk) {
label.setText(chunk.get(chunk.size() - 1));
}
};
setEnableGUI(false);
worker.execute();
}
public void setEnableGUI(boolean e) {
for(Component c : GUIComponents) {
c.setEnabled(e);
}
}
//**
public void calculationPartTwo() {...}
}
This works.
I hope someone can clarify. This solutions feels wrong.
why wrong? the gui thread is for responding to user events only - so you should be doing your heavy lifting in the background - which is what youre doing with a SwingWorker.
also, the best way to prevent a user from changing a componenet is to do exactly that - disable the component before starting the heavu lifting, and enable once its done.
only thing you might want to consider is displaying the results of your calculation in a modal dialog - a JDialog that will pop above the parent window and block it. you could display the intermediate results and progress in this dialog and then once the calculation is done the dialog will close and unblock the UI obscured by it. this will save you fron having to disable all gui components indiviually in a loop and will also give you an option to have a "cancel" button to halt the work immediately.
However, calling any methods to repaint the GUI -inbetween- the calculation (making the calculation stop while the GUI is updated) did not work, and I do not understand why. Can someone explain?
repaint() requests are handled by the RepaintManager and are not done immediately. The RepaintManager basically schedules the repaint. Since repainting is done on the EDT, it can't be done until the EDT is free.
So, I use SwingWorker, to not block the EDT, but then "fake" to block the EDT by disabling everything? This seems really paradox to me.
You can always use an indeterminated JProgressBar. See How to Use Progress Bars.
Or maybe you would prefer to use the Disabled Glass Pane approach.
In some cases you can use:
label.paintImmediately(...);
to force the repainting of a component. But you still have the issue of disabling the GUI so its probably not a solution you should really be using.

How do you assign a per second limit on a paint method in java which repaint itself based on resource availability?

I'm somewhat new to creating games in Java, however my current setup is such that the FPS on the paint method is bounded only by the system. So, my FPS tends to be between 300 and 450. In order to standardize movement speeds on objects, I've been dividing the increment by the FPS so that it will increment that total amount in a one second time-frame.
I have the following code. What I want to do it make it so that map.addEntity() is not called 300 or 400 times per second, in accordance with the FPS; but rather make it so that I can choose, for example, to make the projectile fire at 10 RPS or so. How can I achieve this?
public void mousePressed(MouseEvent e) {
if (gameStarted)
shootProjectile = true;
}
public void paint(Graphics g) {
if (shootProjectile)
map.addEntity(new Projectile("projectile.png", x, y, 0, projectileSpeed));
}
I've been dividing the increment by the FPS
Don't do that! Use a Timer or a swingTimer to update everything at a constant rate. For example you could do something like this:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer(10,new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
updateProjectilePositions();
}
});
timer.start();
}
private static void updateProjectilePositions() {
// TODO Auto-generated method stub
}
}
Note that if you use a swing Timer, your projectile updating will happen on the swing thread. If the updating hangs, your GUI will also hang.
You also should not call map.addEntity inside paint() since paint() does one thing and one thing only: paint everything. You can get around this but mixing up the code that updates things like position with code that renders the objects will eventually bite you.
You should never use FPSs as your quantizer otherwise your game will run at different speeds on different machines and even on the same machine according to frame drops or spikes.
You can do two different things:
use a delta time between each frame update and update your game logic proportionally
use an accumulator in which you add the time delta and then you fire a logic update every fixed amount (so that you don't need to do things in proportion as your logic update will be fixed)

Categories

Resources