1

I got a problem with setting a value to a JProgressBar which i got from inside a type returning method. I know I have to do multi threading but I´m really new to this topic and really don´t know how to implement this.

I try to briefly explain my dilemma in code:

This is my data returning method to count lines in a String (e.g. from a JTextArea or whatsoever).

    public static int countLines(String s) {
        int count = 0;

        String[] words = s.split("\n");

        for(String i: words) {
            count++;
        }
        return count;
    }

What I want to add is a method inside this method that sets my JProgressBar that I created in my JFrame to the value count. E.g. setProgress(count);

Is this even possible? Because i tried several ways of doing this and no matter what, the progressbar only updates AFTER the return value has been sent out.

Do I have to run this method in an own Thread or just the progress setting method? or both?

Cheers!

garlicDoge
  • 197
  • 1
  • 3
  • 18

4 Answers4

2

Do I have to run this method in an own Thread or just the progress setting method? or both?

Both tasks should run in separate threads: business logic (in this case word's count) must be performed in a background thread while progress bar update must run in the Swing thread, also known as Event Dispatch Thread (EDT).

IMO the best way to accomplish this is by using a SwingWorker:

  • Move all count logic to doInBackground() implementation.
  • Attach a PropertyChangeListener to the swing worker to listen for progress property and update the progress bar within this listener.
  • Use setProgress() inside doInBackground to set the worker's progress and fire a property change event notifying your listener.

There are a lot of examples here in SO, just take a look to tag. Also, consider this snippet:

SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

    // Following code is performed in a background thread

    @Override
    protected Void doInBackground() throws Exception {

        String[] words = string.split(System.lineSeparator());
        int totalWords = words.length;

        for (int count = 0; count < totalWords; count++) {
            int progress = (count + 1) * 100 / totalWords;
            setProgress(progress);
        }
        return null;
    }
};

worker.addPropertyChangeListener(new PropertyChangeListener() {

    // Following code is performed in the EDT, as all event handling code is.
    // Progress bar update must be done here, NOT within doInBackground()

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if ("progress".equals(evt.getPropertyName())) {
            Integer progress = (Integer)evt.getNewValue();
            progressBar.setValue(progress); 
        }
    }
});
dic19
  • 17,821
  • 6
  • 40
  • 69
  • this is very similar to the answers of @inquisitor. this will help, thank you! – garlicDoge Oct 23 '14 at 14:08
  • You are weolcome. However that implementation is wrong. Swing components must not be updated within `doInBackground` method. I'll add a snippet to illustrate my suggestions. @garlicDoge – dic19 Oct 23 '14 at 14:10
  • I've just updated my answer. 1) Now progress bar is updated in the right thread (EDT). 2) Progress is a percentual value, so setting `count` as progress bar value is wrong. See the snippet. @garlicDoge – dic19 Oct 23 '14 at 14:24
  • Cool, I´ll play around with this later. Thank you so far. For the value i want to set: I want to set a numeric value, not a percentual one. – garlicDoge Oct 23 '14 at 14:25
  • Then you can play with progress bar's `setString()` method to show the words count and `setProgress()` to set the percentual value. By this way the text will look like you want and progress bar will be filled properly. @garlicDoge – dic19 Oct 23 '14 at 14:37
  • Thank you, all of you were helpfull! I wish I could upvote anything. – garlicDoge Oct 23 '14 at 14:51
  • No worries, 7 more rep and you will ;) – dic19 Oct 23 '14 at 14:54
  • @dic19 above you said, "However that implementation is wrong." Were you saying my implementation was wrong? If so, could you explain why? I'm still new to UI programming and would appreciate any corrections so that I can get better! Thanks – nullByteMe Nov 01 '14 at 00:21
1

Using a SwingWorker you can accomplish it this way:

public class GuiTest extends JFrame
{
   private static final long serialVersionUID = 1L;

   private JProgressBar progressBar;
   private int stringLength;

   GuiTest()
   {
      // Length of your string
      stringLength = yourString.length();

      progressBar = new JProgressBar(0, stringLength);
      ProgressBarWorker worker = new ProgressBarWorker(); 
      worker.addPropertyChangeListener(new PropertyChangeListener() {

         @Override
         public void propertyChange(PropertyChangeEvent evt) {
            if ("progress".equals(evt.getPropertyName())) {
               Integer progress = (Integer)evt.getNewValue();
               progressBar.setValue(progress); 
            }
         }
      });

      worker.execute();

      this.getContentPane().add(progressBar);
      this.setVisible(true);
      this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   }

   private class ProgressBarWorker extends SwingWorker<Void, Void>
   {
       private void countLines(String s) {
          int count = 0;

          String[] words = s.split("\n");

          for(String i: words) {
              count++;
              setProgress(count);
           }
      }

      @Override
      protected Void doInBackground() throws Exception {
        countLines();
        return null;
      }
    }
nullByteMe
  • 6,141
  • 13
  • 62
  • 99
  • What aspect of it was not working? Did you get the same results as before? – nullByteMe Oct 23 '14 at 12:46
  • Sorry, yes I got the same result as before. I played around a bit with this method a day ago (found similar topics here on stackoverflow). The result in short: return type is running the loop, nothing happens to the progressbar. After finishing the loop all the `.setValue(count);`is set to the progressbar. I know it´s logically correct because I dont use multiple threads. – garlicDoge Oct 23 '14 at 12:57
  • I updated my code using a SwingWorker instead. I'm not sure what you're doing entirely, but I kept it sorta generic to get you going. – nullByteMe Oct 23 '14 at 13:33
  • this looks very usefull! in order not to harm the rules i wont post another subquesten according to your answer but instead describe my use-case: i got a class that contains several data return methods (some String, some int) for counting lengths and manipulating strings in my GUI class. I think I would change my Class now to a SWING Worker class - and i think I have to do this for each of my methods so each one gets his `doInBackground()` method... – garlicDoge Oct 23 '14 at 13:50
  • Yeah there are a few ways you can do it depending on how you implement the SwingWorker class. If this answer helps please don't forget to upvote and accept it! – nullByteMe Oct 23 '14 at 14:00
  • 1
    Please note that progress bar value is updated through `doInBackground()` method which runs out of the Event Dispatch Thread (EDT). Swing components should be created and updated in the EDT. You have two options in this case: 1) Use `SwingWorker#setProgress()` and attach a `PropertyChangeListener` to update the progress bar value (just as I did). 2) Use `SwingWorker#publish()` to publish interim results and `SwingWorker#process()` to update the progress bar. I leave you a +1 because I know you'll correct your answer :) – dic19 Nov 01 '14 at 02:01
  • BTW I think *wrong* sounds too harsh, sorry about that. *Not entirely correct* would be more appropriate :) – dic19 Nov 01 '14 at 02:04
  • 1
    @dic19 thanks, I'm still learning about getting things to run properly on the EDT! I'll fix my answer now and thanks for the +1. – nullByteMe Nov 01 '14 at 02:15
  • You are welcome. To learn more about SwingWorker's internal stuff see [here](http://www.javacreed.com/swing-worker-example/) and [here](http://stackoverflow.com/questions/24958793/swingworker-done-is-executed-before-process-calls-are-finished). @inquisitor – dic19 Nov 03 '14 at 11:31
0

If you call paintImmediately on the JProgressBar, then you can update it while your code is running on the event dispatch thread. The other solution is to start another thread.

If the user is supposed to interact with the GUI while your counting code is running, then starting a worker thread (with or without SwingWorker) is the only solution. If the user must wait anyway, and blocking the GUI is not a problem, then the (much simpler) paintImmediately is fine.

lbalazscs
  • 17,474
  • 7
  • 42
  • 50
0

I got stuck with this too couple of weeks ago. I found this site, which helped me a lot on understanding the topic.

All I can say, is that is recommended to start your GUI Applications with the Swing-Library like this:

    public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        MyApplication app = new MyApplication();
        app.setVisible(true);
      }
    });
  }
jntme
  • 602
  • 1
  • 5
  • 18
  • I dont get the difference between your code and mine. Directly in my main method i run my JFrame extending GUI Class -> `new ApplicationWindow().loadContent();` without instanciating it i guess... – garlicDoge Oct 23 '14 at 14:12
  • It's all about the Event Dispatch Thread - this is the name of the Thread in which all GUI activities run under Swing. Everytime you want to change something on your GUI you can dismiss it to this thread with the SwingUtitlities.invokeLater() - Method. – jntme Oct 23 '14 at 14:32