JavaFX使条形图更改条形,在UI线程中延迟

问题描述 投票:1回答:1

我得到了JavaFX主线程,在那里我创建了新的Thread,它扩展了Task并排序和替换了吧。每次都很好,但我想做一些延迟(比如100ms),同时更换以显示逐步排序,也许是动画。问题是当我使用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;
                }
            }
        }
  }
     }
multithreading sorting user-interface javafx task
1个回答
3
投票

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

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.