Schleife verlangsamen

gabelo

Mitglied
Guten Tag,
ich hab ein kleines Programm erstellt, welches beim Betätigen eines Knopfes einen Zählerstand um 1 inkrementiert. Für die GUI habe ich Java Swing verwendet. Nun wollte ich, dass beim Betätigen des Knopfes der Zählerstand - langsam - hochzählt. Dafür hab ich eine For-Schleife implementiert und die Sleep Methode aufgerufen.

Das Problem ist, dass das Label sich nicht aktualisiert. Er zählt bis zur gewünschten Zahl und wartet pro Schleifendurchgang die angegebene Zeit. Nur das Label wird nicht aktualisiert. Was mach ich falsch?

Freundliche Grüße
P.S.: Jegliche weitere Kritik, die beim Überfliegen des Codes euch in den Kopf schießt, könnt ihr mir gerne mitteilen.

Der Code;
[CODE lang="java" title="Zaehler" highlight=""69-81""]package actionListenerJT;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

// Es wird eine Klasse Zaehler erstellt, die eine Sub-Klasse von JFrame
// ist und das ActionListener Interface implementiert
public class Zaehler extends JFrame implements ActionListener {

// Attribute
private JButton knopfZumBeenden;
private JButton zaehlerKnopf;
private JLabel label;
private JPanel panel;
private int gezaehlteZahl = 0;

// Konstruktur
public Zaehler (){
// Das erstellte Objekt ist mit der Super-Klasse JFrame automatisch
//ein Frame und soll einen Namen bekommen
// Die Groesse des Fensters soll festgelegt werden
this.setTitle("ActionListener Beispiel");
this.setSize(400, 100);

// Panel wird erzeugt für die Knoepfe
panel = new JPanel();

// JLabel-Objekt wird erzeugt und beschriftet
label = new JLabel();
label.setText("Zaehlerstand: " + String.valueOf(gezaehlteZahl));

//Knoepfe werden erzeugt und beschriftet
knopfZumBeenden = new JButton ("Beenden");
zaehlerKnopf = new JButton("Drueck mich fuer +1");

//Knoepfe werden dem ActionListener zugeordnet
zaehlerKnopf.addActionListener(this);
//benoetigt als Eingabeparameter ein Objekt mit dem Interface ActionListener
knopfZumBeenden.addActionListener(this);
//benoetigt als Eingabeparameter ein Objekt mit dem Interface ActionListener

//Knoepfe werden dem JPanel hinzugefügt
panel.add(knopfZumBeenden);
panel.add(zaehlerKnopf);

// Label wird dem Panel hinzugefügt
panel.add(label);

// Panel wird dem Objekt (das als Sub-Klasse vom JFrame
//automatisch ein Frame ist) hinzugefügt
this.add(panel);
this.setVisible(true);
}

// Methode

// Mit dem ActionListener Interface muss die Methode actionPerformed
//implementiert werden!

// Wird einer der Knoepfe gedrueckt, wird ein ActionEvent-Objekt erstellt:
// knopfBetaetigung
// Die Referenz auf das Objekt wird als Parameter uebergeben
public void actionPerformed (ActionEvent knopfBetaetigung){


// Ist das erstellte Objekt knopfBetaetigung der zaehlerKnopf,
//soll die gezaehlte Zahl um 1 erhoeht werden
//Dann soll der Label-Text die gezaehlte Zahl ausgeben
if (knopfBetaetigung.getSource() == this.zaehlerKnopf) {

for (int i = 1; i <= 3; i++ ){
gezaehlteZahl = i;
label.setText("Zaehlerstand: " + String.valueOf(gezaehlteZahl));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}
// Ist das erstellte Objekt knopfBetaetigung der knopfZumBeenden,
// soll das Fenster geschlossen werden
else if (knopfBetaetigung.getSource() == this.knopfZumBeenden) {
System.exit(0);
}
}
}[/CODE]
 
Das Problem ist, dass das Label sich nicht aktualisiert. Er zählt bis zur gewünschten Zahl und wartet pro Schleifendurchgang die angegebene Zeit. Nur das Label wird nicht aktualisiert. Was mach ich falsch?
Deine actionPerformed-Methode wird von Swing aufgerufen, die Methode läuft somit im UI-Thread, der auch für das Neuzeichnen/Aktualisieren von Fensterinhalten zuständig ist. Erst, wenn die actionPerformed-Methode verlassen wird, kann auch das Fenster neu gezeichnet werden.

Wenn Du einen automatischen Zähler haben willst, musst Du einen separaten Thread verwenden. Am ehesten dürftest Du mit einem javax.swing.Timer zurechtkommen.
 
Jegliche weitere Kritik, die beim Überfliegen des Codes euch in den Kopf schießt, könnt ihr mir gerne mitteilen
Ich würde:
1) nicht von JFrame erben. Besser einen JPanel erweitern und in JFrame einsetzen.
2) Den Counter in eine eigene Klasse auslagern z.B ein JLabel erweitern.
3) Jedem Button seinen eigenen Listener geben.
Im folgenden Beispiel lassen sich ganz einfach durch die Auslagerung des Counters, zwei verschiedene Countdowns starten.
Der JFrame nimmt den CounterPanel , der das eigentliche Programm realisiert, auf.
[CODE lang="java" title="StartCounter" highlight="7"]import javax.swing.JFrame;

public class StartCounter {
public static void main(String[] args) {
JFrame frame = new JFrame("ActionListener Beispiel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new CounterPanel(500, 60));
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
}
}[/CODE]
[CODE lang="java" title="CounterPanel" highlight="20-34"]import java.awt.Dimension;
import javax.swing.JButton;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class CounterPanel extends JPanel {
private CountDownLabel countDownLabelA = new CountDownLabel("Zähler A");
private CountDownLabel countDownLabelB = new CountDownLabel("Zähler B");

public CounterPanel(int width, int height) {
setPreferredSize(new Dimension(width, height));
JButton exit = new JButton("Ende");
JButton startCountDownA = new JButton("Start A");
JButton startCountDownB = new JButton("Start B");
add(exit);
add(startCountDownA);
add(countDownLabelA);
add(startCountDownB);
add(countDownLabelB);
exit.addActionListener(e -> {
System.exit(0);
});
startCountDownA.addActionListener(e -> {
startCownDown(countDownLabelA, 10);
});
startCountDownB.addActionListener(e -> {
startCownDown(countDownLabelB, 5);
});
countDownLabelA.setCountDownListener(e -> {
printCountDownOverMsg(e.getLabel());
});
countDownLabelB.setCountDownListener(e -> {
printCountDownOverMsg(e.getLabel());
});

}

private static void printCountDownOverMsg(String msg) {
System.out.println("Countdown is over " + msg);
}

private static void startCownDown(CountDownLabel countDown, int duration) {
if (countDown.isRunning())
return;
countDown.start(duration);
}
}[/CODE]
[CODE lang="java" title="CountDownLabel" highlight="33-47"]import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.border.BevelBorder;

@SuppressWarnings("serial")
public class CountDownLabel extends JLabel implements Runnable {
private int value;
private boolean running = false;
private Thread animator = null;
private String label;
private CountDownListener listener;

public CountDownLabel(String label) {
super(" "+ label+ " ");
this.label = label;
setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
}

public String getLabel() {
return label;
}

public int getValue() {
return value;
}

public boolean isRunning() {
return running;
}

@Override
public void run() {
while (running) {
setText(" " + label + " " + value + " ");
if (value <= 0) {
running = false;
if (listener != null)
listener.countDownOver(this);
} else
value--;
repaint();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public void setCountDownListener(CountDownListener listener) {
this.listener = listener;
}

public boolean start(int value) {
if (isRunning())
return false;
this.value = value;
running = true;
animator = new Thread(this);
animator.start();
return true;
}

public void stop() {
running = false;
animator = null;
}

}[/CODE]
[CODE lang="java" title="CountDownListener"]public interface CountDownListener {
public void countDownOver(CountDownLabel e);
}[/CODE]
 
1) unterschreib ich sofort
2) Noch eine Idee: Reines Interface, das die Komponente per getDisplayComponent() liefert, damit man sich nicht auf ein JLabel einschießt.
3) Reine Geschmackssache. Mit einem Switch über das ActionCommand hat man vor Lambda gearbeitet, als noch viel mehr Code anfiel.
Wenn nur ein Befehl ausgeführt wird, liest sich eine Schreibweise ohne geschweifte Klammern meiner Meinung nach übrigens deutlich angenehmer.
Java:
        startCountDownA.addActionListener(e -> startCownDown(countDownLabelA, 10));
        startCountDownB.addActionListener(e -> startCownDown(countDownLabelB, 5));
        countDownLabelA.setCountDownListener(e -> printCountDownOverMsg(e.getLabel()));
        countDownLabelB.setCountDownListener(e -> printCountDownOverMsg(e.getLabel()));
Falls printCountDownOverMsg den Event akzeptiert und sich dort das Label selbst rausholt, kann man's auch als Listener direkt ansprechen.
Java:
        countDownLabelA.setCountDownListener(this::printCountDownOverMsg);
        countDownLabelB.setCountDownListener(this::printCountDownOverMsg);
 

Zurück
Oben