首页 > 解决方案 > 不在 FX 应用程序线程异常。当我尝试从 Thread 调用 Pane.getChildren().add()

问题描述

我希望我的应用程序更新一些元素,但是当我从另一个线程调用 Pane.getChildren().add() 时:

Pane root = new Pane();
Path path;
        new Thread(new Runnable() {
        @Override
        public void run() {
            for (Coordinate coordinate: coordinates){
                try {
                    Thread.sleep(100);
                    root.getChildren().remove(path);
                    test++;
                    path = makePath(coordinates.subList(test, test+5));
                    root.getChildren().add(path);//!!!Exception here
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();

我有这个错误:

Exception in thread "Thread-3" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-3
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:478)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:206)
at forTest.SkladPlanFx$2.run(SkladPlanFx.java:92)
at java.base/java.lang.Thread.run(Thread.java:844)

我试图根据这个链接解决这个问题, 但这种方法没有帮助:Platform.runLater(new Runnable(){});

问题:

  1. 为什么它不起作用?

  2. 我想让我的程序延迟更新自己的 UI,我认为有一个正确的解决方案而不是我的)

提前致谢。

添加评论的人:

如果我使用 Platform.runLater(new Runnable(){

它的工作方式不同。程序只是冻结了,我明白为什么,但我不明白为什么补丁不出现也不改变。

    Platform.runLater(new Runnable(){

@Override
        public void run() {
            for (Coordinate coordinate: coordinates){
                try {
                    Thread.sleep(100);
                    System.out.println(coordinate);
                    root.getChildren().remove(path);
                    test++;
                    path = makePath(coordinates.subList(test, test+5));
                    root.getChildren().add(path);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });

如果我使用按钮效果很好:

        button.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            root.getChildren().remove(path);
            test++;
            path = makePath(coordinates.subList(test, test+5));
            Group group = new Group();
            root.getChildren().add(path);
        }
    });

补充这是您可以尝试的“最小可重现示例”。

按下按钮,就会有我需要的结果;

删除注释“new Thread(new Runnable() {”你会得到“java.lang.IllegalStateException: Not on FX application thread”

删除评论“Platform.runLater(new Runnable(){”,你会得到一个楣板并且一动不动。

package forTest;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import java.util.Random;


public class SkladPlanFx extends Application {

    static int test = 10;
    Path path;

    @Override
    public void start(Stage primaryStage) {
        // create a button with specified text
        Button button = new Button("TEST");
        button.setLayoutX(500);
        button.setLayoutY(850);

        Pane root = new Pane();
        // create a scene specifying the root and the size
        Scene scene = new Scene(root, 1320, 930);

        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                root.getChildren().remove(path);
                test++;
                path = makePath();
                Group group = new Group();
                root.getChildren().add(path);
            }
        });

        root.getChildren().add(button);
        // add scene to the stage
        primaryStage.setScene(scene);
        // make the stage visible
        primaryStage.show();

/*
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++){
                    try {
                        Thread.sleep(100);
                        root.getChildren().remove(path);
                        test++;
                        path = makePath();
                        root.getChildren().add(path);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
*/

/*
        Platform.runLater(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++){
                    try {
                        Thread.sleep(300);
                        System.out.println(i);
                        root.getChildren().remove(path);
                        test++;
                        path = makePath();
                        root.getChildren().add(path);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
*/


    }

   public Path makePath(){
      Path path = new Path();

       Random random = new Random();
      path.getElements().add(new MoveTo(test++,test++));


      for (int i = 0; i < 5; i++){
        path.getElements().add(new LineTo(test++, test++));
      }
        return path;
   }

    public static void main(String[] args) {
        Application.launch(SkladPlanFx.class, args);
    }
}

标签: javafx

解决方案


我已经通过这种方式解决了。

我在这个计时器内使用“new Timer().schedule()”而不是“Thread”加上 Platform.runLater()。

这样路径就可以改变了。

package Fx;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import java.util.*;

public class SkladPlanFx extends Application {

    static int test = 0;
    Path path;

    public static void main(String[] args) {
        SkladPlanFx.launch(SkladPlanFx.class, args);
    }

    @Override
    public void start(Stage primaryStage) {

        Text text = new Text();

        text.setText("qqq");
        text.setLayoutX(450);
        text.setLayoutY(800);

        Pane root = new Pane();
        Scene scene = new Scene(root, 500, 500);

        primaryStage.setScene(scene);
        primaryStage.show();


        //Timer
        new Timer().schedule(
             new TimerTask() {
                 @Override
                 public void run() {
                     System.out.println("ping " + ++test);
                     text.setText(" " + test);

                     //here is the solution
                     Platform.runLater(new Runnable() {
                         @Override public void run() {
                             root.getChildren().remove(path);
                             test++;
                             //path = makePath(coordinates.subList(test, test+5));
                             path = makeTestPath();
                             root.getChildren().add(path);
                         }
                     });

                 }
             }, 0, 1000);

    }

    public Path makeTestPath() {
        Path path = new Path();
        path.setStroke(Color.RED);
        path.getElements().add(new MoveTo(getRandomNumberInRange(1, 500), getRandomNumberInRange(1, 500)));
        for (int i = 0; i < 10; i++) {
            path.getElements().add(new LineTo(getRandomNumberInRange(1, 500), getRandomNumberInRange(1, 500)));
        }
        return path;
    }

    private static int getRandomNumberInRange(int min, int max) {
        if (min >= max) {
            throw new IllegalArgumentException("max must be greater than min");
        }
        Random r = new Random();
        return r.nextInt((max - min) + 1) + min;
    }
}

推荐阅读