Is it possible to wait for a method (say METHOD1) to finish, but if it is running longer than X secs, call another method until METHOD1 returns?
Some pseudocode:
method1();
startCountdown(1000); // time in millis
while (method1() still running) {
method2(); // shows a popup with spinner (Swing/AWT)
}
I guess, it must be done with concurrency, but I am not used to concurrent programming. So, I have no idea how to start.
The UI framework used is Swing/AWT.
So, the basic idea would be to use a combination of a SwingWorker and a Swing Timer.
The idea is if the Timer triggers before the SwingWorker is DONE, you execute some other workflow, otherwise you stop the Timer, for example...
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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JLabel label;
private JButton startButton;
boolean hasCompleted = false;
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
label = new JLabel("Waiting for you");
startButton = new JButton("Start");
add(label, gbc);
add(startButton, gbc);
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
startButton.setEnabled(false);
startWork();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void startWork() {
label.setText("Something wicked this way comes");
// You could build an isoloated workflow, which allowed you to pass
// three targets, the thing to be executed, the thing to be
// executed if time run over and the thing to be executed when
// the task completed (all via a single interface),
// but, you get the idea
Timer timer = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (hasCompleted) {
return;
}
label.setText("Wickedness is a bit slow today");
}
});
timer.setRepeats(false);
SomeLongRunningOperation worker = new SomeLongRunningOperation();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
switch (worker.getState()) {
case DONE:
hasCompleted = true;
timer.stop();
label.setText("All is done");
startButton.setEnabled(true);
break;
}
}
});
worker.execute();
timer.start();
}
}
public class SomeLongRunningOperation extends SwingWorker<Void, Void> {
#Override
protected Void doInBackground() throws Exception {
Thread.sleep(5000);
return null;
}
}
}
Play around with the timings to see what different effects you get.
Why use a SwingWorker? Because it has it's own state callbacks, which makes it easier to deal with
As I said in my comments, you could distill the workflow down into a re-usable concept, something like...
public class TimedTask<V> {
public static interface Task<V> {
public V execute() throws Exception;
}
public static interface TimedTaskListener<V> extends EventListener {
public void taskIsTakingLongThenExepected(TimedTask task);
public void taskDidComplete(TimedTask task, V value);
}
private Task<V> task;
private TimedTaskListener<V> listener;
private V value;
private int timeOut;
private Timer timer;
private SwingWorker<V, Void> worker;
private boolean hasCompleted = false;
public TimedTask(int timeOut, Task<V> task, TimedTaskListener<V> listener) {
this.task = task;
this.listener = listener;
this.timeOut = timeOut;
}
public V getValue() {
return value;
}
public int getTimeOut() {
return timeOut;
}
protected Task<V> getTask() {
return task;
}
protected TimedTaskListener<V> getListener() {
return listener;
}
public void execute() {
if (timer != null || worker != null) {
return;
}
hasCompleted = false;
worker = new SwingWorker<V, Void>() {
#Override
protected V doInBackground() throws Exception {
value = task.execute();
return value;
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
switch (worker.getState()) {
case DONE:
hasCompleted = true;
timer.stop();
getListener().taskDidComplete(TimedTask.this, value);
break;
}
}
});
timer = new Timer(getTimeOut(), new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (hasCompleted) {
return;
}
getListener().taskIsTakingLongThenExepected(TimedTask.this);
}
});
timer.setRepeats(false);
worker.execute();
timer.start();
}
}
And then you could replace the startWork method in the first example with something like...
protected void startWork() {
label.setText("Something wicked this way comes");
TimedTask.Task<Void> task = new TimedTask.Task<Void>() {
#Override
public Void execute() throws Exception {
Thread.sleep(5000);
return null;
}
};
TimedTask<Void> timedTask = new TimedTask(2000, task, new TimedTask.TimedTaskListener<Void>() {
#Override
public void taskIsTakingLongThenExepected(TimedTask task) {
label.setText("Wickedness is taking it's sweet time");
}
#Override
public void taskDidComplete(TimedTask task, Void value) {
label.setText("Wickedness has arrived");
startButton.setEnabled(true);
}
});
timedTask.execute();
}
While SwingWorker is the appropriate tool for the job, for simple tasks you can get away with a Thread for the off-edt long task and a swing Timer to update the GUI:
import java.awt.*;
import javax.swing.*;
import javax.swing.Timer;
public class Main{
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
}
class TestPane extends JPanel{
private static Dimension size = new Dimension(250, 100);
private final JLabel label;
private final JButton start;
private int counter;
private Timer timer;
public TestPane() {
setLayout(new BorderLayout(10, 10));
label = new JLabel("Click START to run long process", JLabel.CENTER);
add(label,BorderLayout.NORTH);
start = new JButton("START");
start.addActionListener(e-> start() );
add(start, BorderLayout.SOUTH);
}
private void start() {
start.setEnabled(false);
int processRunTime = 10;
int updateTime = 1; //if this value >= processRunTime update() is not invoked
counter = 1;
simulateLongProcessOf(processRunTime);
timer = new Timer(1000*updateTime, e->update(counter++));
label.setText("Long process started");
timer.start();
}
private void stop() {
label.setText("Long process ended");
timer.stop();
start.setEnabled(true);
}
#Override
public Dimension preferredSize() {
return size;
}
private void simulateLongProcessOf(int seconds){
Thread t1 = new Thread(()->{
try {
Thread.sleep(1000*seconds);
} catch (InterruptedException ex) {
ex.printStackTrace();
}finally {
SwingUtilities.invokeLater(()->stop());
}
});
t1.start();
}
private void update(int count){
label.setText("Update # "+ count+" : long process is running" );
}
}
I've struggled with this question before.
What I ended up doing was, creating a separate class that extends AsyncTask. Added an interface/listener to this class that returned my object. Right before I start my AsyncTask, I'll disable buttons and put up a loading spinner. Once the AsyncTask comes back, I'll do my processing and reenable the buttons and take down the loading spinner. Of coarse I'm doing a rest call in the example, but it can be applied to anything that takes awhile. The reason why this is a better option than a while loop is that it's won't be burning cycles checking conditions.
public class RestCall extends AsyncTask {
private Context mContext;
private static final String TAG = "RestCall";
private AsyncResponse mListener;
public RestCall(Context context, URL url, AsyncResponse listener) {
this.mListener = listener;
this.mContext = context;
this.url = url;
}
public interface AsyncResponse {
void processFinish(JSONArray results);
}
#Override
protected Object doInBackground(Object[] objects) {
Log.d(TAG, "doInBackground: Thread: " + Thread.currentThread().getName());
return getResultsInJSONArray(url);
}
private JSONArray getResultsInJSONArray(URL url) {
//Here is where you will be doing the bulk of the work
//Doing a rest call and
//Processing results to JSONArray
}
#Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
Log.d(TAG, "onPostExecute: Handing off Object");
mListener.processFinish((JSONArray) o);
}
Now in your original class you'll add the following to your class:
public class myClass
private restCall call;
Than create a listener from that interface you made. Then pass the results to a method.
restCall.AsyncResponse listener = results -> handleResults(results);
With the listener setup you can you can execute your AsyncTask.
//here is were you would throw up the loading bar.
call = new restCall(this, url, listener);
call.execute();
private void handleResults(JSONArray results){
//process what you need to
//take down loading bar
}
Related
I have a basic Swing UI with a single button marked "Play." When the button is pressed the label changes to "Pause". When the button is pressed now it changes to say "Resume."
On "Play" I am instantiating and executing a SwingWorker. What I would like is to be able to pause this thread (NOT cancel it) and resume it according to the button presses described above. However, I'd prefer not to resort to Thread.sleep() in doInBackground(). That seems a bit hackish. Is there any way for the thread running doInBackground to block?
Pause and Resume SwingWorker.doInBackground()
First of all you have to be sure the background task being performed can be paused, otherwise the question doesn't make sense. So let's say the task can be paused, then you might extend SwingWorker class and make your own pausable worker using a simple flag variable to control the background thread status: paused or not paused.
public abstract class PausableSwingWorker<K, V> extends SwingWorker<K, V> {
private volatile boolean isPaused;
public final void pause() {
if (!isPaused() && !isDone()) {
isPaused = true;
firePropertyChange("paused", false, true);
}
}
public final void resume() {
if (isPaused() && !isDone()) {
isPaused = false;
firePropertyChange("paused", true, false);
}
}
public final boolean isPaused() {
return isPaused;
}
}
Subclasses might check isPaused() status in order to efectively proceed with the task or not. For example:
PausableSwingWorker<Void, String> worker = new PausableSwingWorker<Void, String>() {
#Override
protected Void doInBackground() throws Exception {
while (!isCancelled()) {
if (!isPaused()) {
// proceed with background task
} else {
Thread.sleep(200); // Optional sleep to avoid check status continuously
}
}
return null;
}
};
You can also add a PropertyChangeListener to the worker and listen for paused property changes:
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("paused".equals(evt.getPropertyName())) {
System.out.println("Old status: " + evt.getOldValue());
System.out.println("New status: " + evt.getNewValue());
}
}
});
Example (updated to make use of PropertyChangeListener)
Here is a complete example to play with. Please note that if worker is stopped then it cannot be paused nor resumed anymore.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class Demo {
private void createAndShowGUI() {
final JTextArea textArea = new JTextArea(20, 50);
final PausableSwingWorker<Void, String> worker = new PausableSwingWorker<Void, String>() {
#Override
protected Void doInBackground() throws Exception {
while (!isCancelled()) {
if (!isPaused()) {
publish("Writing...");
} else {
Thread.sleep(200);
}
}
return null;
}
#Override
protected void process(List<String> chunks) {
String text = String.format("%s%n", chunks.get(chunks.size() - 1));
textArea.append(text);
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("paused".equals(evt.getPropertyName())) {
String text = (Boolean)evt.getNewValue() ? "Paused..." : "Resumed...";
textArea.append(String.format("%s%n", text));
}
}
});
Action pause = new AbstractAction("Pause") {
#Override
public void actionPerformed(ActionEvent e) {
worker.pause();
}
};
Action resume = new AbstractAction("Resume") {
#Override
public void actionPerformed(ActionEvent e) {
worker.resume();
}
};
Action stop = new AbstractAction("Stop") {
#Override
public void actionPerformed(ActionEvent e) {
worker.cancel(true);
}
};
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonsPanel.add(new JButton(pause));
buttonsPanel.add(new JButton(resume));
buttonsPanel.add(new JButton(stop));
JPanel content = new JPanel(new BorderLayout(8, 8));
content.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
content.add(new JScrollPane(textArea), BorderLayout.CENTER);
content.add(buttonsPanel, BorderLayout.SOUTH);
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
if (!worker.isDone()) {
worker.cancel(true);
}
e.getWindow().dispose();
}
});
frame.add(content);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
worker.execute();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Demo().createAndShowGUI();
}
});
}
abstract class PausableSwingWorker<K, V> extends SwingWorker<K, V> {
private volatile boolean isPaused;
public final void pause() {
if (!isPaused() && !isDone()) {
isPaused = true;
firePropertyChange("paused", false, true);
}
}
public final void resume() {
if (isPaused() && !isDone()) {
isPaused = false;
firePropertyChange("paused", true, false);
}
}
public final boolean isPaused() {
return isPaused;
}
}
}
This is the smallest runnable SSCCE,of my project, that I could implement to show you.
I've read that calling the game logic from the Event Dispacth Thread is a bad practice, how can I separate them, because as you can see update() and repaint() are related into loop
and how can I separate code in a pretty way, I'm getting in trouble with this, trying to find out how to do it.
I've posted a similar question regarding and I got an answer,that says to use a Swing Timer,but i have huge task to make and as i read Swing timer isn't ideal for this scenario.This is the question:
Event Dispatch Thread divided from logic thread,prevent blocking UI
Main class
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class Main {
private static final Main mainFrame = new Main();
private final JFrame frame;
private Main() {
frame = new JFrame();
frame.setUndecorated(true);
frame.add(new MyPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static Main getMainFrameInstance() {
return mainFrame;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Main.getMainFrameInstance();
}
});
}
}
MyPanel Class
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
public class MyPanel extends JPanel implements Runnable,KeyListener,MouseListeners {
private static final long serialVersionUID = 1L;
// thread and loop
private Thread thread;
private boolean running;
private int FPS = 60;
private long targetTime = 1000 / FPS;
private long start;
private long elapsed;
private long wait;
// image
public BufferedImage image;
// foo
private Foo foo;
private Render render = Render.getRenderManagerInstance();
public MyPanel() {
setPreferredSize(new Dimension(700, 700));
setFocusable(true);
requestFocus();
}
public void addNotify() {
super.addNotify();
if (thread == null) {
addKeyListeners(this);
addMouseListener(this);
thread = new Thread(this);
thread.start();
}
}
private void initGraphic() {
image = new BufferedImage(700, 700, BufferedImage.TYPE_INT_RGB);
foo = new Foo();
running = true;
}
public void run() {
initGraphic();
// loop
while (running) {
start = System.nanoTime();
foo.update();
repaint();
elapsed = System.nanoTime() - start;
wait = (targetTime - elapsed / 1000000) - 8;
if (wait <= 0)
wait = 6;
try {
Thread.sleep(wait);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
graphics = image.getGraphics();
((Graphics2D) graphics).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
((Graphics2D) graphics).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
render.setRenderState((Graphics2D) graphics);
graphic.drawImage(image, 0, 0, this);
// clear graphics resources after use them
graphic2D.dispose();
}
public void keyPressed(KeyEvent keyEvent) {
//code not considerable
}
public void keyReleased(KeyEvent keyEvent) {
//code not considerable
}
public void mousePressed(MouseEvent mouseEvent) {
//code not considerable
}
public void mouseReleased(MouseEvent mouseEvent) {
//code not considerable
}
}
This is how it can look like. You'll need to call the following code somewhere in EDT or through Swing Timer. I'm assuming here that your "huge" task will need to update a text field, but it can be any other UI control as well. All of that, just to demonstrate an idea. Do not treat it as a tested code.
//javax.swing.JTextField jfield; The field that needs to be updated. Take it from your Panel
String text = ""; // just a place holder
Object params [] = new Object []{jfield, text};
HugeTaskRunner ht = new HugeTaskRunner(params, new CallBack());
HugeTaskRunner is derived from AbstractTaskRunner, which looks like follows:
public abstract class AbstractTaskRunner extends Thread {
CallBack callBack = null;
Object [] params = new Object[0];
public AbstractTaskRunner (Object [] params, CallBack callBack) {
this.params = params;
this.callBack = callBack;
}
public abstract void doTask ();
#Override
public void run() {
doTask();
if (callBack != null) {
callBack.doCall(new Object[]{"DONE"});
}
}
}
HugeTaskRunner:
public class HugeTaskRunner extends AbstractTaskRunner {
public HugeTaskRunner(Object[] params, CallBack callBack) {
super(params, callBack);
// TODO Auto-generated constructor stub
}
#Override
public void doTask() {
// HERE YOU'LL HAVE TO DO SOME HUGE TASK ACTIONS
// THEN YOU'LL NEED TO CALL callBack.doCall(params) to update GUI
String newText = "Image #1 has been loaded";
params[params.length -1] = newText; // assuming that the last param is for updated text
callBack.doCall(params);
}
}
CallBack class:
public class CallBack {
public void doCall (Object [] params) {
javax.swing.SwingUtilities.invokeLater(new GUIUpdater(params, null));
}
}
GUIUpdater class:
public class GUIUpdater extends AbstractTaskRunner {
public GUIUpdater(Object[] params, CallBack callBack) {
super(params, callBack);
}
#Override
public void doTask() {
// UPDATE YOUR GUI HERE TAKING Swing UI objects from params, e.g.
if (params.length == 1 && params[0].equals("DONE")) {
// HUGE TASK IS COMPLETED, DO SOMETHING IF YOU NEED TO
}
else if (params.length == 2) { // It's a request to update GUI
javax.swing.JTextField txt = (javax.swing.JTextField) this.params[0];
txt.setText((String)this.params[1]);
}
else {
// UNKNOWN REQUEST
}
}
}
nice job , now i just wanna know why if i add into while loop the instruction System.out.println below the progress is shown on both , cmd and Pgbar in the Gui ?? :
while(progress < 99){
System.out.println("into while of PBar Thread progress = "+progress);
if(progress != Path.operationProgress){
operationProgressBar.setValue(progress);
progress = Path.operationProgress;
operationProgressBar.repaint(); } }
need some help around , i can't get the JProgressBar to update, i
can't use SwingWorker, i have to solve this without it . the variable
Path.operationProgress is a static variable from a "Path" class
instance, and it's updated from another thread, so i think the PBar
and Path instances are both executed in user's Threads and not in the
EDT . here is the Code of the progress bar :
import javax.swing.*;
public class Pbar extends Thread {
JProgressBar operationProgressBar;
public Pbar(JProgressBar operationProgressBar) {
this.operationProgressBar = operationProgressBar;
}
#Override
public void run() {
int progress = Path.operationProgress;
while(progress < 99) {
if(progress != Path.operationProgress) {
operationProgressBar.setValue(progress);
progress = Path.operationProgress;
operationProgressBar.repaint();
}}}
}
this is the action that launches the threads :
private javax.swing.JProgressBar operationProgressBar;
private javax.swing.JLabel pathImage;
private javax.swing.JButton simulatedAnnelingButton;
public class TSPGUI extends javax.swing.JFrame {
TSPMG tspInstance;
Path p, result;
String filename = "";
int neighborHood_Type = 1, i = 0;
// ......Constructor Stuff and init()
private void simulatedAnnelingButtonActionPerformed(java.awt.event.ActionEvent evt)
{
Thread sa = new Thread(){
#Override
public void run(){
result = p.SimulatedAnnealing(neighborHood_Type);
String lastCostString = result.Cost() + "";
lastCostLabel.setText(lastCostString);
}};
sa.start();
Pbar pb = new Pbar(operationProgressBar);
pb.start();
}
//Some other Stuff ...
}
If you can't use SwingWorker then use SwingUtilities.invokeLater, e.g.:
if (progress != Path.operationProgress) {
final int progressCopy = progress; // Probably not final so copy is needed
SwingUtilities.invokeLater(new Runnable() {
#Override
void run() {
operationsProgressBar.setValue(progressCopy);
}
});
}
Note: When doing this, everything used in run has to be final or there have to be other measures to access the variables. This code is symbolic in that regard.
You need to do operations on Swing components outside the event dispatching thread, there is no way around this.
I would use a PropertyChangeListener to allow you to make the annealing progress value a "bound" property of the class. Than any observer can follow this property if desired. For example:
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
#SuppressWarnings("serial")
public class TspGui2 extends JPanel {
private static final String ANNEALING_PROGRESS = "Annealing Progress";
private JProgressBar progBar = new JProgressBar(0, 100);
private JLabel valueLabel = new JLabel();
private JButton beginAnnealingBtn = new JButton("Begin Annealing");
private MyAnnealing myAnnealing = new MyAnnealing(this);
public TspGui2() {
beginAnnealingBtn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
beginAnnealing();
}
});
myAnnealing.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(MyAnnealing.ANNEALING)) {
// be sure this is done on the EDT
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int annealedValue = myAnnealing.getAnnealedValue();
setValue(annealedValue);
if (annealedValue >= MyAnnealing.MAX_ANNEALED_VALUE) {
beginAnnealingBtn.setEnabled(true);
}
}
});
}
}
});
progBar.setString(ANNEALING_PROGRESS);
progBar.setStringPainted(true);
JPanel northPanel = new JPanel(new GridLayout(1, 0));
northPanel.add(beginAnnealingBtn);
northPanel.add(valueLabel);
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
add(northPanel);
add(progBar);
}
public void setValue(int value) {
valueLabel.setText("Value:" + value);
progBar.setValue(value);
}
public void beginAnnealing() {
beginAnnealingBtn.setEnabled(false);
setValue(0);
myAnnealing.reset();
new Thread(new Runnable() {
public void run() {
myAnnealing.beginAnnealing();
}
}).start();
}
private static void createAndShowGui() {
TspGui2 mainPanel = new TspGui2();
JFrame frame = new JFrame("TspGui2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class MyAnnealing {
public static final String ANNEALING = "Annealing";
public static final int MAX_ANNEALED_VALUE = 100;
private SwingPropertyChangeSupport propChangeSupport =
new SwingPropertyChangeSupport(this);
private TspGui2 gui;
private int annealedValue;
public MyAnnealing(TspGui2 gui) {
this.gui = gui;
}
public void addPropertyChangeListener(
PropertyChangeListener listener) {
propChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(
PropertyChangeListener listener) {
propChangeSupport.removePropertyChangeListener(listener);
}
public void reset() {
setAnnealedValue(0);
}
// simulate some long process...
public void beginAnnealing() {
long sleepDelay = 100;
while (annealedValue < MAX_ANNEALED_VALUE) {
setAnnealedValue(annealedValue + 1);
try {
Thread.sleep(sleepDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getAnnealedValue() {
return annealedValue;
}
private void setAnnealedValue(int value) {
final int oldValue = this.annealedValue;
this.annealedValue = value;
propChangeSupport.firePropertyChange(ANNEALING, oldValue, annealedValue);
}
}
This is the complete code :
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.Thread;
class jProgressBar {
JProgressBar pb;
JButton start;
int i;
jProgressBar() {
buildGUI();
hookUpEvents();
}
public void buildGUI() {
JFrame fr=new JFrame("Progress Bar");
JPanel p=new JPanel();
p.setLayout(new FlowLayout(FlowLayout.CENTER));
JPanel barPanel=new JPanel();
barPanel.setLayout(new GridLayout(2,0,50,50));
pb=new JProgressBar(0,10);
start=new JButton("Start Demo");
fr.add(p);
barPanel.add(start);
barPanel.add(pb);
p.add(barPanel);
fr.setSize(500,500);
fr.setVisible(true);
}
public void hookUpEvents() {
start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
try {
Runnable r=new Runnable() {
public void run() {
action(ae); // LINE 39
}
};
Thread th=new Thread(r);
th.start();
} catch(Exception exc) {
System.out.println(exc);
}
}
});
}
public void action(ActionEvent ae) {
start.setVisible(false);
try {
Runnable rp=new Runnable() {
public void run() {
i++;
pb.setValue(i);
try {
Thread.sleep(2000);
} catch(Exception exc) {
System.out.println(exc);
}
if(i==5) {
pb.setString("Half Done!");
}
else if(i==10) {
pb.setString("Completed!");
}
}
};
Thread th=new Thread(rp);
th.start();
} catch(Exception exc) {
System.out.println(exc);
}
}
public static void main(String args[]) {
new jProgressBar();
}
}
This is the error produced on cmd:
d:\UnderTest>javac jProgressBar.java
jProgressBar.java:39: local variable ae is accessed from within inner class; needs to be declared fina
l
action(ae);
^
1 error
What is this error and how can I solve this error?
Declare the variable ae as final:
public void actionPerformed(final ActionEvent ae) {
This means that it cannot be assigned a new value, which should be fine according to your current code.
a very nice example for SwingWorker
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
public class SwingWorkerExample extends JFrame implements ActionListener {
private static final long serialVersionUID = 1L;
private final JButton startButton, stopButton;
private JScrollPane scrollPane = new JScrollPane();
private JList listBox = null;
private DefaultListModel listModel = new DefaultListModel();
private final JProgressBar progressBar;
private mySwingWorker swingWorker;
public SwingWorkerExample() {
super("SwingWorkerExample");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new GridLayout(2, 2));
startButton = makeButton("Start");
stopButton = makeButton("Stop");
stopButton.setEnabled(false);
progressBar = makeProgressBar(0, 99);
listBox = new JList(listModel);
scrollPane.setViewportView(listBox);
getContentPane().add(scrollPane);
//Display the window.
pack();
setVisible(true);
}
//Class SwingWorker<T,V> T - the result type returned by this SwingWorker's doInBackground
//and get methods V - the type used for carrying out intermediate results by this SwingWorker's
//publish and process methods
private class mySwingWorker extends javax.swing.SwingWorker<ArrayList<Integer>, Integer> {
//The first template argument, in this case, ArrayList<Integer>, is what s returned by doInBackground(),
//and by get(). The second template argument, in this case, Integer, is what is published with the
//publish method. It is also the data type which is stored by the java.util.List that is the parameter
//for the process method, which recieves the information published by the publish method.
#Override
protected ArrayList<Integer> doInBackground() {
//Returns items of the type given as the first template argument to the SwingWorker class.
if (javax.swing.SwingUtilities.isEventDispatchThread()) {
System.out.println("javax.swing.SwingUtilities.isEventDispatchThread() returned true.");
}
Integer tmpValue = new Integer(1);
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) { //find every 100th prime, just to make it slower
tmpValue = FindNextPrime(tmpValue.intValue());
//isCancelled() returns true if the cancel() method is invoked on this class. That is the proper way
//to stop this thread. See the actionPerformed method.
if (isCancelled()) {
System.out.println("SwingWorker - isCancelled");
return list;
}
}
//Successive calls to publish are coalesced into a java.util.List, which is what is received by process,
//which in this case, isused to update the JProgressBar. Thus, the values passed to publish range from
//1 to 100.
publish(new Integer(i));
list.add(tmpValue);
}
return list;
}//Note, always use java.util.List here, or it will use the wrong list.
#Override
protected void process(java.util.List<Integer> progressList) {
//This method is processing a java.util.List of items given as successive arguments to the publish method.
//Note that these calls are coalesced into a java.util.List. This list holds items of the type given as the
//second template parameter type to SwingWorker. Note that the get method below has nothing to do with the
//SwingWorker get method; it is the List's get method. This would be a good place to update a progress bar.
if (!javax.swing.SwingUtilities.isEventDispatchThread()) {
System.out.println("javax.swing.SwingUtilities.isEventDispatchThread() + returned false.");
}
Integer percentComplete = progressList.get(progressList.size() - 1);
progressBar.setValue(percentComplete.intValue());
}
#Override
protected void done() {
System.out.println("doInBackground is complete");
if (!javax.swing.SwingUtilities.isEventDispatchThread()) {
System.out.println("javax.swing.SwingUtilities.isEventDispatchThread() + returned false.");
}
try {
//Here, the SwingWorker's get method returns an item of the same type as specified as the first type parameter
//given to the SwingWorker class.
ArrayList<Integer> results = get();
for (Integer i : results) {
listModel.addElement(i.toString());
}
} catch (Exception e) {
System.out.println("Caught an exception: " + e);
}
startButton();
}
boolean IsPrime(int num) { //Checks whether a number is prime
int i;
for (i = 2; i <= num / 2; i++) {
if (num % i == 0) {
return false;
}
}
return true;
}
protected Integer FindNextPrime(int num) { //Returns next prime number from passed arg.
do {
if (num % 2 == 0) {
num++;
} else {
num += 2;
}
} while (!IsPrime(num));
return new Integer(num);
}
}
private JButton makeButton(String caption) {
JButton b = new JButton(caption);
b.setActionCommand(caption);
b.addActionListener(this);
getContentPane().add(b);
return b;
}
private JProgressBar makeProgressBar(int min, int max) {
JProgressBar progressBar1 = new JProgressBar();
progressBar1.setMinimum(min);
progressBar1.setMaximum(max);
progressBar1.setStringPainted(true);
progressBar1.setBorderPainted(true);
getContentPane().add(progressBar1);
return progressBar1;
}
private void startButton() {
startButton.setEnabled(true);
stopButton.setEnabled(false);
System.out.println("SwingWorker - Done");
}
#Override
public void actionPerformed(ActionEvent e) {
if ("Start" == null ? e.getActionCommand() == null : "Start".equals(e.getActionCommand())) {
startButton.setEnabled(false);
stopButton.setEnabled(true);
// Note that it creates a new instance of the SwingWorker-derived class. Never reuse an old one.
(swingWorker = new mySwingWorker()).execute(); // new instance
} else if ("Stop" == null ? e.getActionCommand() == null : "Stop".equals(e.getActionCommand())) {
startButton.setEnabled(true);
stopButton.setEnabled(false);
swingWorker.cancel(true); // causes isCancelled to return true in doInBackground
swingWorker = null;
}
}
public static void main(String[] args) {
// Notice that it kicks it off on the event-dispatching thread, not the main thread.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
SwingWorkerExample swingWorkerExample = new SwingWorkerExample();
}
});
}
}
There are some counterproductive issues present.
Swing is single-thread based, and all actions must be done on the EDT. For that reason, your JProgressBar doesn't update correctly. See also Concurrency in Swing.
Don't use Thread.sleep(int) in Swing, and certainly not in an action listener.
By using Runnable, it is possible to update JProgressBar; but as mentioned, the method must be run from invokeLater().
For that, SwingWorker would be better, as shown below and here.
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class TestProgressBar {
private static void createAndShowUI() {
JFrame frame = new JFrame("TestProgressBar");
frame.getContentPane().add(new TestPBGui().getMainPanel());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowUI();
}
});
}
private TestProgressBar() {
}
}
class TestPBGui {
private JPanel mainPanel = new JPanel();
public TestPBGui() {
JButton yourAttempt = new JButton("Your attempt to show Progress Bar");
JButton myAttempt = new JButton("My attempt to show Progress Bar");
yourAttempt.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
yourAttemptActionPerformed();
}
});
myAttempt.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
myAttemptActionPerformed();
}
});
mainPanel.add(yourAttempt);
mainPanel.add(myAttempt);
}
private void yourAttemptActionPerformed() {
Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
JDialog progressDialog = new JDialog(thisWin, "Uploading...");
JPanel contentPane = new JPanel();
contentPane.setPreferredSize(new Dimension(300, 100));
JProgressBar bar = new JProgressBar(0, 100);
bar.setIndeterminate(true);
contentPane.add(bar);
progressDialog.setContentPane(contentPane);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
Task task = new Task("Your attempt");
task.execute();
progressDialog.setVisible(true);
while (!task.isDone()) {
}
progressDialog.dispose();
}
private void myAttemptActionPerformed() {
Window thisWin = SwingUtilities.getWindowAncestor(mainPanel);
final JDialog progressDialog = new JDialog(thisWin, "Uploading...");
JPanel contentPane = new JPanel();
contentPane.setPreferredSize(new Dimension(300, 100));
final JProgressBar bar = new JProgressBar(0, 100);
bar.setIndeterminate(true);
contentPane.add(bar);
progressDialog.setContentPane(contentPane);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
final Task task = new Task("My attempt");
task.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equalsIgnoreCase("progress")) {
int progress = task.getProgress();
if (progress == 0) {
bar.setIndeterminate(true);
} else {
bar.setIndeterminate(false);
bar.setValue(progress);
progressDialog.dispose();
}
}
}
});
task.execute();
progressDialog.setVisible(true);
}
public JPanel getMainPanel() {
return mainPanel;
}
}
class Task extends SwingWorker<Void, Void> {
private static final long SLEEP_TIME = 4000;
private String text;
public Task(String text) {
this.text = text;
}
#Override
public Void doInBackground() {
setProgress(0);
try {
Thread.sleep(SLEEP_TIME);// imitate a long-running task
} catch (InterruptedException e) {
}
setProgress(100);
return null;
}
#Override
public void done() {
System.out.println(text + " is done");
Toolkit.getDefaultToolkit().beep();
}
}
I am using an MVC pattern for my design, when a user presses the search button, I call a search in the model, but I also want to update a progress bar with information returned from that model.
I have tried using a swingworker, but the progress bar does not update. I suspect I am doing something wrong with my threading.
My button as defined in the controller is:
class SearchBtnListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
_view.displayProgress();
}
}
This calls the search in the model and has the following call in the view:
public void displayProgress() {
TwoWorker task = new TwoWorker();
task.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent e) {
if ("progress".equals(e.getPropertyName())) {
_progressBar.setValue((Integer) e.getNewValue());
}
}
});
task.execute();
}
private class TwoWorker extends SwingWorker<Void, Void> {
#Override
protected Void doInBackground() throws Exception {
_model.startSearch(getTerm()); // time intensive code
File file = new File("lock");
while (file.exists()){
setProgress(_model.getStatus());
System.out.println(_model.getStatus()); // never called
}
return null;
}
protected void done(){
updateMain();
}
}
Dummy function defined in Model for testing:
public int getStatus(){
Random r = new Random();
return r.nextInt();
}
Don't call
_progressBar.setValue(_model.getStatus());
from within your SwingWorker as this is calling Swing code from a background thread and is what the PropertyChangeListener is for anyway. Instead, just set the progress property, that's all.
Also, don't call done() from within the doInBackground method as this needs to be called from the EDT by the SwingWorker. So let the SwingWorker itself call this method when it is in fact done.
Also, Done() should be done() -- the first letter shouldn't be capitalized, and you should use #Override annotations in this code so you can be sure that you're overriding methods correctly.
Also, what does this do?
_model.startSearch(_view.getTerm());
Does it call code that takes a while to complete? Should this be initialized from within the SwingWorker doInBackground itself?
Edit:
Another option is to give the Model a bound int property, say called progress, and then add a PropertyChangeListener to it directly letting it update the JProgressBar. For example,
import java.awt.BorderLayout;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.*;
public class MVC_ProgressBarThread {
private static void createAndShowUI() {
MVC_View view = new MVC_View();
MVC_Model model = new MVC_Model();
MVC_Control control = new MVC_Control(view, model);
view.setControl(control);
JFrame frame = new JFrame("MVC_ProgressBarThread");
frame.getContentPane().add(view);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
#SuppressWarnings("serial")
class MVC_View extends JPanel {
private MVC_Control control;
private JProgressBar progressBar = new JProgressBar();
private JButton startActionButton = new JButton("Start Action");
public MVC_View() {
startActionButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
buttonActionPerformed();
}
});
JPanel buttonPanel = new JPanel();
buttonPanel.add(startActionButton);
setLayout(new BorderLayout());
add(buttonPanel, BorderLayout.NORTH);
add(progressBar, BorderLayout.CENTER);
}
public void setControl(MVC_Control control) {
this.control = control;
}
private void buttonActionPerformed() {
if (control != null) {
control.doButtonAction();
}
}
public void setProgress(int progress) {
progressBar.setValue(progress);
}
public void start() {
startActionButton.setEnabled(false);
}
public void done() {
startActionButton.setEnabled(true);
setProgress(100);
}
}
class MVC_Control {
private MVC_View view;
private MVC_Model model;
public MVC_Control(final MVC_View view, final MVC_Model model) {
this.view = view;
this.model = model;
model.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent pce) {
if (MVC_Model.PROGRESS.equals(pce.getPropertyName())) {
view.setProgress((Integer)pce.getNewValue());
}
}
});
}
public void doButtonAction() {
view.start();
SwingWorker<Void, Void> swingworker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
model.reset();
model.startSearch();
return null;
}
#Override
protected void done() {
view.done();
}
};
swingworker.execute();
}
}
class MVC_Model {
public static final String PROGRESS = "progress";
private static final int MAX = 100;
private static final long SLEEP_DELAY = 100;
private int progress = 0;
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public void setProgress(int progress) {
int oldProgress = this.progress;
this.progress = progress;
PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS, oldProgress, progress);
pcs.firePropertyChange(evt);
}
public void reset() {
setProgress(0);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void startSearch() {
for (int i = 0; i < MAX; i++) {
int newValue = (100 * i) / MAX;
setProgress(newValue);
try {
Thread.sleep(SLEEP_DELAY);
} catch (InterruptedException e) {}
}
}
}