2011-12-30

Ultimate Swing, Teil 11

In meinem vergangenen Post habe Sie mit ziemlich viel Theorie versorgt. Diesmal werden wir uns ansehen, wie das ganze in Notes and Tasks realisiert wird. Zunächst die Schleife, die beim Löschen von Kategorien durchlaufen wird:

        String cmd = e.getActionCommand();
        if (CategoriesView.ACTION_DELETE.equals(cmd)) {
          Category[] categories = CategoriesModel
              .getTypeSafeCategories(categoriesView
                  .getSelectedValues());
          categoriesView.clearSelection();
          for (Category c : categories) {
            new DeleteCategoryWorker(c, categoriesModel,
                categoryDelegate).execute();
          }

Das Quelltextfragment ist länger, weil ich diesmal auch den Kontext dargestellt habe. Die eigentliche Schleife besteht nur aus dem Instantiieren von Objekten des Typs DeleteCategoryWorker und dem Aufruf dessen Methode execute(). Wie sieht die Implementierung der Klasse aus?

public class DeleteCategoryWorker extends SwingWorker<Boolean, Void> {

  private final Category c;
  private final CategoriesModel m;
  private final CategoryDelegate d;

  public DeleteCategoryWorker(Category c, CategoriesModel m,
      CategoryDelegate d) {
    this.c = c;
    this.m = m;
    this.d = d;
  }

  @Override
  protected Boolean doInBackground() throws Exception {
    boolean result = false;
    if (c.getTasks() == 0) {
      result = d.delete(c);
    }
    return new Boolean(result);
  }

  @Override
  protected void done() {
    Boolean result = Boolean.FALSE;
    try {
      result = get();
    } catch (InterruptedException e) {
      handleException(e);
    } catch (ExecutionException e) {
      handleException(e.getCause());
    } finally {
      if (Boolean.TRUE.equals(result)) {
        m.removeCategory(c);
      }
    }
  }

  private void handleException(Throwable t) {
    Application.showErrorMessage(t.getLocalizedMessage());
    t.printStackTrace();
  }
}

Sie erinnern sich, dass das Löschen von Kategorien aus zwei Teilen besteht, von denen der eine Aktion Bedienelemente manipuliert und deshalb auf dem EDT ausgeführt werden muss. Natürlich kommt in so einem Fall nur ein SwingWorker in Frage. Deshalb leitet DeleteCategoryWorker davon ab und überschreibt bzw. implementiert die beiden Methoden doInBackground() und done(). Erstere ruft den Service zum Löschen von Kategorien auf. Letztere löscht die korrespondierende Zeile in der Listbox – aber nur, wenn das physikalische Löschen erfolgreich war und überhaupt durchgeführt wurde. Dies geschieht nämlich nicht, wenn eine Kategorie noch in Aufgaben verwendet wird.

Wie Sie sehen, können wir auf sehr elegante Weise parallel auszuführende Tätigkeiten in passende Threads auslagern. Was aber passiert mit den im vorangehenden Post angesprochenen Mehrfachausgaben von Fehlermeldungen? Mein Worker delegiert das Problem, indem er die Methode showErrorMessage() meiner Klasse Application aufruft. Deren aktuelle Implementierung ist sehr einfach gehalten:

  public static void showErrorMessage(String message) {
    JOptionPane.showMessageDialog(INSTANCE, message,
        getString("string.title"), JOptionPane.ERROR_MESSAGE);
  }

Sie kann meiner Forderung, mehrere Fehlermeldungen zu sammeln, nicht nachkommen. Wir werden die Methode also entsprechend erweitern. Allerdings hat das noch etwas Zeit. Wie geht es nun weiter? In einem Folgeposting werde ich Ihnen einen weiteren Worker vorstellen. Er wird die Grundlage für einige wenige Refactoring-Maßnahmen sein. …spannend? Dann bleiben Sie dran…

PS: Mich würde interessieren, wie Ihnen das Programm bisher gefällt. Es wäre schön, wenn Sie aus den aktuellen Quellen eine Version bauen und ausprobieren würden. Tipp: machen Sie doch einmal einen Doppelklick auf eine Aufgabe…

Quelltexte zu TKNotesAndTasks vom 30.12.2011

2011-12-29

Ultimate Swing, Teil 10

Das folgende Quelltextfragment taucht so oder in ähnlicher Form an mehreren Stellen von Notes and Tasks auf:

          for (Task task : tasks) {
            // FIXME: SwingWorker and error handling
            MainController.this.tasksModel.removeTask(task);
            tasksDelegate.delete(task);
          }

Selbst wenn der Kommentar die Defizite nicht unmittelbar benennen würde, fiele ihnen sicher einige Fragen ein. Zum einen ist es unwahrscheinlich, dass weder removeTask() noch delete() Rückgabewerte haben. Warum werden sie nicht ausgewertet? Zum anderen könnte das Löschen von Aufgaben – gerade wenn hierzu ein Webservice aufgerufen wird – durchaus länger dauern. Warum wird die Aktion nicht in einen eigenen Thread ausgelagert? Und schließlich: warum gibt es überhaupt zwei Aufrufe für das Löschen einer Aufgabe? Läge es nicht nahe, sie zu einem zusammenzufassen?

Den letzten Aspekt – die Zerlegung des Löschvorgangs in zwei Teile - möchte ich zunächst nicht weiter vertiefen. Er betrifft die Schichtung von Anwendungen und die Trennung von UI-Logik und Persistenz. Belassen wir es deshalb bis auf Weiteres bei der Information, dass der erste Aufruf (removeTask()) eine Zeile aus der Aufgabenliste entfernt, wohingegen die Methode delete() die Aufgabe physikalisch löscht. Derzeit wird diese als Datei im lokalen Dateisystem abgelegt; später werden wir auf ein Google-Konto zugreifen. Es bleiben also zwei Fragen offen. 1) Wie lagert man die beiden Aufrufe sinnvoll in Threads aus und 2) wie verfährt man im Fehlerfall? Die Antwort auf die zweite Frage scheint auf der Hand zu liegen. “Na, ich fange die Fehler ab und gebe eine entsprechende Meldung aus.”  Das klappt bei einer Aufgabe prima. Aber führen 5 oder 10 Fehlersituationen auch zu entsprechend vielen Meldungen? Was geschieht, wenn solche Meldungen nicht auf irgend einer Console ausgegeben werden, sondern als modaler Dialog?

Treten wir also gedanklich einen Schritt zurück und fragen, warum es zur Ausgabe von mehreren Meldungen kommt. Der Anwender hat zum Beispiel aus einer Liste mehrere Elemente (Aufgaben oder Kategorien) ausgewählt und möchte diese löschen. Auch wenn daraus mehrere (Webservice-)Aufrufe resultieren, repräsentieren diese doch eine Aktion. Konsequenterweise darf es im Fehlerfall auch nur eine entsprechende Meldung geben, die aber – und das ist durchaus aufwendig – über alle aufgetretenen Probleme informiert. Bei dem hier skizzierten Löschen handelt es sich also um eine (fachliche) Transaktion. Das bedeutet, dass alle Änderungen rückgängig gemacht werden müssen, wenn ein Teilvorgang fehl schlägt. Anders formuliert: der Benutzer müsste wieder den Zustand vor dem Anklicken der Schaltfläche Löschen vorfinden. Dies betrifft insbesondere die Liste und die darin ausgewählten Elemente. Unglücklicherweise wird dies sehr schnell sehr aufwendig, wenn Webservices im Spiel sind.Denn dann gibt es in der Regel weder beginTransaction() noch commit() und rollback(). Natürlich gibt es hier Hilfskonstrukte, zum Beispiel in Form kompensierender Serviceaufrufe.Ob es freilich sinnvoll ist, 9 erfolgreich gelöschte Kategorien neu anzulegen, weil das Löschen der 10. fehlgeschlagen ist, muss meiner Meinung nach im Einzelfall geprüft werden.

Für die Architektur von Swing-Anwendungen gilt aber in jedem Fall, dass eine fachliche Transaktion zu maximal einer Meldung führen darf, der Anwender muss aber erkennen, was schief gegangen ist. Wie akribisch die Rekonstruktion des Zustands vor der Transaktion erfolgt, würde ich von den technischen Rahmenbedingungen abhängig machen. Steht eine Transaktionskontrolle zur Verfügung, müssen Sie diese natürlich nutzen.

2011-12-28

Einstieg in Eclipse 3.7

Einstieg in Eclipse 3.7
Eine geballte Ladung Wissen rund um Eclipse 3.7
Ich freue mich sehr, mein zweites Buch in diesem Jahr ankündigen zu können. Es ist die vierte Auflage meines Einstiegs in Eclipse. Einstieg in Eclipse 3.7 ist eine Gemeinschaftsproduktion. Ich konnte mit Yvonne Wolf eine sehr erfahrene Programmiererin als Co-Autorin gewinnen. Für ihre tolle Arbeit bedanke ich mich an dieser Stelle herzlich.

2011-12-18

Ultimate Swing, Teil 9

Wenn Sie mit Notes and Tasks experimentieren, fällt Ihnen vielleicht ein scheinbares Fehlverhalten auf.

Kategorieauswahl als Popup
Kategorieauswahl als Popup

Das Anklicken der Schaltfläche Change Category öffnet wieder und wieder das Popup, ebenso können Sie bei geöffnetem Menü die Registerkarten wechseln.

Sind Popups nicht eine Art modaler Dialog, der nach Anklicken des Hintergrunds verschwindet? Wenn ja, warum wirken Mausereignisse dann auf andere Komponenten?

Das Durchlassen der Mausereignisse ist gewollt.

Wie so oft in Swing kann man das Verhalten aber auf einfache Weise beeinflussen. Mit UIManager.put("PopupMenu.consumeEventOnClose", Boolean.TRUE); sorgen Sie dafür, dass Mausereignisse nicht an andere Komponenten weitergereicht werden, solange das Popup geöffnet ist.

Welches Verhalten würden Sie denn von Popups erwarten? Sollen Klicks außerhalb durchgereicht oder vom Popup konsumiert werden? Ich bin sehr auf Ihre Meinungen gespannt…

2011-12-17

Ultimate Swing, Teil 8

Mal ehrlich, können Sie das Wort Swing noch hören? Keine Angst, ich werde in diesem Weblog schon auch zu anderen Themen was schreiben. Aber vielleicht tut es ja wirklich mal gut, nicht ständig von iOS oder Android lesen zu müssen.

Die Registerkarte Tasks
Die Registerkarte Tasks

Die Registerkarte Categories
Die Registerkarte Categories

Deshalb werden wir uns auch diesmal mit meiner Anwendung Notes and Tasks befassen. Heute ist Quellcode-Tag. Das bedeutet, am Ende dieses Posts finden Sie wie gewohnt ein Archiv, dass Sie ohne große Mühe zu einem lauffähigen Programm übersetzen können sollten. Wenn Sie es starten, können Sie auf der Registerkarte Categories Kategorien eintragen, die Sie anschließend auf der Registerkarte Tasks Aufgaben zuweisen. Kategorien dienen dazu, Aufgaben zu gruppieren. Deshalb habe ich die Kategorien beruflich und privat eingetragen Neue Aufgaben werden übrigens ganz bewusst keiner Kategorie zugeordnet. Deshalb erscheinen sie am Anfang der Liste.

Ich würde mich freuen, wenn Sie mit dem Programm in seiner aktuellen Form spielen. Außer anlegen können Sie mit Aufgaben noch nicht viel, aber das kommt schon noch. Bitte schreiben Sie mir doch über Ihre Erfahrungen beim Spielen mit dem Programm. Ich freue mich auf Ihre Kommentare.

Quelltexte zu TKNotesAndTasks vom 17.12.2011

2011-12-15

Ultimate Swing, Teil 7

Beim Schreiben einer Swing-Anwendung müssen Sie prüfen, welche Operationen Sie auf dem EDT ausführen lassen, und welche besser in einem eigenen Thread abgearbeitet werden. Die bisherigen Schlüsselerkenntnisse nochmals kurz zusammengefasst:

  • alle Manipulationen von Bedienelementen finden immer auf dem EDT statt
  • alles was länger als 200 Millisekunden dauert, läuft in einem eigenen Thread.

Sicher fragen Sie sich, wie ich gerade auf 200 Millisekunden komme. Natürlich ist dies ein künstlicher Wert, mit dem ich vor allem unterstreichen möchte, dass Sie unter allen Umständen die Oberfläche reaktionsbereit halten müssen. Egal, ob Sie auf die Daten eines Wetter-Webservice warten oder eine Datei in das lokale Dateisystemschreiben – es ist inakzeptabel, dass die Benutzeroberfläche für einen spürbaren Zeitraum zu hängen scheint.

Wenn Sie jetzt sagen, dass dies nach viel Arbeit riecht, so haben Sie im Prinzip recht. Der Code, der erforderlich ist, um Operationen auf dem richtigen Thread ablaufen zu lassen, fällt aber geringer aus, als es nach meinen letzten beiden Beispielen vielleicht scheint. Bitte sehen Sie sich die Klasse WorkerDemo1 an.

public class WorkerDemo1 extends JFrame {

  public WorkerDemo1() {
    super("WorkerDemo1");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    JPanel cp = new JPanel(new BorderLayout());
    cp.add(new JLabel(
        "bitte das Fenster nach Belieben vergrößern und verkleinern"),
        BorderLayout.NORTH);
    cp.add(new JPanel() {
      @Override
      protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.red);
        g.drawLine(0, 0, getWidth() - 1, getHeight() - 1);
        g.drawLine(0, getHeight() - 1, getWidth() - 1, 0);
      }
    }, BorderLayout.CENTER);
    final JButton button = new JButton("Hintergrundaktion durchführen");
    button.addActionListener(new ActionListener() {

      public void actionPerformed(ActionEvent e) {
        SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

          @Override
          protected Void doInBackground() throws Exception {
            for (int i = 1; i <= 10; i++) {
              try {
                Thread.sleep(1000);
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
              System.out.println(i);
            }
            return null;
          }

          @Override
          protected void done() {
            button.setEnabled(true);
          }
        };
        button.setEnabled(false);
        worker.execute();
      }
    });
    cp.setPreferredSize(new Dimension(400, 300));
    cp.add(button, BorderLayout.SOUTH);
    setContentPane(cp);
    pack();
    setLocationRelativeTo(null);
  }

  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        Toolkit.getDefaultToolkit().setDynamicLayout(true);
        System.out.println(EventQueue.isDispatchThread());
        new WorkerDemo1().setVisible(true);
      }
    });
  }
}

Da sich die relevanten Änderungen auf die Methode actionPerformed() konzentrieren, zeige ich Ihnen diese noch einmal gesondert:

      public void actionPerformed(ActionEvent e) {
        SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

          @Override
          protected Void doInBackground() throws Exception {
            for (int i = 1; i <= 10; i++) {
              try {
                Thread.sleep(1000);
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
              System.out.println(i);
            }
            return null;
          }

          @Override
          protected void done() {
            button.setEnabled(true);
          }
        };
        button.setEnabled(false);
        worker.execute();
      }

In der Methode geschieht folgendes:

  1. Instantiieren eines Objekts vom Typ SwingWorker
  2. Deaktivieren der Schaltfläche
  3. Aufruf der Methode execute()

Durch Aufruf von execute() wird eine Hintergrundaktivität gestartet. Was hier passiert, legen Sie durch Ihre Implementierung der Methode doInBackground() fest. Aufräumarbeiten nach Beendigung dieser Aufgaben packen Sie in den Methodenrumpf von done().

2011-12-09

Ultimate Swing, Teil 6

Wie Sie aus dem letzten Post wissen, ist es keine gute Idee, länger andauernde Aufgaben auf dem EDT auszuführen. Um Ihre Anwendung nicht zu blockieren, lagern Sie entsprechende Codeteile stattdessen in einen eigenen Thread aus. Wie so etwas aussehen kann, demonstriert Better. Hierbei handelt es sich praktisch um einen Klon von Bad. Nur die Implementierung der Methode actionPerformed() habe ich ausgetauscht.

      public void actionPerformed(ActionEvent e) {
        Thread t = new Thread(new Runnable() {
          @Override
          public void run() {
            for (int i = 1; i <= 10; i++) {
              try {
                Thread.sleep(1000);
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
              System.out.println(i);
            }
            SwingUtilities.invokeLater(new Runnable() {
              @Override
              public void run() {
                button.setEnabled(true);
              }
            });
          }


        });

        button.setEnabled(false);
        t.start();
      }
Zugegebenermaßen sieht das etwas komplizierter aus. Lassen Sie uns deshalb überlegen, was alles ausgeführt werden soll.
  1. Deaktivieren der Schaltfläche
  2. 10 Sekunden warten
  3. Aktivieren der Schaltfläche
Da die Punkte 1) und 3) Swing-Komponenten manipulieren, müssen die entsprechenden Anweisungen auf dem EDT ablaufen. In Bezug auf 1) ist keine entsprechende Vorkehrung erkennbar, in Bezug auf Punkt 3) aber schon. Mit SwingUtilities.invokeLater() sorgen Sie dafür, dass Anweisungsfolgen Swing-gerecht auf dem EDT ausgeführt werden. Punkt 2) repräsentiert ja gerade die Hintergrundverarbeitung und soll deshalb auf einem eigenen Thread abgearbeitet werden. Taushcen Sie doch einmal die Methodenimplementierung aus und starten das Programm. Während in der (Eclipse-)Konsole von 1 bis 10 gezählt wird, können Sie problemlos die Größe des Fensters verändern. Auch die Schaltfläche wird korrekt (de)aktiviert. Also alles in Ordnung. …oder?
Wie Sie wissen, habe ich beim Deaktivieren der Schaltfläche nicht ausdrücklich sichergestellt, dass die Manipulation der Komponente auf dem EDT stattfindet. Offenbar klappt das aber sehr gut. Bitte packen Sie in die main()-Methode der Klasse mal folgende Anweisung: System.out.println(EventQueue.isDispatchThread());
Wenn Sie die Anwendung erneut starten, sehen Sie sehr wahrscheinlich den Wert false. Dass das Programm also gut funktioniert, ist mehr oder weniger Zufall. Auch die Initialisierung des Fensters muss auf dem EDT laufen! Deshalb gehört ein einsprechender invokeLater()-Aufruf als erste und einzige Anweisung in die main()-Methode.

  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        Toolkit.getDefaultToolkit().setDynamicLayout(true);
        System.out.println(EventQueue.isDispatchThread());
        new Better().setVisible(true);
      }
    });
  }
Merke: nur weil es den EDT gibt, bedeutet dies nicht, dass ein Programm darauf läuft.

2011-12-04

Ultimate Swing, Teil 5

Wenn jemand einen Blogpost mit einer scheinbar leicht zu beantwortenden Frage beendet, ist es fast schon offensichtlich, dass die Antwort nicht wie erwartet ausfällt. Natürlich ist es keine gute Idee, Swing-Anwendungen ausschließlich auf dem EDT ablaufen zu lassen. Sehen Sie sich hierzu das folgende Programm an:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Bad extends JFrame {

  public Bad() {
    super("So besser nicht");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    JPanel cp = new JPanel(new BorderLayout());
    cp.add(new JLabel(
        "bitte das Fenster nach Belieben vergrößern und verkleinern"),
        BorderLayout.NORTH);
    cp.add(new JPanel() {
      @Override
      protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.red);
        g.drawLine(0, 0, getWidth() - 1, getHeight() - 1);
        g.drawLine(0, getHeight() - 1, getWidth() - 1, 0);
      }
    }, BorderLayout.CENTER);
    final JButton button = new JButton("Hintergrundaktion durchführen");
    button.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent e) {
        button.setEnabled(false);
        try {
          Thread.sleep(10000);
        } catch (InterruptedException e1) {
        }
        button.setEnabled(true);
      }
    });
    cp.setPreferredSize(new Dimension(400, 300));
    cp.add(button, BorderLayout.SOUTH);
    setContentPane(cp);
    pack();
    setLocationRelativeTo(null);
  }

  public static void main(String[] args) {
    Toolkit.getDefaultToolkit().setDynamicLayout(true);
    new Bad().setVisible(true);
  }
}

Wenn Sie es starten, erscheint das in der folgenden Abbildung dargestellte Fenster.

Screenshot des Programms Bad
Screenshot des Programms Bad

Spielen Sie bitte mit dem Fenster, indem Sie seine Größe verändern. Das rote Kreuz passt sich recht flott an die neuen Ausmaße an. Allerdings nur, bis Sie die Schaltfläche am unteren Rand anklicken. Danach passiert 10 Sekunden lang gar nichts mehr. Selbst das Anklicken des Schließen-Knopfes zeigt in dieser Zeit keine Wirkung. Was passiert hier?

Die entscheidende Zeile ist Thread.sleep(10000);. Diese Anweisung sorgt dafür, dass der aktuelle Thread schlafen gelegt wird. Da Swing, wie Sie wissen, eine single threaded library ist, ist diese Auszeit sozusagen umfassend. Deshalb wird nichts gezeichnet und auch ein etwaiges Klicken auf das Schließen-Feld wird nicht weitergeleitet. Ich behaupte, zu einem großen Teil geht der schlechte Ruf von Swing auf die über viele Jahre lang praktizierte falsche Nutzung zurück. Entschuldigend muss aber angemerkt werden, dass Sun sich lange sehr schwer getan hat, wirkliche Best Practices für Swing zusammenzustellen.

Die Anweisung Toolkit.getDefaultToolkit().setDynamicLayout(true); ist übrigens unter modernen Java-Versionen unnötig. Ich habe sie nur der Vollständigkeit halber eingefügt. Sie müssen sie in Ihren Programmen nicht verwenden.

Nachtrag: Ich weiß bei Blogger nie so genau, ob meine Antworten auf Kommentare den Lesern angezeigt werden. Wenn Sie mir geschrieben haben, schauen Sie doch ein paar Tage später bei den Kommentaren ruhig mal nach.

Ultimate Swing, Teil 4

Haben Sie sich die Mühe gemacht, im Web mal nach “EDT” oder “Event Dispatching Thread” zu suchen?

Die (quasi-)parallele, Abarbeitung von Aufgaben ist seit vielen Jahrzehnten ein großer Wunsch von Anwendern wie Entwicklern. Je nachdem, auf welcher Ebene eine solche Parallelität stattfindet, spricht man von Multitasking oder Multithreading. Multitasking meint – stark vereinfacht ausgedrückt – das gleichzeitige Arbeiten mit mehreren Programmen. Das mag sich nach nichts Besonderem anhören. Dem Benutzer eines frühen Homecomputers würde so etwas aber in grenzenloses Staunen versetzen. Denn wenn er sein BASIC-Programm mit run gestartet hatte (sorry, aber kurz vor Weihnachten werde ich immer ein klein wenig sentimental), war der Rechner damit – und ausschließlich damit – beschäftigt. Auf den “Wir machen Spitzentechnologie preiswert”-Rechnern (kennt jemand diese Werbung noch?) gab es neben dem Hauptprogramm so genannte Accessories (nein, kein Mac). GEM (ein Teil von Ataris TOS) bot nämlich kooperatives Multitasking. Damit das funktionierte, mussten alle Beteiligten von sich aus die Kontrolle wieder an das System abgeben. Der Amiga bot übrigens “echtes”, also präemptives Multitasking.

Multithreading sorgt auf der Ebene von Programmen für Parallelität. Eine Anwendung kann also Daten von einem Webserver herunterladen und (quasi) gleichzeitig andere Dinge tun. Zum Beispiel Benutzereingaben entgegen nehmen, Dateien lesen oder schreiben, oder vielleicht einen weiteren Webservice kontaktieren. In Java ist der Schlüssel hierfür der Thread. Der richtige Umgang mit Threads ist nicht immer einfach zu verstehen – offen gesagt hat die Materie zahlreiche Bände gefüllt. Deshalb kann ich Sie nur ermutigen, bei Bedarf entsprechende Fachliteratur zu lesen.

Der Event Dispatching Thread ist ein Hintergrundthread, der im Zusammenhang mit der Verarbeitung von Ereignissen benötigt wird, die von Komponenten des Abstract Window Toolkits sowie Swing verschickt und empfangen werden. Hierbei handelt es sich unter anderem um Mausklicks, Mausbewegungen, Tastendrücke sowie um höherwertige Ereignisse. Das “simple” Anklicken einer Schaltfläche führt zu zahlreichen Ereignissen, die zwischen beteiligten Komponenten hin und her geschickt werden. Was passiert nun, wenn dieser Code auf unterschiedlichen Threads ausgeführt wird? AWT und das darauf aufsetzende Swing sind nicht Multithreading-fähig. Zustandsänderungen dürfen deshalb nur auf einem bestimmten Thread durchgeführt werden. Er wird EDT (Event Dispatching Thread) genannt. Einen wunderbaren Einblick in die Theorie gibt Graham Hamiltons Post Multithreaded toolkits: A failed dream?.

Was aber bedeutet das? Es liegt doch nahe, dem offenbar entstehenden Ärger aus dem Weg zu gehen, indem man einfach nur einen – DEN – Thread nimmt. Oder?