SwingUtilities.invokeLater() - setzen von GUI-Eigenschaften zur Laufzeit

Ja, das DnD-Thema liegt immer noch als „Noch nicht so ganz abgeschlossen“ in meinem Gewissen. Vom „Wie man aus Applets eine Anwendung macht“-Wiki-Eintrag mal ganz zu schweigen :roll_eyes:

Die Methode, in der der Code implementiert ist, wird als Reaktion auf die Selektion eines JLabels, das dynamisch (zur Laufzeit) auf dieses Panel gesetzt wurde, aufgerufen.

Wenn das, was da gemacht wird, als „Reaktion“ auf irgendeine GUI-Aktion passiert, dann läuft es ja schon auf dem EDT. Wenn man z.B. irgendwo einen ActionListener oder MouseListener oder so dranhängt, dann wird alles, was von der jeweiligen actionPerformed/mousePressed usw. gemacht und aufgerufen wird, vom EDT ausgeführt.

In der „reinen“ GUI-Welt braucht man SwingUtilities.invokeLater darum eigentlich recht selten.


Der Punkt an dieser Stelle ist, dass „irgendein“ Thread das getFont aufruft. Und an irgendeiner anderen Stelle könnte ein anderer Thread (der EDT, oder ein ganz anderer) im gleichen Moment setFont aufrufen. Welcher Font dann tatsächlich bei getFont zurückkommt, weiß man erstmal nicht. Im schlimmsten Fall kann das dann halt mit „unerklärlichen“ Fehlern abkacheln oder sich beliebig komisch verhalten. Das setFont ist zwar noch recht einfach (vielleicht wirklich nur ein setter/getter), aber das weiß man ja erstmal nicht…


Allgemein:

Der Punkt, wo man SwingUtilities.invokeLatern braucht, ist: Wenn man irgendwas mit eigenen Threads macht. Kürzlich hatte ich da auch mal einen (kurzen) Primer in einer Antwort geschrieben: java - Starting threads from inside EDT event handler code in Swing apps - Stack Overflow

Der relevante Teil nochmal kurz: Wenn man sowas macht

someButton.addActionListener(e -> {
    doSomeComputationThatTakesFiveMinutes();
    someLabel.setText("Finished");
});

Dann wird die doSomeComputationThatTakesFiveMinutes-Methode, wie oben schon erwähnt, vom EDT ausgeführt - der damit 5 Minuten lang blockiert ist. Das ist nicht gut. Deswegen könnte man das auf einen anderen Thread legen:

someButton.addActionListener(e -> {
    Thread thread = new Thread(() -> {
        doSomeComputationThatTakesFiveMinutes();
        someLabel.setText("Finished");  
    });
    thread.start();
});

Aber: Das ist genau der Punkt, wo man aufpassen muss. Dieser Thread würde ja auch das someLabel.setText ausführen. D.h. dort würde eine GUI-Komponente von einem Thread verändert, der nicht der EDT ist.

Um das „sauber“ zu machen, müßte man diesen Teil dann wieder in den EDT zurückreichen, und das ist der Punkt, wo man SwingUtilities.invokeLater verwenden muss:

someButton.addActionListener(e -> {
    Thread thread = new Thread(() -> {
 
        // Das passiert im Hintergrundthread
        doSomeComputationThatTakesFiveMinutes();

        SwingUtilities.invokeLater(() -> {
            // Das wird dann wieder auf dem EDT gemacht:
            someLabel.setText("Finished");  
        });
    });
    thread.start();
});

Wie in der Stackoverflow-Antwort auch gesagt: Das kann etwas frickelig und unbequem werden. Für genau dieses Muster gibt es dann den SwingWorker, der das ganze etwas einfacher macht (ohne new Thread und ohne „sichtbares“ invokeLater). Aber wenn man da dann noch einen Dialog mit Fortschrittsbalken und Cancel-Button haben will, wird das auch wieder kompliziert. Dafür hatte ich dann mal GitHub - javagl/SwingTasks: Utility classes for task execution in Swing erstellt

SwingTaskExecutors.create(
    () -> berechneZehnMinutenLangEinErgebnis(),
    ergebnis -> zeigDasImGuiAn(ergebnis)).
    build().execute();

Und der SwingTask kümmert sich um Threads, Dialog, und den ganzen anderen Mist.


Aber wie gesagt: Wenn man keine eigenen Threads startet, und keine Daten aus „Fremd-Threads“ ins GUI bringen will, läuft „praktisch alles“ sowieso schon auf dem EDT.


Es kann fiese Grenzfälle geben. Ein sehr vereinfachtes Beispiel:

void addRowTo(DefaultTableModel model) {
    Object rowData[] = computeSomething();
    model.insertRow(rowData);
}

Dort soll eine Zeile zu einem TableModel hinzugefügt werden. Die computeSomething-Methode rechnet vielleicht sehr lange. Darf man das in einem eigenen Thread machen, oder muss das auf dem EDT gemacht werden?

Die Antwort ist: Es kommt darauf an. Wenn es eine JTable gibt, die das TableModel enthält, dann muss die Änderung auf dem EDT gemacht werden. Wenn das TableModel aber „frisch“ ist und noch nicht in einer JTable liegt, dann hat es nichts mit GUI-Kompnenten zu tun, und kann auch in einem Fremd-Thread verändert werden…

1 „Gefällt mir“