什么阻止了JavaFX应用程序中的UI线程?

时间:2017-12-14 15:44:12

标签: multithreading javafx fxml

我有一个简单的JavaFX应用程序,它有两个小圆圈,应该每0.5秒更改一次它们的位置。后来这应该成为行星模拟。此时,我的空间物体的位置在单独的线程中发生变化,当按下“开始模拟”按钮时,该线程会启动。同时我希望我的圆圈(代表行星)一次又一次地使用存储在spaceObject对象中的当前位置。当我将重新绘制限制为三次(而不是通过while ( true ) {这是我实际想要的无限量)时,我看到GUI在循环运行时没有更新。但是在循环结束后,圆圈移动到新位置,而后台的计算线程仍在运行。为什么我的GUI线程在while ( i < 3 ) {时被阻止,如何同时使用圈子的当前位置更新我的GUI?这是我的代码:

Main.java

package plantenbahnen;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));

        Scene scene = new Scene(root);

        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Controller.java

package plantenbahnen;

import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.Pane;

public class Controller implements Initializable {

    @FXML private Pane paneDraw;
    @FXML private Pane paneControls;

    private ArrayList<SpaceObject> universe = new ArrayList<>();
    private Thread calcThread;

    @Override
    public void initialize(URL url, ResourceBundle rb) {

        SpaceObject sun = new SpaceObject("sun", 600, 600);
        universe.add(sun);

        SpaceObject earth = new SpaceObject("earth", 450, 450);
        universe.add(earth);

        MyCalculations myCalc = new MyCalculations(universe);
        calcThread = new Thread(myCalc);

        Draw.drawPlanets(universe, paneDraw);
    }    

    @FXML private void buttonStartSimulation(ActionEvent event) throws InterruptedException {    
        calcThread.start();

        Platform.runLater(new Runnable() {
            @Override public void run() {
                int i = 0;
                //while ( true ) {   // this line is what I want
                while ( i < 3 ) {
                    Draw.drawPlanets(universe, paneDraw);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        System.out.println(e);
                    }
                    i++;
                }
            }
        });
    }    
}

MyCalculations.java

package plantenbahnen;

import java.util.ArrayList;

public class MyCalculations implements Runnable {

    ArrayList<SpaceObject> universe;

    public MyCalculations (ArrayList<SpaceObject> universe) {
        this.universe = universe;
    }

    @Override
    public void run(){
        double toAdd = 100.0;
        while ( true ) {
            for (SpaceObject so: universe) {
                so.setx(so.getx() + toAdd);
                so.sety(so.gety() + toAdd);
            }
            if ( toAdd > 0.0 ) {
                toAdd = -300.0;
            } else {
                toAdd = 300.0;
            }
        }
    }
}

Draw.java

package plantenbahnen;

import java.util.ArrayList;
import javafx.scene.Node;
import javafx.scene.layout.Pane;

public class Draw {

    public static void drawPlanets(ArrayList<SpaceObject> universe, Pane pane) {
        for (Node child: pane.getChildren()) {
            System.out.println(child);
        }

        // Clear objects first
        for (SpaceObject so: universe) {
            if ( pane.getChildren().contains(so) ) {
                pane.getChildren().remove(so);
                System.out.println("Removing ... " + so.getName());
            }
        }

        double paneHalfWidth = pane.getPrefWidth() / 2.0;
        double paneHalfHeight = pane.getPrefHeight() / 2.0;
        double scaleFactor = 0.1;

        for (SpaceObject so: universe) {
            so.setCenterX(so.getx() * scaleFactor + paneHalfWidth);
            so.setCenterY(so.gety() * scaleFactor + paneHalfHeight);
            System.out.println("x=" + so.getCenterX() + "   y=" + so.getCenterY());
            so.setRadius(2);
            //so.setColour(Color.BLACK);
            pane.getChildren().add(so);
        }
    }
}

SpaceObject.java

package plantenbahnen;

import javafx.scene.shape.Circle;

public class SpaceObject extends Circle {

    private double x,y;
    private String name;

    SpaceObject(String name, double x, double y) {
        this.x = x;
        this.y = y;
        this.name = name;
    }

    public double getx(){
        return this.x;
    }
    public void setx(double value){
        this.x=value;
    }
    public double gety(){
        return this.y;
    }

    public void sety(double value){
        this.y=value;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

FXMLDocument.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane fx:id="AnchorPane" prefHeight="700.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="plantenbahnen.Controller">
   <children>
      <Pane fx:id="paneDraw" prefHeight="700.0" prefWidth="800.0">
         <children>
            <Pane fx:id="paneControls" prefHeight="66.0" prefWidth="174.0">
               <children>
                  <Button fx:id="buttonStartSimulation" layoutX="26.0" layoutY="21.0" mnemonicParsing="false" onAction="#buttonStartSimulation" text="Start simulation" />
               </children>
            </Pane>
         </children></Pane>
   </children>
</AnchorPane>

非常感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

尝试这样的事情:

 @FXML private void buttonStartSimulation(ActionEvent event) throws InterruptedException {    

    calcThread.start();

    Thread updaterThread = new Thread( () -> {
        @Override public void run () {
            int i = 0;
            while ( true ) {   // this line is what I want
                Platform.runLater( () -> Draw.drawPlanets(universe, paneDraw) );
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(e);
                }
                i++;
            }
        }
    }
    updaterThread.setDaemon ( true );
    updaterThread.start();
}

你想确保你对Platform.runLater()的所有调用都很短,没有睡眠,快速返回,并做最少的计算 - 所有这些调用必须完成&#34;中间& #34; UI的其他更新,例如调整窗口大小,管理按钮按下等。

顺便说一下 - 我不确定你是否需要&#34; calcThread&#34;和&#34; updaterThread&#34;。我怀疑它们应该是一个线程。但这是一个很好的概念证明。

答案 1 :(得分:0)

感谢大家的帮助。我最终使用Timeline。所有我必须改变的是控制器,它看起来像这样(只显示相关的代码):

public class Controller implements Initializable {

    // ...
    private Thread calcThread;
    Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.5), new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            Draw.drawPlanets(universe, paneDraw);
        }
    }));

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // ...
        MyCalculations myCalc = new MyCalculations(universe);
        calcThread = new Thread(myCalc);
        // ...
    }    

    @FXML private void buttonStartSimulation(ActionEvent event) throws InterruptedException {    
        calcThread.start();
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.play();
    }    
}