0

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.

mKorbel
  • 109,525
  • 20
  • 134
  • 319
Trent
  • 1,595
  • 15
  • 37
  • 2
    Consider creating and posting an [sscce](http://sscce.org), a small compilable and runnable program that we can run unchanged and that demonstrates your problem for us. – Hovercraft Full Of Eels Nov 24 '12 at 02:12
  • You state that your problem only occurs *some* of the time and not all of the time which gives off a strong smell of a Swing threading issue. Again, an [sscce](http://sscce.org) would help answer this issue -- but only if this question is important to you and you haven't yet gotten a correct answer. – Hovercraft Full Of Eels Nov 24 '12 at 02:40
  • Umm, you could put a button and progress bar on a jpanel and then substitute "// process 'f'" with Thread.sleep(1000); and that would be an sscce I suppose. – Trent Nov 24 '12 at 03:42
  • 1
    Since you are the one asking for free help for your question, the onus of effort to create and post an SSCCE should be yours. Besides, I've already created one, and the code works, ***if*** the JProgressBar references are done correctly. Since there's no way I can guess how you're wiring this, I will leave it up to you to post your sscce to show us, but again only if the question is important to you. – Hovercraft Full Of Eels Nov 24 '12 at 03:44
  • I'm not sure what you mean by references. This code is exactly as is in my program, when the button is pressed, jButtonActionPerformed is called, which in turn initialises a new instance of ProgBar and executes it to process the folders. I really can't understand what needs to be explained further. Is there something at fault with the code I've posted or should it be working fine? Is there is limit to how quickly/often the propertyChange event can be triggered? That's all I'm asking... – Trent Nov 24 '12 at 03:53
  • What I'm talking about is how you wire your JProgressBar to the listener. Please look at how I wire it in my example below. The key is that the JProgressBar that is displayed is the same as the one being acted on in the SwingWorker. Again, the general rule and your task is to isolate and show your bug. Without that no one will be able to help you. And no, there is no "limit" on how often the event can be triggered. Your assumption there is incorrect. Again, create an SSCCE and thereby isolate the bug. – Hovercraft Full Of Eels Nov 24 '12 at 03:55
  • Regarding your edit, `"I.E. my assumption that setProgress was firing too quickly was correct. A ProgressMonitor might be a better solution"`, I don't agree. The coalescing of setProgress won't effect the value, and only will be for calls that are near instantaneous. This won't have an visible effect on what the user sees and won't prevent the progress monitor from showing progress. – Hovercraft Full Of Eels Nov 24 '12 at 04:38
  • Umm, you did read the javadocs right? Multiple calls to setProgress can be combined and then the propertyChange event will only take the last setProgress call. The propertyChange event is where the progressBar's value is set, so this perfectly explains why I am seeing setProgress called frequently and propertyChange triggered a lot less frequently, with the latest values. sleeping for 1 second (the sscce) is obviously enough to let propertyChange be triggered before a new value is passed to it (hence the correct/expected update), whereas processing the folders is quicker than I thought it was. – Trent Nov 24 '12 at 04:46
  • Yes, I've read them now and previously. The propertyChange is where the propertyBar's value is set but not where the int that is used to set value is set and that is key. That is what determines the PBar's ultimate, and regardless if calls are coalesced, the ultimate value will be correct. Unless you can prove that this is the cause in code, you're likely barking up the wrong tree. But this is ultimately your problem to solve and you are free to assume what you wish. All I can say is I've been using this sort of method for years, and this aspect has *never* been an issue. – Hovercraft Full Of Eels Nov 24 '12 at 04:59
  • OK, take your sscce. Add System.out.println("setProgress called with: " + count); between setProgress(count) and Thread.sleep(400), you will see that setProgress and propertyChange are not 1:1 and this is the problem. I understand that the int is set outside of propertyChange, but propertyChange is what updates the progress bar and displays it visually, so if propertyChange is not firing as often as setProgress, the progressBar is only getting partial updates, as per the docs. I hope this explanation/code modification gets my point across more clearly for you. – Trent Nov 24 '12 at 05:11
  • 1
    -1 for insisting on not showing an SSCCE and voting to close (not a real question, because there _is no_ problem) – kleopatra Nov 24 '12 at 10:49
  • The reason for the discrepancy is due to my use of random.nextInt(...). I allow for a 0 result, and if the progress value is not changed, then the PropertyChangeListener is not called. This is all well documented. To fix it, change the above to `random.nextInt(...) + 1` – Hovercraft Full Of Eels Nov 24 '12 at 12:08

3 Answers3

3

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.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
0

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.

Trent
  • 1,595
  • 15
  • 37
0

If you want listeners to be called immediately, you can try the following (which worked for me):

setProgress(1);
firePropertyChange("progress", 0, 1);
mostar
  • 4,723
  • 2
  • 28
  • 45