I have a program currently and have tried to implement a progress bar with my code. Bellow is an example of the code currently. The main GUI is in its own class and instantiates other classes to then execute code within those classes' methods. An example is as follows:
class MainClass {
public javax.swing.JProgressBar progressBar;
private void combineActionPerformed(java.awt.event.ActionEvent evt) {
Combine combiner = new Combine();
combiner.Merge(folder);
}
}
It takes a folder listing and then goes to the Combine class which has the following code:
public class Combine extends SwingWorker<Integer,Integer>{
public void Merge(Folder []){ (for int i=0;i<folder.length;i++){
merge(folder[i]);
}
public void Merge(folder[]){
output stream;
}
}
How do I implement the swing worker properly in this example to make a progress update to the MainClass progress bar as each iteration of i occurs?
To begin, your worker is missing some methods it should implement, such as doInBackground() and done(). You also need a constructor to pass Folder[].
public class Combine extends SwingWorker<Integer,Integer>{
Folder[] folders;
public Combine (Folder[] folders)
{ this.folders = folders; }
private void Merge(Folder [])
{ (for int i=0;i<folder.length;i++)
{
merge(folder[i]);
//Send the message of progress here, it will be executed
//from doInBackground()
setProgress(....);
}
}
private void Merge(folder){
output stream;
}
protected Integer doInBackground()
{
merge(folders);
return null;
}
protected void done()
{ .... }
}
Then you would call this worker with
Combine combiner = new Combine(folders);
combiner.execute();
To track progress, this example is from SwingWorker API:
combiner.addPropertyChangeListener(
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
Use the setProgress method as you are doing your processing to send the update. You could simply count how many files there are and call setProgress with (numberProcessed*100)/totalNumber. Note that the multiplication is done first to prevent rounding issues.
This will cause the PropertyChangeListeners to be notified of the changes, this will happen on the EDT so it will be safe to update your JProgressBar from it.
Related
This question already has answers here:
Can a progress bar be used in a class outside main?
(3 answers)
Closed 7 years ago.
It is the first time I have to work with a progress bar and I'm facing a problem, besides I try to call its setValue(x) from everywhere it keeps on 0% and goes straight to 100% after my method routine finishes.
I tried to make an inner class that extends Thread, then after I tried to start a new Thread within my "main" method, then for the last I tried to use the Observer. These ones seems to have worked according to this posts but unfortunately not to me
Update JProgressBar from new Thread
Problem making a JProgressBar update values in Loop (Threaded)
please, could someone help me???
public class MainClass {
private void checkFiles() {
Task task = new Task();
task.start();
//here I have some Files validation...I don't think it is important to solve the progressbar problem
//so it will be ommited
//in this point I tried to call update to test the observer solution I found in another post here
//task.update(null, null);
JOptionPane.showMessageDialog(this, "Done!");
//here the bar jumps from 0% to 100%
}
private class Task extends Thread implements Observer {
public Task() {
}
//Dont bother with the calculum as I haven't finished working on them....
//The relevant thing here is that it starts a new Thread and I can see the progress
//increasing on console using system.out but my progress bar still don't change from 0%.
public void run() {
int maxSize = 100;
final int partsSize = maxSize / listaArquivosSelecionados.size();
while (listFilesValidated.size() != listFilesToValidate.size()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int progress = listFilesValidated.size() * partsSize;
System.out.println("Progress" + progress);
progressBar.setValue(progress);
}
});
try {
Thread.sleep(100);
}
catch (InterruptedException e) {}
}
}
//Just tried to set any value to check if it would update before the files validation thread finishes its work.
#Override
public void update(Observable arg0, Object arg1) {
progressBar.setValue(66);
}
}
You can create another class of ProgressBar (see Oracle tutorial) and use this:
ProgressBar pbFrame = new ProgressBar();
pbFrame.setVisible(true);
Executors.newSingleThreadExecutor().execute(new Runnable() {
#Override
public void run() {
// run background process
}
});
Or you can use SwingWorker, for example:
SwingWorker worker = new SwingWorker<MyReturnType, Void>() {
#Override
public MyReturnType doInBackground() {
// do your calculation and return the result. Change MyReturnType to whatever you need
}
#Override
public void done() {
// do stuff you want to do after calculation is done
}
};
I had the same question some years ago.
I'm trying to update a progress bar and I can't do it. My code is something like this:
public class MyWorker extends SwingWorker<Void, Void> {
public Void doInBackground(){
howMany=Integer.parseInt(textField.getText());
String result=longMethod(howMany);
label.setText("Hello, you have "+result);
}
}
public class Event implements ActionListener{
public void actionPerformed(ActionEvent e){
label2.setText("Whatever");
button.setEnabled(false);
myWorer.addPropertyChangeListener(this);
myWorker.execute();
}
public void propertyChange(PropertyChangeEvent event){
if("progress".equals(event.getPropertyName())){
int currentPercent = (int)event.getNewValue();
progressBar.setValue(currentPercent);
}
}
}
So I can't use setProgress in doInBackground since the updating is made by longMethod() which is the method containing a big slow loop, placed in another class. I've made something similar passing from that method a variable to the class which contains the JFrame and then offering the possibility to see that progress if you click another button.
I don't know if there is some way of making that button (or text field) refresh itself every X seconds without clicking it or a way to use the setProgress from the method longMethod()
Thank you!
What you need is some way for longMethod to return progress information.
You could, for example, create a simple interface which you could pass to longMethod which would, when it knows, update the progress...
public interface ProgressMonitor {
/**
* Passes the progress of between 0-1
*/
public void progressUpdated(double progress);
}
Then in your doInBackground method, you would pass an instance of the ProgressMonitor to the longMethod
public class MyWorker extends SwingWorker<Integer, Integer> {
public Integer doInBackground(){
// It would be better to have obtained this value before
// doInBackground is called, but that's just me...
howMany=Integer.parseInt(textField.getText());
String result=longMethod(howMany, new ProgressMonitor() {
public void progressUpdated(double progress) {
setProgress((int)(progress * 100));
}
});
//label.setText("Hello, you have "+result);
publish(result);
return result;
}
protected void process(List<Integer> chunks) {
label.setText("Hello, you have "+chunks.get(chunks.size() - 1));
}
}
This is esstenially an example of the observer pattern
Now, if you can't modify longMethod, then you have no means by which you can update the progress, because you have no way of knowing what the longMethod is doing...
If there is a way to pass the Progress bar to the SwingWorker, then the SwingWorker would have a reference to that progress bar and be able to update it.
I have a JFrame that does things. I have a JButton hidden from view in that JFrame. In a SwingWorker i have a utility Class such as checkNDownloadFile of which I pass a JButton to it. So it can make it visible/usable when the process completes.
My question is, is this acceptable. I dont know of any other method to do this effect. (Keep in mind the checkNDownloadFile class is all static. Its only needed/ran once.)
Sudo Code
-----------------------------------------------------------------
myWorker Class
protected Void doInBackground() throws Exception {
//Loading time consuming data.
//Execute Dialog of the question variety.
//Loading more time consuming data.
//Create JFrame
AtomFrame frame = new AtomFrame();
frame.start();
checkNDownloadFile.setButton(frame.fileButton)
checkNDownloadFile.start();
return null;
}
-----------------------------------------------------------------
checkNDownloadFile Class
public static void start() {
//Do the other task at hand
if (complete && good) {
fileButton.setVisible(true);
} else {
//other stuff
}
}
Answer Code
-----------------------------------------------------------------
myWorker Class
protected Void doInBackground() throws Exception {
//Loading time consuming data.
//Execute Dialog of the question variety.
//Loading more time consuming data.
//Create JFrame
//Moved to Main Method to be created by EDT.
//AtomFrame frame = new AtomFrame();
//frame.start();
publish("Executing");
boolean returnedB = checkNDownloadFile.start();
if (returnedB) {
publish("Good");
} else {
//Maybe implement
//checkNDownloadFile.getError();
publish("Bad");
}
return null;
}
-----------------------------------------------------------------
checkNDownloadFile Class
public static void start() {
//Do the other task at hand
if (complete && good) {
return true
} else {
//Maybe implement
//setError("");
return false
}
}
Do not update the GUI from your implementation of doInBackground().
Do update the GUI from your implementation of process() or done(), as shown here, and here.
You may have to re-factor your checkNDownloadFile() method to provide the desired granularity for a sensible progress display.
See also Watching a Directory for Changes.
I've read many different articles about JProgressBar...including the dodgy code found over at Java; here.
Most indicate you need a SwingWorker to get things happening properly, which makes perfect sense, I understand that much. I am finding that when I call setProgress(value) to update the progressbar, it's not triggering the propertyChange event most of the time. I've checked the value I'm passing to setProgess and it definitely changes every time, so I'm not sure if it's just firing the event too quickly? Please see relevant code below, any help/explanation would be greatly appreciated.
class ProgBar extends SwingWorker
{
public ProgBar()
{
addPropertyChangeListener(new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if ("progress".equals(evt.getPropertyName()))
{
int value = (Integer)evt.getNewValue();
System.out.println("propertyChange called with: " + value);
loginProg.setValue(value);
}
}
});
loginProg.setStringPainted(true);
loginProg.setValue(0);
setProgress(0);
}
#Override
public Void doInBackground() throws InterruptedException
{
...
int count = 0;
for (Folder f : folders)
{
... // process 'f'
setProgress((int)Math.min(((double)count/folders.length)*100.0, 100.0));
}
...
return null;
}
#Override
public void done()
{
System.out.println("Done called.");
setProgress(100);
loginProg.setValue(100);
}
}
JProgressBar called with this;
private void jButtonActionPerformed(java.awt.event.ActionEvent evt)
{
// Create new thread to run progess bar.
// Otherwise won't be able to update progress bar.
ProgBar pb = new ProgBar();
pb.execute();
}
}
EDIT:
Yeah, so I should have read the Javadocs better;
Because PropertyChangeListeners are notified asynchronously on the Event Dispatch Thread multiple invocations to the setProgress method might occur before any PropertyChangeListeners are invoked. For performance purposes all these invocations are coalesced into one invocation with the last invocation argument only.
For example, the following invokations:
setProgress(1);
setProgress(2);
setProgress(3);
might result in a single PropertyChangeListener notification with the value 3.
I.E. my assumption that setProgress was firing too quickly was correct. A ProgressMonitor might be a better solution.
This isn't an answer but a demonstration sscce, to show you just what I meant:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;
import javax.swing.*;
public class TestProgBar {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ProgBar progBar = new ProgBar();
// **** this is key and where your code may be deficient ***
JProgressBar prog = progBar.getProg();
progBar.execute();
JOptionPane.showMessageDialog(null, prog);
}
});
}
}
class ProgBar extends SwingWorker<Void, Void> {
private JProgressBar loginProg = new JProgressBar();
public ProgBar() {
addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
int value = (Integer) evt.getNewValue();
System.out.println("propertyChange called with: " + value);
loginProg.setValue(value);
}
}
});
loginProg.setStringPainted(true);
loginProg.setValue(0);
setProgress(0);
}
public JProgressBar getProg() {
return loginProg;
}
#Override
public Void doInBackground() throws InterruptedException {
int count = 0;
int max = 5;
Random random = new Random();
// simulate uploading files
while (count < 100) {
count += random.nextInt(max);
if (count > 100) {
count = 100;
}
setProgress(count);
Thread.sleep(400);
}
// for (Folder f : folders) {
// setProgress((int) Math.min(((double) count / folders.length) * 100.0,
// 100.0));
// }
return null;
}
#Override
public void done() {
System.out.println("Done called.");
setProgress(100);
loginProg.setValue(100);
}
}
Again, this code works fine, suggesting that the code you've loaded does not show the error. You need to do further work isolating the error and getting it into code so we can test it.
Yeah, so I should have read the Javadocs better;
Because PropertyChangeListeners are notified asynchronously on the Event Dispatch Thread multiple invocations to the setProgress method might occur before any PropertyChangeListeners are invoked. For performance purposes all these invocations are coalesced into one invocation with the last invocation argument only.
For example, the following invokations:
setProgress(1);
setProgress(2);
setProgress(3);
might result in a single PropertyChangeListener notification with the value 3.
I.E. my assumption that setProgress was firing too quickly was correct. A ProgressMonitor might be a better solution. I've confirmed this with the SSCCE and my program, both are simply firing setProgress too quickly and as a result, only the last value passed to setProgress is being passed through to the PropertyChange event.
If you want listeners to be called immediately, you can try the following (which worked for me):
setProgress(1);
firePropertyChange("progress", 0, 1);
I'm in the midst of porting a C# program over to Java that makes heavy use of delegates and the delegate's BeginInvoke method to notify of an event asynchronously. Take a data communication thread for example. It might have to notify another worker thread of its state as well as the GUI.
It seems to me that the best way to notify of the various events for different classes is to have an IClassNameHereWatcher interface that defines all of the types of events that the class "publishing" the event would need to notify about and then each class that needs to listen would implement this interface and register itself as a listener. The thing I'm not quite sure about is how to make this asynchronous. Here's approximately what I'm referring to:
public interface IFrobWatcher {
void frobDidSomething();
void frobReceivedData(object data);
}
public class Frob implements Runnable {
List<IFrobWatcher> watchers = new ArrayList<IFrobWatcher>();
private void run() {
while (true) {
// Long running task
if (blah) notifyWeDidSomething();
notifyOfData(someDataObject);
}
}
public void addWatcher(IFrobWatcher watcher) {
watchers.Add(watcher);
}
private void notifyWeDidSomething() {
for (IFrobWatcher watcher : watchers) {
watcher.frobDidSomething(); // How do I make this asynchronous?
}
}
private void notifyOfData(object someDataObject) {
for (IFrobWatcher watcher : watchers) {
watcher.frobReceivedData(someDataObject); // How do I make this asynchronous?
}
}
}
public class FrobWatcher implements IFrobWatcher {
private Frob frobToWatch;
public FrobWatcher(Frob frob) {
frobToWatch = frob;
frobToWatch.addListener(this);
}
public void FrobDidSomething() {
System.out.println("Frob did something!");
}
public void FrobReceivedData(object received) {
System.out.println("Frob received: " + received.ToString());
}
public static void main(string[] args) {
Frob f = new Frob();
FrobWatcher fw = new FrobWatcher(f);
(new Thread(f)).start();
}
}
And this is a fairly simplified example, but hopefully it conveys what I'm looking for. If something isn't clear or doesn't make sense, please comment and I'll try to clarify or fix the example.
I would recommend an Executor from the java.util.concurrent package.
This is how asynchronous callbacks are handled in the Raptor framework:
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(
filesPoller, 0 /*initial delay*/,
checkInterval,
TimeUnit.MILLISECONDS
);
NB. this scheduling is recurring.