JavaFX - 在中间暂停调用UI更新方法

时间:2018-02-17 17:32:24

标签: java user-interface javafx methods thread-sleep

我正在尝试在imageView上移动GridPane JavaFx 学校项目。应该在UI上按顺序可视化/更新移动。

我尝试了AnimationTimersThreads以及其他所有内容,但我并不熟悉这一领域。我在Thread中启动PlaygroundController,并在方法结束时每次调用一次睡眠。

在第二个代码片段中,我创建了PlaygroundController类的对象,并连续三次调用这些方法。我的目标是使用ImageView方法将moveRight() 1列向右移动,暂停一秒,然后调用moveDown()方法向下移动,依此类推。

使用我的代码,它会在延迟3秒后一起完成所有3种方法。

private Thread thread;
public PlaygroundController() { 
    thread = new Thread();
    thread.start();

}

public void run(ActionEvent evt) throws InterruptedException {  
    Level1Solution level = new Level1Solution(this);

}

public void moveRight() throws InterruptedException {   
    if(GridPane.getColumnIndex(avatarIcon) < 10) {
        GridPane.setColumnIndex(avatarIcon, GridPane.getColumnIndex(avatarIcon) +1);

    }else{
        Alert alert = new Alert(AlertType.WARNING);
        alert.setTitle("Fehler!");
        alert.setHeaderText("Weiter geht es nicht!");
        alert.setOnHidden(evt -> Platform.exit());
        alert.show();
    }
    avatarColumn = GridPane.getColumnIndex(avatarIcon);
    Thread.sleep(1000);
}

第二个片段:

package controller;

public class Level1Solution {


    PlaygroundController solution;

    public Level1Solution(PlaygroundController solution) throws InterruptedException {

        this.solution = solution;
        solution.moveRight();
        solution.moveDown();
        solution.moveRight();

    }

}

1 个答案:

答案 0 :(得分:1)

您的Level1Solution构造函数可能是在FX Application Thread上执行的。它在PlaygroundController实例上调用三个方法,每个方法(可能)改变UI元素的位置,然后暂停当前(即FX应用程序)线程一秒钟。

请注意,您在PlaygroundController中创建的主题并没有真正执行任何操作:它没有Runnable并且不会覆盖自己的run()方法,所以它基本上什么都不做。你启动那个线程 - 所以它在后台执行它的run()方法,它会立即退出(因为它无关)。

FX应用程序线程负责呈现UI。因为您在FX应用程序线程上执行Level1Solution构造函数,所以在该构造函数完成之前无法呈现该UI;即三秒后,UI将反映您对其所做的所有更改。

出于这样的原因,你应该从不阻止FX应用程序线程。另请注意,必须在该线程上对UI 进行实际更改。

要在特定时间点在FX应用程序线程上执行代码,您应该使用某种animation。对于您的用例,Timeline会很好用;只需为每个时间点定义关键帧,并使用调用方法的事件处理程序:

public class Level1Solution {


    PlaygroundController solution;

    public Level1Solution(PlaygroundController solution) throws InterruptedException {

        this.solution = solution;

        Timeline timeline = new Timeline(
            new KeyFrame(Duration.seconds(0), e -> solution.moveRight()),
            new KeyFrame(Duration.seconds(1), e -> solution.moveDown()),
            new KeyFrame(Duration.seconds(2), e -> solution.moveRight()));

        timeline.play();
    }

}

当然,移除Thread中的PlaygroundController和暂停:

public PlaygroundController() { 
}

public void run(ActionEvent evt) throws InterruptedException {  
    Level1Solution level = new Level1Solution(this);

}

public void moveRight() throws InterruptedException {   
    if(GridPane.getColumnIndex(avatarIcon) < 10) {
        GridPane.setColumnIndex(avatarIcon, GridPane.getColumnIndex(avatarIcon) +1);

    }else{
        Alert alert = new Alert(AlertType.WARNING);
        alert.setTitle("Fehler!");
        alert.setHeaderText("Weiter geht es nicht!");
        alert.setOnHidden(evt -> Platform.exit());
        alert.show();
    }
    avatarColumn = GridPane.getColumnIndex(avatarIcon);

    // never block the UI thread:
    // Thread.sleep(1000);
}

如果你有一个特定的要求Level1Controller构造函数必须调用这三种方法,而不是以任何方式处理暂停本身,那么它会变得更加困难。一种方法是将您调用的方法提交给单个线程执行程序,其中提交的任务执行UI操作,然后暂停一段时间。单个线程上的暂停将确保在执行任何其他任务之前有一段时间。请注意,在这种情况下,任务将在后台线程上,因此对UI的实际更改必须包含在Platform.runLater(...)的调用中,以便在FX应用程序线程上执行它们。

这看起来像是:

public class PlaygroundController {

    private ExecutorService exec ;

    public PlaygroundController() { 
        // single thread executor using a daemon thread 
        // (so it will not prevent application exit)
        exec = Executors.newSingleThreadExecutor(runnable -> {
            Thread t = new Thread(runnable);
            t.setDaemon(true);
            return t ;
        });
    }

    public void run(ActionEvent evt) throws InterruptedException {  
        Level1Solution level = new Level1Solution(this);

    }

    public void moveRight() throws InterruptedException {   

        Runnable task = () -> {
            Platform.runLater(() -> {
                if(GridPane.getColumnIndex(avatarIcon) < 10) {
                    GridPane.setColumnIndex(avatarIcon, GridPane.getColumnIndex(avatarIcon) +1);

                } else {
                    Alert alert = new Alert(AlertType.WARNING);
                    alert.setTitle("Fehler!");
                    alert.setHeaderText("Weiter geht es nicht!");
                    alert.setOnHidden(evt -> Platform.exit());
                    alert.show();
                }
                avatarColumn = GridPane.getColumnIndex(avatarIcon);
            });
            Thread.sleep(1000);
        };

        exec.submit(task);
    }

    // ...

}

然后只是

public class Level1Solution {


    PlaygroundController solution;

    public Level1Solution(PlaygroundController solution) throws InterruptedException {

        this.solution = solution;
        solution.moveRight();
        solution.moveDown();
        solution.moveRight();

    }

}

应该达到预期的效果。这种方法似乎有点过于复杂:上面的动画解决方案更为可取。