Kristallkugel
Greifenfeder
Einhorn Zauberstab
Alraune
Pegasus im Sonnenuntergang

JProgressbar und Multi-Threading

Einführung

Im Gegensatz zu anderen Swing Komponenten ist der Einsatz des JProgressbar als Fortschrittsanzeige aufwändig zu implementieren. Der Grund dafür ist der Einsatz von Multi-Threads (mehreren eigenständigen Prozessen) welche die Applikation schnell unübersichtlich und fehleranfällig machen können.

Ein zeitintensiver Rechenvorgang mit Fortschrittsanzeige kann auch ohne Multi-Thread Einsatz, nur über den Event Dispatch Thread (der für das Zeichnen des GUI zuständig ist) abgearbeitet werden. Allerdings wäre dann der EDT durch die aufwändige Operationen blockiert und das GUI würde einfrieren. Deshalb gilt:

  • Zeitintensive Vorgänge sollten nicht über den Event Dispatch Thread ausgeführt werden, da die Applikation einfriert.
  • Auf Swing Kompnenten sollte nur über den Event Dispatch Thread zugegriffen werden.

Da die Fortschrittsanzeige ein GUI-Aspekt ist und synchron zum Fortschritt, der in den meisten Fällen ein zeitintensiver Vorgang ist, ausgeführt werden soll, muss ein zweiter Thread erstellt werden.

Aufwand  Schwierigkeitsgrad 5 Schwierigkeitsgrad 5 Schwierigkeitsgrad 5 Schwierigkeitsgrad 5 Schwierigkeitsgrad 5

Multi-Thread erstellen

EDT

Alle GUI-Komponenten werden also weiterhin über den EDT benutzt. Auch die JProgressbar, die den Fortschritt des Prozesses visualisiert, wird über den EDT benutzt.
Für die Ausführung des zeitintensiven Prozess wird ein neuer Thread erstellt, dem beliebige Parameter mitgegeben werden.

  /**
   * Über dieses Button Model wird ein Aktion-Listener registriert.
   * Bei Knopfdruck wird die Progressbar und der dazugehörigen Dialog instanziert.
   * Der Thread "Worker" übernimmt nach der Ausfürung,
   * über die execute Methode, alle Prozesse, die nicht über das GUI geführt
   * werden dürfen und bekommt dementsprechende Parameter mit.
   * Der Dialog wird erst nach der Ausführung des Threads mit setVisible() gestartet.
   *
   */

  private void initialSendButtonModel() {
    fSendButtonModel.addActionListener(new ActionListener() {
      
      @Override
      public void actionPerformed(ActionEvent e) {

        /* Progressbar */
        final JProgressBar progressBar = new JProgressBar(0, fTask.getSize());
        progressBar.setValue(0);
        progressBar.setStringPainted(true);

        /* Progress-Dialog */
        final JDialog progressDialog = FrameFactory.createDialog("Fortschritt");
        progressDialog.getContentPane().add(progressBar);

        final Worker worker = new Worker(fTask, progressDialog);
        worker.addPropertyChangeListener(new PropertyChangeListener() {
         
          @Override
          public void propertyChange(PropertyChangeEvent evt) {
            int progress = worker.getProgress();
            if (progress == 0) {
              progressBar.setIndeterminate(true);
            } else {
              progressBar.setIndeterminate(false);
              progressBar.setValue(progress);
            }
          }
        });
        worker.execute();
        progressDialog.setVisible(true);
      }
    });
  }

SwingWorker

Das Handling von Threads ist seit Verion 6.0 (auch 1.6 genannt) einfacher geworden, durch die Funktionen der SwingWorker Klasse. Beim erstellen eines eigenen Threads kann von der Klasse SwingWorker abgeleitet werden. Daraufhin muss die Methode doInBackground implementiert werden.

Sobald die finale Methode execute (die aufgrund der Vererbung durch SwingWorker, über die neue Instanz zugänglich ist) aufgerufen wird, wird die Methode doInBackground ausgeführt.
In dieser Methode muss der zeitaufwändige Prozess getätigt werden. Dabei wir der Fortschritt mit der Methode setProgress erhöht. Durch den Aufruf von setProgress wird der  Listener für den Thread notifiziert und die propertyChange Methode aufgerufen, die den Wert der Progressbar erhöht. Dies geschieht wiederum im EDT.

/**
 * Der Worker ist ein Thread um nicht GUI-betreffende Prozesse durch zu führen.
 *
 * @see SwingWorker
 */

public class
Worker extends SwingWorker<Void, Void> {

  private final JDialog fProgressDialog;
  private final MyTask fTask;

  /**
   * @param task            Prozess gekappselt in einer Klasse.
   * @param progressDialog  Dialog mit Fortschrittsbalken,
   *                        der nach Beendigung des Prozesses geschlossen wird.
   *
   */

  public Worker(MyTask task, JDialog progressDialog) {
    fTask = task;
    fProgressDialog = progressDialog;
  }

  @Override
  protected Void doInBackground() {
    for (int i = 0; i < fTask.getSize(); ++i) {
      fTask.doTaskWithNummer(i);
      setProgress(i);
    }
    return null;
  }

  /**
   * done() wird nach der doInBackground Methode ausgeführt.
   * Hier wird eine Message ausgegeben, dass der zeitaufwändige Vorgang beendet wurde.
   * Da der Vorgang zu Ende ist kann der Dialog mit der Progressbar geschlossen werden.
   */
  @Override

  protected void done() {
    JOptionPane.showMessageDialog(FrameFactory.getMainFrame(),
        "<html>Der Prozess <b>" + fTask.getName() + "</b> wurde erfolgreich durchgeführt.</html>",
        "Prozess erfolgreich beendet",
        JOptionPane.INFORMATION_MESSAGE);
    fProgressDialog.dispose();
  }
}

Zusammenfassung

Durch den Aufruf der execute Methode wird der zeitintensive Prozess fast synchron zum Fortschrittsbalken ausgeführt. Der Dialog wiederum blockiert während dieser Zeit den Zugriff auf das Haupt-Fenster, ist aber immer noch aktiv für Benutzerinteraktionen.

Literatur