【问题标题】:JavaFX make bar chart changing bars with delay in UI ThreadJavaFX使条形图在UI线程中延迟更改条形
【发布时间】:2023-03-08 21:40:01
【问题描述】:

我得到了 JavaFX 主线程,在那里我创建了扩展 Task 并排序和替换栏的新线程。一切都很好,但我想在替换显示逐步排序时做一些延迟(比如 100 毫秒),也许是动画。问题是当我使用 Thread.sleep() 或 TranslateTransition() 时,它只是将所有延迟毫秒加在一起,这是在更改条形之前发生的一个大延迟。如何使延迟在 UI 线程中正常工作?

在主类中:

       Sorting sorting = new Sorting();
       sortThread = new Thread(sorting, "sort");
       sortThread.start();
       sortThread.join();

我的分类扩展了任务

  public class Sorting extends Task<Void> {

  //some stuff here

    @Override
    protected Void call() throws Exception {


        taskThread = new Thread(counter, "time");
        taskThread.setDaemon(true);
        taskThread.start();

        int n = array_tmp.length;
        int  temp;
        for (int i = 0; i < n; i++) {
            for (int j = 1; j < (n - i); j++) {
                if (array_tmp[j - 1] > array_tmp[j]) {

                        //replacing bars
                        Node n1 = barChart.getData().get(j-1).getData().get(0).getNode();
                        Node n2 = barChart.getData().get(j).getData().get(0).getNode();

                        double x1 = n1.getTranslateX() + ((barChart.getWidth()-69)/array_tmp.length);
                        double x2 = n2.getTranslateX() - ((barChart.getWidth()-69)/array_tmp.length);

                        n1.setTranslateX(x1);
                        n2.setTranslateX(x2);

                        barChart.getData().get(j-1).getData().get(0).setNode(n2);
                        barChart.getData().get(j).getData().get(0).setNode(n1);


                    temp = array_tmp[j - 1];
                    array_tmp[j - 1] = array_tmp[j];
                    array_tmp[j] = temp;
                }
            }
        }
  }
     }

【问题讨论】:

  • 您始终可以使用工作线程为每个变体创建不同的图表,而不是修改第一个图表,只需为您希望显示的每个步骤显示一个新图表。
  • 那是非常浪费资源的操作
  • 查看Timeline。这可能正是您正在寻找的。​​span>
  • 目前还不清楚您认为您发布的代码会做什么。在第一个代码块中,您创建一个执行某些操作的线程,启动它,然后立即调用join(),这当然会阻塞,直到该线程完成。这基本上完全等同于直接在当前线程中运行任务。在call() 方法内部:counter 是什么,执行它的线程的目的是什么?另外,请注意您的 call() 方法从后台线程操作 UI,这违反了 JavaFX 线程规则。
  • 不要停止它,因为这会停止更新您想要查看的 gui。正如我已经建议的here,您可以使用PauseTransitions 进行延迟。

标签: multithreading sorting user-interface javafx task


【解决方案1】:

JavaFX 中的线程有两个基本规则:

  1. 作为实际显示的场景图一部分的 UI 组件(节点)只能从 JavaFX 应用程序线程访问。其他一些操作(例如创建新的Stage)也受此规则约束。
  2. 任何长时间运行或阻塞的操作都应该在后台线程(即不是 JavaFX 应用程序线程)上运行。这是因为 JavaFX 应用程序线程是呈现 UI 和响应用户交互所需的线程。因此,如果您阻止 FX 应用程序线程,则无法呈现 UI,并且应用程序将变得无响应,直到您的操作完成。

javafx.concurrent API 提供了用于管理可在后台线程上运行的代码和在 FX 应用程序线程上执行回调的工具。

javafx.animation API 还提供了允许在特定时间在 JavaFX 应用程序线程上执行 UI 代码的类。请注意,动画 API 完全避免创建后台线程。

因此,对于您的用例,如果您想为条形图中两个条形的交换设置动画,您可以使用动画 API 来实现。创建执行此类交换的动画的一般方法可能如下所示:

private <T> Animation createSwapAnimation(Data<?, T> first, Data<?, T> second) {
    double firstX = first.getNode().getParent().localToScene(first.getNode().getBoundsInParent()).getMinX();
    double secondX = first.getNode().getParent().localToScene(second.getNode().getBoundsInParent()).getMinX();

    double firstStartTranslate = first.getNode().getTranslateX();
    double secondStartTranslate = second.getNode().getTranslateX();

    TranslateTransition firstTranslate = new TranslateTransition(Duration.millis(500), first.getNode());
    firstTranslate.setByX(secondX - firstX);
    TranslateTransition secondTranslate = new TranslateTransition(Duration.millis(500), second.getNode());
    secondTranslate.setByX(firstX - secondX);
    ParallelTransition translate = new ParallelTransition(firstTranslate, secondTranslate);

    translate.statusProperty().addListener((obs, oldStatus, newStatus) -> {
        if (oldStatus == Animation.Status.RUNNING) {
            T temp = first.getYValue();
            first.setYValue(second.getYValue());
            second.setYValue(temp);
            first.getNode().setTranslateX(firstStartTranslate);
            second.getNode().setTranslateX(secondStartTranslate);
        }
    });

    return translate;
}

这里的基本思想非常简单:我们测量两个节点之间的 x 坐标距离;记下它们当前的translateX 属性,然后创建两个转换来移动节点,以便它们占据彼此的位置。这两个转换是并行执行的。当转换完成时(由转换状态从RUNNING 更改为其他值来指示),图表中的值被交换,translateX 属性重置为之前的值(这些效果将在视觉上抵消,但现在图表数据将反映两者已交换的事实)。

如果您想执行一种排序算法,在排序中为交换设置动画,在算法的每个步骤之间暂停,您可以使用后台线程执行此操作(您也可以使用动画执行此操作 - 但这看起来很简单,也许更有指导意义)。

这里的想法是创建一个Task,其call() 方法执行排序算法,在不同点暂停以允许用户查看正在发生的事情。因为我们正在暂停(阻塞),所以这不能在 FX 应用程序线程上运行,因为阻塞会阻止 UI 更新,直到整个过程完成。

这是一个冒泡排序的实现(为简单起见)。在排序的每次迭代中,我们:

  • 用绿色突出显示要比较的两个条*
  • 暂停以便用户可以看到
  • 如果需要交换值:
    • 获取上面定义的动画并运行它*
  • 再次暂停,然后
  • 重置颜色*。

上述伪代码中标有 * 的步骤更改了 UI,因此它们必须在 FX 应用程序线程上执行,因此需要将它们包装在对 Platform.runLater(...) 的调用中,这会导致提供的代码在 FX 上执行应用程序线程。

这里最后一个棘手的部分(这非常棘手)当然是动画需要一些时间来执行。所以我们必须安排我们的后台线程等到动画完成。我们通过创建一个计数为 1 的CountDownLatch 来做到这一点。动画完成后,我们将锁存器倒数。然后在将动画提交到Platform.runLater(..) 之后,我们的后台线程通过调用latch.await() 等待闩锁倒计时再继续。后台线程需要等待某些东西在 FX 应用程序线程上运行是很不寻常的,但这是在您确实需要它的情况下执行此操作的一种技术。

冒泡排序的实现如下所示

private Task<Void> createSortingTask(Series<String, Number> series) {
    return new Task<Void>() {
        @Override
        protected Void call() throws Exception {

            ObservableList<Data<String, Number>> data = series.getData();
            for (int i = data.size() - 1; i >= 0; i--) {
                for (int j = 0 ; j < i; j++) {

                    Data<String, Number> first = data.get(j);
                    Data<String, Number> second = data.get(j + 1);

                    Platform.runLater(() -> {
                        first.getNode().setStyle("-fx-background-color: green ;");
                        second.getNode().setStyle("-fx-background-color: green ;");
                    });

                    Thread.sleep(500);

                    if (first.getYValue().doubleValue() > second.getYValue().doubleValue()) {
                        CountDownLatch latch = new CountDownLatch(1);
                        Platform.runLater(() -> {
                            Animation swap = createSwapAnimation(first, second);
                            swap.setOnFinished(e -> latch.countDown());
                            swap.play();
                        });
                        latch.await();
                    }
                    Thread.sleep(500);

                    Platform.runLater(() -> {
                        first.getNode().setStyle("");
                        second.getNode().setStyle("");
                    });
                }
            }
            return null;
        }
    };
}

这是一个完整的演示。由于排序算法及其暂停被封装为Task,因此如果需要,我们可以利用它的回调和状态属性。例如,我们在开始任务之前禁用按钮,并在完成时使用onSucceeded 处理程序再次启用它们。添加“取消”选项也很容易。

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javafx.animation.Animation;
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;

public class AnimatedBubbleSort extends Application {

    private Random rng = new Random();

    private ExecutorService exec = Executors.newCachedThreadPool(runnable -> {
        Thread t = new Thread(runnable);
        t.setDaemon(true);
        return t;
    });

    @Override
    public void start(Stage primaryStage) {
        BarChart<String, Number> chart = new BarChart<>(new CategoryAxis(), new NumberAxis());
        chart.setAnimated(false);
        Series<String, Number> series = generateRandomIntegerSeries(10);
        chart.getData().add(series);

        Button sort = new Button("Sort");

        Button reset = new Button("Reset");
        reset.setOnAction(e -> chart.getData().set(0, generateRandomIntegerSeries(10)));

        HBox buttons = new HBox(5, sort, reset);
        buttons.setAlignment(Pos.CENTER);
        buttons.setPadding(new Insets(5));

        sort.setOnAction(e -> {
            Task<Void> animateSortTask = createSortingTask(chart.getData().get(0));
            buttons.setDisable(true);
            animateSortTask.setOnSucceeded(event -> buttons.setDisable(false));
            exec.submit(animateSortTask);
        });

        BorderPane root = new BorderPane(chart);
        root.setBottom(buttons);
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private Task<Void> createSortingTask(Series<String, Number> series) {
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {

                ObservableList<Data<String, Number>> data = series.getData();
                for (int i = data.size() - 1; i >= 0; i--) {
                    for (int j = 0 ; j < i; j++) {

                        Data<String, Number> first = data.get(j);
                        Data<String, Number> second = data.get(j + 1);

                        Platform.runLater(() -> {
                            first.getNode().setStyle("-fx-background-color: green ;");
                            second.getNode().setStyle("-fx-background-color: green ;");
                        });

                        Thread.sleep(500);

                        if (first.getYValue().doubleValue() > second.getYValue().doubleValue()) {
                            CountDownLatch latch = new CountDownLatch(1);
                            Platform.runLater(() -> {
                                Animation swap = createSwapAnimation(first, second);
                                swap.setOnFinished(e -> latch.countDown());
                                swap.play();
                            });
                            latch.await();
                        }
                        Thread.sleep(500);

                        Platform.runLater(() -> {
                            first.getNode().setStyle("");
                            second.getNode().setStyle("");
                        });
                    }
                }
                return null;
            }
        };
    }

    private <T> Animation createSwapAnimation(Data<?, T> first, Data<?, T> second) {
        double firstX = first.getNode().getParent().localToScene(first.getNode().getBoundsInParent()).getMinX();
        double secondX = first.getNode().getParent().localToScene(second.getNode().getBoundsInParent()).getMinX();

        double firstStartTranslate = first.getNode().getTranslateX();
        double secondStartTranslate = second.getNode().getTranslateX();

        TranslateTransition firstTranslate = new TranslateTransition(Duration.millis(500), first.getNode());
        firstTranslate.setByX(secondX - firstX);
        TranslateTransition secondTranslate = new TranslateTransition(Duration.millis(500), second.getNode());
        secondTranslate.setByX(firstX - secondX);
        ParallelTransition translate = new ParallelTransition(firstTranslate, secondTranslate);

        translate.statusProperty().addListener((obs, oldStatus, newStatus) -> {
            if (oldStatus == Animation.Status.RUNNING) {
                T temp = first.getYValue();
                first.setYValue(second.getYValue());
                second.setYValue(temp);
                first.getNode().setTranslateX(firstStartTranslate);
                second.getNode().setTranslateX(secondStartTranslate);
            }
        });

        return translate;
    }

    private Series<String, Number> generateRandomIntegerSeries(int n) {
        Series<String, Number> series = new Series<>();
        for (int i = 1; i <= n; i++) {
            series.getData().add(new Data<>(Integer.toString(i), rng.nextInt(90) + 10));
        }
        return series;
    }

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-03
    • 2012-01-19
    • 1970-01-01
    • 2013-03-22
    • 1970-01-01
    • 2012-10-18
    • 2013-12-11
    相关资源
    最近更新 更多