JavaFX - 在画布上使用更改属性(位置)绘制对象

时间:2015-09-15 17:01:20

标签: java model-view-controller canvas javafx

我正在尝试在JavaFX中创建一个项目 - 由点表示的机器人 - 基于某些通信协议在画布上移动。到目前为止,我已经发现遵循MVC方法,我应该将相关数据(机器人)保存在ObservableList类型的Object中,然后控制器可以使用它来更新画布。

运行Controller.java的test()方法我得到以下错误,我无法解决。我已经注释了Controller.java中的相关行 - 删除它也解决了错误消息。

我意识到这种方法可能与最佳实践相去甚远。我将非常感谢有关如何解决此问题或更好地使用Observable的任何提示,以允许控制器在更改后在画布上绘制机器人位置。

Main.java

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

import java.io.IOException;

public class Main extends Application {

    public Stage primaryStage;
    private BorderPane rootLayout;
    private final Model model = new Model();

    @Override
    public void start(Stage primaryStage) throws Exception{

        //    this.model = new Model();
        this.primaryStage = primaryStage;
        this.primaryStage.setTitle("");
        initRootLayout();

    }

    public void initRootLayout(){
        try {

            // Load root layout from fxml file.
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(Main.class.getResource("Overview.fxml"));
            rootLayout = (BorderPane) loader.load();

            // Show the scene containing the root layout.
            Scene scene = new Scene(rootLayout);
            primaryStage.setScene(scene);
            primaryStage.setHeight(500);
            primaryStage.setWidth(500);

            // Give the controller access to the main app.
            Controller controller = loader.getController();
            controller.initModel(model);



            primaryStage.show();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

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

Model.java

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class Model {

    private final ObservableList<Robot> robots = FXCollections.observableArrayList();

    public ObservableList<Robot> getRobots(){ return robots;}

}

Robot.java

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;

import java.util.Random;

public class Robot implements Runnable {


    private IntegerProperty id = new SimpleIntegerProperty();
    public void setID(int id){this.id.set(id);}
    public int getId(){ return this.id.get();};
    private Memory memory;

    private DoubleProperty positionX = new SimpleDoubleProperty();
    public double getPositionX() {return positionX.get();};
    public DoubleProperty positionXProperty(){ return positionX;}

    private DoubleProperty positionY = new SimpleDoubleProperty();
    public double getPositionY() {return positionY.get();};
    public DoubleProperty positionYProperty(){ return positionY;}

    public void setPosition (double x, double y){

        this.positionX.set(x);
        this.positionY.set(y);
        this.memory.setPosition(x,y);

    }

    // Constructor
    public Robot(int id, double x, double y){

        this.setID(id);
        memory = new Memory();
        this.setPosition(x, y);

    }



    public double[] getPosition(){
        double [] ret = new double[2];
        ret[0] = this.memory.getPosition()[0];
        ret[1] = this.memory.getPosition()[1];
        return ret;
    }

    public void move (double targetx, double targety){
        System.out.println("Agent carrying " + this.getId + " has moved from " + this.getPositionX() + " / " + this.getPositionY() + " to " + targetx + " / " + targety + " ." );
        this.setPosition(targetx, targety);


    }

    public void run(){

        while(true){
            // the robot gets assigned a new position based on a communication
            // protocoll used by the Robots which I have removed for convenience
            Random r = new Random();
            double nextX = 0 + (100) * r.nextDouble();
            double nextY = 0 + (100) * r.nextDouble();
            this.move(nextX, nextY);

            try{
                Thread.sleep(2000);
            }
            catch(InterruptedException e){
                System.out.println(e);
            }
        }
    }
}

Memory.java

public class Memory {

    double[] position = new double[2];
    double[] plast = new double[2];

    // constructor
    public Memory(){

        double[] position = new double[2];
        double[] plast = new double[2];

    }

    public double[] getPosition(){
        return position;
    }


    public double[] getLastPosition(){
        return plast;
    }

    public void  setLastPosition(double x, double y){
        plast[0] = x;
        plast[1] = y;
    }
    public void setPosition(double x, double y){
        position[0] = x;
        position[1] = y;
    }


}

Controller.java

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;

public class Controller {

    private Model model;

    public void initModel(Model model) {
        this.model = model ;

        for (Robot robot : model.getRobots()){
            robot.positionXProperty().addListener(new ChangeListener<Number>() {
                @Override
                public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                    System.out.println("Observed change in value X of Robot carrying ID: " + robot.getId());
                    drawPoint(robot.getPositionX(), robot.getPositionY());
                    // if I comment out the above line I get no errors
                }
            });
            robot.positionYProperty().addListener(new ChangeListener<Number>() {
                @Override
                public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                    System.out.println("Observed change in value Y of Robot carrying ID: " + robot.getId());
                    drawPoint(robot.getPositionX(), robot.getPositionY());
                    // if I comment out the above line I get no errors
                }
            });
        }
    }

    @FXML
    Canvas canvas;


    public void test(){
        GraphicsContext gc =canvas.getGraphicsContext2D();
        gc.strokeRect(0,0,100,100);

        Robot a = new Robot(0,25,25);
        Robot b = new Robot(1,50,50);

        model.getRobots().add(a);
        model.getRobots().add(b);

        Thread ta = new Thread(a);
        Thread tb = new Thread(b);

        initModel(model);

        System.out.println("Starting threads.");
        ta.start();
        tb.start();


    }

    public void drawPoint(double x, double y){
        GraphicsContext gc = canvas.getGraphicsContext2D();
        gc.setFill(Color.rgb(255, 0, 0));
        gc.fillOval(x, y, 5, 5);
    }
}

Overview.fxml

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

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

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller">
   <top>
      <Button fx:id="buttonTest" mnemonicParsing="false" onAction="#test" text="test()" BorderPane.alignment="CENTER" />
   </top>
   <center>
      <Canvas fx:id="canvas" height="200.0" width="200.0" BorderPane.alignment="CENTER" />
   </center>
</BorderPane>

错误:

java.lang.InternalError: Unrecognized PGCanvas token: -96
    at com.sun.javafx.sg.prism.NGCanvas.renderStream(NGCanvas.java:1146)
    at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:595)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2067)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1959)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:576)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2067)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1959)
    at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:474)
    at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:327)
    at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:91)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
    at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:125)
    at java.lang.Thread.run(Thread.java:745)

1 个答案:

答案 0 :(得分:0)

因为您正在观察机器人的坐标并更改响应中的UI,所以您必须更改FX应用程序线程上的坐标才能遵守JavaFX单线程规则。 (可以想象,听众会在FX应用程序线程上安排对UI的更改,但是您必须担心坐标上的同步,因为它们将从两个不同的线程访问。)

因此,您的run()方法应该是

public void run(){

    while(true){
        // the robot gets assigned a new position based on a communication
        // protocoll used by the Robots which I have removed for convenience

        Platform.runLater(() -> {

            Random r = new Random();
            double nextX = 0 + (100) * r.nextDouble();
            double nextY = 0 + (100) * r.nextDouble();
            this.move(nextX, nextY);

        });

        try{
            Thread.sleep(2000);
        }
        catch(InterruptedException e){
            System.out.println(e);
        }
    }
}

这解决了眼前的问题;你可能会发现还有其他人。