Reputation: 385
I want to generate a random number 5 times and repeat that 6 times. Every time a random number is generated, I update the GUI with that number and a progress bar.
In order to do so, I've thought of a SwingWorker that generates the random number on the doInBackground() method, and with the publish() method shows that number on the GUI. I've done this with no problem at all, the problem comes when I try to do that 6 times, because in the done() method I add a blank space on the TextField that I use to show all the numbers
How can I do to run 6 times that SwingWorker, and that it starts after the done() method of the SwingWorker is completed?
Thanks!
Edit: the code snippet
public class SwingWorkerEjemplo extends SwingWorker<Void, Integer> {
private JProgressBar pBar;
private JTextField txtSalida;
private JTextArea txtArea;
public SwingWorkerEjemplo(JProgressBar pBar,JTextField txt, JTextArea txtArea) {
this.pBar = pBar;
addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())){
pBar.setValue((Integer)evt.getNewValue());
}
}
});
this.pBar.setVisible(true);
this.pBar.setStringPainted(true);
this.pBar.setValue(0);
setProgress(0);
this.txtSalida = txt;
this.txtArea = txtArea;
}
@Override
protected Void doInBackground() throws Exception {
int num;
for (int i=0;i<5;i++){
num = ThreadLocalRandom.current().nextInt(1, 7);
publish(num);
setProgress(i+1);
Thread.sleep(100);
}
return null;
}
@Override
protected void done() {
try {
txtSalida.setText(txtSalida.getText()+" ");
Thread.sleep(0);
txtArea.append("");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected void process(List<Integer> chunks) {
Integer valor = chunks.get(chunks.size()-1);
txtSalida.setText(txtSalida.getText()+String.valueOf(valor));
}
}
And in the MainWindow.java
SwingWorkerEjemplo swe = new SwingWorkerEjemplo(pBar, txtNumeros, txtSalida);
swe.execute();
System.out.println("Worker 1");
swe = new SwingWorkerEjemplo(pBar, txtNumeros, txtSalida);
swe.execute();
System.out.println("Worker 2");
swe = new SwingWorkerEjemplo(pBar, txtNumeros, txtSalida);
swe.execute();
System.out.println("Worker 3");
swe = new SwingWorkerEjemplo(pBar, txtNumeros, txtSalida);
swe.execute();
System.out.println("Worker 4");
swe = new SwingWorkerEjemplo(pBar, txtNumeros, txtSalida);
swe.execute();
System.out.println("Worker 5");
swe = new SwingWorkerEjemplo(pBar, txtNumeros, txtSalida);
swe.execute();
System.out.println("Worker 6");
The result is first all the Worker 1 Worker 2 Worker 3 Worker 4 Worker 5 Worker 6 and then something like xxxxxyyyyyzzzzzaaaaabbbbbcccccddddd
Upvotes: 0
Views: 1485
Reputation: 285405
How can I do to run 6 times that SwingWorker, and that it starts after the done() method of the SwingWorker is completed?
You can't. As per the SwingWorker API documentation:
SwingWorker is only designed to be executed once. Executing a SwingWorker more than once will not result in invoking the doInBackground method twice.
So if you need an action repeated with a delay, do so in your worker with a while loop, and then output the results to your GUI through the SwingWorker's publish/process pair. If you go this route, you'd create a SwingWorker<Void, Integer>
(if it needs to produce int output). If on the other hand you want to run the worker 6 times, each in response to an event, then simply create a new worker each time one is needed.
I wonder though if all you need is the much simpler Swing Timer.
Edit
I haven't seen what the Swing Timer does, so I can't answer that.
If you need something done intermittently with a delay, then a Swing Timer (please see link) is the way to go. When you create one you pass in 2 parameters, an int representing the delay time, and an ActionListener -- whose actionPerformed method will be called repeatedly separated roughly by the delay. So if you need a random int generated every second, you'd pass in 1000 (1000 milliseconds = 1 second) and an ActionListener, and in the listener's actionPerformed method, you generate the random int and pass it to whoever needs it. Note that all code in the actionPerformed method is called on the Swing event thread.
If on the other hand you need to run a bit of code that will take a long time to perform, such as a database look up, reading from a socket, reading or writing from a file, you would then use a SwingWorker, since by doing this you can run the long piece of code in a background thread and event-thread safely extract the information back to the GUI.
And a quick question, by worker you mean the GUI?
No, the worker = SwingWorker.
So with the code you've posted, if that's all you want to do -- update a JProgressBar with delay, then use a Swing Timer. If on the other hand, you are wanting to do a long-running process and wish to update the JProgressBar while the process is running, then use a SwingWorker. Note that if you have the latter need, you don't need to use the publish/process method pair. SwingWorker has a progress property that's a "bound" property, meaning that if you change it by calling setProgress(...)
the SwingWorker will notify any PropertyChangeListeners that might be attached to it. I use this a lot.
For example:
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
public class ProgressExampleGui {
private JPanel mainPanel = new JPanel();
private JProgressBar progressBar = new JProgressBar();
private JButton pressMeBtn = new JButton(new MyAction("Press Me", KeyEvent.VK_P, this));
public ProgressExampleGui() {
progressBar.setStringPainted(true);
progressBar.setString("");
mainPanel.add(pressMeBtn);
mainPanel.add(progressBar);
}
public void setProgress(int progress) {
progressBar.setValue(progress);
progressBar.setString(progress + "%");
}
public JComponent getMainComponent() {
return mainPanel;
}
public void setEnabled(boolean enabled) {
pressMeBtn.setEnabled(enabled);
}
private static void createAndShowGui() {
ProgressExampleGui progExampleGui = new ProgressExampleGui();
JFrame frame = new JFrame("Progress Example");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(progExampleGui.getMainComponent());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
@SuppressWarnings("serial")
class MyAction extends AbstractAction {
private ProgressExampleGui gui;
public MyAction(String name, int mnemonic, ProgressExampleGui gui) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
this.gui = gui;
}
@Override
public void actionPerformed(ActionEvent e) {
AbstractButton source = (AbstractButton) e.getSource();
gui.setProgress(0);
source.setEnabled(false);
MyWorker myWorker = new MyWorker();
myWorker.addPropertyChangeListener(new WorkerPropChngListener(gui));
myWorker.execute();
}
}
class WorkerPropChngListener implements PropertyChangeListener {
private ProgressExampleGui gui;
public WorkerPropChngListener(ProgressExampleGui gui) {
this.gui = gui;
}
@Override
public void propertyChange(PropertyChangeEvent pcEvt) {
MyWorker myWorker = (MyWorker) pcEvt.getSource();
if ("progress".equals(pcEvt.getPropertyName())) {
int progress = ((Integer)pcEvt.getNewValue()).intValue();
gui.setProgress(progress);
}
if (SwingWorker.StateValue.DONE.equals(pcEvt.getNewValue())) {
try {
myWorker.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
gui.setEnabled(true);
}
}
}
class MyWorker extends SwingWorker<Void, Void> {
private static final int MAX_INCR = 8;
private static final long SLEEP_TIME = 200;
private static final int MAX_VALUE = 100;
private int value = 0;
private Random random = new Random();
@Override
protected Void doInBackground() throws Exception {
while (value < MAX_VALUE) {
value += random.nextInt(MAX_INCR);
value = Math.min(value, MAX_VALUE);
Thread.sleep(SLEEP_TIME);
setProgress(value);
}
return null;
}
}
Upvotes: 4