Timer nach und nach ablaufen lassen

Buba235

Erfahrenes Mitglied
Hallo Leute!

Ich versuche mich gerade in Java ein zu arbeiten und hab mir ein kleines GUI Programm geschrieben, in dem ich mehrere Male einen Countdown laufen lassen möchte. Ich habe aber ein Problem: Ich weiß nicht wie ich einen Timer nach dem anderen laufen lassen soll. Mein Code ist so aufgebaut, dass es 8 JTextFields mit vorgegebenen Werten (meistens 30 Sekunden) gibt. Dazu einen Button, der den Countdown startet. Wenn ich mein Programm starte, dann wird nur der erste Countdown ausgeführt, aber kein weiterer. Vielleicht könnt ihr mir da weiter helfen. Ich blicke das ganze nicht so recht. Hier mal mein Code:

Java:
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;


public class mainTimer extends JFrame {
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    // Variable declaration
    final int SIZE = 8;
    JLabel timerLabel;
    JLabel[] lblFields = new JLabel[SIZE];
    String strLabelText[] = new String[SIZE];
    int counter;
    JTextField[] tfFields = new JTextField[SIZE];
    JButton button;
    Timer timer;

    public mainTimer () {
        setLayout(new GridLayout(9, 9, 5, 5));

        // Set later label text
        strLabelText[0] = " Full Plank: ";
        strLabelText[1] = " Elbow Plank: ";
        strLabelText[2] = " Raised Leg Plank (left): ";
        strLabelText[3] = " Raised Leg Plank (right): ";
        strLabelText[4] = " Side Plank (left): ";
        strLabelText[5] = " Side Plank (right): ";
        strLabelText[6] = " Full Plank: ";
        strLabelText[7] = " Elbow Plank: ";

        // Create different JTextFields with default values
        for (int i = 0; i < tfFields.length; i++) {
            // Create new JLabel and ad them to the grid
            lblFields[i] = new JLabel(strLabelText[i], SwingConstants.LEFT);
            add(lblFields[i]);

            // Add default value to JTextField
            if (i == 0 || i == 7) {
                tfFields[i] = new JTextField("60", 5);
            } else {
                tfFields[i] = new JTextField("30", 5);
            }
            add(tfFields[i]);
        }

        // Button to start the timer
        button = new JButton("Start timing");
        add(button);

        timerLabel = new JLabel("Waiting...", SwingConstants.CENTER);
        add(timerLabel);

        for (int j = 0; j < tfFields.length; j++) {
            event e = new event(tfFields[j]);
            button.addActionListener(e);
        }
    }

    public class event implements ActionListener {
        JTextField tf;

        // Constructor
        public event (JTextField eventTF) {
            this.tf = eventTF;
        }
       
        public void actionPerformed(ActionEvent e) {
            int count = (int) (Double.parseDouble(tf.getText()));
            timerLabel.setText("Time left: " + count);

            TimeClass tc = new TimeClass(count);
            timer = new Timer(1000, tc);
            timer.start();
        }
    }

    public class TimeClass implements ActionListener {
        int counter;
       
        public TimeClass (int counter) {
            this.counter = counter;
        }
       
        public void actionPerformed (ActionEvent tc) {
            counter--;
           
            if (counter >= 1) {
                timerLabel.setText("Time left: " + counter);
            } else {
                timer.stop();
                timerLabel.setText("Done!");
                Toolkit.getDefaultToolkit().beep();
            }
        }
    }

    // Main start method
    public static void main (String args[]) {
        mainTimer gui = new mainTimer();
        gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        gui.setSize(310, 250);
        gui.setTitle("Timer Program");
        gui.setVisible(true);
    }
}

Danke schon einmal für euere Hilfe!
 

Bratkartoffel

gebratene Kartoffel
Premium-User
Hi Buba235,

habe meinen Beitrag etwas zu schnell abgeschickt und wollte den nochmal in Ruhe neu erstellen. Hätte nicht gemeint dass du so schnell bist :)
Zum Nachvollzug: Meine Vermutung war, dass durch die Schleife um Zeile 59 immer die ActionListener überschrieben und nicht hinzugefügt wurden. Das wollte ich aber zuerst noch verifizieren, habe schon länger nicht mehr mit Swing gearbeitet.

Erstelle nur einen ActionListener und iteriere da drin über deine tfFields. Wie genau muss ich mir erst genauer anschauen, ich verstehe gerade deine Architektur nicht auf Anhieb.

Grüsse,
BK
 

Buba235

Erfahrenes Mitglied
Hallo Bratkartoffel!

Ich hab meinen Beitrag auch wieder raus genommen. Da war ich dann auch zu schnell.
Wenn du der Meinung sein solltest, dass auch meine Architektur "Mist" ist, dann kann ich die auch ändern (sofern mir etwas besseres einfällt ;-))
 

Bratkartoffel

gebratene Kartoffel
Premium-User
Hi Buba,

ich behaupte nicht dass deine Architektur Mist ist, ich habe nur gerade kein Eclipse zur Hand und kann das so aus dem Kopf raus gerade nicht umfassen. Ich melde mich dann vermutlich gegen Abend nochmal, solang kein anderer User dir bereits bei der Lösung geholfen hat.

Grüsse,
BK
 

Buba235

Erfahrenes Mitglied
Hallo!

Nicht falsch verstehen. Mit Mist meinte ich, dass es sicher einen besseren Weg als meinen gibt. Also Kritik ist hier durchaus erwünscht. Solltest du einen besseren Weg kennen würde ich mir das alles anhören - ich will ja lernen.

Danke schon mal.
 

HonniCilest

Erfahrenes Mitglied
Ich glaube einfach nur du machst es dir zu umständlich :D

Ich hoffe ich habe es richtig verstanden, dass pro Sekunde einfach jedes Feld um 1 decrementiert werden soll (solange größer 0) per Timer, der auf Button Klick gestartet wird.

Wenn du aber nur einen Button hast, dann starte doch auch nur einen Timer und nicht pro Feld einen Timer, bei dem du auch noch diesen einen Timer immer wieder überschreibst.

Der Timer muss dann vermutlich gestoppt werden, sobald alle Felder 0 sind.

Also, im ActionEvent vom Timer über deine Textfelder iterieren und decrementieren. Wobei es hier sicher auch bessere Lösungen gibt als Textfelder, denn dann musst du die Werte immer wieder parsen.
 

Buba235

Erfahrenes Mitglied
Hallo HonniCilest!

So ganz ist es nicht, wie du das denkst. Wenn ich den button drücke dann soll der timer das erste Feld auf 0 laufen lassen. Wenn das erledigt ist, soll das 2te Feld auf 0 laufen (und zwar auch von Anfang an). Danach das 3te usw.
Das es bessere Lösungen als Textfelder gibt, ist mir klar. Um ehrlich zu sein, weiß ich nicht mal warum ich Textfelder genommen habe. Das ist dann aber eine "Schönheitskorrektur", die sich, nach Beheben des eigentlichen Problems, auch recht schnell ändern lässt.

Hast du denn eine Lösung für mein Problem? Kritik jeder Art ist willkommen!
 

Bratkartoffel

gebratene Kartoffel
Premium-User
Hi,

ich würde (wie von HonniCilest vorgeschlagen) mit einem Array von Int-Werten arbeiten. In deinem Thread dann alle Array-Elemente durchgehen und den ersten Wert > 0 um 1 runterzählen. Anschliessend der Update aller Textfelder mit den aktuellen Werten und eine Sekunde warten. Solange, bis alle int-Werte auf 0 stehen.

Soweit mal die Theorie, bin leider bis jetzt noch nicht dazu gekommen mir das genauer anzuschauen.

Grüsse,
BK
 

Buba235

Erfahrenes Mitglied
Hallo Bratkartoffel!

Leider weiß ich nicht genau was du meinst. Ich steh ziemlich auf dem Schlauch. Wo genau soll ich denn das int array abarbeiten?

Java:
public class TimeClass implements ActionListener {
        int counter;

        public TimeClass (int counter) {
            this.counter = counter;
        }

        public void actionPerformed (ActionEvent tc) {
            counter--;

--> Soll hier ein int array abgearbeitet werden?

            if (counter >= 1) {
                timerLabel.setText("Time left: " + counter);
            } else {
                timer.stop();
                timerLabel.setText("Done!");
                Toolkit.getDefaultToolkit().beep();
            }
        }
 

HonniCilest

Erfahrenes Mitglied
Hallo HonniCilest!

So ganz ist es nicht, wie du das denkst. Wenn ich den button drücke dann soll der timer das erste Feld auf 0 laufen lassen. Wenn das erledigt ist, soll das 2te Feld auf 0 laufen (und zwar auch von Anfang an). Danach das 3te usw.

Naja, das wäre aber nur eine minimale Anpassung er Logik aus meiner Sicht. Nichts desto trotz hast du auch nur einen Timer. Aktuell initialisierst du diesen einen Timer mehrmals.

Ich persönlich würde für ActionListener auch (fast) niemals eigene Klassen schreiben. Ist syntaktisch sicher nicht falsch, aber aus meiner Sicht unnötig viel Code. Java 8 bietet hier anonyme Interfaces (Lambda), was ich an der Stelle super praktisch finde. Du siehst den Code dann auch an direkt an der Stelle wo du ihn brauchst.

Ansonsten würde ich dich mit der Blume noch bitten an die Java Konventionen zu halten. Dazu gehört z.B. das Großschreiben von Klassennamen.

Ich würde die JFrame Settings auch nicht in der main-Methode vornehmen, sondern im Konstruktor.

Und du brauchst auch unbedingt noch ein Block für deinen Timer, denn dein Programm macht sicher noch verrückte Sachen wenn du mehrmals auf den Button klickst.


Die Struktur die ich meine habe ich dir kurz zusammengestellt mit der bitte dir das anzusehen und die Vorteile zu verstehen. Einen Teil davon musst du natürlich selbst implementieren.

Java:
package tutorials.de;

import java.awt.GridLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.Timer;


public class MainTimer extends JFrame {
 
    private static final long serialVersionUID = 1L;

    private static final int FIRE_TIMER = 1000;
 
    private static final int FIELD_MIN = 0;
    private static final int FIELD_MAX = 100;
 
    private JButton startTimer;
    private Timer timer;
    private JLabel timerLabel;
 
    private JLabel[] fieldNames;
    private JSpinner[] numericFields;
 
    private int counter = 0;
 
 
    public MainTimer () {
        // Settings
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(310, 250);
        setTitle("Timer Program");
  
        setLayout(new GridLayout(9, 9, 5, 5));

        fieldNames = new JLabel[]{
                new JLabel(" Full Plank: ", SwingConstants.LEFT),
                new JLabel(" Elbow Plank: ", SwingConstants.LEFT),
                new JLabel(" Raised Leg Plank (left): ", SwingConstants.LEFT),
                new JLabel(" Raised Leg Plank (right): ", SwingConstants.LEFT),
                new JLabel(" Side Plank (left): ", SwingConstants.LEFT),
                new JLabel(" Side Plank (right): ", SwingConstants.LEFT),
                new JLabel(" Full Plank: ", SwingConstants.LEFT),
                new JLabel(" Elbow Plank: ", SwingConstants.LEFT)
        };
  
        numericFields = new JSpinner[] {
                new JSpinner(new SpinnerNumberModel(60, FIELD_MIN, FIELD_MAX, 1)),
                new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
                new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
                new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
                new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
                new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
                new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
                new JSpinner(new SpinnerNumberModel(60, FIELD_MIN, FIELD_MAX, 1))
        };

        for (int i = 0; i < fieldNames.length; i++) {
            add(fieldNames[i]);
            add(numericFields[i]);
        }
  
        // Timer configuration
        timer = new Timer(FIRE_TIMER, ae -> {
            // TODO: Timerfunktion anpassen     
            timerLabel.setText("" + counter++);
        });

        // Button to start the timer
        startTimer = new JButton("Start timing");
        startTimer.addActionListener(ae -> {
            // TODO: Timer darf nicht mehrmals gestartet werden
            // ggf. willst du ihn bei erneuten Klick auch stoppen?
            // Tipp: timer.isRunning()
      
            timer.start();
        });
        add(startTimer);

        timerLabel = new JLabel("Waiting...", SwingConstants.CENTER);
        add(timerLabel);
    }
 
    // Main start method
    public static void main (String args[]) {
        MainTimer gui = new MainTimer();
        gui.setVisible(true);
    }
}

PS.: Ich wette Bratkartoffel kann es mit dieser Struktur nun auch "mit dem Kopf umfassen" ;)
 
Zuletzt bearbeitet: