I have a JAVA6 GUI handling data import to our database. I have implemented a working JProgressBar. I understand that changes made to the GUI must be done via the event dispatch thread--which I do not think I am doing (properly/at all).
the background Worker thread, UploadWorker, is constructed by passing in the a JProgressBar created in the main program, and sets changes the value of the progress bar directly once it is finished:
// when constructed, this gets set to the main program's JProgressBar.
JProgressBar progress;
protected Void doInBackground() throws Exception {
write("<!-- Import starting at " + getCurrentTime() + " -->\n");
boolean chunked = false;
switch (importMethod) {
//do some importing
}
write("<!-- Import attempt completed at " + getCurrentTime() + "-->\n");
//here changes to the GUI are made
progress.setMaximum(0);
progress.setIndeterminate(false);
progress.setString("Finished Working");
return null;
}
This works fine, but sometimes(not always) throws me several NPE's in the std out, and users are complaining:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at javax.swing.plaf.basic.BasicProgressBarUI.updateSizes(Unknown Source)
...etc...
Anyway, I believe there is something I need to do to get these updates executed on the proper thread, correct? How?
There are a number of ways you could do this, you could use the process method of the SwingWorker to also update the progress bar, but for me, this couples your worker to the UI, which isn't always desirable.
A better solution is to take advantage of the SwingWorkers progress and PropertyChange support, for example....
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker worker = (SwingWorker) evt.getSource();
switch (worker.getState()) {
case DONE:
// Clean up here...
break;
}
} else if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
// You could get the SwingWorker and use getProgress, but I'm lazy...
pb.setIndeterminate(false);
pb.setValue((Integer)evt.getNewValue());
}
}
});
worker.execute();
This means you could do this for ANY SwingWorker, so long as it was the worker was calling setProgress internally...
public static class ProgressWorker extends SwingWorker {
public static final int MAX = 1000;
#Override
protected Object doInBackground() throws Exception {
for (int index = 0; index < MAX; index++) {
Thread.sleep(250);
setProgress(Math.round((index / (float)MAX) * 100f));
}
return null;
}
}
The benefit of this is that the PropertyChange event notification is called from within the context of the of Event Dispatching Thread, making it safe to update the UI from within.
And fully runnable example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class SwingWorkerProgressExample {
public static void main(String[] args) {
new SwingWorkerProgressExample();
}
public SwingWorkerProgressExample() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JProgressBar pb;
public TestPane() {
setLayout(new GridBagLayout());
pb = new JProgressBar(0, 100);
pb.setIndeterminate(true);
add(pb);
ProgressWorker worker = new ProgressWorker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker worker = (SwingWorker) evt.getSource();
switch (worker.getState()) {
case DONE:
// Clean up here...
break;
}
} else if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
// You could get the SwingWorker and use getProgress, but I'm lazy...
System.out.println(EventQueue.isDispatchThread());
pb.setIndeterminate(false);
pb.setValue((Integer) evt.getNewValue());
}
}
});
worker.execute();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public static class ProgressWorker extends SwingWorker {
public static final int MAX = 1000;
#Override
protected Object doInBackground() throws Exception {
for (int index = 0; index < MAX; index++) {
Thread.sleep(250);
setProgress(Math.round((index / (float) MAX) * 100f));
}
return null;
}
}
}
You can just create a new Runnable that performs GUI updates and invoke it in a GUI thread using SwingUtilities.invokeLater
Related
I have a function called CreateAccount. I need it to run and also need to show a progress bar. When I click the button, the method will start. And I need to start showing loading progress bar. Once method is done progress bar also should stop at 100. If the method gets more time to do the job, progress bar also needs to load slowly.
I tried using following code but it is not synchronizing progress bar with the method. So how can I do that?
Here is my code:
private static int t = 0;
private void createAccountBtnActionPerformed(java.awt.event.ActionEvent evt) {
progressBar.setValue(0);
progressBar.setStringPainted(true);
new Thread(new Runnable() {
#Override
public void run() {
//CreateAccount();
for (t = 0; t <= 100; t++) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
CreateAccount();
progressBar.setValue(t);
}
});
try {
java.lang.Thread.sleep(100);
}
catch(InterruptedException e) { }
}
}
}).start();
}
Because of the single threaded nature of Swing, you can't perform long running or blocking operations from within the context of the Event Dispatching Thread, nor can you update the UI from outside the context of the Event Dispatching Thread.
See Concurrency in Swing for more details.
Both these things you are taking care of. The problem is, this means that it's possible for the background thread to do more work than is been presented on the UI and there's nothing you can do about. The the best bet is simply trying too keep the UI up-to-date as much as possible
A possible better solution might be to use a SwingWorker, which is designed to make updating the UI easier. See Worker Threads and SwingWorker for more details.
The following example shows a progress bar which will run for 10 seconds with a random delay of up to 500 milliseconds between each update. The progress bar is then update based on the amount of time remaining.
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 java.time.Duration;
import java.time.Instant;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public final class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JProgressBar pb;
private JButton btn;
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
btn = new JButton("Go");
pb = new JProgressBar();
add(btn, gbc);
add(pb, gbc);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
btn.setEnabled(false);
makeItProgress();
}
});
}
protected void makeItProgress() {
SwingWorker<Double, Double> worker = new SwingWorker<Double, Double>() {
#Override
protected Double doInBackground() throws Exception {
Duration duration = Duration.ofSeconds(10);
Instant startTime = Instant.now();
Duration runningTime = Duration.ZERO;
Random rnd = new Random();
setProgress(0);
do {
Thread.sleep(rnd.nextInt(500));
Instant now = Instant.now();
runningTime = Duration.between(startTime, now);
double progress = (double) runningTime.toMillis() / (double) duration.toMillis();
setProgress((int) (progress * 100));
} while (duration.compareTo(runningTime) >= 0);
setProgress(100);
return 1.0;
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
SwingWorker worker = (SwingWorker) evt.getSource();
if (evt.getPropertyName().equals("progress")) {
int value = (int) evt.getNewValue();
pb.setValue(value);
} else if (evt.getPropertyName().equals("state") && worker.getState() == SwingWorker.StateValue.DONE) {
pb.setValue(100);
btn.setEnabled(true);
}
}
});
worker.execute();
}
}
}
The point of this example is, the progress and the work are mixed into a single operation (the doInBackground method of the SwingWorker) so they are more closely related. The SwingWoker then notifies the PropertyChangeListener of updates, to which it can react to safely on the Event Dispatching Thread
Here is my code... How can I make it work so that it runs the loop while the user is holding a button and stops when the user releases the button?
public void nextPrimeNum()
{
x = false;
int b = 2;
ArrayList<Integer> next = new ArrayList<Integer>();
while(x)
{
next = factors(b);
if(next.size()==2)
{
System.out.println(b);
}
b++;
}
System.out.println("End");
}
public void keyPressed(KeyEvent e)
{
if(e.getKeyCode() == 401)
{
x = true;
}
}
public void keyRealesed(KeyEvent e)
{
if(e.getKeyCode() == 402)
{
x = false;
}
}
GUI and multi-thread programming is inherently difficult.
So, this is as simple as it could be, without violating best practices too much.
You need several things:
A separate Thread for printing primes:
Its run method loops for ever, but pauses when the Space key is not pressed.
(see Defining and Starting a Thread for more info)
A KeyListener which will be called from AWT's event dispatch thread:
The event handling methods are designed to finish fast, so that other events
(like moving, resizing and closing the frame) still are handled fast.
(see How to Write a Key Listener
and The Event Dispatch Thread for more info)
A visible GUI component (JFrame) for adding the KeyListener
Some synchronization between the 2 threads (via synchronized, notify and wait)
so that the prime-printing starts/continues on keyPressed
and suspends on keyReleased
(see Guarded Blocks for more info)
Initialize and start the whole GUI by invoking initGUI.
(see Initial Threads for more info)
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class Main implements Runnable, KeyListener {
public static void main(String[] args) {
SwingUtilities.invokeLater(Main::initGUI);
}
private static void initGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JLabel("Press SPACE key for printing primes"));
frame.pack();
frame.setLocationRelativeTo(null); // center on screen
frame.setVisible(true);
Main main = new Main();
frame.addKeyListener(main);
Thread thread = new Thread(main);
thread.start();
}
private boolean spaceKeyPressed;
private boolean isPrime(int n) {
for (int i = 2; i < n; i++) {
if (n % i == 0)
return false;
}
return true;
}
#Override
public void run() {
for (int n = 2; /**/; n++) {
while (!spaceKeyPressed) {
synchronized (this) {
try {
wait(); // waits until notify()
} catch (InterruptedException e) {
// do nothing
}
}
}
if (isPrime(n)) {
System.out.println(n);
}
}
}
#Override
public void keyTyped(KeyEvent e) {
// do nothing
}
#Override
public synchronized void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
spaceKeyPressed = true;
notifyAll(); // cause wait() to finish
}
}
#Override
public synchronized void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
spaceKeyPressed = false;
notifyAll(); // cause wait() to finish
}
}
}
So, the answer is - it's complicated. It covers broad topics such as concurrency (in general), GUI development, best practices with the specific API (Swing) which are better covered in more detail by reading through the various tutorials (and experimenting)
Concurrency
Creating a GUI With JFC/Swing
Concurrency in Swing
Worker Threads and SwingWorker
How to Use Actions
How to Use Key Bindings
The example presents two ways to execute the "loop" (which is presented in the doInBackground method of the CalculateWorker class).
You can press and hold the JButton or press and hold the [kbd]Space[kbd] bar, both will cause the "main loop" to run, updating the JTextArea with the results...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTextArea ta;
private CalculateWorker worker;
public TestPane() {
setLayout(new BorderLayout());
ta = new JTextArea(20, 20);
ta.setEditable(false);
add(new JScrollPane(ta));
worker = new CalculateWorker(ta);
JButton btn = new JButton("Press");
btn.getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
System.out.println("...isRunning = " + worker.isRunning());
if (!worker.isRunning()) {
return;
}
System.out.println("...isPressed = " + btn.getModel().isPressed());
System.out.println("...isPaused = " + worker.isPaused());
if (btn.getModel().isPressed()) {
worker.pause(false);
} else {
worker.pause(true);
}
}
});
add(btn, BorderLayout.SOUTH);
worker.execute();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), "Space.released");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), "Space.pressed");
am.put("Space.released", new CalculateAction(false, worker));
am.put("Space.pressed", new CalculateAction(true, worker));
}
public class CalculateWorker extends SwingWorker<List<String>, String> {
private AtomicBoolean run = new AtomicBoolean(true);
private AtomicBoolean paused = new AtomicBoolean(false);
private ReentrantLock pausedLocked = new ReentrantLock();
private Condition pausedCondition = pausedLocked.newCondition();
private JTextArea ta;
public CalculateWorker(JTextArea ta) {
this.ta = ta;
pause(true);
}
public void stop() {
run.set(false);
pausedLocked.lock();
pausedCondition.signalAll();
pausedLocked.unlock();
}
public void pause(boolean pause) {
paused.set(pause);
pausedLocked.lock();
pausedCondition.signalAll();
pausedLocked.unlock();
}
public boolean isPaused() {
return paused.get();
}
public boolean isRunning() {
return run.get();
}
#Override
protected List<String> doInBackground() throws Exception {
List<String> values = new ArrayList<>(256);
long value = 0;
System.out.println("!! Start running");
while (run.get()) {
while (paused.get()) {
System.out.println("!! I'm paused");
pausedLocked.lock();
try {
pausedCondition.await();
} finally {
pausedLocked.unlock();
}
}
System.out.println("!! Start loop");
while (!paused.get() && run.get()) {
value++;
values.add(Long.toString(value));
publish(Long.toString(value));
Thread.sleep(5);
}
System.out.println("!! Main loop over");
}
System.out.println("!! Run is over");
return values;
}
#Override
protected void process(List<String> chunks) {
for (String value : chunks) {
ta.append(value);
ta.append("\n");
}
ta.setCaretPosition(ta.getText().length());
}
}
public class CalculateAction extends AbstractAction {
private boolean start;
private CalculateWorker worker;
public CalculateAction(boolean start, CalculateWorker worker) {
putValue(NAME, "Calculate");
this.start = start;
this.worker = worker;
}
#Override
public void actionPerformed(ActionEvent e) {
worker.pause(start);
}
}
}
}
Is there a simpler solution?
Of course, I always go for the most difficult, hard to understand solutions first (sarcasm)
While it "might" be possible to reduce the complexity, the example presents a number of "best practice" concepts which you would do well to learn and understand.
The solution could also be done differently depending on the API used, so, it's the "simplest" solution for the specific API choice.
I wanted to do it from the console!
Java can't do that - it's console support is rudimentary at best and doesn't support a concept of "key pressed/released" actions (since it's running in a single thread, it would be impossible for it to do otherwise).
There "are" solutions you might try, but they would require a third party library linked to native binaries to implement, which would (possibly) reduce the number of platforms it would run on
sorry but it's the first time i use Threads.
i want Parlami class thread to sleep and be awaken only by the actionListener.
I tried this way but it isn't working, he still sleeps.
Is it right to use thread this way or should i use wait() ?
package parlami;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* #author giacomofava
*/
public class Parlami
{
public boolean finito = false;
public String s="";
public void ascolta()
{
int i=0;
while (i<=1500)
{
// dormi 50 millisecondi
try
{
Thread.sleep(50);
i+=40;
}
catch (InterruptedException e)
{
}
while (voce.SpeechInterface.getRecognizerQueueSize() > 0)
{
s = s+"\n"+voce.SpeechInterface.popRecognizedString();
}
}
}
public String scrivi()
{
return "Hai detto: "+s;
}
public void leggi()
{
voce.SpeechInterface.synthesize(s);
}
public void dormi(int milli)
{
try
{
System.out.println("i'm sleeping");
Thread.sleep(milli);
}
catch (InterruptedException ex)
{
System.out.println("i'm awake ");
ascolta();
}
}
}
this is the gui:
public class GUI extends JFrame
{
private Parlami p;
private JPanel nord, centro;
private JButton registra, leggi;
private JTextArea display;
public static void main(String[] args)
{
new GUI();
}
public GUI()
{
p=new Parlami();
initComponents();
}
private void initComponents()
{
voce.SpeechInterface.init("./lib", true, true,"./lib/gram", "vocabolario");
// N O R D
nord=new JPanel();
display=new JTextArea("");
display.setForeground(Color.GREEN);
display.setBackground(Color.BLACK);
nord.setBackground(Color.BLACK);
nord.add(display);
// C E N T R O
centro=new JPanel();
registra=new JButton("tieni premuto per registrare");
registra.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
Thread.currentThread().interrupt();// <-------- HERE I TRY TO AWAKE HIM
display.setText(p.scrivi());
}
});
centro.add(registra);
leggi=new JButton("leggi");
centro.add(leggi);
this.setLayout(new BorderLayout());
this.add(nord, BorderLayout.NORTH);
this.add(centro, BorderLayout.CENTER);
this.setSize(700,300);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
p.dormi(50000); // <-------- HERE I TELL HIM TO SLEEP
}
}
If you call Thread.sleep on the Swing event thread, you will put the entire application to sleep rendering it useless, but more importantly, there's no need to do this. You simply have the ActionListener activate whichever object needs activation as this is how event-driven programming works.
If you need a delay in a Swing application, use a Swing Timer, something that has been discussed over and over again on this site.
This is a basic concept of thread wait/notify associated with the topic of thread locks. Basically, you have some common object which is acting as the "lock", one thread "waits" on this thread and when another thread needs to, it "notifies" the monitors that some action has occurred to which they should/can respond.
It'd start by having a look at Lock Objects for more details.
Below is a very basic example of the concept, a Thread is allowed to run continuously, but which "waits" on the common lock. The ActionListener of the button "notifies" the lock when it is pressed, allowing the Thread to continue working until, once again, blocks at the "wait"
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Thread t = new Thread(new Runner());
t.start();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static final Object LOCK = new Object();
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
JButton btn = new JButton("Press me");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
synchronized (LOCK) {
LOCK.notifyAll();
}
}
});
add(btn);
}
}
public class Runner implements Runnable {
#Override
public void run() {
while (true && !Thread.currentThread().isInterrupted()) {
synchronized (LOCK) {
try {
System.out.println("Nothing to see here, just waiting");
LOCK.wait();
} catch (InterruptedException ex) {
}
}
System.out.println("Look at me, I'm busy");
}
}
}
}
Remember, Swing is single threaded, never perform any action which is blocking within the context of the Event Dispatching Thread, equally, never update the UI from outside the EDT.
If you need to update the UI for some reason from the other thread, then I suggest you have a look at SwingWorker, which will make your life much simpler. See Worker Threads and SwingWorker for more details.
You have an ActionListener which is notified when the button is activated, why do you need a monitor lock to perform the associated action? Does it take a noticeable amount of time to start the required action? You could just start a new thread when the button is clicked.
If you're waiting for some kind of timeout, then, to be honest, a Swing Timer is probably more suited to the task
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();
}
});
}
}
I have put together an application that opens text files and allows users to edit them (eg: text editor)
Some text files can be arbitrarily large, so it would take some time to open them. I have added a progress bar to inform the user that stuff is actually happening, and am using a swing worker to perform the actual file loading, giving it a reference to a text area to dump all the text.
I also have a flag in the main application called isFileLoaded which is true if there's a file open, and false otherwise. Ideally, the swing worker should set that value after it finishes loading the file and doing any processing that it needs to do.
I have written the swing worker as a separate class, so it's not nested inside my main Frame class that holds all of the GUI logic, mainly because I do not like to define classes inside classes purely for aesthetic reasons. As such, I am currently passing a reference to the entire Frame to the swing worker and letting it set the value of the flag.
Is this a good way to do things? Are there better ways?
Consider rather adding a PropertyChangeListener which holds a reference to your Frame (an anonymous inner-class would be just fine for that matter) and which listens to the "state" property. The value of the event will be equal to StateValue.DONE when the SwingWorker has finished.
Here is a fully working 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.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.SwingWorker.StateValue;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestSwingWorker {
private JProgressBar progressBar;
protected void initUI() {
final JFrame frame = new JFrame();
frame.setTitle(TestSwingWorker.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton button = new JButton("Clik me to start work");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
doWork();
}
});
progressBar = new JProgressBar(0, 100);
frame.add(progressBar, BorderLayout.NORTH);
frame.add(button, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
private boolean someFlag;
protected void doWork() {
SwingWorker<Void, Integer> worker = new SwingWorker<Void, Integer>() {
#Override
protected Void doInBackground() throws Exception {
for (int i = 0; i < 100; i++) {
// Simulates work
Thread.sleep(10);
publish(i);
}
return null;
}
#Override
protected void process(List<Integer> chunks) {
progressBar.setValue(chunks.get(chunks.size() - 1));
}
#Override
protected void done() {
progressBar.setValue(100);
progressBar.setStringPainted(true);
progressBar.setString("Done");
}
};
worker.getPropertyChangeSupport().addPropertyChangeListener("state", new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (StateValue.DONE.equals(evt.getNewValue())) {
someFlag = true;
}
}
});
worker.execute();
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
UnsupportedLookAndFeelException {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestSwingWorker().initUI();
}
});
}
}
You should restructure your code a little to avoid using the whole Frame which indeed is not really clean (but if it works who cares).
If you want to be more cool from a design point of view you should use a model:
class FileModel
{
boolean isLoading;
// getter and setter that notifies
}
and pass only this model to your worker, and once done set the flag.