0

I am trying to set up the buttons on the following program, but they will not control the program properly. I am not sure why they are not working. The reverse button works, but the start and stop buttons do not.

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class ch30 extends Application {
  @Override // Override the start method in the Application class
  public void start(Stage primaryStage) {       
    FanPane fan = new FanPane();

    HBox hBox = new HBox(5);
    Button btPause = new Button("Pause");
    Button btResume = new Button("Resume");
    Button btReverse = new Button("Reverse");
    hBox.setAlignment(Pos.CENTER);
    hBox.getChildren().addAll(btPause, btResume, btReverse);

    BorderPane pane = new BorderPane();
    pane.setCenter(fan);
    pane.setBottom(hBox);

    // Create a scene and place it in the stage
    Scene scene = new Scene(pane, 200, 200);
    primaryStage.setTitle("Exercise15_28"); // Set the stage title
    primaryStage.setScene(scene); // Place the scene in the stage
    primaryStage.show(); // Display the stage

     //Runnable first = new Begin();

     //Thread first = new Thread();

     //t1.start();


        Thread first = new Thread(new Runnable() {
            @Override public void run() {
                while (true) {
                    try {
                        //Pause
                        Thread.sleep(100);

                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    Platform.runLater(new Runnable() {
                        @Override public void run() {

                          fan.move(); 

                        }
                    });
                }
            }
        });

                first.start();



   //Timeline animation = new Timeline(
      //new KeyFrame(Duration.millis(100), e -> fan.move()));
    //animation.setCycleCount(Timeline.INDEFINITE);
    //animation.play(); // Start animation

    scene.widthProperty().addListener(e -> fan.setW(fan.getWidth()));
    scene.heightProperty().addListener(e -> fan.setH(fan.getHeight()));

   //btPause.setOnAction(e -> first.wait());
    btResume.setOnAction(e -> first.start());
    btReverse.setOnAction(e -> fan.reverse());
  }

  /**
   * The main method is only needed for the IDE with limited
   * JavaFX support. Not needed for running from the command line.
   */
  public static void main(String[] args) {
    launch(args);


  }
} 

class FanPane extends Pane {
  private double w = 200;
  private double h = 200;
  private double radius = Math.min(w, h) * 0.45;
  private Arc arc[] = new Arc[4];   
  private double startAngle = 30;
  private Circle circle = new Circle(w / 2, h / 2, radius);

  public FanPane() {
    circle.setStroke(Color.BLACK);
    circle.setFill(Color.WHITE);
    getChildren().add(circle);

    for (int i = 0; i < 4; i++) {
      arc[i] = new Arc(w / 2, h / 2, radius * 0.9, radius * 0.9, startAngle + i * 90, 35);
      arc[i].setFill(Color.RED); // Set fill color
      arc[i].setType(ArcType.ROUND);
      getChildren().addAll(arc[i]); 
    } 
  }

  private double increment = 5;

  public void reverse() {
    increment = -increment;
  }

  public void move() {
    setStartAngle(startAngle + increment);
  }

  public void setStartAngle(double angle) {
    startAngle = angle;
    setValues();
  }

  public void setValues() {
    radius = Math.min(w, h) * 0.45;
    circle.setRadius(radius);
    circle.setCenterX(w / 2);
    circle.setCenterY(h / 2);

    for (int i = 0; i < 4; i++) {
      arc[i].setRadiusX(radius * 0.9);
      arc[i].setRadiusY(radius * 0.9);
      arc[i].setCenterX(w / 2);
      arc[i].setCenterY(h / 2);
      arc[i].setStartAngle(startAngle + i * 90);
    }     
  }

  public void setW(double w) {
    this.w = w;
    setValues();
  }

  public void setH(double h) {
    this.h = h;
    setValues();
  }
}
milliehol
  • 11
  • 4
  • `wait` doesn't halt the thread, but rather makes the current (calling) thread wait until someone calls `notify` on the same object. Have a look at [this question](http://stackoverflow.com/questions/19894607/java-how-to-stop-thread) – Itai Apr 21 '16 at 05:32

1 Answers1

2

This should be done with a Timeline, I know it's your homework and for some crazy reason your homework has been specified to not use a Timeline. But for anybody else, don't do it this way, just use a Timeline.

That said...

You mention start and stop buttons of which you have none. I assume start means resume and stop means pause as those are the buttons you do have. So I will answer accordingly.

The easiest way to deal with this is to use a boolean variable to control whether or not the fan is moving.

Define a member of your application:

private boolean paused = false;

In your thread only move the fan if not paused:

Platform.runLater(() -> { if (!paused) fan.move(); });

Configure your buttons to set your flag:

btPause.setOnAction(e -> paused = true);
btResume.setOnAction(e -> paused = false);

I've just put the pause variable directly in the calling application, but you could encapsulate the pause status inside the fan object if you wished.

Normally when dealing with multi-threaded stuff you have to be careful about data getting corrupted due to race-conditions. For example, you would use constructs like AtomicBoolean or synchronized statements. But runLater puts everything on to the JavaFX application thread, so you don't necessarily need to worry about that.

There are alternate mechanisms you could use to ensure that your thread didn't keep looping and and sleeping, such as wait/notify or Conditions, but for a sample like this, you probably don't need that here.

Updated Application

Updated sample demonstrating the suggested modifications, tested on JDK 8u60, OS X 10.11.4.

public class ch30 extends Application {
    private boolean paused = false;

    @Override 
    public void start(Stage primaryStage) {
        FanPane fan = new FanPane();

        HBox hBox = new HBox(5);
        Button btPause = new Button("Pause");
        Button btResume = new Button("Resume");
        Button btReverse = new Button("Reverse");
        hBox.setAlignment(Pos.CENTER);
        hBox.getChildren().addAll(btPause, btResume, btReverse);

        BorderPane pane = new BorderPane();
        pane.setCenter(fan);
        pane.setBottom(hBox);

        Thread first = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {
                    break;
                }
                Platform.runLater(() -> { if (!paused) fan.move(); });
            }
        });
        first.setDaemon(true);    
        first.start();

        btPause.setOnAction(e -> paused = true);
        btResume.setOnAction(e -> paused = false);
        btReverse.setOnAction(e -> fan.reverse());

        Scene scene = new Scene(pane, 200, 200);
        scene.widthProperty().addListener(e -> fan.setW(fan.getWidth()));
        scene.heightProperty().addListener(e -> fan.setH(fan.getHeight()));
        primaryStage.setScene(scene); 
        primaryStage.show(); 
    }
}

Aside

Set the daemon status of your thread so that your application shuts down cleanly when somebody closes the main stage.

first.setDaemon(true);
jewelsea
  • 150,031
  • 14
  • 366
  • 406
  • This actually does not work. Now I am using first.stop() to pause the program. However, I am not able to restart the thread after I stop it. Do you know why the resume button will not work? – milliehol Apr 22 '16 at 23:47
  • The solution presented in this answer does work. You should never invoke [thread.stop()](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#stop--), it is deprecated and the documentation notes that its usage is unsafe. Also, if you did, the thread state will go to [Thread.State.TERMINATED](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.State.html#TERMINATED). This is an [end state](http://www.uml-diagrams.org/examples/java-6-thread-state-machine-diagram-example.html). A terminated thread will stay terminated forever, it could never resume processing. – jewelsea Apr 23 '16 at 00:00
  • when I try to use your solution, it says that I cannot use the boolean variable unless it is final. When I declare it as final, I cannot set it as true or false in the button area. Do you know why I am getting this error? – milliehol Apr 23 '16 at 00:15
  • I cannot definitively identify your error cause without the full text of the error. Probably, it is because you are creating an [effectively final variable](http://stackoverflow.com/questions/20938095/difference-between-final-and-effectively-final). If you make the variable a member of the application it works fine (at least for me). It fails if you declare the variable local to a method. If you continue having issues, I'll post a complete solution later. You may also need to use Java 8 (which is what I did), compiling the application with `javac -target 1.8` (though I don't think so). – jewelsea Apr 23 '16 at 00:42