How to update a progress bar from a method inside SwingWorker - java

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.

Related

Async callback and listener

I have the following interfaces:
public interface NumberOfCellsListener {
public void numberOfCellsChanged(int patientId, int numOfCells);
}
public interface NumberOfCells {
public void register(NumberOfCellsListener listener);
public int numOfCells(int patientId);
}
public interface ProbabilityOfCancer {
//this may be a time consuming calculation
public double probability(int patientId, int numOfCells, bool gender, double weight);
}
This is the structure of my cancer probability calculator. My task is to implement this such that it can be called asynchronously due to the time consuming nature of the calculations. I am new to Java, can anyone please guide me on how to implement these interfaces? I am not sure about the structure i.e. what goes where. I am guessing I should get the new number of cells from the method numberOfCellsChanged in NumberOfCellsListener's implementation. But since it is a void method, I am not sure what to do.
An Interface method that does not return something usually has the function of changing the internal state, or of using some other means of output (for example to print something to the console, send something over a network, or save it to a database or file.).
As for the specific interfaces:
A Listener as in NumberOfCellsListener is meant to be installed ('registered') with an object that calls a listener's method, in case a specific event occures. In your case, based on the interface name, I would assume, that your method is called, when the number of cells of some object changes. So the question for you should be, what that information (at that point) requires you to do, and do that in the method.
A class implementing NumberOfCells is supposed to make true of the above statement. It has a register method, that should put the listener in some sort of collection (maybe a List - e.g. an ArrayList?) and then, when a certain event occures, invoke all the list's listener's numberOfCellsChanged-methods.
So an example implementation could be:
public class NumberOfCellsPrinter implements NumberOfCellsListener {
public void numberOfCellsChanged(int patientId, int numOfCells) {
System.out.println("The number of cells for parentId:" + parentId + " has changed to " + numOfCells + ".");
}
}
...
public class PetriDish implements NumberOfCells {
private ArrayList<NumberOfCellsListener> listeners = new ArrayList<>();
private int numOfCells = 0;
public void register(NumberOfCellsListener listener) {
if (listener != null && !listeners.contains(listener)) {
listeners.add(listener);
}
}
public int numOfCells(int patientId) {
for (NumberOfCellsListener listener : listeners) {
listener.numberOfCellsChanged(parentId, numOfCells);
}
return numOfCells;
}
}
While this example is totally meaningless, and will always produce 0 as numOfCells, it should demonstrate the idea of listeners.
It is very important, that you familiarize yourself with the risks and traps concerning parallelism, since this is a key factor of your objective.
Try the following to learn about async callbacks in Java. You can find more tutorials and explanations here
// Java program to illustrate Asynchronous callback
interface OnGeekEventListener {
// this can be any type of method
void onGeekEvent();
}
class B {
private OnGeekEventListener mListener; // listener field
// setting the listener
public void registerOnGeekEventListener(OnGeekEventListener mListener)
{
this.mListener = mListener;
}
// My Asynchronous task
public void doGeekStuff()
{
// An Async task always executes in new thread
new Thread(new Runnable() {
public void run()
{
// perform any operation
System.out.println("Performing operation in Asynchronous Task");
// check if listener is registered.
if (mListener != null) {
// invoke the callback method of class A
mListener.onGeekEvent();
}
}
}).start();
}
// Driver Program
public static void main(String[] args)
{
B obj = new B();
OnGeekEventListener mListener = new A();
obj.registerOnGeekEventListener(mListener);
obj.doGeekStuff();
}
}
class A implements OnGeekEventListener {
#Override
public void onGeekEvent()
{
System.out.println("Performing callback after Asynchronous Task");
// perform some routine operation
}
// some class A methods
}

JProgressBar doesn't update in real time within a loop [duplicate]

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.

Setting up a progress bar in Java

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.

Java Swing - Updating the view from multiple thread

This question may be duplicated because I found a lot of similar question, but not the answer to my problem: I need to update the view of my SWING application from different SwingWorker.
I have a View class with a JTextArea and a JTable that i need to update during the execution of the Threads. The view also has a Start button that launch all the threads.
The controller listens for the button to be clicked then launch the threads:
public class MonitorPageController {
private MonitorPage monitorPage;
private List<Mission> missions;
class StartButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < missions.size(); i++) {
MyWorker worker = new MyWorker(missions.get(i));
worker.execute();
}
}
}
}
Then I have MyWorker class that manage the model:
public class MyWorker extends SwingWorker<Integer, String> {
private Mission m;
//<dec>
Block block1 = new Block();
Block block2 = new block();
Block block3 = new Block();
Block block4 = new Block();
public MyWorker(Mission mission) {
this.m = mission;
}
#Override
protected Integer doInBackground() throws Exception {
//<exe>
block1.addObserver(block2);
block2.addObserver(block3);
block3.addObserver(block4);
block4.addObserver(block2);
block1.update(null, m);
return 4;
}
}
In the end I have the Block class that is where I need to update the GUI (JTable and JTextArea):
public class Block extends Node implements Observer {
public Mission run(Mission m) {
m.setStatus(Mission.UNEXECUTED);
// HERE I WANT TO NOTIGY THE VIEW OF THE CHANGE OF STATUS OF THE MISSION
return m;
}
#Override
public void update(Observable o, Object arg) {
Mission m = this.run((Mission) arg);
setChanged();
notifyObservers(m);
}
}
EDIT: Mission is a simple class with the attribute: int status
I already tried with another observer pattern: I setted the mission as observable and the MonitorPageController as the observer. Then in the setter method of the status in class Mission I added the setChanged() and the notifyObservers() methods. In the end in the Observer (MonitoPageController) I implemented the update() method to call the view and update the gui.
I liked this way because it's clean and easy to implement, but I don't now why, after calling the notifyObserver() nothing was happening, so I discarded this solution, even if it seems to be the right one
Update the UI calling SwingUtilities.invokeLater() :
public class Block extends Node implements Observer {
public Mission run(Mission m) {
m.setStatus(Mission.UNEXECUTED);
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
//UPDATE UI HERE
}
});
return m;
}
#Override
public void update(Observable o, Object arg) {
Mission m = this.run((Mission) arg);
setChanged();
notifyObservers(m);
}
}
I found a possible solution, maybe there better ideas, but this works for now:
Creating the SwingWorker in MonitorPageController I changed the constructor passing the istance of the MonitorPageController too.
MyWorker worker = new MyWorker(misssions.get(i), this);
Then in MyWorker class when I create Block1, Block2, ...I pass them the istance of the MyWorker:
Block block1 = new Block(this);
Block block2 = new Block(this);
....
In the same class (MyWorker) I created a method:
public void log(Mission m, String s) {
controller.log(m, s);
}
Controller is the istance of the MonitorPageController that created the worker.
Now the inside block class, when I want to notify the change of status, I can call:
parentWorker.log(mission, "some string");
In the end the log() method in the MonitorPageController calls the view method to update the components...by now it seems to work...

JProgressBar not triggering propertyChange on setProgress

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);

Categories