≡ Menu

Updating a JProgressBar

This is a “simple” example of how to update a progress bar in a Swing application.

package org.javachannel;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by odinsbane on 9/22/14.
 * 
 * This is a quick example of a progress bar that updates.
 * 
 * This code is distributed as is; without warranty; or guarantee. It can
 * be freely used, modified and distributed without attribution.
 */
public class ProgressBarExample {
  ExecutorService service = Executors.newSingleThreadExecutor();

  /**
   * Sets up the gui, which is just a JFrame, JButton and a JProgressBar.
   */
  private void buildGui() {
    JFrame frame = new JFrame("progress bar example");
    final JButton start = new JButton("start");
    final JProgressBar progress = new JProgressBar();

    start.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent evt) {
            start.setEnabled(false);

            //starts long running task off of EDT.
            service.submit(new Runnable() {
              public void run() {
                for (int i = 0; i < 100; i++) {
                  //the portion of work.
                  try {
                    Thread.sleep(10);
                  } catch (InterruptedException e1) {
                    //this might be a good spot to quit working.
                    e1.printStackTrace();
                  }
                  //update the progress bar on the EDT.
                  final int j = i;
                  EventQueue.invokeLater(new Runnable() {
                    public void run() {
                      progress.setValue(j);
                    }
                  });

                }

                //work finished.
                EventQueue.invokeLater(new Runnable() {
                  public void run() {
                    progress.setValue(100);
                    start.setEnabled(true);
                  }
                });

              }
            });
          }
        }
    );

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLayout(new FlowLayout());
    frame.add(start);
    frame.add(progress);

    frame.pack();
    frame.setVisible(true);
  }


  public static void main(String[] args) {
    ProgressBarExample example = new ProgressBarExample();

    //ALL gui work should be managed on the EDT.
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        example.buildGui();
      }
    });
  }
}

In this example, the ‘start’ button begins a ‘long-running’ task. The task is started using an ActionListener, which starts the task on the Event Dispatch Thread (EDT). So the first thing to do is to ‘send’ the task to another thread, this is done by creating a Runnable and submitting the Runnable to the ExecutorService service.

As the task runs the progress bar needs to be updated; this should be performed on the EDT. By creating a new Runnable and submitting via EventQueue.invokeLater(), the task will be executed when Swing gets a chance, and the working thread will continue without waiting.

A small caveat is that the ‘start’ button is disabled during execution, and enabled when the task is completed. This is because when we fire the task on another thread, the EDT is not blocked and you could press the button again rather than waiting.

These concepts can still be applied to JavaFX type applications, although the environment’s obviously different and will involve some important changes – for example, you’d use Platform.runLater() instead of EventQueue.invokeLater().

Here’s an example of an equivalent program, using JavaFX instead. The layout isn’t an exact match, even with the equivalence of controls, thanks to the Scene not being packed like the JFrame is.

This example code relies on Java 8 and the use of lambdas, which shortens the code but remains equivalent:

package org.javachannel;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class JavaFXProgressBarExample extends Application {
  ExecutorService service = Executors.newSingleThreadExecutor();

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

  @Override
  public void start(Stage primaryStage) throws Exception {
    primaryStage.setTitle("progress bar example");
    ProgressBar progress = new ProgressBar();
    progress.setProgress(0.0);
    Button start = new Button();
    start.setText("start");
    start.setOnAction(event -> {
      service.submit(() -> {
        Platform.runLater(() -> start.setDisable(true));
        for (int i = 0; i < 100; i++) {
          //the portion of work.
          try {
            Thread.sleep(10);
          } catch (InterruptedException e1) {
            //this might be a good spot to quit working.
            e1.printStackTrace();
          }
          final double j = i / 100.0;
          Platform.runLater(() -> progress.setProgress(j));
        }
        Platform.runLater(() -> progress.setProgress(1.0));
        Platform.runLater(() -> start.setDisable(false));
      });
    });
    FlowPane root = new FlowPane();
    root.getChildren().add(start);
    root.getChildren().add(progress);
    primaryStage.setScene(new Scene(root, 250, 40));
    primaryStage.show();
  }
}

Comments on this entry are closed.