JavaFX обновляет AnchorPane из другого потока - PullRequest
0 голосов
/ 04 июня 2018

Так что я немного застрял на этом.У меня довольно простая игра, в которой вы перемещаете корабль по сетке с помощью клавиш со стрелками.

Я добавил еще один поток с каким-то монстром, который должен автоматически перемещаться по сетке.Из операторов печати видно, что поток работает, а монстр движется, однако местоположение изображения не обновляется.

Я нашел несколько похожих вопросов, и есть много рекомендаций по использованию Platfrom.runLater.Но я не уверен, соответствует ли это моему конкретному случаю, и если да, то как его реализовать.

Вот что делает класс Monster, перемещая монстра вправо на одну позицию каждую секунду.Как я уже упоминал, я регистрирую текущее местоположение каждый раз, когда вызывается setX(), поэтому я вижу, что местоположение обновляется.

import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import java.awt.Point;

public class Monster implements Runnable {

    private Point currentPoint;
    private OceanMap map;

    public Monster(int x, int y) {
        this.currentPoint = new Point(x, y);
        this.map = OceanMap.getInstance();
    }

    public Point getLocation() {
        System.out.println(this.currentPoint.toString());
        return this.currentPoint;
    }

    private void setNewLocation(Point newLocation) {
        this.currentPoint = newLocation;
    }

    private void setY(int newY) {
        this.currentPoint.y = newY;
        this.setNewLocation(new Point(this.currentPoint.x, this.currentPoint.y));
    }

    private void setX(int newX) {
        this.currentPoint.x = newX;
        this.setNewLocation(new Point(this.currentPoint.x, this.currentPoint.y));
        System.out.println(this.currentPoint.toString());
    }

//    public void addToPane() {
//        System.out.println("this is called");
//        iv.setX(this.currentPoint.x + 1 * 50);
//        iv.setY(this.currentPoint.y * 50);
//        obsrvList.add(iv);
//    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setX(this.currentPoint.x + 1);
        }

    }

}

А вот поток JavaFX.

/* Monster resources */
private Image monsterImage = new Image(getClass().getResource("monster.png").toExternalForm(), 50, 50, true, true);
private ImageView monsterImageView1 = new ImageView(monsterImage);
private Monster monster1;
private Thread monsterThread;

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

    root = new AnchorPane();
    scene = new Scene(root, scale * xDimensions, scale * yDimensions);

    oceanStage.setScene(scene);
    oceanStage.setTitle("Ocean Explorer");

    /* Draw Grid */
    for (int x = 0; x < xDimensions; x++) {
        for (int y = 0; y < yDimensions; y++) {
            Rectangle rect = new Rectangle(x * scale, y * scale, scale, scale);
            rect.setStroke(Color.BLACK);
            rect.setFill(Color.PALETURQUOISE);
            root.getChildren().add(rect);
        }
    }

    oceanStage.show();

    monsterThread = new Thread(monster1);
    monsterThread.start();
    Platform.runLater(() -> {
        monsterImageView1.setX(monster1.getLocation().x * scale);
        monsterImageView1.setY(monster1.getLocation().y * scale);
        root.getChildren().add(monsterImageView1);
    });

    startSailing();
}

Я могу предоставить больше кода, если это необходимо, это все, что я считал уместным на данный момент.

Итак, мой вопрос, как я могу обновить пользовательский интерфейс потока JavaFX из другого потока?

1 Ответ

0 голосов
/ 04 июня 2018

Пока вы обновляете currentPoint внутри Monster, это значение никогда не распространяется на monsterImageView1.Вам следует преобразовать currentPoint в свойство, а затем связать его:

class Point {
    final int x;
    final int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

class Monster implements Runnable {
    private ReadOnlyObjectWrapper<Point> location = new ReadOnlyObjectWrapper<>();

    public Monster(int x, int y) {
        setLocation(new Point(x, y));
    }

    public Point getLocation() {
        return this.location.get();
    }

    private void setLocation(Point location) {
        this.location.set(location);
    }

    public ReadOnlyProperty<Point> locationProperty() {
        return this.location.getReadOnlyProperty();
    }

    private void setY(int newY) {
        this.setLocation(new Point(this.getLocation().x, newY));
    }

    private void setX(int newX) {
        this.setLocation(new Point(newX, this.getLocation().y));
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // run the update on the FX Application Thread for thread safety as well as to prevent errors in certain cases
            Platform.runLater(() -> this.setX(this.getLocation().x + 1));
        }
    }
}

monsterThread = new Thread(monster1);
monsterThread.start();
monsterImageView1.xProperty().bind(Bindings.createIntegerBinding(() -> monster1.getLocation().x * scale, monster1.locationProperty()));
monsterImageView1.yProperty().bind(Bindings.createIntegerBinding(() -> monster1.getLocation().y * scale, monster1.locationProperty()));
root.getChildren().add(monsterImageView1);

Однако, как упоминает @James_D, подход Timeline будет гораздо более подходящимчтобы решить это правильным способом:

class Monster {
    private ReadOnlyObjectWrapper<Point> location = new ReadOnlyObjectWrapper<>();
    private Timeline timeline;

    public Monster(int x, int y) {
        setLocation(new Point(x, y));

        timeline = new Timeline(new KeyFrame(Duration.seconds(1), event -> {
            setX(getLocation().x + 1);
        }));
        timeline.setCycleCount(Timeline.INDEFINITE);
    }

    public void start() {
        timeline.play();
    }

    // NOTE: remember to call stop() or this will result in a memory leak
    public void stop() {
        timeline.stop();
    }

    public Point getLocation() {
        return this.location.get();
    }

    private void setLocation(Point location) {
        this.location.set(location);
    }

    public ReadOnlyProperty<Point> locationProperty() {
        return this.location.getReadOnlyProperty();
    }

    private void setY(int newY) {
        this.setLocation(new Point(this.getLocation().x, newY));
    }

    private void setX(int newX) {
        this.setLocation(new Point(newX, this.getLocation().y));
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...