When I start my application it opens a JFrame (the main window) and a JFilechooser to select an input directory, which is then scanned.
The scan method itself creates a new JFrame which contains a JButton and a JProgressBar and starts a new Thread which scans the selected Directory. Up until this point everything works fine.
Now I change the Directory Path in my Main Window, which calls the scan method again. This time it creates another JFrame which should contain the JProgressBar and the JButton but it shows up empty (The JFrame Title is still set).
update:
minimal example
public class MainWindow
{
private JFrame _frame;
private JTextArea _textArea;
private ProgressBar _progress;
public MainWindow() throws InterruptedException, ExecutionException
{
_frame = new JFrame("Main Window");
_textArea = new JTextArea();
_frame.add(_textArea);
_frame.setSize(200, 200);
_frame.setVisible(true);
_textArea.setText(doStuffinBackground());
_progress.dispose();
}
private String doStuffinBackground() throws InterruptedException,
ExecutionException
{
setUpProgressBar();
ScanWorker scanWorker = new ScanWorker();
scanWorker.execute();
return scanWorker.get();
}
private void setUpProgressBar()
{
// Display progress bar
_progress = new ProgressBar();
}
class ProgressBar extends JFrame
{
public ProgressBar()
{
super();
JProgressBar progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
progressBar.setStringPainted(false);
add(progressBar);
setTitle("Progress Window");
setSize(200, 200);
toFront();
setVisible(true);
}
}
class ScanWorker extends SwingWorker<String, Void>
{
#Override
public String doInBackground() throws InterruptedException
{
int j = 0;
for (int i = 0; i < 10; i++)
{
Thread.sleep(1000);
j += 1;
}
return String.valueOf(j);
}
}
public static void main(String[] args) throws InvocationTargetException,
InterruptedException
{
SwingUtilities.invokeAndWait(new Runnable()
{
public void run()
{
// Start the main controller
try
{
new MainWindow();
}
catch (InterruptedException | ExecutionException e) {}
}
});
}
}
From the basic looks of your scan method, you are blocking the Event Dispatching Thread, when you scan the directory, which is preventing it from updating the UI.
Specifically, you don't seem to truly understand what Callable and FutureTask are actually used for or how to use them properly...
Calling FutureTask#run will call the Callable's call method...from within the current thread context.
Take a look at Concurrency in Swing for more details...
Instead of trying to use FutureTask and Callable in this manner, consider using a SwingWorker, which is designed to do this kind of work (and uses Callable and FutureTask internally)
Have a look at Worker Threads and SwingWorker for more details
Now, before you jump down my throat and tell me that "it works the first time I ran it", that's because you're not starting your UI properly. All Swing UI's should be create and manipulated from within the context of the Event Dispatching Thread. You main method is executed in, what is commonly called, the "main thread", which is not the same as the EDT. This is basically setting up fluke situation in where the first time you call scan, you are not running within the context of the EDT, allowing it to work ... and breaking the single thread rules of Swing in the process...
Take a look at Initial Threads for more details...
I would also consider using a JDialog instead of another frame, even if it's not modal, it makes for a better paradigm for your application, as it really should only have a single main frame.
Updated based on new code
So, basically, return scanWorker.get(); is a blocking call. It will wait until the doInBackground method completes, which means it's block the EDT, still...'
Instead, you should be making use of the publish, process and/or done methods of the SwingWorker
import java.util.ArrayList;
import java.util.List;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class MainWindow {
private JFrame _frame;
private JTextArea _textArea;
private ProgressBar _progress;
public MainWindow() {
_frame = new JFrame("Main Window");
_textArea = new JTextArea();
_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
_frame.add(new JScrollPane(_textArea));
_frame.setSize(200, 200);;
_frame.setVisible(true);
doStuffinBackground();
}
private void doStuffinBackground() {
// _progress = new ProgressBar();
// ScanWorker scanWorker = new ScanWorker();
// scanWorker.execute();
// return scanWorker.get();
_progress = new ProgressBar();
ScanWorker worker = new ScanWorker(_textArea, _progress);
worker.execute();
_progress.setVisible(true);
}
class ProgressBar extends JDialog {
public ProgressBar() {
super(_frame, "Scanning", true);
JProgressBar progressBar = new JProgressBar();
progressBar.setIndeterminate(true);
progressBar.setStringPainted(false);
add(progressBar);
setTitle("Progress Window");
pack();
setLocationRelativeTo(_frame);
}
}
class ScanWorker extends SwingWorker<List<String>, String> {
private JTextArea textArea;
private ProgressBar progressBar;
protected ScanWorker(JTextArea _textArea, ProgressBar _progress) {
this.textArea = _textArea;
this.progressBar = _progress;
}
#Override
protected void process(List<String> chunks) {
for (String value : chunks) {
textArea.append(value + "\n");
}
}
#Override
public List<String> doInBackground() throws Exception {
System.out.println("...");
int j = 0;
List<String> results = new ArrayList<>(25);
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
j += 1;
System.out.println(j);
results.add(Integer.toString(j));
publish(Integer.toString(j));
}
return results;
}
#Override
protected void done() {
progressBar.dispose();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MainWindow();
}
});
}
}
Related
I've downloaded a small Java project from oracle website to create a progress bar.
I understand it, but I need to apply it in a different way, the application is creating a thread in the background so the progress bar can be updated accordingly (doInBackground()).
My question is, how can I replace this kind of process in the background in this application by a method from my application (method is just doing a kind of batch processing on a database), can someone help please?
Here is code by Oracle:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
import java.util.Random;
public class ProgressBarDemo2 extends JPanel
implements ActionListener, PropertyChangeListener {
private JProgressBar progressBar;
private JButton startButton;
private JTextArea taskOutput;
private Task task;
class Task extends SwingWorker<Void, Void> {
/*
* Main task. Executed in background thread.
*/
#Override
public Void doInBackground() {
Random random = new Random();
int progress = 0;
//Initialize progress property.
setProgress(0);
//Sleep for at least one second to simulate "startup".
try {
Thread.sleep(1000 + random.nextInt(2000));
} catch (InterruptedException ignore) {}
while (progress < 100) {
//Sleep for up to one second.
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException ignore) {}
//Make random progress.
progress += random.nextInt(10);
setProgress(Math.min(progress, 100));
}
return null;
}
/*
* Executed in event dispatch thread
*/
public void done() {
Toolkit.getDefaultToolkit().beep();
startButton.setEnabled(true);
taskOutput.append("Done!\n");
}
}
public ProgressBarDemo2() {
super(new BorderLayout());
//Create the demo's UI.
startButton = new JButton("Start");
startButton.setActionCommand("start");
startButton.addActionListener(this);
progressBar = new JProgressBar(0, 100);
progressBar.setValue(0);
//Call setStringPainted now so that the progress bar height
//stays the same whether or not the string is shown.
progressBar.setStringPainted(true);
taskOutput = new JTextArea(5, 20);
taskOutput.setMargin(new Insets(5,5,5,5));
taskOutput.setEditable(false);
JPanel panel = new JPanel();
panel.add(startButton);
panel.add(progressBar);
add(panel, BorderLayout.PAGE_START);
add(new JScrollPane(taskOutput), BorderLayout.CENTER);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
}
/**
* Invoked when the user presses the start button.
*/
public void actionPerformed(ActionEvent evt) {
progressBar.setIndeterminate(true);
startButton.setEnabled(false);
//Instances of javax.swing.SwingWorker are not reusuable, so
//we create new instances as needed.
task = new Task();
task.addPropertyChangeListener(this);
task.execute();
}
/**
* Invoked when task's progress property changes.
*/
public void propertyChange(PropertyChangeEvent evt) {
if ("progress" == evt.getPropertyName()) {
int progress = (Integer) evt.getNewValue();
progressBar.setIndeterminate(false);
progressBar.setValue(progress);
taskOutput.append(String.format(
"Completed %d%% of task.\n", progress));
}
}
/**
* Create the GUI and show it. As with all GUI code, this must run
* on the event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("ProgressBarDemo2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
JComponent newContentPane = new ProgressBarDemo2();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
First, I'd recommend by defining a concept of a "progressable" state
public interface Progressable {
public void setProgress(int progress); // I prefer double, but we'll keep it inline with the rest of the API
}
Then, the entry point for your tasks would take a reference to Progressable
public class MySuperAwesomeLongRunningTask ... {
//...
private Progressable progressable;
public void performTask(Progressable progressable) {
this.prgressable = progressable
}
protected void methodThatDoesSomeWork() {
// Do some super duper work...
// calculate the progress of that work some how,
// based on your implementation...
int progress = ...;
progressable.setProgress(progress);
}
}
Then, create a SwingWorker which implements Progressable and calls your work...
class Task extends SwingWorker<Void, Void> implements Progressable {
private MySuperAwesomeLongRunningTask taskToBeDone;
public Task(MySuperAwesomeLongRunningTask taskToBeDone) {
self.taskToBeDone = taskToBeDone;
}
/*
* Main task. Executed in background thread.
*/
#Override
public Void doInBackground() {
taskToBeDone.performTask(this);
return null;
}
/*
* Executed in event dispatch thread
*/
public void done() {
// What ever you need to do...
}
}
Now, because SwingWorker already has a method called setProgress(int) it automatically conforms to Progressable (so long as you implement it), so when MySuperAwesomeLongRunningTask calls setProgress, it will actually be calling the SwingWorkers implementation.
This means, that the rest of the code basically remains the same, expect, I'd change
if ("progress" == evt.getPropertyName()) {
to
if ("progress".equals(evt.getPropertyName())) {
because comparing Strings with == is bad idea (and freaks me out :P)
I wanted to monitor the progress of my file getting copied from source to destination. I have used synchronized keyword but somehow it not working as i expect it to be, my logic might be wrong. I will be glad if you help me out.
Here is my Code.
public class Download extends javax.swing.JFrame {
int val=0;
private Timer t;
private ActionListener a;
/* Creates new form Download */
public Download() {
initComponents();
jProgressBar1.setValue(val);
a = new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
if (jProgressBar1.getValue() < val)
jProgressBar1.setValue(jProgressBar1.getValue()+1);
else
t.stop();
}
};
}
public synchronized void copy(String source,String url)
{
try {
val+=25;
t=new Timer(200,a);
t.start();
FileInputStream fs = new FileInputStream(source);
FileOutputStream os = new FileOutputStream(url);
int b;
while ((b = fs.read()) != -1) {
os.write(b);
}
os.close();
fs.close();
} catch (Exception E) {
E.printStackTrace();
}
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
JFileChooser chooser = new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
String url = null;
int returnValue = chooser.showDialog(null, "Select");
if (returnValue == JFileChooser.APPROVE_OPTION) {
url = chooser.getSelectedFile().getPath();
} else {
dispose();
}
JOptionPane.showMessageDialog(this,"Wait for Completion");
if(CB1.isSelected()==true)
{
File f = new File(getClass().getResource("/PCycle/Ele.pdf").getFile());
String source= f.getAbsolutePath();
copy(source,(url+"\\"+CB1.getText()+".pdf"));
}
if(CB2.isSelected()==true)
{
File f = new File(getClass().getResource("/PCycle/Mech.pdf").getFile());
String source= f.getAbsolutePath();
copy(source,(url+"\\"+CB2.getText()+".pdf"));
}
if(CB3.isSelected()==true)
{
File f = new File(getClass().getResource("/PCycle/Phy.pdf").getFile());
String source= f.getAbsolutePath();
copy(source,(url+"\\"+CB3.getText()+".pdf"));
}
if(CB4.isSelected()==true)
{
File f = new File(getClass().getResource("/PCycle/Civil.pdf").getFile());
String source= f.getAbsolutePath();
copy(source,(url+"\\"+CB4.getText()+".pdf"));
}
JOptionPane.showMessageDialog(this,"Completed");
try {
jProgressBar1.setValue(100);
Thread.sleep(3000);
} catch (InterruptedException ex) {
Logger.getLogger(Download.class.getName()).log(Level.SEVERE, null, ex);
}
System.exit(0);
}
}
Here I tried to implement a logic in such a that, whenever we call "copy" method it will copy the file from one location to another and before that it should run the timer method by which the progress on the jProgressBar is displayed. But unfortunately even after using synchronized it is not displaying the progress for each file.
The problem is you are blocking Swing's Event Dispatching Thread (EDT).
Swing does all drawing when the EDT is not busy responding to events. In this case jButton1ActionPerformed is not returning until all files have been copied. So although a Timer is started during each copy() call, the timers never get a chance to expire, because jButton1ActionPerformed has never returned.
In this case, you want to use a SwingWorker to copy the files in a background thread.
When you want to start copying the files:
start the timer in the main thread
create and start the SwingWorker.
open a model dialog to block further user actions (or otherwise disable the UI)
As the timer expires, your progress bar will advance, and be drawn.
When the SwingWorker is done() (which is executed on the EDT),
stop the timer
dismiss the dialog (or re-enable the UI)
Note: Do not create or access any UI items, or create/start/stop timers, from the background worker thread. These actions must only be performed on the EDT.
Rough example, showing disabling UI element, starting SwingWorker, publishing from the worker to show progress (which file is being download), enabling UI when the worker finishes.
File copy is faked using a 3 seconds sleep.
package progress;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;
#SuppressWarnings("serial")
public class Download extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(Download::new);
}
private final JButton downloadBtn = new JButton("Start Download");
private final JProgressBar progressBar = new JProgressBar();
private final Timer timer = new Timer(200, this::timerTick);
Download() {
super("Download Example");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 300);
setLocationByPlatform(true);
downloadBtn.addActionListener(this::startDownload);
add(downloadBtn, BorderLayout.PAGE_START);
progressBar.setStringPainted(true);
add(progressBar, BorderLayout.PAGE_END);
setVisible(true);
}
private void startDownload(ActionEvent evt) {
downloadBtn.setEnabled(false);
timer.start();
DownloadWorker worker = new DownloadWorker("File1", "FileB", "AnotherFile");
worker.execute();
}
private void timerTick(ActionEvent evt) {
progressBar.setValue(progressBar.getValue()+2);
}
private class DownloadWorker extends SwingWorker<Void, String> {
private final String[] files;
DownloadWorker(String ...files) {
this.files = files;
progressBar.setValue(0);
}
#Override
protected Void doInBackground() throws Exception {
for(String file : files) {
publish(file);
// Copy the file
Thread.sleep(3000); // Pretend copy takes a few seconds
}
return null;
}
#Override
protected void process(List<String> chunks) {
String file = chunks.get(chunks.size()-1); // Just last published filename
progressBar.setString("Downloading "+file + " ...");
}
#Override
protected void done() {
progressBar.setString("Complete");
progressBar.setValue(100);
timer.stop();
downloadBtn.setEnabled(true); // Re-enable UI
}
}
}
Consider this basic Swing program, consisting out of two buttons:
public class main {
public static void main(String[] args) {
JFrame jf = new JFrame("hi!");
JPanel mainPanel = new JPanel(new GridLayout());
JButton longAction = new JButton("long action");
longAction.addActionListener(event -> doLongAction());
JButton testSystemOut = new JButton("test System.out");
testSystemOut.addActionListener(event -> System.out.println("this is a test"));
mainPanel.add(longAction);
mainPanel.add(testSystemOut);
jf.add(mainPanel);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
public static void doLongAction() {
SwingUtilities.invokeLater(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("Interrupted!");
}
System.out.println("Finished long action");
});
}
}
I want my second button testSystemOut to be usable while the first one is working on its long action (here, I put a 3 second sleep in it). I can do that by manually putting doLongAction() in a Thread and call start(). But I've read I should use SwingUtilities instead, which works exactly like EventQueue here. However, if I do so, my Button freezes for the duration of its action.
Why?
By using SwingUtilities.invokeLater, you are calling the enclosed code, including the Thread.sleep(...) call, on the Swing event thread, which is something you should never do since it puts the entire event thread, the thread responsible for drawing your GUI's and responding to user input, to sleep -- i.e., it freezes your application. Solution: use a Swing Timer instead or do your sleeping in a background thread. If you are calling long-running code and using a Thread.sleep(...) to simulate it, then use a SwingWorker to do your background work for you. Please read Concurrency in Swing for the details on this. Note that there is no reason for the SwingUtilities.invokeLater where you have it since the ActionListener code will be called on the EDT (the Swing event thread) regardless. I would however use SwingUtilities.invokeLater where you create your GUI.
e.g.,
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame jf = new JFrame("hi!");
JPanel mainPanel = new JPanel(new GridLayout());
JButton testSystemOut = new JButton("test System.out");
testSystemOut.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("this is a test");
}
});
mainPanel.add(new JButton(new LongAction("Long Action")));
mainPanel.add(new JButton(new TimerAction("Timer Action")));
mainPanel.add(testSystemOut);
jf.add(mainPanel);
jf.pack();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);
}
});
}
#SuppressWarnings("serial")
public static class LongAction extends AbstractAction {
private LongWorker longWorker = null;
public LongAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
setEnabled(false);
longWorker = new LongWorker(); // create a new SwingWorker
// add listener to respond to completion of the worker's work
longWorker.addPropertyChangeListener(new LongWorkerListener(this));
// run the worker
longWorker.execute();
}
}
public static class LongWorker extends SwingWorker<Void, Void> {
private static final long SLEEP_TIME = 3 * 1000;
#Override
protected Void doInBackground() throws Exception {
Thread.sleep(SLEEP_TIME);
System.out.println("Finished with long action!");
return null;
}
}
public static class LongWorkerListener implements PropertyChangeListener {
private LongAction longAction;
public LongWorkerListener(LongAction longAction) {
this.longAction = longAction;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue() == SwingWorker.StateValue.DONE) {
// if the worker is done, re-enable the Action and thus the JButton
longAction.setEnabled(true);
LongWorker worker = (LongWorker) evt.getSource();
try {
// call get to trap any exceptions that might have happened during worker's run
worker.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
#SuppressWarnings("serial")
public static class TimerAction extends AbstractAction {
private static final int TIMER_DELAY = 3 * 1000;
public TimerAction(String name) {
super(name);
int mnemonic = (int) name.charAt(0);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
setEnabled(false);
new Timer(TIMER_DELAY, new TimerListener(this)).start();
}
}
public static class TimerListener implements ActionListener {
private TimerAction timerAction;
public TimerListener(TimerAction timerAction) {
this.timerAction = timerAction;
}
#Override
public void actionPerformed(ActionEvent e) {
timerAction.setEnabled(true);
System.out.println("Finished Timer Action!");
((Timer) e.getSource()).stop();
}
}
}
Don't use SwingUtilities.invokeLater(...) when you want to execute some long-running code. Do that in a separate normal thread.
Swing is not multi-threaded, it's event-driven. Because of that there are methods like SwingUtilities.invokeLater(...). You have to use those methods if you want to alter Swing-Components from a different thread (since Swing is not thread-safe), for example if you want to change a Button's text.
Everything thats GUI-Related runs in that Swing-Thread, e.g. Cursor-Blinks, Messages from the OS, User Commands, etc.
Since its a single thread, every long running Code in this thread it will block your GUI.
If you just do some long-running code that isn't GUI-related, it shouldn't run in the Swing-Event-Thread, but in its own separated thread.
See
https://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html
for why Swing is not Multi-Threaded.
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.
An application I am writing consists, among others, a JButton and a JTextArea. A click on the button leads to a long calculation, resulting in a text shown in the JTextArea. Even though the calculation is long, I can have middle-results on the go (think, for example, of an application which approximates pi up to 100 digits - every few seconds I could write another digit). The problem is, that even if I write (being in the ActionListener class because the button invoked the calculation) to set the text of the JTextArea to something, it isn't shown while the calculation is done, and I can only see the end result, after the calculation is over.
Why is it so, and how can I fix it?
Thank you in advance.
Your problem is that you're doing a long calculation in the main Swing thread, the EDT, and this will freeze your entire GUI until the process has completed itself. A solution is to use a background thread for your calculation, and an easy way to do this it to use a SwingWorker to create a thread background to the main Swing thread, the EDT, and publish/process the interim results into the JTextArea. For more on SwingWorkers and the EDT, please look here: Concurrency in Swing
Also, if you provide a decent sscce we can probably give you a more detailed response perhaps even with sample code.
An example SSCCE:
import java.awt.event.*;
import java.text.DecimalFormat;
import java.util.List;
import javax.swing.*;
public class InterimCalc {
private JPanel mainPanel = new JPanel();
private JTextField resultField = new JTextField(10);
private JButton doItBtn = new JButton("Do It!");
private DecimalFormat dblFormat = new DecimalFormat("0.0000000000");
private SwingWorker<Void, Double> mySwingWorker = null;
public InterimCalc() {
mainPanel.add(doItBtn);
mainPanel.add(resultField);
displayResult(0.0);
doItBtn.addActionListener(new DoItListener());
}
public void displayResult(double result) {
resultField.setText(dblFormat.format(result));
}
public JPanel getMainPanel() {
return mainPanel;
}
private class DoItListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (mySwingWorker != null && !mySwingWorker.isDone()) {
mySwingWorker.cancel(true);
}
displayResult(0.0);
mySwingWorker = new MySwingWorker();
mySwingWorker.execute();
}
}
private class MySwingWorker extends SwingWorker<Void, Double> {
private static final int INTERIM_LENGTH = 10000; // how many loops to do before displaying
#Override
protected Void doInBackground() throws Exception {
boolean keepGoing = true;
long index = 1L;
double value = 0.0;
while (keepGoing) {
for (int i = 0; i < INTERIM_LENGTH; i++) {
int multiplier = (index % 2 == 0) ? -1 : 1;
value += (double)multiplier / (index);
index++;
}
publish(value);
}
return null;
}
#Override
protected void process(List<Double> chunks) {
for (Double dbl : chunks) {
displayResult(dbl);
}
}
}
private static void createAndShowUI() {
JFrame frame = new JFrame("Decay Const");
frame.getContentPane().add(new InterimCalc().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() {
public void run() {
createAndShowUI();
}
});
}
}
you may also want to display some sort of spinning gif or "progress bar" to show that the answer is being calculated; feedback to the user is good.
(once you are using a swingworker, then the gui won't freeze and the gui can do its own thing while the calculation is taking place)