JavaRanch Home    
 
This page:         last edited 28 June 2007         What's Changed?         Edit

J Progress Bar Doesnt Update   

A common problem posted in the Swing forum is something of this nature: "I created a JProgressBar? and update its value, but the display doesn't change". An example of some code exhibiting this behavior is as follows:


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class test1 implements ActionListener {    
    
    static JProgressBar jpb;
    static test1 _this;
    
    public test1 (){
       _this = this;
    }
    
    private static void createAndShowGUI(){
        JFrame aFrame = new JFrame("Swing Thread Example:  Broken Threading");
        JButton aButton = new JButton("Do Something");
        aFrame.getContentPane().add(aButton, BorderLayout.SOUTH);
        aButton.addActionListener(_this);        
        JPanel aPanel = new JPanel();
        aPanel.add(new JLabel("Progress:"));
        jpb = new JProgressBar(0,100);      
        aPanel.add(jpb);
        aFrame.getContentPane().add(aPanel);                              
        aFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        aFrame.pack();
        aFrame.setVisible(true);  
    }
    
    public static void main(String args[]){
       test1 t = new test1();         
       javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            createAndShowGUI();
        }
    });
    }
    
    public void actionPerformed(ActionEvent ae){
        System.out.println("actionPerformed ");
        for (int i = 0; i <= 100; i++){
            jpb.setValue(i);
            System.out.println("actionPerformed sets jpb value to: "+i);
            try{Thread.sleep(50);} // make the process last a while
            catch (InterruptedException e){}
        }
    }
}

I suggest that you copy the above code, compile and run it so you can get a sense of the problem.

If you have run the code, you will witness that the console reports each value of i set in actionPerformed but the GUI is not updated until the for loop is complete. Those with sharp eyes will also notice that the button "Do Something" remains depressed until the loop is complete. This is broken, for sure, but what is the problem?

The code that paints the GUI and events generated by the GUI are executed on a single thread, called the "event-dispatching thread". (If you've never heard of "threads", consult the Java Tutorial on Threads here: http://java.sun.com/docs/books/tutorial/essential/threads/index.html. Threads make it possible to do several things at the same time in a single program.) In the program above, we are using the event-dispatching thread to run the for loop. While we are using it, the event-dispatching thread is unable to update the GUI. Hence the JProgressBar? remaining at position 0 and the JButton appearing to be depressed until the for loop completes.

Now that we've identified the problem, how do we fix it? The answer in your case will depend on your program requirements and your comfort level with threads. The Java Swing Tutorial has a page on Using Threads with Swing (http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html) which is a good starting point for fixing this problem.

The simplest solution would be to create a new thread to handle the long-running for loop, freeing the event dispatch thread to do its work. If the use of wait/notify to synchronized the threads in the code below confuses you, see the Java Tutorial on Threads (http://java.sun.com/docs/books/tutorial/essential/threads/index.html) for an explanation.


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class test2 implements ActionListenerRunnable {    
    
    static JProgressBar jpb;
    static test2 _this;
    
    public test2 (){
       _this = this;
    }
    
    private static void createAndShowGUI(){
        JFrame aFrame = new JFrame("Swing Thread Example:  Fixed Threading");
        JButton aButton = new JButton("Do Something");
        aFrame.getContentPane().add(aButton, BorderLayout.SOUTH);
        aButton.addActionListener(_this);        
        JPanel aPanel = new JPanel();
        aPanel.add(new JLabel("Progress:"));
        jpb = new JProgressBar(0,100);      
        aPanel.add(jpb);
        aFrame.getContentPane().add(aPanel);                              
        aFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        aFrame.pack();
        aFrame.setVisible(true);  
    }
    
    public static void main(String args[]){
       test2 t = new test2();  
       new Thread(t).start();
       javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            createAndShowGUI();
        }
    });
    }
    
    public void actionPerformed(ActionEvent ae){            
    // signal the worker thread to get crackin
        synchronized(this){notifyAll();}
    }
    
// worker thread 
    public void run(){
        while(true){
         // wait for the signal from the GUI
            try{synchronized(this){wait();}}
            catch (InterruptedException e){}
         // simulate some long-running process like parsing a large file
            for (int i = 0; i <= 100; i++){
               jpb.setValue(i);
               System.out.println("actionPerformed sets jpb value to: "+i);
               try{Thread.sleep(50);} // make the process last a while
               catch (InterruptedException e){}
           }
        }
    }
}
Now when one presses the button "Do Something", the event dispatch thread notifies the thread in our class and continues on its way. The worker thread does the heavy lifting in the for loop and the event dispatch thread is free to keep the GUI up to date. The JProgressBar? updates smoothly and the JButton remains depressed only as long as we hold the mouse down.

One may ask why we just didn't create the thread when it is needed (i.e. in the actionPerformed() method). The most obvious reason is that threads aren't cheap. Each thread requires that stack memory and native resources be allocated. Those resources need to be freed when the thread has run its course. This is unnecessary churn when one can create one long-lived thread and dispatch work to it as needed.

A more subtle reason is that a child thread inherits the parent thread's priority. Priority determines which thread gets run when. Higher priority threads get more CPU time than lower. Threads of equal priority may share CPU time or run to the exclusion of the other equal priority threads, depending on the JVM implementation. If we create a thread to handle our work in the actionPerformed method, the parent thread is the event dispatch thread. The two threads would have the same priority and the worker thread could monopolize the CPU, starving the event dispatch thread. The end result would be the same behavior we saw in the test1 code: the GUI not being updated.


CategoryCodeSamples

JavaRanchContact us — Copyright © 1998-2014 Paul Wheaton