我正在尝试使用JavaFX
创建一个cryptex程序,但有时渲染会被破坏。
为什么会这样?我该怎么做才能解决这个问题?我发现很多其他帖子都说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);
}
}
}
}
}
答案 0 :(得分:2)
您的问题与the one seen here相同,直接在JavaFX中使用java.util.Timer
不是线程安全的。
这是因为timer使用了它自己的后台线程,但是如果你想对GUI进行任何更新,你需要使用JavaFX GUI线程(JavaFX创建和处理的特殊线程) )。试图从另一个线程触摸GUI会产生类似你所看到的问题。
您可以通过将更改包装在Platform.runLater
块中,将更改传递给JavaFX components
到特殊的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 Timeline
,而不必担心线程问题:
Timeline timer = new Timeline(new KeyFrame(
Duration.millis(10),
event -> {//Same for loop as above}
));
timer.setCycleCount(Timeline.INDEFINITE);
timer.play();