I have made a very simple code to show it here, i have a button that should show a JDialog to check the progress status, i am using the invoke late to go through EDT and my loop isn't in the run method, so why isn't my bar updating ?
here is the code
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class JBarEx extends JFrame {
private JTextField progStatus = new JTextField("Undefined");
private JButton dialogBtn = new JButton("Show Progression dialog");
final JDialog dlg = new JDialog((JFrame) null, "prog Title", false);
final JProgressBar dpb = new JProgressBar(0, 100);
public JBarEx() {
JPanel pan = new JPanel();
dialogBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
showProgress();
}
});
progStatus.setEditable(false);
pan.add(progStatus);
pan.add(dialogBtn);
setContentPane(pan);
this.setSize(200, 100);
setVisible(true);
}
public void showProgress() {
dlg.add(BorderLayout.CENTER, dpb);
dlg.add(BorderLayout.NORTH, new JLabel("prog message"));
dlg.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dlg.setSize(300, 75);
dlg.setLocationRelativeTo(null);
dlg.setVisible(true);
for (int i = 0; i < 100; i++) {
final int ii = i;
try {
Thread.sleep(25);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
updateBar(ii);
}
});
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void updateBar(int newValue) {
dpb.setValue(newValue);
}
public static void main(String[] args) {
JBarEx jbx = new JBarEx();
}
}
Your showProgress method is being executed within the context of the Event Dispatching Thread. The EDT is responsible for, amongst other things, processing paint requests. This means that so long as your for-loop is executing, the EDT can not process any new paint requests (or handle the invokeLater events either) as it is blocking the EDT.
While there are any number of possible ways to solve the problem, based on your code example, the simplest would be to use a SwingWorker.
It has the capacity to allow your to execute the long running task the a background thread (freeing up the EDT), but also allows you means for publishing updates (if required) so that they can be processed in the EDT and also provides handy progress notification.
For example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
public class SwingWorkerProgress {
public static void main(String[] args) {
new SwingWorkerProgress();
}
public SwingWorkerProgress() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JProgressBar pbProgress;
private JButton start;
public TestPane() {
setBorder(new EmptyBorder(10, 10, 10, 10));
pbProgress = new JProgressBar();
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(4, 4, 4, 4);
gbc.gridx = 0;
gbc.gridy = 0;
add(pbProgress, gbc);
start = new JButton("Start");
gbc.gridy++;
add(start, gbc);
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
start.setEnabled(false);
ProgressWorker pw = new ProgressWorker();
pw.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (name.equals("progress")) {
int progress = (int) evt.getNewValue();
pbProgress.setValue(progress);
repaint();
} else if (name.equals("state")) {
SwingWorker.StateValue state = (SwingWorker.StateValue) evt.getNewValue();
switch (state) {
case DONE:
start.setEnabled(true);
break;
}
}
}
});
pw.execute();
}
});
}
}
public class ProgressWorker extends SwingWorker<Object, Object> {
#Override
protected Object doInBackground() throws Exception {
for (int i = 0; i < 100; i++) {
setProgress(i);
try {
Thread.sleep(25);
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
}
Check out Concurrency in Swing for more details
Even if you fix the loop as others have pointed out, you'd still block the event dispatch thread. The for loop is run in showProgress() which is called from an event listener. The updates are pushed to the event queue, but that does not get processed until the loop has completed.
Use a Swing Timer instead. Something like this:
Timer timer = new Timer(25, new ActionListener() {
private int position;
#Override
public void actionPerformed(ActionEvent e) {
position++;
if (position < lastPosition) {
updateBar(position);
} else {
((Timer) e.getSource).stop();
}
}
});
timer.start();
where lastPosition would be the state where you want the progress bar to stop.
Unrelated to that bug, but a bug still, you should not create swing components outside the event dispatch thread. It's best to do it right from the start:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JBarEx jbx = new JBarEx();
}
});
}
for (int i = 0; i < 0; i++) {
You will never enter this code so will never call the updateBar(..) method
i needs to be greater than 0 in this case. If it is 1 then updateBar will be called once, if 2 then updateBar will be called twice etc
Also rather than doing
Thread.sleep(25);
take a look at java executors as these will help with your scheduling and remove the need for the sleep
Related
I have a process to be performed in an ActionListener call. This process can take some time. So I have to inform that is processing during the time of the execution in a label.
My problem is that Swing doesn't repaint the label during the thread of ActionListener, so I can't show the "Processing" message.
What is the best way to solve that?
package test;
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;
public class MyTestPane extends JPanel{
/**
*
*/
private static final long serialVersionUID = 340444475514103360L;
private JLabel myLabel;
private JButton myButton;
public MyTestPane() {
initComponents();
}
private void initComponents() {
myLabel = new JLabel("Ready");
myButton = new JButton("Start");
myButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
myActionListenter();
}
});
add(myLabel);
add(myButton);
}
private void myActionListenter(){
myLabel.setText("Starting count ...");
try{
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
myLabel.setText("Finishing count ...");
}
public static final void main(String argv[]) {
JFrame frame = new JFrame();
frame.add(new MyTestPane());
frame.pack();
frame.setVisible(true);
}
}
When you want to do something heavy (in terms of time required to be finished), like Thread.sleep, you can't simply do it in the same thread the GUI runs. When your heavy task starts on the same thread as the GUI, the GUI thread is busy calculating your heavy task and therefore it cannot receive any events. That's why the whole GUI freezes. Because it is busy.
Now in order to solve that read concurrency in Swing.
To sum it up for you, all Swing applications must run on their single-per-application thread. This thread is called the Event Dispatch Thread. Because all GUI events (mouse pointers moving, buttons clicked, windows resizing, etc) happen there.
So, when you want to do something heavy, you do it in another thread and since they are invisible to the user(GUI) they are called background threads. Swing has its own API to create background threads. They are called SwingWorkers. You can find how to use one in the doc.
Here is an example of doing something heavy (Thread.sleep) and at the same time publishing the progress (I don't know if you are interested in that). Also, I try to keep things some sort of abstract, in order to keep the concerns separated.
I suggest you to run it and have some experiments with it.
public class WorkerExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
MyPanel myPanel = new MyPanel();
myPanel.setPreferredSize(new Dimension(400, 300));
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(myPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static class MyPanel extends JPanel implements ProgressableView {
private JLabel countLabel;
private JProgressBar progressBar;
public MyPanel() {
super(new FlowLayout());
countLabel = new JLabel("Progress: 0");
add(countLabel);
progressBar = new JProgressBar();
progressBar.setVisible(false);
add(progressBar);
JButton startHeavyTaskButton = new JButton("Start Heavy Task");
startHeavyTaskButton.addActionListener(e -> {
startHeavyTaskButton.setEnabled(false);
startHeavyTaskButton.setText("Please wait..");
progressBar.setValue(0);
progressBar.setVisible(true);
Runnable restoreButtonAvailability = () -> {
startHeavyTaskButton.setEnabled(true);
startHeavyTaskButton.setText("Start Heavy Task");
};
new DoSomethingHeavyAndShowProgressWorker(this, restoreButtonAvailability).execute();
});
add(startHeavyTaskButton);
}
#Override
public void setProgress(int progress) {
if (progress >= 99) {
countLabel.setText("Finished!!");
progressBar.setVisible(false);
} else {
progressBar.setValue(progress);
countLabel.setText("Progress: " + progress);
}
}
}
private static interface ProgressableView {
void setProgress(int progress);
}
private static class DoSomethingHeavyAndShowProgressWorker extends SwingWorker<Void, Integer> {
private ProgressableView view;
private Runnable onDone;
public DoSomethingHeavyAndShowProgressWorker(ProgressableView view, Runnable onDone) {
this.view = view;
this.onDone = onDone;
}
#Override
protected Void doInBackground() throws Exception {
for (int i = 0; i < 100; i++) {
Thread.sleep(200);
publish((i + 1)); // Publish will call process() method in the GUI thread
}
return null;
}
#Override
protected void process(List<Integer> chunks) {
if (chunks.isEmpty())
return;
int progress = chunks.get(chunks.size() - 1);
view.setProgress(progress);
}
#Override
protected void done() {
try {
get(); // To catch exceptions happend in background
if (onDone != null)
onDone.run();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
}
Here is a preview:
I put the counter code in a separate thread so it would run separately from the Event Dispatch Thread that all Swing components must be created and executed.
Here's the complete runnable code.
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.SwingUtilities;
public class TimerTestPanel extends JPanel {
private static final long serialVersionUID = 340444475514103360L;
private JLabel myLabel;
private JButton myButton;
public TimerTestPanel() {
initComponents();
}
private void initComponents() {
myLabel = new JLabel("Ready");
myButton = new JButton("Start");
myButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
myActionListenter();
}
});
add(myLabel);
add(myButton);
}
private void myActionListenter() {
myLabel.setText("Starting count ...");
new Thread(new Counter()).start();
}
public static final void main(String argv[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TimerTestPanel());
frame.pack();
frame.setVisible(true);
}
});
}
public class Counter implements Runnable {
#Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
myLabel.setText("Finishing count ...");
}
});
}
}
}
I think I can use an invokeLater to perform the process, so the ActionListener can change the label without waiting de long process to be finished, but I don't if it is the best solution.
package test;
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.SwingUtilities;
public class MyTestPane extends JPanel{
/**
*
*/
private static final long serialVersionUID = 340444475514103360L;
private JLabel myLabel;
private JButton myButton;
public MyTestPane() {
initComponents();
}
private void initComponents() {
myLabel = new JLabel("Ready");
myButton = new JButton("Start");
myButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
myActionListenter();
}
});
add(myLabel);
add(myButton);
}
private void myActionListenter(){
myLabel.setText("Starting count ...");
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try{
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
myLabel.setText("Finishing count ...");
}
});
}
public static final void main(String argv[]) {
JFrame frame = new JFrame();
frame.add(new MyTestPane());
frame.pack();
frame.setVisible(true);
}
}
I would like to update a panel on my main frame (in class GUI) from my controller. Basically, the panel should be updated every simulation step.
for (int step = 0; step < simSteps; step++){
this.gui.updateGUI(this.stats, step);
}
In my GUI class, the method updateGUI(...) looks like this:
public void updateGUI(Stats stats, int step){
this.stats = stats;
getContentPane().remove(pollutionPanel);
pollutionPanel = new BarPanel(settings, stats, possibleColors);
getContentPane().add(pollutionPanel);
validate();
repaint();
}
and my BarPanel class overrides the paintComponent method where I simply use a graphic object to paint a few things.
When starting the simulation, the panel is only updated after the last simulation step although I already know that the updateGUI() method is called in every simulation step (I made some debugging outputs).
Does anyone have an idea why this is happening?
So, basic concept, you want to generate some animation, where by the UI is updated over a period of time. Not an uncommon request, with plenty of examples.
Things to keep in mind:
Swing is single threaded
Don't do anything which might block the Event Dispatching Thread, this will prevent the UI from been update until such time that control is passed back to it
Don't update the UI from outside the context of the EDT
Okay, that sounds a little contradictive, but Swing provides a couple of tools to make your life easier
You could...
Use a SwingWorker
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
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.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface StepContainer {
public void addStep(int index);
}
public class TestPane extends JPanel implements StepContainer {
private int step = 0;
public TestPane() {
setLayout(new GridBagLayout());
PopulateWorker worker = new PopulateWorker(10, this);
worker.execute();
}
#Override
public void addStep(int step) {
JButton btn = new JButton(Integer.toString(step));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(btn, gbc);
revalidate();
repaint();
}
}
public class PopulateWorker extends SwingWorker<Void, Integer> {
private int steps;
private StepContainer parent;
public PopulateWorker(int steps, StepContainer parent) {
this.steps = steps;
this.parent = parent;
}
#Override
protected Void doInBackground() throws Exception {
Thread.sleep(1000);
for (int index = 0; index < steps; index++) {
publish(index);
Thread.sleep(1000);
}
return null;
}
#Override
protected void process(List<Integer> chunks) {
for (int step : chunks) {
parent.addStep(step);
}
}
}
}
Use a Swing Timer
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
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.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface StepContainer {
public void addStep(int index);
}
public class TestPane extends JPanel implements StepContainer {
private int step = 0;
public TestPane() {
setLayout(new GridBagLayout());
Timer timer = new Timer(1000, new PopulateAction(10, this));
timer.start();
}
#Override
public void addStep(int step) {
JButton btn = new JButton(Integer.toString(step));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(btn, gbc);
revalidate();
repaint();
}
}
public class PopulateAction implements ActionListener {
private int steps;
private StepContainer parent;
private int step;
public PopulateAction(int steps, StepContainer parent) {
this.steps = steps;
this.parent = parent;
}
#Override
public void actionPerformed(ActionEvent e) {
parent.addStep(step);
if (step == steps - 1) {
((Timer) e.getSource()).stop();
step = 0;
}
step++;
}
}
}
Which you use will depend on the complexity of your problem, SwingWorker uses it's own Thread to call doInBackground, allowing you to process time consuming tasks, but provides the ability to update the UI safely.
Timer triggers it's updates within the context of the EDT, making it save to update the UI from, but not to perform time consuming tasks in
If you update GUI very quickly, you don't see the intermediate changes, so add some pause beetween each step in loop like:
new Thread() {
public void run() {
for (int step = 0; step < simSteps; step++){
this.gui.updateGUI(this.stats, step);
try {
Thread.currentThread().sleep(1000); // wait 1s before each step
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
public void updateGUI(final Stats stats, final int step){
// the declaration of stats in this class should be marked as volatile like:
// private volatile Stats;
this.stats = stats;
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
getContentPane().remove(pollutionPanel);
pollutionPanel = new BarPanel(settings, stats, possibleColors);
getContentPane().add(pollutionPanel);
validate();
repaint();
}
});
}
Remeber also, that this loop above should not be run by EventThread, so you should not run this code directly in an ActionListener (eg. after JButton was pressed), but rather in the ActionListener.actionPerformed() create new Thread, that contains the above code in the run() method, then start such thread using yourThread.start(). You can alternatively use other way to run it in separate thread, but it is too broad topic to fit in scope of your question.
I'm trying to change text of JLabel during program execution. I know that program execution blocks EDT so I use timer to do this work. But timer starts only after the cycle finishes it's execution although timer.start() is located before cycle in source code.
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.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
#SuppressWarnings("serial")
public class TestUpdate extends JFrame {
JLabel lab;
JButton btn;
public TestUpdate() {
super("Update test");
setDefaultCloseOperation(EXIT_ON_CLOSE);
btn = new JButton();
btn.setText("Start test");
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Timer tm = new Timer(1, new ActionListener() {
public void actionPerformed(ActionEvent e) {
lab.setText("Text is successfully changed");
lab.repaint();
}
});
tm.setRepeats(false);
tm.setInitialDelay(0);
tm.start();
long startTime=System.currentTimeMillis();
while (true) {
if (System.currentTimeMillis()-startTime >= 3000) break;
try {Thread.sleep(500);} catch (InterruptedException e1) {}
}
lab.setText("Three seconds elapsed");
btn.setEnabled(false);
}
});
add(btn,"South");
lab = new JLabel("Should be changed before 3 seconds elapsed");
lab.setHorizontalAlignment(SwingConstants.CENTER);
add(lab);
setSize(300, 200);
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {new TestUpdate(); } });
}
}
Timer event is executed by the EDT, so first event will be executed when current task (your action listener code with while loop) will complete. That is why you observe such behaviour.
I need a one-time pause in this program for what I'm trying to do. I display some text in a Java Swing JFrame, repaint shows it, I wait 1.5 sec, then I change the text.
Basically, I started with this:
statusLabel.setText(s);
appFrame.repaint();
Thread.sleep(1500);
statusLabel.setText(y);
appFrame.repaint();
But this wasn't working. Thread.sleep() would invoke before repaint had finished, meaning s would never be shown. I read a lot of places that you're not supposed to use Thread.sleep() in swing applications because it pauses all threads, even the threads trying to repaint, and that to pause something triggered by actionPerformed() you need to use a Java Swing Timer.
Which is all well and fine, except I can't find a single place that offers a decent explanation on how they work. Since, as far as I can tell, timers are specifically used for repeating events on a timer. I just want a 1.5 second delay between 2 repaints.
I tried doing this...
statusLabel.setText(s);
appFrame.repaint();
Timer timer = new Timer(1500, new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
}
});
timer.setInitialDelay(1500);
timer.setRepeats(false);
timer.start();
statusLabel.setText(y);
appFrame.repaint();
...adding a timer with a 1.5 sec initial delay, no repeating, and no body to its actionPerformed event, so that it literally does nothing but wait that 1.5 sec, but it didn't work.
As coded in your example, it looks like the timer would "work", it just doesn't do anything because the actionPerformed method is empty. You might be thinking that timer.start() blocks and waits for the timer to trigger, but it fact it returns immediately. The way timers work is that the timer's actionPerformed method will be invoked from the UI thread when it is supposed to be. Placing code inside the actionPerformed method of a timer is a good way to update the UI state periodically.
Have you tried placing statusLabel.setText(y); inside the actionPerformed method of your ActionListener?
statusLabel.setText(s);
Timer timer = new Timer(1500, new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
statusLabel.setText(y);
}
});
timer.setRepeats(false);
timer.start();
If that's still not working, then consider providing a runnable example which demonstrates your problem. This will result in less confusion and better responses
Updated
What you "seem" to be wanting to do, is set up a series of events which get trigger at different times...Rather then using separate Timers, you should be using a single Timer like a loop, each time it ticks, you check it's state and make some decisions about what should be done, for example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
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 Flashy {
public static void main(String[] args) {
new Flashy();
}
public Flashy() {
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 static class TestPane extends JPanel {
private JLabel flash;
private JButton makeFlash;
protected static final Color[] FLASH_COLORS = new Color[]{Color.BLUE, Color.RED, Color.GREEN, Color.YELLOW};
protected static final int[] FLASH_DELAY = new int[]{1000, 2000, 3000, 4000};
private int flashPoint;
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
flash = new JLabel("Flash");
flash.setOpaque(true);
makeFlash = new JButton("Make Flash");
add(flash, gbc);
add(makeFlash, gbc);
makeFlash.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
flashPoint = -1;
Timer timer = new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Timer timer = ((Timer)e.getSource());
flashPoint++;
if (flashPoint < FLASH_COLORS.length) {
flash.setBackground(FLASH_COLORS[flashPoint]);
System.out.println(FLASH_DELAY[flashPoint]);
timer.setDelay(FLASH_DELAY[flashPoint]);
} else {
flash.setBackground(null);
timer.stop();
makeFlash.setEnabled(true);
}
}
});
timer.setInitialDelay(0);
timer.start();
makeFlash.setEnabled(false);
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
Now, if you wanted to do something really fancy, you could devise a series of key frames over a given period of time.
This means that you could change the duration of the animation, without needing to change any other piece of code, for example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
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 Flashy {
public static void main(String[] args) {
new Flashy();
}
public Flashy() {
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 static class TestPane extends JPanel {
private JLabel flash;
private JButton makeFlash;
protected static final Color[] FLASH_COLORS = new Color[]{Color.BLUE, Color.RED, Color.GREEN, Color.YELLOW};
protected static final double[] FLASH_DELAY = new double[]{0, 0.2, 0.4, 0.6};
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
flash = new JLabel("Flash");
flash.setOpaque(true);
makeFlash = new JButton("Make Flash");
add(flash, gbc);
add(makeFlash, gbc);
makeFlash.addActionListener(new ActionListener() {
private int playTime = 10000;
private long startTime;
private int currentFrame = -1;
#Override
public void actionPerformed(ActionEvent e) {
startTime = System.currentTimeMillis();
Timer timer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Timer timer = ((Timer) e.getSource());
long now = System.currentTimeMillis();
long duration = now - startTime;
double progress = (double) duration / (double) playTime;
int keyFrame = 0;
for (keyFrame = 0; keyFrame < FLASH_DELAY.length; keyFrame++) {
double current = FLASH_DELAY[keyFrame];
double next = 1d;
if (keyFrame + 1 < FLASH_DELAY.length) {
next = FLASH_DELAY[keyFrame + 1];
}
if (progress >= current && progress < next) {
break;
}
}
if (keyFrame < FLASH_COLORS.length) {
flash.setBackground(FLASH_COLORS[keyFrame]);
}
if (duration >= playTime) {
timer.stop();
makeFlash.setEnabled(true);
flash.setBackground(null);
}
}
});
timer.setInitialDelay(0);
timer.start();
makeFlash.setEnabled(false);
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
}
A much more advanced concept, which is demonstrated in this answer
I've got a viewport, and I've attached a change listener to it. Whenever I scroll through my viewport, my change listener gets called about four times. I'm not sure how to avoid this; I only want the call to happen once?
There's no way around this, JViewport will fire several stateChanged events because it's providing notification about changes to a number of properties...
From the JavaDocs...
Adds a ChangeListener to the list that is notified each
time the view's size, position, or the viewport's extent size has
changed.
At this point, it's kind of hard to know what to suggest as we don't know what it is you are trying to achieve, however, if you have to use a ChangeListener, you could set up a coalescing mechanism. That is, rather then responding to each event, you basically wait until a long enough delay has occurred between events before responding to it...
For example...
public class DelayedChangeHandler implements ChangeListener {
private Timer timer;
private ChangeEvent last;
public DelayedChangeHandler() {
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
stableStateChanged();
}
});
timer.setRepeats(false);
}
#Override
public void stateChanged(ChangeEvent e) {
last = e;
timer.restart();
}
protected void stableStateChanged() {
System.out.println("Finally...");
}
}
Basically, this is a ChangeListener implementation that uses a non-repeating javax.swing.Timer with a short delay. Each time stateChanged is called, the timer is restart. Finally, when the timer is allowed to "tick", it calls stableStateChanged indicating that enough time has passed since the last event was raised.
This assumes that you don't so much care about what caused the event, only that the event occured...
A runnable example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class TestViewport {
public static void main(String[] args) {
new TestViewport();
}
public TestViewport() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JPanel pane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
};
JScrollPane sp = new JScrollPane(pane);
sp.getViewport().addChangeListener(new DelayedChangeHandler());
sp.getViewport().addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(sp);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class DelayedChangeHandler implements ChangeListener {
private Timer timer;
private ChangeEvent last;
public DelayedChangeHandler() {
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
stableStateChanged();
}
});
timer.setRepeats(false);
}
#Override
public void stateChanged(ChangeEvent e) {
last = e;
timer.restart();
}
protected void stableStateChanged() {
System.out.println("Finally...");
}
}
}
You can try to use AdjustmentListener for gettign scroll event once, try next:
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.io.UnsupportedEncodingException;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
public class Example {
public static void main(String[] args) throws UnsupportedEncodingException {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JScrollPane pane = new JScrollPane(new JTextArea());
pane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
if(e.getValueIsAdjusting()){
return;
}
System.out.println("vertical scrolled");
System.out.println("bar value = " + e.getValue());
}
});
frame.setContentPane(pane);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Here is another example.