1. Diese Seite verwendet Cookies. Wenn du dich weiterhin auf dieser Seite aufhältst, akzeptierst du unseren Einsatz von Cookies. Weitere Informationen

Timer nach und nach ablaufen lassen

Dieses Thema im Forum "Java Grundlagen" wurde erstellt von Buba235, 10. Januar 2017.

  1. Buba235

    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:

    Code (Java):
    1. import java.awt.event.*;
    2. import java.awt.*;
    3. import javax.swing.*;
    4.  
    5.  
    6. public class mainTimer extends JFrame {
    7.     /**
    8.      *
    9.      */
    10.     private static final long serialVersionUID = 1L;
    11.  
    12.     // Variable declaration
    13.     final int SIZE = 8;
    14.     JLabel timerLabel;
    15.     JLabel[] lblFields = new JLabel[SIZE];
    16.     String strLabelText[] = new String[SIZE];
    17.     int counter;
    18.     JTextField[] tfFields = new JTextField[SIZE];
    19.     JButton button;
    20.     Timer timer;
    21.  
    22.     public mainTimer () {
    23.         setLayout(new GridLayout(9, 9, 5, 5));
    24.  
    25.         // Set later label text
    26.         strLabelText[0] = " Full Plank: ";
    27.         strLabelText[1] = " Elbow Plank: ";
    28.         strLabelText[2] = " Raised Leg Plank (left): ";
    29.         strLabelText[3] = " Raised Leg Plank (right): ";
    30.         strLabelText[4] = " Side Plank (left): ";
    31.         strLabelText[5] = " Side Plank (right): ";
    32.         strLabelText[6] = " Full Plank: ";
    33.         strLabelText[7] = " Elbow Plank: ";
    34.  
    35.         // Create different JTextFields with default values
    36.         for (int i = 0; i < tfFields.length; i++) {
    37.             // Create new JLabel and ad them to the grid
    38.             lblFields[i] = new JLabel(strLabelText[i], SwingConstants.LEFT);
    39.             add(lblFields[i]);
    40.  
    41.             // Add default value to JTextField
    42.             if (i == 0 || i == 7) {
    43.                 tfFields[i] = new JTextField("60", 5);
    44.             } else {
    45.                 tfFields[i] = new JTextField("30", 5);
    46.             }
    47.             add(tfFields[i]);
    48.         }
    49.  
    50.         // Button to start the timer
    51.         button = new JButton("Start timing");
    52.         add(button);
    53.  
    54.         timerLabel = new JLabel("Waiting...", SwingConstants.CENTER);
    55.         add(timerLabel);
    56.  
    57.         for (int j = 0; j < tfFields.length; j++) {
    58.             event e = new event(tfFields[j]);
    59.             button.addActionListener(e);
    60.         }
    61.     }
    62.  
    63.     public class event implements ActionListener {
    64.         JTextField tf;
    65.  
    66.         // Constructor
    67.         public event (JTextField eventTF) {
    68.             this.tf = eventTF;
    69.         }
    70.        
    71.         public void actionPerformed(ActionEvent e) {
    72.             int count = (int) (Double.parseDouble(tf.getText()));
    73.             timerLabel.setText("Time left: " + count);
    74.  
    75.             TimeClass tc = new TimeClass(count);
    76.             timer = new Timer(1000, tc);
    77.             timer.start();
    78.         }
    79.     }
    80.  
    81.     public class TimeClass implements ActionListener {
    82.         int counter;
    83.        
    84.         public TimeClass (int counter) {
    85.             this.counter = counter;
    86.         }
    87.        
    88.         public void actionPerformed (ActionEvent tc) {
    89.             counter--;
    90.            
    91.             if (counter >= 1) {
    92.                 timerLabel.setText("Time left: " + counter);
    93.             } else {
    94.                 timer.stop();
    95.                 timerLabel.setText("Done!");
    96.                 Toolkit.getDefaultToolkit().beep();
    97.             }
    98.         }
    99.     }
    100.  
    101.     // Main start method
    102.     public static void main (String args[]) {
    103.         mainTimer gui = new mainTimer();
    104.         gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    105.         gui.setSize(310, 250);
    106.         gui.setTitle("Timer Program");
    107.         gui.setVisible(true);
    108.     }
    109. }
    Danke schon einmal für euere Hilfe!
     
  2. Bratkartoffel

    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
     
  3. Buba235

    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 ;))
     
  4. Bratkartoffel

    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
     
  5. Buba235

    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.
     
  6. HonniCilest

    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.
     
  7. Buba235

    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!
     
  8. Bratkartoffel

    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
     
  9. Buba235

    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?

    Code (Java):
    1. public class TimeClass implements ActionListener {
    2.         int counter;
    3.  
    4.         public TimeClass (int counter) {
    5.             this.counter = counter;
    6.         }
    7.  
    8.         public void actionPerformed (ActionEvent tc) {
    9.             counter--;
    10.  
    11. --> Soll hier ein int array abgearbeitet werden?
    12.  
    13.             if (counter >= 1) {
    14.                 timerLabel.setText("Time left: " + counter);
    15.             } else {
    16.                 timer.stop();
    17.                 timerLabel.setText("Done!");
    18.                 Toolkit.getDefaultToolkit().beep();
    19.             }
    20.         }
     
  10. HonniCilest

    HonniCilest Erfahrenes Mitglied

    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.

    Code (Java):
    1. package tutorials.de;
    2.  
    3. import java.awt.GridLayout;
    4.  
    5. import javax.swing.JButton;
    6. import javax.swing.JFrame;
    7. import javax.swing.JLabel;
    8. import javax.swing.JSpinner;
    9. import javax.swing.SpinnerNumberModel;
    10. import javax.swing.SwingConstants;
    11. import javax.swing.Timer;
    12.  
    13.  
    14. public class MainTimer extends JFrame {
    15.  
    16.     private static final long serialVersionUID = 1L;
    17.  
    18.     private static final int FIRE_TIMER = 1000;
    19.  
    20.     private static final int FIELD_MIN = 0;
    21.     private static final int FIELD_MAX = 100;
    22.  
    23.     private JButton startTimer;
    24.     private Timer timer;
    25.     private JLabel timerLabel;
    26.  
    27.     private JLabel[] fieldNames;
    28.     private JSpinner[] numericFields;
    29.  
    30.     private int counter = 0;
    31.  
    32.  
    33.     public MainTimer () {
    34.         // Settings
    35.         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    36.         setSize(310, 250);
    37.         setTitle("Timer Program");
    38.  
    39.         setLayout(new GridLayout(9, 9, 5, 5));
    40.  
    41.         fieldNames = new JLabel[]{
    42.                 new JLabel(" Full Plank: ", SwingConstants.LEFT),
    43.                 new JLabel(" Elbow Plank: ", SwingConstants.LEFT),
    44.                 new JLabel(" Raised Leg Plank (left): ", SwingConstants.LEFT),
    45.                 new JLabel(" Raised Leg Plank (right): ", SwingConstants.LEFT),
    46.                 new JLabel(" Side Plank (left): ", SwingConstants.LEFT),
    47.                 new JLabel(" Side Plank (right): ", SwingConstants.LEFT),
    48.                 new JLabel(" Full Plank: ", SwingConstants.LEFT),
    49.                 new JLabel(" Elbow Plank: ", SwingConstants.LEFT)
    50.         };
    51.  
    52.         numericFields = new JSpinner[] {
    53.                 new JSpinner(new SpinnerNumberModel(60, FIELD_MIN, FIELD_MAX, 1)),
    54.                 new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
    55.                 new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
    56.                 new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
    57.                 new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
    58.                 new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
    59.                 new JSpinner(new SpinnerNumberModel(30, FIELD_MIN, FIELD_MAX, 1)),
    60.                 new JSpinner(new SpinnerNumberModel(60, FIELD_MIN, FIELD_MAX, 1))
    61.         };
    62.  
    63.         for (int i = 0; i < fieldNames.length; i++) {
    64.             add(fieldNames[i]);
    65.             add(numericFields[i]);
    66.         }
    67.  
    68.         // Timer configuration
    69.         timer = new Timer(FIRE_TIMER, ae -> {
    70.             // TODO: Timerfunktion anpassen    
    71.             timerLabel.setText("" + counter++);
    72.         });
    73.  
    74.         // Button to start the timer
    75.         startTimer = new JButton("Start timing");
    76.         startTimer.addActionListener(ae -> {
    77.             // TODO: Timer darf nicht mehrmals gestartet werden
    78.             // ggf. willst du ihn bei erneuten Klick auch stoppen?
    79.             // Tipp: timer.isRunning()
    80.      
    81.             timer.start();
    82.         });
    83.         add(startTimer);
    84.  
    85.         timerLabel = new JLabel("Waiting...", SwingConstants.CENTER);
    86.         add(timerLabel);
    87.     }
    88.  
    89.     // Main start method
    90.     public static void main (String args[]) {
    91.         MainTimer gui = new MainTimer();
    92.         gui.setVisible(true);
    93.     }
    94. }
    PS.: Ich wette Bratkartoffel kann es mit dieser Struktur nun auch "mit dem Kopf umfassen" ;)
     
    Zuletzt bearbeitet: 11. Januar 2017 um 21:43 Uhr
  11. HonniCilest

    HonniCilest Erfahrenes Mitglied

    Ja je nachdem. Ich meinem Beispiel habe ich allerdings auf JSpinner zurückgegriffen, welche die Funktion getPreviousValue bietet. Ich würde sie hier lesen uns setzen solange wie möglich, also den countdown direkt in den Feldern anzeigen anstatt im Label, so sieht man auch visuel direkt wo sich der Timer befindet. Falls die vorher eingetragenen Werte anschließend relevant sind, so kann man ja optional die Werte vorher noch in ein Array abspeichern und nach Timer Ende wieder setzen.
     
  12. Buba235

    Buba235 Erfahrenes Mitglied

    Hallo HonniCilest!

    Wow! Danke für die Hilfe. Das ist im Prinzip genau die Art von Hilfe, die ich wollte. Keine fertige Lösung, sondern Denkansätze. Klasse von dir.
    Ich habe diesen Code nun so angepasst, dass der timer sowohl das Label, als auch das jeweilige JSpinner Feld runter zählt. Also nur den Timer angepasst. Ich halte es nicht für sonderlich elegant, aber es klappt. Vielleicht kannst du ja mal deine Meinung dazu sagen:

    Code (Java):
    1.         // Timer configuration
    2.         timer = new Timer(FIRE_TIMER, ae -> {
    3.             counter = (int) numericFields[countArray].getValue();
    4.             numericFields[countArray].setValue(numericFields[countArray].getPreviousValue());
    5.             timerLabel.setText("" + counter--);
    6.  
    7.             if (counter >= 1) {
    8.                 timerLabel.setText("Time left: " + counter);
    9.             } else {
    10.                 Toolkit.getDefaultToolkit().beep();
    11.                 timerLabel.setText("Done!");
    12.  
    13.                 if (countArray != (numericFields.length -1)) {
    14.                     countArray++;
    15.                 } else {
    16.                     timerLabel.setText("Countdown finished!");
    17.                     timer.stop();
    18.                 }
    19.             }
    20.         });
    Die Sache mit dem mehrfach starten des Timers und gegebenenfalls Pausieren habe ich noch nicht gemacht.
     
  13. HonniCilest

    HonniCilest Erfahrenes Mitglied

    Grundsätzlich geht das schon in die richtige Richtung. Was mir spontan auffällt, du hast nun getPreviousValue und dann counter--. Setz das getPreviousValue for die counter-Zuweisung dann brauchst du das -- auch nicht :)

    Ein Hinweis ist mir noch aufgefallen. Neben erneutem Buttonklick solltest du dir auch überlegen was bei anderen Benutzereingaben passieren soll während der Timer läuft, z.B. beim Spinner hoch oder runterklicken.
     
  14. Buba235

    Buba235 Erfahrenes Mitglied

    Hallo!

    Danke noch mal an alle für euere Hilfe. Es hat mir nicht nur Spaß gemacht, sondern ja auch etwas gebracht. Ich werde alle Tipps und Hinweise beachten und weiter draus lernen.

    Gruß
    Buba
     
Die Seite wird geladen...