Swing keine JLabel-Aktualisierung bei "externem" Aufruf durch Helferklasse

Doc Cyber

Mitglied
Hallo zusammen!

Wenn ich die setText-Methode eines JLabel-Objekt durch einen Aufruf (quasi ein Rückruf)aus einer Helferklasse ausführe, wird der Label nicht aktualisiert. Neben setText() habe ich noch verschiedenes probiert: repaint(), repaint(1), validate, revalidate.
(Auch ein repaint() etc des übergeordneten JPanel zeigt keinerlei Wirkung.)

Das untenstehende Beispiel-Progrämmchen ist lauffähig.
Der Code besteht nur aus wenigen Zeilen, verteilt auf zwei Klassen.

Habt Dank für jede hilfreiche Antwort. 🙂
Java:
package test.labels;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JLabel;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class LabelTest extends JFrame {

   private JPanel contentPane;
   private JLabel infoLabel;
  
   public LabelTest() {
     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     setBounds(0, 0, 160, 120);
     contentPane = new JPanel();
     setContentPane(contentPane);
     contentPane.setLayout(null);
    
     JButton btnNewButton = new JButton("do something");
     btnNewButton.addActionListener(new ActionListener() {
       public void actionPerformed(ActionEvent e) {
         LabelHelper helper = new LabelHelper(infoLabel, contentPane);
         helper.doSomething();
       }
     });
     btnNewButton.setBounds(10, 11, 126, 23);
     contentPane.add(btnNewButton);
    
     infoLabel = new JLabel("info");
     infoLabel.setBounds(10, 60, 126, 14);
     contentPane.add(infoLabel);
   }
  
   public static void main(String[] args) {
     EventQueue.invokeLater(new Runnable() {
       public void run() {
         try {
           LabelTest frame = new LabelTest();
           frame.setVisible(true);
           frame.setLocationRelativeTo(null);
         } catch (Exception e) {
           e.printStackTrace();
         }
       }
     });
   }
}

package test.labels;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class LabelHelper {
   private JLabel infoLabel;

   public LabelHelper(JLabel infoLabel) {
     this.infoLabel = infoLabel;
   }
  
   public void doSomething() {
     for (int i = 1; i<= 3; i++) {
       // infoLabel wird nicht aktualisiert!
       infoLabel.setText("Meldung " + i);
       try {
         Thread.sleep(1000);
       } catch (InterruptedException e) {
         e.printStackTrace();
       }
     }
     infoLabel.setText("Fertig");
   }
}
 

Anhänge

Zuletzt bearbeitet von einem Moderator:
Der Code funktioniert schon so richtig 😉
Du drückst den Button, deine UI friert für 3 Sekunden ein und danach steht eben "Fertig" am Label.

Das Problem ist das du den Aufruf von "Thread.sleep" im selben Thread ausführst wie das "setText". Dadurch legst du den Thread schlafen und er kann natürlich die UI nicht neu zeichnen.
Dann wacht er wieder auf und führt wieder "setText" aus und danach "sleep". Das ganze sooft bis die Schleife zu Ende ist und dann eben "Fertig" in das Label schreibt.
Danach ist er fertig und kann neu zeichnen.

Hier eine genauere Erklärung warum die UI einfriert: http://stackoverflow.com/questions/15616660/gui-freeze-after-click-on-button
Mit Hilfe des SwingWorker könnte man so etwas realisieren: http://stackoverflow.com/questions/782265/how-do-i-use-swingworker-in-java
 
Das Problem ist das du den Aufruf von "Thread.sleep" im selben Thread ausführst wie das "setText". Dadurch legst du den Thread schlafen und er kann natürlich die UI nicht neu zeichnen.

Hallo Joose,

danke für deine Antwort.
Mir ist schon klar, dass mein Code korrekt funktioniert. 🙂

Das sleep(1000) dient im Beispiel ja auch nur zur Illustration des Effektes. (FALLS es denn zu einer Aktualisierung käme, würde man ohne diese Verzögerung ja am Bildschirm nichts sehen, weil es zu schnell ginge.)
In meiner echten Anwendung habe ich sehr zeitaufwendige Operationen, und da brauche ich kein sleep() - im Gegenteil. Allerdings möchte ich eine Rückmeldung über den Fortschritt dieser Operationen.
Und da besteht eben das Problem, welches ich simplifiziert gepostet hatte.

Ich schau mir jetzt mal deine Links an und hoffe, dass ich dort fündig werde.
 
vor deinem try-Teil nach dem du setText() aufgerufen hast noch ein infoLabel.upadeUI(); dann zeichnet er neu bevor er in den sleep geht
 
Das sleep(1000) dient im Beispiel ja auch nur zur Illustration des Effektes. (FALLS es denn zu einer Aktualisierung käme, würde man ohne diese Verzögerung ja am Bildschirm nichts sehen, weil es zu schnell ginge.)

Das ist mir schon klar das du damit nur die Verzögerung simulieren wolltest.
Aber durch den ActionListener wird der Code im EDT ausgeführt, wenn du diesen per sleep schlafen legst, kann er die UI nicht neuzeichnen.

In meiner echten Anwendung habe ich sehr zeitaufwendige Operationen, und da brauche ich kein sleep() - im Gegenteil. Allerdings möchte ich eine Rückmeldung über den Fortschritt dieser Operationen.

Zeitaufwändige Operationen sollten immer in eigene Threads ausgelagert werden (vor allem wenn mit einer UI dazu gearbeitet wird), dadurch ist eben so eine Darstellung des Fortschritts möglich.

Die Stichwörter zu diesem Problem sind: EventDispatcherThread, SwingWorker, SwingUtilities, invokeLater, ......

EDIT: Es gibt wie strußi auch zeigt Möglichkeiten ein Update der UI zu erzwingen was aber in diesem Fall unnötig wäre. Deine UI wird sowieso neu gezeichnet (zu sehen daran das nach 3 Sekunden das Label auf "Fertig" geändert wird). Man darf halt nur nicht den "Zeichen"thread schlafen legen 😉
Daher finde ich die Variante von strußi auch nicht geeigent in diesem Fall.
 
Die Stichwörter zu diesem Problem sind: EventDispatcherThread, SwingWorker, SwingUtilities, invokeLater, ......

Habe ich alles schon mehrfach gelesen, aber bislang nicht gebraucht und mich deshalb auch noch nicht näher damit beschäftigt. Jetzt scheint es aber notwendig zu sein.
Ich melde mich wieder - entweder mit der konkreten Lösung meines Beispielproblems, oder mit weiteren Fragen.

Zunächst herzlichen Dank; du hast mir geholfen, der Lösung auf die Spur zu kommen.
 
vor deinem try-Teil

Du meinst so:
Java:
  public void doSomething() {
     for (int i = 1; i<= 3; i++) {
       // infoLabel wird nicht aktualisiert!
       infoLabel.setText("Meldung " + i);
       infoLabel.updateUI(); // <== nutzt nichts!
       contentPane.updateUI(); // <== auch kein Effekt!
       try {
         Thread.sleep(1000);
       } catch (InterruptedException e) {
         e.printStackTrace();
       }
     }
     infoLabel.setText("Fertig");
   }

Aber leider hilft das alles nicht weiter. Kein Effekt.
 
Zuletzt bearbeitet von einem Moderator:
Füge ein setText mal in den ActionListener ein, bevor du die externe Klasse aufrufst.
Merkst du was? Auch dieser Text wird nicht angezeigt. Die UI wird erst dann neu gezeichnet, wenn der ActionListener beendet ist.
Starte also den Aufruf des Helpers in einem eigenen Thread.
Was du hierfür verwendest, bleibt dir überlassen. EventQue funktioniert genauso gut, wie das, was ich dir hier zeige.
Java:
        btnNewButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                new Thread() {
                    public void run() {
                        LabelHelper helper = new LabelHelper(infoLabel);
                        helper.doSomething();
                    }
                }.start();
            }
        });
 

Neue Themen


Zurück
Oben