I have the following code...
runNow.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
final Search s = (Search) node.getUserObject();
// Swing worker
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
#Override
public Void doInBackground() {
s.run();
return null;
}
#Override
public void done() {
window.updateResultsPanel(s.getResults(), s.getName());
}
};
worker.run();
}
});
This is a popup menu action which should create a new SwingWorker, free up the GUI, do some work, then update the results of the window. However, right now when I click the menu item the GUI becomes locked up until the work completes which is baffling, the whole point of using a SwingWorker is so that won't happen.
Any ideas as to what I'm doing wrong would be greatly appreciated.
-Cody
You should be calling SwingWorker#execute to start the worker, not run
Run is exposed by the Runnable interface, but execute actually schedules the worker to run at some time in the future.
Have to change method SwingWorker.run() to SwingWorker.execute() and it worked like a charm.
Related
I'm developing an Eclipse plugin that will contribute to the GUI with a view.
The view is updated with informations from a versioning system when the user selects a folder or a file in the workspace.
In order to avoid collecting data everytime the user goes through the project subfolders and files, I need to wait for 3 seconds in order to be sure that the file or folder is the one of interest.
I'm currently doing this using a Swing Timer.
This is ok for small amount of data, but for large amount of data the GUI blocks, waiting for the timer to execute the update function.
I know for this kind of task I can use SwingWorker but I can't figure out how to delay the task and to restart the delay when needed.
Can anyone give me a solution on how to correctly solve this problem ?
Here is my current code:
public void resetTimerIfNeeded()
{
if(timer.isRunning())
timer.restart();
else
timer.start();
}
public void timer()
{
selectionTimer = new Timer(3000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
Display.getDefault().syncExec(new Runnable(){
#Override
public void run()
{
updateView();
selectionTimer.stop();
}
});
}
});
}
Since Eclipse uses SWT rather than Swing it is best to avoid using Swing code.
You can run code in the UI thread after a delay using UIJob, something like:
UIJob job = new UIJob("Job title") {
#Override
public IStatus runInUIThread(IProgressMonitor monitor) {
updateView();
return Status.OK_STATUS;
}
};
job.schedule(3000);
Alternatively you can use Display.timerExec:
Display.getDefault().timerExec(3000, new Runnable(){
#Override
public void run()
{
updateView();
}
});
Schedule it as a Job instead: https://eclipse.org/articles/Article-Concurrency/jobs-api.html . Use a UIJob if the entirety of what it's doing is interacting with the UI. The cancel/schedule and sleep/wakeUp methods will be of interest , see http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/core/runtime/jobs/Job.html for the JavaDoc.
I need to keep focus on JTextField. Application uses Swing library. I need set focus on that field from time to time in order to avoid user mistakes that would change focus to other comonents. I suppose I need to use SwingWorker. Set focus is an operation on Swing
component so it should be invoked in EDT. My question is how to write SwingWorker to do that?
I know that method done() pass tasks to be invoked in EDT but I need this task to be invoked every let's sey 2 seconds. Method done() is called one time.
So maybe sth like this will be ok?
public class myWorker extends SwingWorker<Void, Void> {
#Override
protected Void doInBackground() throws Exception {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
//here set focus on JTextField
return null;
}
});
}}
Edit:
I noticed that method process() that is a part of SwingWorker may be appropriate beacuse it is invoked in EDT. I'm not sure but this method is probably invoked always when I call publish() metod. So could you tell me if this code is valid to do this task?
private class KeepFocusWorker extends SwingWorker<Void, Void>
{
#Override
protected Void doInBackground() throws Exception
{
while(true)
{
publish();
}
}
#Override
protected void process(List<Void> chunks)
{
codeBar.requestFocusInWindow();
}
}
Use javax.swing.Timer instead of SwingWorker. In this case actionPerformed will be executed in EDT. Also to set focus in a component, you need to call requestFocus. As the name suggests, it is a request only and not guaranteed. So you may change you approach.
Timer timer = new Timer(2000, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
codeBar.requestFocus();
}
});
timer.setRepeats(true);
timer.start();
Surely it's better to limit the user's ability to take focus away from the textfield in the first place? Personally I don't see why it's an issue but I suppose it's better to keep focus in the one component rather than letting the user shift focus only for it to be shifted back every few seconds.
Therefore you could add a FocusListener to the component, override the focusLost method and basically requestFocus() again.
codeBar.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
}
public void focusLost(FocusEvent e) {
codeBar.requestFocus();
}
});
NB I've not actually tried this myself but can't see why it wouldn't work.
Alternatively you can use an InputVerifier which always returns false to prevent focus being taken away.
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 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.
In my java application I am using swing to implement the UI. There is a button called theButton which is engaged with some IO operation in the following timely steps :
the button originally has the text "Click to connect"
then before the connect operation starts I want the theButton reads
"Connecting in progress..."
then the IO operation gets started
once the IO operation is done theButton now reads "connected ( click to disconnect)".
Issue:
I am using the following code, but first of all the button's text doesn't change to "Connecting in progress..." before the IO starts! as well button doenst actually get disabled before the IO starts! What should I do here?
--
// theButton with text "Click to connect is clicked"
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
theButton.setText("Trying to connect...");
theButton.setEnabled(false);// to avoid clicking several times! Some users cannot wait
theButton.repaint();
// doing some IO operation which takes few seconds
theButton.setText("connected ( click to disconnect)");
theButton.setEnabled(true);
theButton.repaint();
}
});
Your problem is here:
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
theButton.setText("Trying to connect...");
theButton.setEnabled(false);
theButton.repaint();
// doing some IO operation which takes few seconds // **********
theButton.setText("connected ( click to disconnect)");
theButton.setEnabled(true);
theButton.repaint();
}
});
The code marked with the ******* comment is running on the EDT and will tie it up freezing your app and all it's painting.
Use a SwingWorker instead to run the code in a background thread.
Note that there is no need to use invokeLater(...) for code in an ActionListener since this code is already running on the EDT by default.
Also get rid of your repaint() calls since they aren't needed and they don't help.
Add a PropertyChangeListener to your SwingWorker to listen for when it is done, and then you can reset your JButton.
Instead do:
// code not compiled nor tested
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
theButton.setText("Trying to connect...");
theButton.setEnabled(false);
MySwingWorker mySwingWorker = new MySwingWorker();
mySwingWorker.addPropertyChangeListener(new PropertyChangeListener() {
// listen for when SwingWorker's state is done
// and reset your button.
public void propertyChange(PropertyChangeEvent pcEvt) {
if (pcEvt.getNewValue() == SwingWorker.StateValue.DONE) {
theButton.setText("connected ( click to disconnect)");
theButton.setEnabled(true);
}
}
});
mySwingWorker.execute();
}
});
and
// code not compiled nor tested
public class MySwingWorker extends SwingWorker<Void, Void> {
#Override
public void doInBackground() throws Exception {
// doing some IO operation which takes few seconds
return null;
}
}
And be sure to read: Concurrency in Swing.