So I'm creating a JProgressBar that displays the progress of a CSV manipulation, where every line is read and checked if there are no null values in obligatory (NOT NULL) columns. For that, I've created a SwingWorker Task that handles converting the number of lines in the file to 100% on the maximum progress value, and adding up on the progress on the correct rate.
That's the SwingWorker:
public static class Task extends SwingWorker<String, Object> {
private int counter;
private double rate;
public Task(int max) {
// Adds the PropertyChangeListener to the ProgressBar
addPropertyChangeListener(
ViewHandler.getExportDialog().getProgressBar());
rate = (float)100/max;
setProgress(0);
counter = 0;
}
/** Increments the progress in 1 times the rate based on maximum */
public void step() {
counter++;
setProgress((int)Math.round(counter*rate));
}
#Override
public String doInBackground() throws IOException {
return null;
}
#Override
public void done() {
Toolkit.getDefaultToolkit().beep();
System.out.println("Progress done.");
}
}
My PropertyChangeListener, which is implemented by the JProgressBar wrapper:
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setIndeterminate(false);
progressBar.setValue((Integer) evt.getNewValue());
}
}
Then, where I actually use it, I override the doInBackground() method with the processing I need, calling step() on every iteration.
Task read = new Task(lines) {
#Override
public String doInBackground() throws IOException {
while(content.hasNextValue()) {
step();
// Processing
}
return output.toString();
}
};
read.execute();
return read.get();
So what is happening: the processing works and succeeds, then done() is called, and just after that the propertyChange() registers two 'state' events and one 'progress' event, setting the ProgressBar's progress from 0% to 100%.
What is happening What I thought was happening (check Hovercraft's answer for clarification) is described in the JavaDocs:
Because PropertyChangeListeners are notified asynchronously on the Event Dispatch Thread multiple invocations to the setProgress method might occur before any PropertyChangeListeners are invoked. For performance purposes all these invocations are coalesced into one invocation with the last invocation argument only.
So, after all that, my question is: am I doing something wrong? If not, is there a way for me to make the Event Dispatch Thread notify the PropertyChangeListeners as the onProgress() happens, or at least from time to time?
Obs.: the processing I'm testing takes from 3~5s.
Your problem is here:
read.execute();
return read.get();
get() is a blocking call, and so calling it from the event thread immediately after executing your worker will block the event thread and your GUI.
Instead, it should be called from a call-back method such as the done() method or from the property change listener after the worker has changed its state property to SwingWorker.StateValue.DONE.
For example
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
#SuppressWarnings("serial")
public class TestSwingWorkerGui extends JPanel {
private JProgressBar progressBar = new JProgressBar(0, 100);
private Action myAction = new MyAction("Do It!");
public TestSwingWorkerGui() {
progressBar.setStringPainted(true);
add(progressBar);
add(new JButton(myAction));
}
private class MyAction extends AbstractAction {
public MyAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
myAction.setEnabled(false);
Task read = new Task(30) {
#Override
public String doInBackground() throws Exception {
int counter = getCounter();
int max = getMax();
while (counter < max) {
counter = getCounter();
step();
TimeUnit.MILLISECONDS.sleep(200);
}
return "Worker is Done";
}
};
read.addPropertyChangeListener(new MyPropListener());
read.execute();
}
}
private class MyPropListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if ("progress".equals(name)) {
progressBar.setIndeterminate(false);
progressBar.setValue((Integer) evt.getNewValue());
} else if ("state".equals(name)) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
myAction.setEnabled(true);
#SuppressWarnings("unchecked")
SwingWorker<String, Void> worker = (SwingWorker<String, Void>) evt.getSource();
try {
String text = worker.get();
System.out.println("worker returns: " + text);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
private static void createAndShowGui() {
TestSwingWorkerGui mainPanel = new TestSwingWorkerGui();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class Task extends SwingWorker<String, Void> {
private int counter;
// private double rate;
private int max;
public Task(int max) {
// Adds the PropertyChangeListener to the ProgressBar
// addPropertyChangeListener(gui);
// !!rate = (float)100/max;
this.max = max;
setProgress(0);
counter = 0;
}
/** Increments the progress in 1 times the rate based on maximum */
public void step() {
counter++;
int progress = (100 * counter) / max;
progress = Math.min(100, progress);
setProgress(progress);
// setProgress((int)Math.round(counter*rate));
}
public int getCounter() {
return counter;
}
public int getMax() {
return max;
}
#Override
public String doInBackground() throws Exception {
return null;
}
#Override
public void done() {
Toolkit.getDefaultToolkit().beep();
System.out.println("Progress done.");
}
}
Related
I am using a Worker class which extends SwingWorker and process String values in it's process method and prints them to a JTextArea.
So I am making a lot of publish(some_string) calls to print the strings to the text area, but when I execute my Worker thread - it doesn't print all the text I publish in the doInBackground() method. It misses a lot of publish calls, and I get only partial of the words I want.
But when I excute the thread and put a breakpoint and follow it step by step I can see that it does print all the strings from the publish calls.
Doing what it's supposed to do.
Why is it working in debug mode but in normal mode it doesn't?
My code:
public class Worker extends SwingWorker<Void , String>
{
public Worker(int int optionOfWork){};
protected Void doInBackground() throws Exception
{
...
publish("Hellow");
publish("This is a test");
...
// a lot of **publish(some_word) calls**
...
publish("Some more words");
}//doInBackground()
#override
protected void process(List<String> wordsToPrint)
{
String lastWordRecieved = wordsToPrint.get(wordsToPrint.size()-1);
mainWindowTextArea.append(lastWord); // not printing\appending all the words sent here by the publish calls
}
#override
protected void done()
{
publish("\nDone");
}
}//Worker class
All I am doing in the main thread is making a Worker object and excuting it:
Worker worker = new Worker();
and I start it using worker.excute();
Change this:
#override
protected void process(List<String> wordsToPrint)
{
String lastWordRecieved = wordsToPrint.get(wordsToPrint.size()-1);
mainWindowTextArea.append(lastWord); // not printing\appending all the words sent here by the publish calls
}
#override
protected void done()
{
publish("\nDone");
}
to this:
#override
protected void process(List<String> wordsToPrint) {
for (String text: wordsToPrint) {
mainWindowTextArea.append(text + "\n");
}
}
#override
protected void done() {
publish("\nDone");
try {
get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
The for loop is generally how you process through the String list passed into the process method. Don't forget to call get() on your SwingWorker if only to check to see if any exceptions have been thrown. If still stuck, then yes, create and post that mcve.
You may need to put some Thread.sleep(10) between your publish(...) calls to prevent the calls from hogging CPU.
For example:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
#SuppressWarnings("serial")
public class SwingWorkerExample extends JPanel {
private JTextArea textArea = new JTextArea(30, 40);
private MyWorker myWorker;
public SwingWorkerExample() {
textArea.setFocusable(false);
JScrollPane scrollPane = new JScrollPane(textArea);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
JPanel bottomPanel = new JPanel();
bottomPanel.add(new JButton(new AbstractAction("Start Worker") {
#Override
public void actionPerformed(ActionEvent e) {
if (myWorker != null && !myWorker.isDone()) {
myWorker.setLoopRunning(false);
}
myWorker = new MyWorker();
myWorker.execute();
}
}));
bottomPanel.add(new JButton(new AbstractAction("Stop Worker") {
#Override
public void actionPerformed(ActionEvent e) {
if (myWorker != null && !myWorker.isDone()) {
myWorker.setLoopRunning(false);
}
}
}));
setLayout(new BorderLayout());
add(scrollPane);
add(bottomPanel, BorderLayout.PAGE_END);
}
private class MyWorker extends SwingWorker<Void, String> {
private volatile boolean loopRunning = true;
#Override
protected Void doInBackground() throws Exception {
for (int j = 0; j < 1000 && loopRunning; j++) {
for (int i = 0; i < 1000 && loopRunning; i++) {
String text = String.format("My Text %03d", i);
publish(text);
TimeUnit.MILLISECONDS.sleep(10);
}
}
return null;
}
#Override
protected void process(List<String> chunks) {
for (String text : chunks) {
textArea.append(text + "\n");
}
}
#Override
protected void done() {
textArea.append("Done\n");
try {
get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
public void setLoopRunning(boolean running) {
this.loopRunning = running;
}
public boolean isLoopRunning() {
return loopRunning;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
SwingWorkerExample mainPanel = new SwingWorkerExample();
JFrame frame = new JFrame("SwingWorkerExample");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
}
You are appending only last published string (in given chunk)
#override
protected void process(List<String> wordsToPrint)
{
String lastWordRecieved = wordsToPrint.get(wordsToPrint.size()-1);
mainWindowTextArea.append(lastWord); // not printing\appending all the words sent here by the publish calls
}
Change that to
#override
protected void process(List<String> wordsToPrint)
{
for(String part:wordsToPrint){
mainWindowTextArea.append(part);
}
}
When you call publish that does not mean that process will be called right away. Rather that, published intems gets stacked, and passed as list of items in some point of time.
I have a GUI created in a class called MainFrame. One of the JPanels of the GUI displays the current time and date, by second. When the user decides to use the GUI to analyze data, it invokes a class that processes data. When the data process is happening, the timer pauses, then resumes when the dataprocess is over. How can I have the timer continuously run even if the program is running? The timer is its own thread, but I do not understand where to start a thread for a JPanel.
Here are some code cut-outs
App.java (app to start the entire GUI)
public class App {
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MainFrame();
}
});
}
}
MainFrame (class that handles the JPanels and dataprocess impl)
public class MainFrame extends JFrame {
private DataProcess dataProcess = null;
...
...
private StatusPanel statusPanel;
...
...
public MainFrame() {
...
setJMenuBar(createFrameMenu());
initializeVariables();
constructLayout();
createFileChooser();
constructAppWindow();
}
private void initializeVariables() {
this.dataProcess = new DataProcess();
...
this.statusPanel = new StatusPanel();
...
}
private void constructLayout() {
JPanel layoutPanel = new JPanel();
layoutPanel.setLayout(new GridLayout(0, 3));
layoutPanel.add(dataControlsPanel());
setLayout(new BorderLayout());
add(layoutPanel, BorderLayout.CENTER);
add(statusPanel, BorderLayout.PAGE_END);
}
StatusPanel (panel that shows timer etc)
public class StatusPanel extends JPanel {
private static final long serialVersionUID = 1L;
private JLabel statusLabel;
private JLabel timeLabel;
private Timer timer;
public StatusPanel() {
initializeVariables();
constructLayout();
startTimer();
}
private void constructLayout() {
setLayout(new FlowLayout(FlowLayout.CENTER));
add(statusLabel);// , FlowLayout.CENTER
add(timeLabel);
}
public void startTimer() {
this.timer.start();
}
public void stopTimer() {
this.timer.setRunning(false);
}
private void initializeVariables() {
this.statusLabel = new JLabel();
this.timeLabel = new JLabel();
this.statusLabel.setText(StringConstants.STATUS_PANEL_TEXT);
this.timer = new Timer(timeLabel);
}
}
Timer.java (timer that is used in StatusPanel)
public class Timer extends Thread {
private boolean isRunning;
private JLabel timeLabel;
private SimpleDateFormat timeFormat;
public Timer(JLabel timeLabel) {
initializeVariables(timeLabel);
}
private void initializeVariables(JLabel timeLabel) {
this.timeLabel = timeLabel;
this.timeFormat = new SimpleDateFormat("HH:mm:ss dd-MM-yyyy");
this.isRunning = true;
}
#Override
public void run() {
while (isRunning) {
Calendar calendar = Calendar.getInstance();
Date currentTime = calendar.getTime();
timeLabel.setText(timeFormat.format(currentTime));
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
}
Data process is done in the dataControlsPanel by use of actionlisteners.
When the user decides to use the GUI to analyze data, it invokes a class that processes data. When the data process is happening, the timer pauses, then resumes when the dataprocess is over. How can I have the timer continuously run even if the program is running
First of all, your timer should be a javax.swing.Timer or "Swing" Timer. This is built to work specifically on the Swing event thread and so should avoid many of the Swing threading problems that your current code shows -- for example, here: timeLabel.setText(timeFormat.format(currentTime)); -- this makes a Swing call from a background thread and is dangerous code. Next
The processing code should go into a SwingWorker. When the worker executes, you can pause the Swing Timer by calling stop() on the Timer, or simply let the timer to continue to run. When the SwingWorker has completed its action -- something I usually listen for with a PropertyChangeListener added to the SwingWorker, listening for its state property to change to SwingWorker.StateValue.DONE, call get() on the worker to extract any data it holds and more importantly to capture any exceptions that might be thrown.
For example:
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
#SuppressWarnings("serial")
public class MyApp extends JPanel {
// display the date/time
private static final String DATE_FORMAT = "HH:mm:ss dd-MM-yyyy";
private static final DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
// timer updates measures seconds, but updates every 0.2 sec's to be sure
private static final int TIMER_DELAY = 200;
// JLabel that shows the date/time
private JLabel timeLabel = new JLabel("", SwingConstants.CENTER);
// JButton's Action / listener. This starts long-running data processing
private Action dataProcessAction = new DataProcessAction("Process Data");
// the SwingWorker that the above Action executes:
private LongRunningSwProcess longRunningProcess;
// label to display the count coming from the process above
private JLabel countLabel = new JLabel("00");
public MyApp() {
// create a simple GUI
JPanel dataProcessingPanel = new JPanel();
dataProcessingPanel.add(new JButton(dataProcessAction)); // button that starts process
dataProcessingPanel.add(new JLabel("Count:"));
dataProcessingPanel.add(countLabel);
setLayout(new BorderLayout());
add(timeLabel, BorderLayout.PAGE_START);
add(dataProcessingPanel);
showTimeLabelCurrentTime();
// create and start Swing Timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
// display count from swing worker
public void setCount(int newValue) {
countLabel.setText(String.format("%02d", newValue));
}
// clean up code after SwingWorker finishes
public void longRunningProcessDone() {
// re-enable JButton's action
dataProcessAction.setEnabled(true);
if (longRunningProcess != null) {
try {
// handle any exceptions that might get thrown from the SW
longRunningProcess.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
// display the current time in our timeLabel JLabel
private void showTimeLabelCurrentTime() {
long currentTime = System.currentTimeMillis();
Date date = new Date(currentTime);
timeLabel.setText(dateFormat.format(date));
}
// Timer's ActionListener is simple -- display the current time in the timeLabel
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
showTimeLabelCurrentTime();
}
}
// JButton's action. This starts the long-running SwingWorker
private class DataProcessAction extends AbstractAction {
public DataProcessAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
setEnabled(false); // first disable the button's action
countLabel.setText("00"); // reset count label
// then create SwingWorker and listen to its changes
longRunningProcess = new LongRunningSwProcess();
longRunningProcess.addPropertyChangeListener(new DataProcessListener());
// execute the swingworker
longRunningProcess.execute();
}
}
// listen for state changes in our SwingWorker
private class DataProcessListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(LongRunningSwProcess.COUNT)) {
setCount((int)evt.getNewValue());
} else if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
longRunningProcessDone();
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("My App");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new MyApp());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// mock up of SwingWorker for long-running action
class LongRunningSwProcess extends SwingWorker<Void, Integer> {
public static final String COUNT = "count";
private static final int MIN_TIME_OUT = 5;
private static final int MAX_TIME_OUT = 10;
private int count = 0;
#Override
protected Void doInBackground() throws Exception {
// all this mock up does is increment a count field
// every second until timeOut reached
int timeOut = MIN_TIME_OUT + (int) (Math.random() * (MAX_TIME_OUT - MIN_TIME_OUT));
for (int i = 0; i < timeOut; i++) {
setCount(i);
TimeUnit.SECONDS.sleep(1);
}
return null;
}
// make count a "bounded" property -- one that will notify listeners if changed
public void setCount(int count) {
int oldValue = this.count;
int newValue = count;
this.count = newValue;
firePropertyChange(COUNT, oldValue, newValue);
}
public int getCount() {
return count;
}
}
Below is the compiled program replica of actual problem code,
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class Dummy {
public static boolean getUserCheck(int size, boolean Check) {
if (Check) {
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
if (ret > 0) {
System.out.println("User said No: " + ret);
return false;
} else if (ret <= 0) {
System.out.println("user said Yes: " + ret);
return true;
}
}
return true;
}
public static void workerMethod1() {
System.out.println("am worker method 1");
}
public static void workerMethod2() {
System.out.println("am worker method 2");
}
public static void main(String[] args) {
System.out.println("mainthread code line 1");
int size = 13;
boolean thresholdBreach = true;
if (getUserCheck(size, thresholdBreach)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod1();
}
});
SwingUtilities.invokeLater(new Runnable() {
public void run() {
workerMethod2();
}
});
}
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
}
}
where i would like to run the if{} block in main() on separate thread. Because these 2 lines,
System.out.println("mainthread code line 2");
System.out.println("mainthread code line 3");
need not wait for completion of if(){} block
Another problem is, experts recommend to run confirm-dialog methods on event thread.
int ret = JOptionPane.showConfirmDialog(null, size + " entries, Yes or no?",
"Warning", 0);
Please help me!!!!
JOptionPane is a Swing method and should be called on the EDT, the Event Dispatch Thread, and only on this thread, and so it suggests that all your code above should be on the EDT, and that most of your SwingUtilities.invokeLater(new Runnable() calls are completely unnecessary. The only necessary ones will be the main one, where you launch your Swing GUI code, and any areas where Swing calls need to be made from within background threads. Again, if any of the above code is being made within background threads, then the JOptionPane should not be in that thread.
For more specific information in this or any other answer, please provide more specific information in your question. Let's end all confusion. The best way to get us to fully and quickly understand your problem would be if you were to to create and post a minimal example program, a small but complete program that only has necessary code to demonstrate your problem, that we can copy, paste, compile and run without modification.
I have a sneaking suspicion that a decent refactoring along MVC lines could solve most of your problems. Your code is very linear with its lines of code that must follow one another and its if blocks, and it is also tightly coupled with your GUI, two red flags for me. Perhaps better would be less linear code, more event and state-driven code, code where your background code interacts with the GUI via observer notification, and where the background code likewise responds to state changes in the GUI from control notification.
Your control needs two SwingWorkers, one to get the row count and the other to get the rest of the data if the user decides to do so. I'd add a PropertyChangeListener to the first SwingWorker to be notified when the row count data is ready, and then once it is, present it to the view for the user to select whether or not to proceed. If he decides to proceed, I'd then call the 2nd SwingWorker to get the main body of the data.
For example, a rough sketch of what I'm talking about:
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
#SuppressWarnings("serial")
public class SwingWorkerFooView extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = 300;
private JProgressBar progressBar;
private JDialog dialog;
public SwingWorkerFooView() {
add(new JButton(new ButtonAction("Foo", this)));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
public boolean showOptionGetAllData(int numberOfRows) {
String message = "Number of rows = " + numberOfRows + ". Get all of the data?";
String title = "Get All Of Data?";
int optionType = JOptionPane.YES_NO_OPTION;
int result = JOptionPane.showConfirmDialog(this, message, title, optionType);
return result == JOptionPane.YES_OPTION;
}
public void showProgressBarDialog() {
progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
Window window = SwingUtilities.getWindowAncestor(this);
dialog = new JDialog(window, "Hang on", ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel();
panel.add(progressBar);
dialog.add(panel);
dialog.pack();
dialog.setLocationRelativeTo(this);
dialog.setVisible(true);
}
public void closeProgressBarDialog() {
dialog.dispose();
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SwingWorkerFoo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SwingWorkerFooView());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class ButtonAction extends AbstractAction {
Workers workers = new Workers();
private SwingWorker<Integer, Void> firstWorker;
private SwingWorker<List<String>, Void> secondWorker;
private SwingWorkerFooView mainGui;
public ButtonAction(String name, SwingWorkerFooView mainGui) {
super(name);
this.mainGui = mainGui;
}
#Override
public void actionPerformed(ActionEvent e) {
firstWorker = workers.createFirstWorker();
firstWorker.addPropertyChangeListener(new FirstPropertyChangeListener());
firstWorker.execute();
mainGui.showProgressBarDialog();
}
private class FirstPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
int numberOfRows = firstWorker.get();
boolean getAllData = mainGui.showOptionGetAllData(numberOfRows);
if (getAllData) {
secondWorker = workers.createSecondWorker();
secondWorker.addPropertyChangeListener(new SecondPropertyChangeListener());
secondWorker.execute();
mainGui.showProgressBarDialog();
} else {
// user decided not to get all data
workers.cleanUp();
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
private class SecondPropertyChangeListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
mainGui.closeProgressBarDialog();
try {
List<String> finalData = secondWorker.get();
// display finalData in the GUI
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
}
class Workers {
// database object that may be shared by two SwingWorkers
private Object someDataBaseVariable;
private Random random = new Random(); // just for simulation purposes
private class FirstWorker extends SwingWorker<Integer, Void> {
#Override
protected Integer doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
// here we create our database object and check how many rows there are
int rows = random.nextInt(10 + 10); // this is just for demonstration purposes only
// here we create any objects that must be shared by both SwingWorkers
// and they will be saved in a field of Workers
someDataBaseVariable = "Fubar";
return rows;
}
}
private class SecondWorker extends SwingWorker<List<String>, Void> {
#Override
protected List<String> doInBackground() throws Exception {
// The Thread.sleep(...) is not going to be in final production code
// it's just to simulate a long running task
Thread.sleep(4000);
List<String> myList = new ArrayList<>();
// here we go through the database filling the myList collection
return myList;
}
}
public SwingWorker<Integer, Void> createFirstWorker() {
return new FirstWorker();
}
public void cleanUp() {
// TODO clean up any resources and database stuff that will not be used.
}
public SwingWorker<List<String>, Void> createSecondWorker() {
return new SecondWorker();
}
}
The key to all of this is to not to think in a linear console program way but rather to use observer design pattern, i.e., listeners of some sort to check for change of state of both the GUI and the model.
It's essentially:
create worker
add observer to worker (property change listener)
execute worker
show progress bar dialog or notify user in some way that worker is executing.
The listener will be notified when the worker is done, and then you can query the worker (here via the get() method call) as to its end result.
Then the progress dialog can be closed
And the view can display the result or get additional information from the user.
Yes; SwingUtilities.invokeLater() simply places your runnable on the AWT event queue to be processed later, and it is safe to do so at any time.
I have an ExectorService and the following code, everything is working fine with the futures and the concurrency. However, when I try to update my progress bar from SomeClass(), it seems to only update the UI after the invokeAll() is complete...basically the progress bar only updates once everything is complete which deems it useless.
How can I resolve this? I've looked at CompletionServices as well as SwingWorkers but I don't know how to apply them to my code. Any assistance will be appreciated.
class SomeClass() {
private static class Result {
private final String someVar;
public Result(String code) {
this.someVar = code;
}
}
public static Result compute(Object obj) {
// ... compute stuff
someVar = "computedResult";
return Result(someVar);
}
public someFunction() {
List<Callable<Result>> tasks = new ArrayList<Callable<Result>>();
for (Object f : listOfObjects) {
Callable<Result> c = new Callable<Result>() {
#Override
public Result call() throws Exception {
someClassUI.jProgressBar.setValue(50);
return compute(file);
}
};
tasks.add(c);
}
List<Callable<Result>> tasks = new ArrayList<Callable<Result>>();
List<Future<Result>> results = executorService.invokeAll(tasks);
for (Future<Result> fr : results) {
String value = fr.get().resultValue;
}
}
}
class SomeClassUI {
public static jProgressBar;
public someClassUI() {
jProgressBar = new JProgressBar(0,100);
}
private void button1ActionPerformed(ActionEvent e) {
SomeClass theClass = new SomeClass();
theClass.someFunction();
}
}
edit: edited to add some extra code to help understanding
You're accessing a Swing component from a thread other than the event dispatch thread. That is forbidden by the Swing threading policy.
Use this code to update the progress bar from the background thread:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
someClassUI.jProgressBar.setValue(50);
}
});
Read more about concurrency in swing in the official swing tutorial.
I've been testing with code similar to yours, until I realized the following:
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException
Executes the given tasks, returning a list of Futures holding their status and results when all complete.
That "when all complete" is what is causing the behaviour of the progress bar. In other words, if you get the list of Future only when all tasks complete, then, obviously, iterating over them and updating the bar would be so fast that you see only the last update, when the bar is full.
What you could do, like I did, is calling submit for each of your tasks, and add the Futures individually to a list.
The example code below has been tested, and works here. You should be able to adapt it to your own purposes.
Listener interface:
public interface UpdateListener {
void update(double percent);
}
Task Executor:
public class SomeClass {
// instance variables
private UpdateListener listener;
private ExecutorService executor;
/** Parameter constructor of objects of class SomeClass. */
public SomeClass(UpdateListener l) {
listener = l;
executor = Executors.newFixedThreadPool(4);
}
/** */
public void doIt() throws InterruptedException, ExecutionException {
int numOfTasks = 5, completedTasks = 0;
List<Future<Integer>> results = new ArrayList<>();
// Submit each of your tasks. Here I create them manually.
for (int i = 0; i < numOfTasks; ++i) {
final int I = i;
Callable<Integer> c = new Callable<Integer>() {
#Override
public Integer call() throws Exception {
try {
Thread.sleep((long) I * 1000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
return new Integer(I);
}
};
results.add(executor.submit(c));
}
// Retrieve individual results and update progress bar.
for (Future<Integer> fr : results) {
Integer i = fr.get();
++completedTasks;
listener.update((double) completedTasks / numOfTasks);
}
}
}
UI class:
public class SomeClassUI implements Runnable, UpdateListener {
// instance variables
private JProgressBar bar;
private JFrame frame;
private SomeClass t;
/** Empty constructor of objects of class SomeClassUI. */
public SomeClassUI() {
t = new SomeClass(this);
}
/** Builds the interface. */
public void run() {
bar = new JProgressBar(0, 100);
bar.setStringPainted(true);
JPanel panel = new JPanel(new FlowLayout());
panel.setPreferredSize(new Dimension(200, 100));
panel.add(bar);
frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
/** Method from the interface. Updates the progress bar. */
#Overrides
public void update(double percent) {
final double PERCENT = percent;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int v = (int) (100 * PERCENT);
bar.setValue(v);
}
});
}
/** Tests the program. */
public void go() {
SwingUtilities.invokeLater(this);
try {
t.doIt();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main() {
new SomeClassUI().go();
}
}
This question is related somewhat to the one i asked HERE.
Now, i have a class "Controller" which consists of the main method and all the swing components. there is a class named "VTOL" which consists of a variable named "altitude"(i have declared this variable volatile as of now).
here is a class that consists of a thread which runs in the background:
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* #author Vineet
*/
public class Gravity extends Thread {
String altStr;
double alt;
Controller ctrl = new Controller();
#Override
public void run() {
while (true) {
alt=VTOL.altitude;
System.out.println(alt);
alt = alt-0.01;
VTOL.altitude= (int) alt;
altStr=new Integer(VTOL.altitude).toString();
ctrl.lblAltitude.setText(altStr);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Firstly, the problem i was facing initially was that i couldnt update value of "altitude" it remained 0 throughout the execution of program. So i declared it as volatile (I dont know if its a good practice)
Secondly, there is a jLabel in Controller class named "lblAltitude", i wish to update its value as its changed in this thread, but somehow thats not happening.
How can i do that?
A solution is to use a SwingPropertyChangeSupport object, to make altitude a "bound" property with this support object, to have your GUI listener to this model class and to thereby notify the GUI of changes in altitude.
e.g.,
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
public class Gravity implements Runnable {
public static final String ALTITUDE = "altitude";
private SwingPropertyChangeSupport swingPcSupport = new SwingPropertyChangeSupport(this);
private volatile double altitude;
#Override
public void run() {
while (true) {
double temp = altitude + 10;
setAltitude(temp); // fires the listeners
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public double getAltitude() {
return altitude;
}
public void setAltitude(double altitude) {
Double oldValue = this.altitude;
Double newValue = altitude;
this.altitude = newValue;
// this will be fired on the EDT since it is a SwingPropertyChangeSupport object
swingPcSupport.firePropertyChange(ALTITUDE, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.removePropertyChangeListener(listener);
}
}
For a more complete runnable example:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class GravityTestGui extends JPanel {
private static final long ALT_SLEEP_TIME = 400;
private static final double ALT_DELTA = 5;
JLabel altitudeLabel = new JLabel(" ");
private Gravity gravity = new Gravity(ALT_SLEEP_TIME, ALT_DELTA);
public GravityTestGui() {
add(new JLabel("Altitude:"));
add(altitudeLabel);
gravity.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (Gravity.ALTITUDE.equals(pcEvt.getPropertyName())) {
String altText = String.valueOf(gravity.getAltitude());
altitudeLabel.setText(altText);
}
}
});
new Thread(gravity).start();
}
private static void createAndShowGui() {
GravityTestGui mainPanel = new GravityTestGui();
JFrame frame = new JFrame("GravityTest");
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 Gravity implements Runnable {
public static final String ALTITUDE = "altitude";
private SwingPropertyChangeSupport swingPcSupport = new SwingPropertyChangeSupport(this);
private volatile double altitude;
private long sleepTime;
private double delta;
public Gravity(long sleepTime, double delta) {
this.sleepTime = sleepTime;
this.delta = delta;
}
#Override
public void run() {
while (true) {
double temp = altitude + delta;
setAltitude(temp); // fires the listeners
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public double getAltitude() {
return altitude;
}
public void setAltitude(double altitude) {
Double oldValue = this.altitude;
Double newValue = altitude;
this.altitude = newValue;
// this will be fired on the EDT since it is a SwingPropertyChangeSupport object
swingPcSupport.firePropertyChange(ALTITUDE, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
swingPcSupport.removePropertyChangeListener(listener);
}
}
Whenever you modify a Swing component, you need to ensure that this event happens in the Event Dispatch Thread (i.e. EDT).
A third approach would be to have your Swing component know about the model, VTOL.
In Gravity, you'd update VTOL.altitude, then call repaint on the component. e.g.
while (true) {
VTOL.altitude -= 0.01;
VTOL.makeAnyOtherChangesHereAsWell();
controller.repaint();
// sleep, break etc. left as an exercise for the reader
}
Then, in the paintComponent() method (or maybe somewhere else in all the paint calls, there's a slight chance it needs to be elsewhere...) of Controller, which you know is running on the EDT
// update my widgets from the VTOL model - may want this in a method
String altStr=new Integer(VTOL.altitude).toString();
this.lblAltitude.setText(altStr);
// may be more, e.g. ...
this.lblFuelSupply.setText(VTOL.getFuelSupply());
super.paintComponent(); // now go draw stuff...
This is a bit tighter coupled than SwingPropertyChangeSupport, but the coupling is all between very related classes, so it is "reasonable", and in some ways this may be "clearer". And the Event Dispatch Queue will combine multiple repaints so this isn't as inefficient as it first appear. If multiple threads are updating stuff and queuing up multiple repaints(), only the last repaint() actually does anything.
A disadvantage is that if your GUI has a gazillion widgets and you update all of them every time this may get a bit slow. But processors are amazingly fast nowadays.