Search code examples
javamultithreadingswingjprogressbar

Java - JProgress bar not showing (threaded)


I am adding a feature to a program to save some content to file. The progress is shown by a progress bar (in its own JFrame), but the progress bar is only being displayed on the last value it reads. I have a global being updated by the main thread, that represents the % of work completed, and the other thread reads this global and updates the progress bar accordingly. Right now when it runs, the JFrame is empty, then activity completes, then the progress bar shows itself with complete amount. How do i make it update the progress as it goes along (and show the JProgressbar from the start)? Here is my code:

public class GenomeAnnotator{
    private JProgressBar csvProgressBar;
    private JFrame csvSaveLoadFrame;     //for the progress bar
    private Container csvCon;
    private double csvPercentSaved;  //% of work completed

    public JFrame m_frame;    //main program frame


....



public static void main(String[] args){
    ...
    showGUI();
    ...
}

public void showGUI(){
   ...

   JMenu file = new JMenu("File");
   JMenu exptann = new JMenu("Export annotation..);
   JMenuItem exptcsv = newJMenuItem("CSV format");
   exptcsv.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {

          ..determine output file + checks...

          System.out.println("Writing to .csv file......"); 

          csvSaveLoadFrame = new JFrame("Saving to csv file..");
          csvProgressBar =new JProgressBar(0,100);

          csvSaveLoadFrame.setSize(300,100);
          csvCon = csvSaveLoadFrame.getContentPane();
          csvCon.setLayout(null);
          csvProgressBar.setBounds(10,10,280,20);
          csvCon.add(csvProgressBar);
          csvSaveLoadFrame.setResizable(false);
          csvSaveLoadFrame.setVisible(true);

          ORF [] ora= orfPanel.getAcceptedOrfs();
          int val;
          double toload = blastData.size() + ora.length; //how much work
          double loaded=0.0; //how much work completed


          /*Thread that will read % value from global and update prog. bar*/
          Thread progressBarMover = new Thread() {
              @Override
              public void run() {
                  int previous=0;
                  while(csvPercentSaved<100){

                      csvProgressBar.setValue((int)csvPercentSaved);
                      //tried putting a sleep() in here when testing
                      //values from global is read successfully


                      }
                  }
                  System.out.println("Thread done!");
                  csvPercentSaved = 0; //reset value when done
                  csvSaveLoadFrame.setVisible(false);
              }
          };
          progressBarMover.start();


          for (int k=0; k<blastData.size(); k++) {

              ..do output work...

              loaded+=1; //update % values
              csvPercentSaved = (loaded/toload)*100;
              val = (int)csvPercentSaved;
              System.out.println("main complete "+val+"%");
          }



          for (int k=0; k<ora.length; k++) {

              ...do more ouput work...

              loaded+=1;
              csvPercentSaved = (loaded/toload)*100; //update % value
              val = (int)csvPercentSaved;
              System.out.println("main complete "+val+"%");

          }
          System.out.println("Output file finished!");
          csvPercentSaved = 100;

      }

  });
exptann.add(exptcsv);
file.add(exptann);
}

EDIT

found solution here: https://weblogs.java.net/blog/mkarg/archive/2010/01/03/did-you-know-swingworker-can-send-progress-status


Solution

  • Several issues there:

    • Most most important (and I missed this initially!), you're not doing your long running code within the background thread but rather within the Swing event thread, the EDT. I am meaning these two for loops: A) for (int k=0; k<blastData.size(); k++) {...} and B) for (int k=0; k<ora.length; k++) {...} which looks to be the code where you're loading or saving information. This will freeze your GUI right up.
    • Also important, you're doing Swing calls from within a background thread, including setting the progress bar's value and setting a JFrame's visiblity, something that you never want to do, and that mostly negates the benefits of using the background thread in the first place.
    • In other words, you're doing all your Swing threading work exactly backwards -- making Swing calls from the background thread and running the long process in the event thread.
    • Instead, do the opposite -- do all the long-running work in a background thread and make all of the non-thread-safe Swing calls on the EDT.
    • One way to do this is to use a SwingWorker, do your loading and saving from within its doInBackground(...) method
    • and set its progress field as progress is being made..
    • You would then monitor the worker's progress field in a PropertyChangeListener, this being done on the EDT, and then use this to set your progress bar's value.
    • Or if you have to use your own background thread, then
      • Have the inner class implement Runnable, not extend Thread
      • If you make Swing calls from within your background thread, then wrap these calls in a Runnable and queue them onto the Swing event thread via SwingUtilities.invokeLater(yourRunnable)

    More minor issues:

    • You should not be using null layouts and absolute positioning but rather use layout managers. While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.
    • Your secondary dialog window should be a JDialog, and probably a modal JDialog, not another JFrame. You're not creating and showing a new stand-alone program, but rather are displaying a dialog window off of the main GUI window. If you want the main GUI window non-functioning while the dialog is displayed, then the modal JDialog is the way to go, as it works just like a JOptionPane (which is a form of a modal JDialog), and makes the calling window non-functional while its visible.

    For some of my code examples: