I execute the task in this class and the Dialog pops up as a white box. The print statement IS printing out the progress values I'm expecting, but nothing shows up on the Dialog until after the operation is complete. I can see the progress bar flash visible for a millisecond before the dialog is closed at the end. Absolutely no clue what's going on :\
public class ProgressDialog extends JDialog {
private JProgressBar pb;
private SwingWorker<Boolean, Void> task;
public SwingWorker<Boolean, Void> getTask(){
return task;
}
public ProgressDialog(final String call){
setTitle("Working...");
setLayout(new BorderLayout());
setBounds(300,300,300,100);
pb = new JProgressBar(0, 100);
pb.setValue(0);
pb.setVisible(true);
pb.setStringPainted(true);
add(pb, BorderLayout.CENTER);
setVisible(true);
task = new SwingWorker<Boolean, Void>(){
public Boolean doInBackground(){
switch(call){
case "Category": pb.setValue(Category.getProgress());
while(pb.getValue()<99){
try{
Thread.sleep(500);
} catch (InterruptedException e){
Thread.currentThread().interrupt();
}
pb.setValue(Category.getProgress());
System.out.println(pb.getValue());
repaint();
revalidate();
}
break;
}
return true;
}
public void done(){
dispose();
}
};
}
}
EDIT: tried this change. no dice. Why am I not even getting a progress bar at 0%? It only appears once it is at 100%
public class ProgressDialog extends JDialog {
private JProgressBar pb;
private SwingWorker<Boolean, Integer> task;
public SwingWorker<Boolean, Integer> getTask(){
return task;
}
public ProgressDialog(final String call){
setTitle("Working...");
setLayout(new BorderLayout());
setBounds(300,300,300,100);
pb = new JProgressBar(0, 100);
pb.setValue(0);
pb.setStringPainted(true);
add(pb, BorderLayout.CENTER);
setVisible(true);
task = new SwingWorker<Boolean, Integer>(){
public Boolean doInBackground(){
switch(call){
case "Category": setProgress(Category.getProgress());
while(pb.getValue()<99){
try{
Thread.sleep(500);
} catch (InterruptedException e){
Thread.currentThread().interrupt();
}
setProgress(Category.getProgress());
}
break;
}
return true;
}
public void done(){
//dispose();
}
};
task.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
System.out.println((Integer)evt.getNewValue());
pb.setValue((Integer)evt.getNewValue());
pb.revalidate();
pb.repaint();
}
}
});
}
}
You're trying to set the progress bar's state from within the SwingWorker's doInBackground method, from a background thread -- which makes no sense. The whole reason for using a SwingWorker is to allow you to do a background process in a Swing GUI, so you don't make Swing calls from a background thread, and so that you don't tie up the Swing thread with a long-running bit of code.
You should not make Swing calls from this background process. Instead use the publish/process methods as the tutorials will show you. Or perhaps better, set the SwingWorker's progress field, and use a PropertyChangeListener on the SwingWorker to allow the progress bar to react to it.
Regardless, the bottom line:
Use the SwingWorker to do background work.
Do not make Swing calls from within the SwingWorker's doInBackground method.
Use publish to push data from the background method into the Swing thread realm.
Use the process method to handle this data being pushed.
SwingWorker has a progress property that is also handy to use for allowing Swing code to respond to changes in background states.
If you go this route, use a PropertyChangeListener.
You almost never want to use setBounds(...) or null layout. Trust me as someone who has written hundreds of Swing programs, this one will bite you in the end.
It looks as if your Category is using a static method for getting its progress. Again, this is something you almost never want to do. A progress field suggests state, and this should be part of the instance fields of an object, never static.
Here's an SSCCE to demonstrate how you should be updating your JProgressBar. Copy/paste this and run it.
Notice how we update the progress bar by calling publish(i) which sends the integer to the process() method. The SwingWorker sends results to the process() method in chunks, but we are only using an Integer to update the JProgressBar so all we care about it the LAST chunk. In this SSCCE, we go from 1-1000. If you examine the console, you'll see that a lot of numbers between 1-1000 are being skipped because we are updating too fast for the SwingWorker to catch up (but that's ok. That's why it delivers results in chunks).
NOTE: the process() method was originally designed for programmers to return real-time results from their long-running processes and update the GUI. So, if you were doing a database fetch, you could update a JTable with the results you return. I hate doing things that way, though. So 99% of the time I just use an "indeterminate" JProgressBar and wait till the done() method to publish my results. Occaisionally, however, I'll use a "determinate" JProgressBar and update like we do in this SSCCE. Never have I used process() to return and publish actual data. :) But, that's what it was originally designed to do.
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
/**
*
* #author Ryan
*/
public class Test {
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
go();
}
});
}
public static void go() {
JFrame frame = new JFrame();
JProgressBar jpb = new JProgressBar();
jpb.setIndeterminate(false);
int max = 1000;
jpb.setMaximum(max);
frame.add(jpb);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
new Task(jpb, max).execute();
}
static class Task extends SwingWorker<Void, Integer> {
JProgressBar jpb;
int max;
public Task(JProgressBar jpb, int max) {
this.jpb = jpb;
this.max = max;
}
#Override
protected void process(List<Integer> chunks) {
jpb.setValue(chunks.get(chunks.size()-1)); // The last value in this array is all we care about.
System.out.println(chunks.get(chunks.size()-1));
}
#Override
protected Void doInBackground() throws Exception {
for(int i = 0; i < max; i++) {
Thread.sleep(10); // Sleep for 1/10th of a second
publish(i);
}
return null;
}
#Override
protected void done() {
try {
get();
JOptionPane.showMessageDialog(jpb.getParent(), "Success", "Success", JOptionPane.INFORMATION_MESSAGE);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
}
}
EDIT: I created a diagram that should be a helpful reference when handling SwingWorker so you know where to place your code.
Related
How can the EDT communicate to an executing SwingWorker? There a lot of ways for the SwingWorker to communicate information back to the EDT - like publish/process and property changes but no defined way (that I have seen) to communicate in the other direction. Seems like good old Java concurrent inter-thread communication would be the way to go via wait() and notify(). This doesn't work. I'll explain later. I actually got it to work but it uses an ugly polling mechanism. I feel like there should be a better way. Here is the process that I am trying to accomplish:
From the main Swing UI (EDT) a user starts a SwingWorker long-running task (the engine).
At some point the engine needs information from the EDT so it communicates this back to the EDT. this could be done through publish/process update of a visible UI component. Importantly, this step DOES NOT block the EDT because other things are also going on.
The engines blocks waiting for an answer.
At some point the user notices the visual indication and provides the required information via some UI (EDT) functionality - like pressing a Swing button.
The EDT updates an object on the engine. Then "wakes up" the engine.
The engine references the updated object and continues to process.
The problem I have with wait()/notify() is that in step 3 any invocation of wait() in doInBackground() causes the done() method to be immediately fired and the SwingWorker to be terminated.
I was able to get the above process to work by using an ugly sleep() loop in doInBackground():
for (;;)
{
Thread.sleep(10);
if (fromEDT != null)
{
// Process the update from the EDT
System.out.println("From EDT: " + fromEDT);
fromEDT = null;
break;
}
}
What this really is that in step 5 the engine wakes itself up and checks for updates from the EDT.
Is this the best way to do this? I kind of doubt it.
The following is an mre demonstrating a SwingWorker paused and waiting for user's input:
import java.awt.*;
import java.util.List;
import javax.swing.*;
public class SwingWorkerWaitDemo {
public static void creategui(){
JFrame f = new JFrame("SwingWorker wait Demo");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.add(new MainPanel());
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
creategui();
}
}
class MainPanel extends JPanel {
private static final String BLANK = " ";
private MyWorker swingWorker;
private final JLabel output, msg;
private final JButton start, stop, respond;
MainPanel() {
setLayout(new BorderLayout(2, 2));
start = new JButton("Start");
start.addActionListener(e->start());
stop = new JButton("Stop");
stop.setEnabled(false);
stop.addActionListener(e->stop());
JPanel ssPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
ssPane.add(start); ssPane.add(stop);
add(ssPane, BorderLayout.PAGE_START);
output = new JLabel(BLANK);
JPanel outputPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
outputPane.add(output);
add(outputPane, BorderLayout.CENTER);
msg = new JLabel(BLANK);
respond = new JButton("Respond");
respond.addActionListener(e->respond());
respond.setEnabled(false);
JPanel responsePane = new JPanel();
responsePane.add(msg); responsePane.add(respond);
add(responsePane, BorderLayout.PAGE_END);
}
#Override
public Dimension getPreferredSize(){
return new Dimension(400, 200);
}
private void start() {
start.setEnabled(false);
stop.setEnabled(true);
swingWorker = new MyWorker();
swingWorker.execute();
}
private void stop() {
stop.setEnabled(false);
swingWorker.setStop(true);
}
private void message(String s){
msg.setText(s);
}
private void clearMessage(){
msg.setText(BLANK);
}
private void askForUserResponse(){
respond.setEnabled(true);
message("Please respond " );
}
private void respond(){
clearMessage();
respond.setEnabled(false);
swingWorker.setPause(false);
}
class MyWorker extends SwingWorker<Integer, Integer> {
private boolean stop = false;
private volatile boolean pause = false;
#Override
protected Integer doInBackground() throws Exception {
int counter = 0;
while(! stop){
publish(counter++);
if(counter%10 == 0) {
pause = true;
askForUserResponse();
while(pause){ /*wait*/ }
}
Thread.sleep(500);
}
return counter;
}
#Override
protected void process(List<Integer> chunks) {
for (int i : chunks) {
output.setText(String.valueOf(i));
}
}
#Override
protected void done() {
message("All done");
}
void setStop(boolean stop) {
this.stop = stop;
}
void setPause(boolean pause) {
this.pause = pause;
}
}
}
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'm fairly new to Java and I am trying to make a GUI. This is the code in my GUI.java file. It contains a button and a label. When I click the button, the label is supposed to show "loading..." and enter a static void method in the Main class (in Main.java) called searcher and does stuff. After searcher is done, label becomes "".
Problem: Label doesn't change at all when I press press the button. Seems like neither the setText in the actionListener nor searcher() works. However, the other "stuff" I wanted it to do inside searcher() still works fine. I don't see any errors.
Note: If I try to call searcher() from the main it works fine.
GUI.java:
public class GUI extends JFrame implements ActionListener{
public JButton button = new JButton("Refresh!");
public JLabel label = new JLabel("");
public GUI(){
Container pane = getContentPane();
button.addActionListener(this);
button.setActionCommand("refresh");
pane.add(button);
pane.add(label);
}
}
public void actionPerformed(ActionEvent e) {
if ("refresh".equals(e.getActionCommand())) {
label.setText("Loading...");
Main.searcher(this, "", "");
label.setText("");
}
}
Main.java:
public class Main{
public static void searcher(GUI gu, String popup, String url) {
gu.label.setText("Loading...");
//do stuff
gu.label.setText("");
}
public static void main(String[] args) {
GUI gu = new GUI ();
}
}
EDIT: I've changed to code to use SwingWorker and propertylistener as suggested, but I'm having trouble now. Firstly, 'this' no longer refers to the GUI.. what should I pass in the searcher method to pass the current instance of class GUI?
I'm also getting this error and I'm not really sure how to fix it:
.\GUI.java:77: error: is not abstract and does not override abstract method propertyChange(PropertyChangeEvent) in PropertyChangeListener
PropertyChangeListener propChangeListn = new PropertyChangeListener() {^
public void actionPerformed(ActionEvent e) {
if ("refresh".equals(e.getActionCommand())) {
label.setText("Loading...");
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
public Void doInBackground() throws Exception {
Main.searcher(this, "", "http://maple.fm/api/2/search?server=0");
return null;
}
};
//worker.addPropertyChangeListener(new propertyChangeListener listener) {
PropertyChangeListener propChangeListn = new PropertyChangeListener() {
public void propertyChanged(PropertyChangeEvent pcEvt) {
if (pcEvt.getNewValue() == SwingWorker.StateValue.DONE) {
label.setText("");
}
}
};
worker.addPropertyChangeListener(propChangeListn);
worker.execute();
}
Yours is a classic Swing threading issue where you are tying the Swing event thread with a long-running process, preventing this thread from updating the GUI's graphics or from interacting with the user. The solution is the same as always -- use a background thread to do your long-running processing. If you used a SwingWorker for this, you could even add a PropertyChangeListener to it and then be notified when the worker has completed its task, allowing you to update the GUI with this information.
Google Concurrency in Swing and click on the first hit for more on this.
e.g.,
public void actionPerformed(ActionEvent e) {
if ("refresh".equals(e.getActionCommand())) {
label.setText("Loading...");
// create a SwingWorker object
final SwingWorker<Void, Void> worker = new Swingworker<Void, Void>() {
// long running code would go in doInBackground
public Void doInBackground() throws Exception {
Main.searcher(...);
return null;
}
}
// add a listener to worker to be notified when it is done
worker.addPropertyChangeListener(PropertyChangeListener listener) {
public void propertyChanged(PropertyChangeEvent pcEvt) {
// if the worker is done...
if (pcEvt.getNewValue() == SwingWorker.StateValue.DONE) {
label.setText("");
// you will probably want to call get() on your worker here
// and catch any exceptions that may have occurred.
}
}
}
// it may seem counter-intuitive, but you need to start the worker with
// execute() *after* setting all the above code up.
worker.execute();
}
}
I'm trying to:
display a text in a jLabel,
wait for two seconds,
then write a new text in the jLabel
this should be simple, but I get a strange bug:
the first text is never written, the application just waits for 2 seconds and then displays the final text. here is the example code:
private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {
displayLabel.setText("Clicked!");
// first method with System timer
/*
long t0= System.currentTimeMillis();
long t1= System.currentTimeMillis();
do{
t1 = System.currentTimeMillis();
}
while ((t1 - t0) < (2000));
*/
// second method with thread.sleep()
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {}
displayLabel.setText("STOP");
}
with this code, the text "Clicked!" is never displayed. I just get a 2 seconds - pause and then the "STOP" text.
I tried to use System timer with a loop, or Thread.sleep(), but both methods give the same result.
Just to provide more background on Andrew Thompson's comment: the EDT is responsible for handling gui updates. If you block it using Thread.sleep(...) those updates are blocked as well. That's why you don't see the first text - the EDT just can't do the update on the label.
Here's a runnable example which does what you're after. As Andrew Thompson's comment stated, a SwingWorker is a good way to approach this problem.
The basic principal is to never block the Event Dispatch Thread. That's the thread responsible for repainting the GUI and responding to user interaction, so if you do something computationally expensive on the EDT, your GUI will stop responding.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.ExecutionException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
public class ButtonTest {
public static void main(String[] args) {
// create a frame and a button
JFrame frame = new JFrame();
final JButton button = new JButton("Button");
frame.add(button);
// add an action listener to the button
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
// change the button text right away
button.setText( "Clicked" );
// create a SwingWorker which simply waits 2000 milliseconds
// simulating a computation being performed
SwingWorker<String, Object> worker = new SwingWorker<String, Object>() {
#Override
public String doInBackground() {
// it's safe to call Thread.sleep( ) here
// doInBackground is executed on a separate worker
// thread
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
return "Done";
}
#Override
protected void done() {
// done() is executed back on the Swing thread
// so it's safe to updated the state of the button
try {
button.setText(get());
} catch (Exception e) { }
}
};
// run the worker
worker.execute();
}
});
frame.setSize( 300, 300 );
frame.setVisible( true );
}
}
You are messing with the event dispatcher thread.
That will cause un-expected UI behavior as you are seeing. If you plan to do these type of animations, make sure to read up on what #Andrew Thompson suggested and also, see if you can read this - Filthy rich clients
Better to use a Swing Timer as shown in curde-example below:(yes, it is crude, I did not worry about stopping the timer etc):
public class DelayTest extends JPanel{
JLabel messageLabel = new JLabel();
JButton actionButton = new JButton("Click Me");
String[] messages = {"Clicked", "Stop!"};
int i=0;
public DelayTest(){
super();
add(messageLabel);
add(actionButton);
actionButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
if(i<=1)
messageLabel.setText(messages[i++]);
}
});
timer.start();
}
});
}
}
Edit
Why not stop the Timer:
#Override
public void actionPerformed(ActionEvent evt) {
if (i <= 1) {
messageLabel.setText(messages[i++]);
} else {
((Timer)evt.getSource()).stop();
}
}
});
How can I update the JProgressBar.setValue(int) from another thread?
My secondary goal is do it in the least amount of classes possible.
Here is the code I have right now:
// Part of the main class....
pp.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent event){
new Thread(new Task(sd.getValue())).start();
}
});
public class Task implements Runnable {
int val;
public Task(int value){
this.val = value;
}
#Override
public void run() {
for (int i = 0; i <= value; i++){ // Progressively increment variable i
pbar.setValue(i); // Set value
pbar.repaint(); // Refresh graphics
try{Thread.sleep(50);} // Sleep 50 milliseconds
catch (InterruptedException err){}
}
}
}
pp is a JButton and starts the new thread when the JButton is clicked.
pbar is the JProgressBar object from the Main class.
How can I update its value?(progress)
The code above in run() cannot see the pbar.
Always obey swing's rule
Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.
What you can do is to create an observer that will update your progress bar -such as
- in this instance you want to show progress of data being loaded on click of a button.
DemoHelper class implements Observable and sends updates to all observers on when certain percent of data is loaded.
Progress bar is updated via public void update(Observable o, Object arg) {
class PopulateAction implements ActionListener, Observer {
JTable tableToRefresh;
JProgressBar progressBar;
JButton sourceButton;
DemoHelper helper;
public PopulateAction(JTable tableToRefresh, JProgressBar progressBarToUpdate) {
this.tableToRefresh = tableToRefresh;
this.progressBar = progressBarToUpdate;
}
public void actionPerformed(ActionEvent e) {
helper = DemoHelper.getDemoHelper();
helper.addObserver(this);
sourceButton = ((JButton) e.getSource());
sourceButton.setEnabled(false);
helper.insertData();
}
public void update(Observable o, Object arg) {
progressBar.setValue(helper.getPercentage());
}
}
Shameless plug: this is from source from my demo project
Feel free to browse for more details.
You shouldn't do any Swing stuff outside of the event dispatch thread. To access this, you need to create a Runnable with your code in run, and then pass that off to SwingUtilities.invokeNow() or SwingUtilities.invokeLater(). The problem is that we need a delay in your JProgressBar checking to avoid jamming up the Swing thread. To do this, we'll need a Timer which will call invokeNow or later in its own Runnable. Have a look at http://www.javapractices.com/topic/TopicAction.do?Id=160 for more details.
There is need not to call pbra.repaint explicitly.
Update JProgressBar shall be done through GUI dispatch thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Remember to make pbar final variable.
pbar.setValue(i);
}
});