加载窗格(GUI)时的JavaFx ProgressIndicator(JavaFx ProgressIndicator while Loading Pane (GUI))
In my application, I have to build big panes with a lot of content. I will show a ProgressIndicator while the GUI is loading.
My first test, I will show a ProgressIndicator while I adding a lot of tabs into a TabPane.
That's my test Code:
public class SampleController implements Initializable { private TabPane tabPane; @FXML private BorderPane borderPane; ProgressIndicator myProgressIndicator; Task<Void> myLongTask; @Override public void initialize(URL location, ResourceBundle resources) { myProgressIndicator = new ProgressIndicator(); Pane p1 = new Pane(myProgressIndicator); tabPane = new TabPane(); Pane p2 = new Pane(tabPane); myLongTask = new Task<Void>() { @Override protected Void call() throws Exception { for (int i = 1; i < 1000; i++) { // Thread.sleep(10); Tab newTab = new Tab("Number:" + i); tabPane.getTabs().add(newTab); } return null; } }; borderPane.centerProperty().bind(Bindings.when(myLongTask.runningProperty()).then(p1).otherwise(p2)); new Thread(myLongTask).start(); } }
But the application will show the window if the Task has finished. If I replace the lines inside the for-loop with Thread.sleep(10) the application show the Indicator and, after all, sleep, it shows the GUI.
How can I show an Indicator while the GUI is not loaded already?
解决方案
You have a Task that creates a result (i.e. a TabPane). Therefore it's more convenient to use TabPane as type parameter instead of Void also you should call updateProgress to update the progress property and bind that property to the progress property of the ProgressIndicator.
The result can be added to the BorderPane in the onSucceded handler instead of creating a (more or less) complicated binding:
Task<TabPane> myLongTask; @Override public void initialize(URL url, ResourceBundle rb) { myLongTask = new Task<TabPane>() { @Override protected TabPane call() throws Exception { TabPane tabPane = new TabPane(); List<Tab> tabs = tabPane.getTabs(); final int count = 1000 - 1; for (int i = 1; i <= count; i++) { Thread.sleep(10); Tab newTab = new Tab("Number:" + i); tabs.add(newTab); updateProgress(i, count); } return tabPane; } }; myLongTask.setOnSucceeded(evt -> { // update ui with results tabPane = myLongTask.getValue(); borderPane.setCenter(new Pane(tabPane)); }); // add progress indicator to show progress of myLongTask myProgressIndicator = new ProgressIndicator(); myProgressIndicator.progressProperty().bind(myLongTask.progressProperty()); borderPane.setCenter(new Pane(myProgressIndicator)); new Thread(myLongTask).start(); }
Simply creating the tabs is fast however, and you won't see any progress indicator in the UI. Layouting a TabPane with 999 Tabs however is rather slow. The UI will most likely freeze for a short time. You can work around this by adding only a limited number of Tabs in each frame:
Return a List<Tab> from the task instead of a TabPane; these Tabs should not be added to the TabPane (yet). You can use a AnimationTimer to add a fixed number of tabs each frame:
final List<Tab> result = ...; // your tab list // number of elements added each frame final int step = 5; final int size = result.size(); AnimationTimer timer = new AnimationTimer() { int index = 0; @Override public void handle(long now) { tabPane.getTabs().addAll(result.subList(index, Math.min(size, index+step))); index += step; if (index >= size) { this.stop(); } } }; timer.start();
在我的应用程序中,我必须构建包含大量内容的大窗格。我将在加载GUI时显示一个ProgressIndicator。
我的第一个测试,我将在TabPane中添加大量标签时显示ProgressIndicator。
这是我的测试代码:
公共类SampleController实现Initializable { 私人TabPane tabPane; @FXML private BorderPane borderPane; ProgressIndicator myProgressIndicator; 任务< Void> myLongTask; @Override public void initialize(URL location,ResourceBundle resources) { myProgressIndicator = new ProgressIndicator(); Pane p1 =新窗格(myProgressIndicator); tabPane = new TabPane(); Pane p2 = new Pane(tabPane); myLongTask = new任务< Void>() { @Override protected Void call()抛出异常 { for(int i = 1; i< 1000; i ++) { // Thread.sleep(10); Tab newTab = new Tab(Number:+ i); tabPane.getTabs()。add(newTab); } 返回null; } }; borderPane.centerProperty()。bind(Bindings.when(myLongTask.runningProperty())。then(p1).otherwise(p2)); new Thread(myLongTask).start(); } }
但如果任务有,应用程序将显示窗口完了。如果我用 Thread.sleep(10)替换for循环中的行,应用程序会显示指标,毕竟,它会显示GUI。
如果GUI尚未加载,我如何显示指标?
解决方案
您有一个任务,它会创建一个结果(即 TabPane )。因此,使用 TabPane 作为类型参数而不是 Void 更方便你也应该调用 updateProgress 更新 progress property 并将该属性绑定到 ProgressIndicator 的进度属性。
结果可以添加到 onSucceded handler 而不是创建(或多或少)复杂的绑定:
T问< TabPane> myLongTask; @Override public void initialize(URL url,ResourceBundle rb){ myLongTask = new Task< TabPane>(){ @Override protected TabPane call()抛出异常{ TabPane tabPane = new TabPane(); 列表< Tab> tabs = tabPane.getTabs(); final int count = 1000 - 1; for(int i = 1; i< = count; i ++){ Thread.sleep(10); Tab newTab = new Tab(Number:+ i); tabs.add(newTab); updateProgress(i,count); } return tabPane; } }; myLongTask.setOnSucceeded(evt - > { //用结果更新ui tabPane = myLongTask.getValue(); borderPane.setCenter(new Pane(tabPane)); }); //添加进度指示器以显示myLongTask的进度 myProgressIndicator = new ProgressIndicator(); myProgressIndicator.progressProperty()。bind(myLongTask.progressProperty()); borderPane.setCenter(new Pane(myProgressIndicator)); new Thread(myLongTask).start(); }
然而,简单地创建选项卡很快,您将看不到任何进展用户界面中的指标。使用999 Tabs 布置 TabPane 但是相当慢。用户界面很可能会在很短的时间内冻结。您可以通过在每个帧中仅添加有限数量的 Tab 来解决此问题:
返回从任务列出< Tab> 而不是 TabPane ;这些 Tab 不应添加到 TabPane (还)。您可以使用 AnimationTimer 每帧添加固定数量的标签:
final List< Tab> result = ...; //你的标签列表 //每帧添加的元素数量 final int step = 5; final int size = result.size(); AnimationTimer timer = new AnimationTimer(){ int index = 0; @Override public void handle(现在很久){ tabPane.getTabs()。addAll(result.subList(index,Math.min(size,index + step) )); index + = step; if(index> = size){ this.stop(); } } }; timer.start();