JavaFX - 在两者之间暂停调用UI更新方法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaFX - 在两者之间暂停调用UI更新方法相关的知识,希望对你有一定的参考价值。

我正在研究一个JavaFx学校项目,我试图将imageView移动到GridPane。应该在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();

    }

}
答案

你的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 Application Thread上执行它们。

这看起来像是:

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();

    }

}

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

以上是关于JavaFX - 在两者之间暂停调用UI更新方法的主要内容,如果未能解决你的问题,请参考以下文章

JavaFX:从线程更新控制器类中的 UI 元素

从 JavaFX 中的不同线程更新 UI

JavaFX 媒体 - 暂停();方法使 MediaPlayer 快进?

JavaFX:在 UI 屏幕之间导航的最佳实践

JavaFx监听serversocket并根据输入更新UI遇到了问题

Javafx - 从 java 类更新 UI 中的进度指示器