1

I am trying to use JavaFX to code a stopwatch. But I am pretty new to java and still have problems to really understand OOP. I need to have to classes calling each others methods. I know that static methods and variables should be avoided. The gui has buttons start and stop which should start and stop the timer. I have a "TimerClass" set up. There is a method inside which runs code every second when it has been activated once. So when I hit the start button I want that to happen. This seems to work already. But every second the variable "secondsPassed" is updated the label on my gui needs to be updated as well.

I could make the lblTime Button and the corresponding method static. Also I heard about about setting the constructors in a way that you can make every instance be equal to another existing instance. But I still do not understand it enough to be able to use it. (Java: classes with instance of each other) I also tried creating a "helper class". This was to avoid having to instance two classes from one another. But the helper class would have still been another instance of "TimerClass". I have some code to show what I have tried so far.

package application;

import application.TimerClass;

import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;

public class Controller {
    TimerClass timerClass = new TimerClass();

    @FXML
    Button btnStart;
    @FXML
    Button btnPause;
    @FXML
    Label lblTime;

    public void btnStartAction() {
        timerClass.timerStart();
    }

    public void btnPauseAction() {
        timerClass.timerPause();
    }

    public void setLblTime(int input) {
        lblTime.setText(""+input);
    }

}
package application;

import application.Controller;
import application.Helper;

import java.util.Timer;
import java.util.TimerTask;

public class TimerClass {
    static int secondsPassed = 0;

    Controller controller = new Controller();
    TimerClass timerClass = new TimerClass();
    Helper helper;

    public TimerClass(TimerClass input){
        this.helper = timerClass;
    }

    public TimerClass() {

    }

    Timer mytimer = new Timer();
    TimerTask timerTask = new TimerTask() {

        @Override
        public void run() {
            secondsPassed++;
            System.out.println("" + secondsPassed);
        }
    };

    public void timerStart() {
        mytimer.scheduleAtFixedRate(timerTask, 1000, 1000);
    }

    public void timerPause() {
        mytimer.cancel();
    }

    public int getSecondsPassed() {
        return secondsPassed;
    }

}

Thanks in advance!

J_D
  • 740
  • 8
  • 17
Leonhard Wolf
  • 93
  • 2
  • 10

2 Answers2

4

You don't need to instance the classes from one another. In fact this would be a bad idea, since it the instance of the Controller class is a different one to the one used with your scene.

Instead you should pass the existing class to your TimerClass. (Note that you may want to "hide" the Controller behind a interface to make the TimerClass more reuseable.)

public class TimerClass {
    // don't make this static
    // otherwise you won't be able to use multiple instances at the same time
    volatile int secondsPassed = 0; 

    private final Controller controller;


    public TimerClass(Controller controller) {
        this.controller = controller;
    }

    TimerTask timerTask = new TimerTask() {

        @Override
        public void run() {
            secondsPassed++;
            // update on the JavaFX application thread
            Platform.runLater(() -> controller.setLblTime(secondsPassed));
        }
    };

    public void timerStart() {
        mytimer.scheduleAtFixedRate(timerTask, 1000, 1000);
    }

    public void timerPause() {
        mytimer.cancel();
    }

    public int getSecondsPassed() {
        return secondsPassed;
    }

}
public class Controller {
    TimerClass timerClass = new TimerClass(this);
    ...
}

You may want to take a look at the first suggestion in the following answer though: https://stackoverflow.com/a/9966213/2991525

fabian
  • 80,457
  • 12
  • 86
  • 114
  • If you're looking for the most re-usability you probably want to not pass in the Controller itself, but something like an `OnTimerTick` interface, which can then be called to do anything. For example `Controller1 implements TimerClass.OnTimerTick` which sets a label, while `Controller2 implements TimerClass.OnTimerTick` could rotate a line around a point (clock face). – kendavidson Jun 10 '19 at 12:45
  • Thanks for your super fast answer! I tried your code and it works. So that is pretty cool. But I am having problems wrapping my head around what you have done. I have not worked with threads yet so I would leave that one for later. Can you explain just logically what you did with the constructor in "TimerClass" and the instancing in "Controller"? – Leonhard Wolf Jun 11 '19 at 12:59
  • I think I got it. **TimerClass:** - first you create a reference value which is just null - inside of the constructor the left part of the equation refers to controller inside of TimerClass created earlier (which is null) - the right side takes the input "controller" of the type Controller **Controller:** - you create a new instance of the TimerClass - the variable "this" inside the parentheses is passed on to the constructor of this new object of TimerClass - inside this new "timerClass" the instance "controller" now refers to Controller itself. Is that correct? – Leonhard Wolf Jun 11 '19 at 17:09
  • The thing you refer to as "creating a reference" is actually the declaration of a field. The default value doesn't really matter, since the `final` keyword makes sure a value is assigned in the constructor an the value isn't modified later. The constructor simply gets a reference to the controller as parameter and stores this value in the field to make it accessible to the methods of `TimerClass`. (`this.controller` refers to the field, `controller` to the parameter of the constructor). – fabian Jun 11 '19 at 17:32
0

Hey Leonhard great job so far, seems you have a decent understanding of things so far. Let me know if I understand the problem correctly, so you have a button that you press called 'start'. That button makes the seconds start ticking by, and in addition you have a label that displays the seconds that are going by, but the label doesn't update to show the current seconds?

The first thing that comes to mind is setting a boolean value for the start button.

if (** when the user clicks the button **) {
      buttonBoolean = !buttonBoolean; //this just turns false to true; and true to false.
}

Then in the method that adds the seconds, make an 'if' statement to check if the start button is on.

if (buttonBoolean == true) {
    seconds++;
    setLabelValue = seconds;
}

If it is true, then update the seconds and set the value of the label to the updated value.

Let me know if this helps!

jose reyes
  • 1,533
  • 1
  • 13
  • 17
  • 2
    `== true` is redundant, `if (buttonBoolean)` does the same job with less typing – JonK Jun 10 '19 at 12:11
  • Thanks for answering so quickly. First of all: yes, I think we are on the same page about what I am trying to achieve. But my problem is that within the "public void run()" method inside of "TimerClass" I have to update the label that is inside of the class "Controller". And I could not find another way to do that than to create instances of these classes of each other. This did not work. But fabian's answer worked! – Leonhard Wolf Jun 11 '19 at 13:08