JavaFX不能正确地重新绘制。

3
我正在尝试使用JavaFX编写密码圆盘程序,但有时渲染会出现损坏。
应该显示什么:

This

然而,有时候旋转会出现以下问题:

this

为什么会发生这种情况?我该怎么解决?我找到了很多其他帖子,说JavaFX应该为您处理所有渲染,但强制重新绘制似乎是唯一的解决办法,而我也无法弄清如何做到这一点。
以下是我的代码:
import java.awt.Image;
import java.util.Timer;
import java.util.TimerTask;

import javafx.application.Application;
import javafx.scene.effect.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.*;
import javafx.stage.Stage;

public class Cryptex extends Application{
    private Spool[] spools;
    private double radius = 100, perspectiveScale = 0.1;
    Stage stage;
    Scene scene;

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

    @Override
    public void start(Stage primaryStage){
        stage = primaryStage;
        stage.setTitle("Criptex");

        Group root = new Group();
        scene = new Scene(root, 550, 400);
        stage.setScene(scene);
        stage.show();

        spools = new Spool[5];
        spools[0] = new Spool("jdkwndityc", -150);
        spools[1] = new Spool("lqhmnxfgso", -75);
        spools[2] = new Spool("usjnzuvbid", 0);
        spools[3] = new Spool("ihgkewobde", 75);
        spools[4] = new Spool("cdelsprkar", 150);
        for (Spool sp : spools){
            sp.angle = Math.random()*Math.PI*2;
            root.getChildren().add(sp);
        }

        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                for (Spool s : spools){
                    if (s.angle != 0){
                        if (Math.abs(s.distToAngle(0)) < Math.toRadians(1)){
                            System.out.println("snap");
                            s.snapToAngle(0);
                            for (Spool.Cell c : s.cells){
                                c.draw(s.angle);
                            }
                        }
                        else {
                            s.rotate(s.directionToAngle(0));
                            System.out.println((s.x+150)/75+1 + ": "+Math.round((s.angle/Math.PI)*180));
                            for (Spool.Cell c : s.cells){
                                c.draw(s.angle);
                            }
                        }
                    }
                }
                //System.out.println("--");
            }
        }, 0, 10);
    }

    public class Spool extends Group{
        double angle;
        char[] chars;
        Image[] letters;
        int x;
        Cell[] cells;
        public Spool(String charList, int x){
            chars = charList.toCharArray();
            letters = new Image[chars.length];
            this.x = x;
            angle = 0;
            cells = new Cell[chars.length];
            for (int i = 0; i < chars.length; i++){
                cells[i] = new Cell(i);
                this.getChildren().add(cells[i]);
            }
        }

        public void rotate(double distance){
            angle += Math.toRadians(distance);
            if (angle >= Math.PI*2){
                angle -= Math.PI*2;
            }
            if (angle < 0){
                angle += Math.PI*2;
            }
        }
        //TODO: check if this is way off
        public double distToAngle(double angle){
            rotate(0);
            if ((this.angle > angle && this.angle - angle > Math.PI) ||
                    (this.angle < angle && angle - this.angle > Math.PI)){
                return Math.abs(this.angle - angle);
            }
            else {
                return -Math.abs(this.angle - angle);
            }
        }
        public double closestAngle(){
            int closest = 0;
            for (int i = 0; i < chars.length; i++){
                if (Math.abs(distToAngle(i*((Math.PI*2)/chars.length))) 
                        < Math.abs(distToAngle(closest*((Math.PI*2)/chars.length)))){
                    closest = i;
                }
            }
            return closest;
        }
        public boolean snapToAngle(double angle){
            if (Math.abs(this.distToAngle(angle)) > Math.toRadians(1)){
                if (this.distToAngle(angle) > 0){
                    this.rotate(-1);
                }
                else {
                    this.rotate(1);
                }
                return false;
            }
            else if (this.angle != angle){
                this.angle = angle;
            }
            return true;
        }
        public double indexToAngle(int index){
            return ((Math.PI*2)/chars.length)*index;
        }
        public double perspectiveWidth(double d){
            return Math.sqrt(Math.pow(radius, 2) - Math.pow(d, 2)) * perspectiveScale;
        }
        public double toAngle(int index){
            return ((Math.PI*2)/chars.length)*(index) + angle + ((Math.PI*2)/(chars.length*2));
        }
        public int directionToAngle(double angle){
            return (int) Math.signum(this.distToAngle(angle));
        }

        public class Cell extends Group {
            private int index;
            PerspectiveTransform pt = new PerspectiveTransform();
            public Cell(int index){//, double angle, Stage stage){
                this.index = index;

                Text text = new Text();
                //System.out.println("char: " + String.valueOf(chars[c]));
                text.setText(String.valueOf(chars[index]).toUpperCase());
                text.setFont(Font.font("Monospaced", FontWeight.BOLD, 36));
                text.setFill(Color.BLACK);
                text.setX(9);
                text.setY(32);

                Rectangle rect = new Rectangle(40, 40);
                rect.setFill(Color.BEIGE);
                rect.setStrokeType(StrokeType.OUTSIDE);
                rect.setStrokeWidth(3);
                rect.setStroke(Color.BLACK);

                this.draw(angle);
                this.setCache(true);
                this.getChildren().addAll(rect, text);  
            }

            public void draw(double angle){
                double cx = stage.getWidth()/2 + x;
                double cy = (stage.getHeight()-100)/2;
                if (cy + radius*Math.sin(toAngle(index)+angle) <= cy + radius*Math.sin(toAngle(index+1)+angle)){
                    this.setVisible(true);
                    pt.setUlx(cx - 20 - perspectiveWidth(radius*Math.sin(toAngle(index)+angle)));
                    pt.setUrx(cx + 20 + perspectiveWidth(radius*Math.sin(toAngle(index)+angle)));
                    pt.setLrx(cx + 20 + perspectiveWidth(radius*Math.sin(toAngle(index+1)+angle)));
                    pt.setLlx(cx - 20 - perspectiveWidth(radius*Math.sin(toAngle(index+1)+angle)));

                    pt.setUly(cy + radius*Math.sin(toAngle(index)+angle));
                    pt.setUry(cy + radius*Math.sin(toAngle(index)+angle));
                    pt.setLry(cy + radius*Math.sin(toAngle(index+1)+angle));
                    pt.setLly(cy + radius*Math.sin(toAngle(index+1)+angle));
                    this.setEffect(pt);
                }
                else {
                    this.setVisible(false);
                }
            }
        }
    }       
}
1个回答

2
您的问题与此处所见相同,直接在JavaFX中使用java.util.Timer是不安全的。
这是因为计时器使用自己的后台线程,但如果您想对GUI进行任何更新,您需要使用JavaFX GUI线程(JavaFX创建和处理的特殊线程)。试图从另一个线程触碰GUI会导致类似于您遇到的问题。
您可以通过在Platform.runLater块中包装更改来将更改传递给JavaFX 组件的特殊JavaFX线程。
代码看起来像这样:
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
    @Override
    public void run() {
        Platform.runLater(() -> { //Lambda for Runnable
            for (Spool s : spools){
                if (s.angle != 0){
                    if (Math.abs(s.distToAngle(0)) < Math.toRadians(1)){
                        System.out.println("snap");
                        s.snapToAngle(0);
                        for (Spool.Cell c : s.cells){
                            c.draw(s.angle);
                        }
                    }
                    else {
                        s.rotate(s.directionToAngle(0));
                        System.out.println((s.x+150)/75+1 + ": "
                            + Math.round((s.angle/Math.PI)*180));
                        for (Spool.Cell c : s.cells){
                            c.draw(s.angle);
                        }
                    }
                }
            }
        });
    }
}, 0, 10);

纯JavaFX风格:

您还可以使用JavaFX Timeline,无需担心线程问题:

Timeline timer = new Timeline(new KeyFrame(
        Duration.millis(10),
        event -> {//Same for loop as above}
));
timer.setCycleCount(Timeline.INDEFINITE);
timer.play();

1
对于任何想要具有正确约定的完整类的人:https://gist.github.com/Emrage/6de2c7451bc4b68f830aa6e7260a2ddd - Kiraged

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接