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

Ist es hier richtig/notwendig, dieses Fragment an den EDT zu übergeben?
Es sollten ja eigentlich alle Veränderungen an GUI-Elementen über den EDT laufen…
Ich bin mir aber in diesem Fall nicht sicher.

    Font font = label.getFont();

    SwingUtilities.invokeLater(() -> {
        fontComboBox.setSelectedItem(font.getName());
        sizeSpinner.setValue(font.getSize());

        switch (font.getStyle()) {
            case Font.BOLD:
                boldButton.setSelected(true);
                italicButton.setSelected(false);
                break;
            case Font.ITALIC:
                boldButton.setSelected(false);
                italicButton.setSelected(true);
                break;
            case Font.BOLD | Font.ITALIC:
                boldButton.setSelected(true);
                italicButton.setSelected(true);
                break;
            default:
                boldButton.setSelected(false);
                italicButton.setSelected(false);
        }
    });

Also, wie du schon schreibst: sowas sollte über den EDT gehen. Weil Swing nicht thread-safe ist. Zumindest weite Teile davon. Laut doku soll es Methoden geben, die mit Threadsafe gelabelt (= annotiert?) sind. Diese kann man dann auch sicher aus anderen threads aufrufen ( https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html ).

Alles klar, danke Tomate. :+1:

Ich würde das auch immer an den EDT übergeben, das Innere aber in eine eigene Methode auslagern.

Ja, würde ich auch machen, allerdings brauche ich das derzeit nur dort.
Danke für deine Antwort.

Das sieht aber eigentlich so aus, als wäre das schon Code, der „vom GUI aus“ (d.h. im EDT) ausgeführt wird…?

Falls das nicht, dann sollte/müßte eigentlich auch das

Font font = label.getFont();

im EDT gemacht werden - sonst hat man ein Data Race, und zwar ggf. ein fieses.

Codeteile, die NUR irgendwas mit GUI-Components machen in eine Methode zu packen, kann aber hilfreich sein:

void doStuffThatDependsOnOrChangesTheGui() {
    if (!SwingUtilities.isEventDispatchThread()) {
        SomeException up = new SomeException ("You're on the wrong thread here...");
        throw up; // :-D 
    }
    ...
}

(Besser ist’s wenn’s gar nicht so unübersichtlich wird, dass solche Watchdogs notwendig sind, aber … ja)

1 Like

Danke Marco, ich liebe deine Antworten, weil ich dabei bisher IMMER etwas dazu gelernt habe! :+1::clap:

Der Code liegt in einer Klasse, die von JPanel erbt und eine größere Benutzeroberfläche aus weiteren Panels zusammensetzt. 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.
Vielleicht erinnerst du dich noch an das DnD-Thema, in dem du mir auch sehr geholfen hast.

Die Methode liest Eigenschaften aus einem ans Label gekoppelte Model aus (Font-Eigenschaften) und setzt damit (sichtbare) Zustände auf anderen GUI-Elementen. Konkret geht es hier um eine Komponente, die eingestellte Schrifteigenschaften grafisch darstellt.

Ich habe davon bisher noch nichts gehört. Habe mir gerade mal dazu ein paar Themen aufgerufen.

Aber vielleicht noch eine Frage: Wann/wo ist der beste Ort/Zeitpunkt, um Daten an den EDT zu übergeben?
Was passiert, wenn man das mehrmals (durch verschachtelte Methodenaufrufe) macht?

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: https://stackoverflow.com/questions/58099010/starting-threads-from-inside-edt-event-handler-code-in-swing-apps/58099865#58099865

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 https://github.com/javagl/SwingTasks 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 Like

Ich weiß, für die Arbeit am Applet -> Anwendung -Thema finde ich im Moment keine Zeit, dieses Projekt ist hoch angebunden.
Und das DnD-Thema brennt nicht, es wäre schön, das noch mal aufzuarbeiten, im Moment bin ich erst mal zufrieden, dass man damit arbeiten kann, auch wenn es noch nicht so aussieht, wie wir das schon mal besprochen haben.

Danke für die erläuternden Zeilen, das hat mir geholfen. Sieht so aus, als könnte ich einige EDT-Aufrufe entfernen, weil die ohnehin schon in ihm laufen.