JavaFX使条形图通过UI线程中的延迟更改条形图 [英] JavaFX make bar chart changing bars with delay in UI Thread
问题描述
我有JavaFX主线程,在这里创建了新线程,该线程扩展了Task并进行了排序和替换.一切都很好,但是我想在替换时进行一些延迟(例如100ms)以逐步显示排序,也许是动画.问题是,当我使用Thread.sleep()或TranslateTransition()时,它只是将所有延迟毫秒相加在一起,而这是在更改小节之前发生的一大延迟.如何使延迟在UI线程中正常工作?
I got JavaFX main thread, there I create new Thread that extends Task and sort and replace bars. Everyhing is good, but I want to make some delays(like 100ms) while replacing to show step by step sorting, maybe with animation. The problem is when I use Thread.sleep() or TranslateTransition() it just sum all delays miliseconds together in one big delay that happens before changing bars. How can I make delay that will work properly in UI thread?
在主班:
Sorting sorting = new Sorting();
sortThread = new Thread(sorting, "sort");
sortThread.start();
sortThread.join();
我的课程排序扩展了Task
And my class Sorting extends Task
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;
}
}
}
}
}
推荐答案
JavaFX中有两个基本的线程规则:
There are two basic rules to threading in JavaFX:
-
只能从JavaFX应用程序线程访问作为实际显示的场景图的一部分的
- UI组件(节点).其他一些操作(例如,创建新的
Stage
)也要遵守此规则. - 任何长时间运行或阻塞的操作都应在后台线程(即不是JavaFX应用程序线程)上运行.这是因为JavaFX应用程序线程是呈现UI和响应用户交互所需的线程.因此,如果您阻止了FX Application Thread,则无法呈现UI,并且该应用程序将变得无响应,直到您的操作完成为止.
- UI components (nodes) that are part of a scene graph that is actually displayed can only be accessed from the JavaFX application thread. Some other operations (such as creating a new
Stage
) are also subject to this rule. - Any long-running or blocking operation should be run on a background thread (i.e. not the JavaFX application thread). This is because the JavaFX application thread is the one needed to render the UI and respond to user interaction. Consequently, if you block the FX Application Thread, then the UI cannot be rendered and the application will become unresponsive until your operation completes.
javafx.concurrent
API 提供了用于管理可在后台线程上运行的代码以及在FX应用程序线程上执行回调的工具.
The javafx.concurrent
API provides facilities for managing code that can be run on background threads and executing callbacks on the FX application thread.
javafx.animation
API 还提供了允许在特定时间在JavaFX应用程序线程上执行UI代码的类.请注意,动画API完全避免创建后台线程.
The javafx.animation
API additionally provides classes that allow UI code to be executed on the JavaFX application thread at specific times. Note that the animation API avoids creating background threads at all.
因此,在您的用例中,如果要对条形图中的两个条形交换进行动画处理,可以使用animation API进行.创建执行这种交换的动画的通用方法可能如下所示:
So for your use case, if you want to animate the swapping of two bars in the bar chart, you can do so with the animation API. A general method that creates an animation that performs such a swap might look like this:
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
属性重置为以前的值(这些效果将在视觉上抵消) ,但现在图表数据将反映出两者已被交换的事实).
The basic idea here is pretty simple: we measure the distance in the x-coordinates between the two nodes; make a note of their current translateX
properties, and then create two transitions which move the nodes so they take each others positions. Those two transitions are executed in parallel. When the transitions are complete (indicated by the status of the transition changing from RUNNING
to something else), the values in the chart are exchanged and the translateX
properties reset to their previous values (the effect of these will cancel out visually, but now the chart data will reflect the fact that the two have been exchanged).
如果您想执行一个排序算法,使排序中的交换动画化,在算法的每个步骤之间暂停,则可以使用后台线程来执行此操作(您也可以通过动画来执行此操作-但这可以似乎足够简单,也许更具指导性.)
If you want to perform a sort algorithm which animates the exchanges in the sorting, pausing between each step of the algorithm, you can do this using a background thread (you may be able to do this with an animation too - but this seems simple enough and is perhaps more instructional).
这里的想法是创建一个Task
,其call()
方法执行排序算法,在各个点处暂停以使用户可以查看正在发生的情况.因为我们正在暂停(阻塞),所以不能在FX Application Thread上运行它,因为阻塞会阻止UI更新,直到整个过程完成为止.
The idea here is to create a Task
whose call()
method performs the sort algorithm, pausing at various points to allow the use to see what is happening. Because we are pausing (blocking), this cannot be run on the FX Application Thread, as the blocking would prevent the UI being updated until the entire process was complete.
这里是冒泡排序的实现(为简单起见).在排序的每个迭代中,我们:
Here is an implementation of a bubble sort (for simplicity). On each iteration of the sort, we:
- 以绿色突出显示要比较的两个条形*
- 暂停,以便用户可以看到
- 如果需要交换值:
- 获取上面定义的动画并运行它*
- highlight the two bars to be compared in green*
- pause so the user can see that
- if the values need to be exchanged:
- get the animation defined above and run it*
上面的伪代码中标记为*的步骤更改了UI,因此它们必须在FX Application线程上执行,因此需要将它们包装在对
Steps marked * in the above psuedocode change the UI, so they must be executed on the FX Application thread, so they need to be wrapped in a call to
Platform.runLater(...)
, which causes the provided code to be executed on the FX Application Thread.这里的最后一个棘手的部分(这是非常棘手的)是动画当然需要一些时间来执行.因此,我们必须安排背景线程等待动画完成.为此,我们创建一个计数为1的
CountDownLatch
.动画完成后,我们将闩锁递减计数.然后,在将动画提交到Platform.runLater(..)
之后,我们的后台线程通过调用latch.await()
来等待闩锁递减计数,然后继续进行操作.后台线程需要等待某些东西在FX Application Thread上运行是非常不寻常的,但这是一种在需要时执行此操作的技术.The last tricky part here (and this is unusually tricky) is that the animation, of course, takes some time to execute. So we must arrange for our background thread to wait until the animation is complete. We do this by creating a
CountDownLatch
with a count of 1. When the animation is complete, we count the latch down. Then after submitting the animation toPlatform.runLater(..)
, our background thread just waits for the latch to count down before proceeding, by callinglatch.await()
. It is quite unusual for a background thread to need to wait for something to run on the FX Application Thread, but this is one technique to do that in a case where you do need it.气泡排序的实现看起来像
The implementation of the bubble sort thus looks like
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
处理程序在完成任务后再次启用它们.同样,添加取消"选项也很容易.Here is a complete demo. Since the sort algorithm, with its pauses, is encapsulated as a
Task
, we can leverage its callbacks and state properties if we need. As an example, we disable the buttons before starting the task, and use theonSucceeded
handler to enable them again when it completes. It would be easy to add a "cancel" option too.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); } }
这篇关于JavaFX使条形图通过UI线程中的延迟更改条形图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!