JavaFX 定期更新 UI 数组和循环

JavaFX Update UI With Arrays With Loops Periodically

我有一个大集团。它有 10 个组,每个组有一个矩形和两个文本,一个用于名称,一个用于值。我有这 10 组从大到小的数据。我有两个存储我的数据的数组,它们只有 10 个数据。我想用循环显示 UI 中的数据,然后我将清除我的数组并获得新的 10 个数据,我也会在 UI 中显示它。我可以在控制台上看到更改,但它没有出现在 UI 上。我无法控制我的数组,代码是 运行 总是不停地,并且 UI 仅在我的数据完成时才更改。如何定期显示我的数据?

我的循环

            public Group allBars;

            while(bars.size()!=0){
            for (int i = 0; i < 10; i++) {
                top10.add(bars.get(i));
                top10years.add(years.get(i));
            }
            for (int i = 0; i < top10.size(); i++) {
                bars.remove(0);
            }
            for (int i = 0; i < 10; i++) {
                Group group = (Group) allBars.getChildren().get(i);
                Rectangle rectangle = (Rectangle) group.getChildren().get(0);
                Text value = (Text) group.getChildren().get(1);
                Text name = (Text) group.getChildren().get(2);
                rectangle.setFill(Color.BLACK);
                value.setText(String.valueOf(top10.get(i).getValue()));
                name.setText(top10.get(i).getName());
                year.setText(top10years.get(i));
                System.out.println(year.getText()+" "+name.getText()+" "+value.getText());
                TimeUnit.MILLISECONDS.sleep(500);
            }
            top10.clear();
            top10years.clear();
        }

我的 ScreenBuilder screenshot

我的UIscreenshot

我的控制台日志

1500 Vijayanagar 500
1500 Cairo 400
1500 Hangzhou 250
1500 Tabriz 250
1500 Gauda 200
1500 Istanbul 200
1500 Paris 185
1500 Guangzhou 150
1500 Nanjing 147
<----------------->
1501 Beijing 672
1501 Vijayanagar 500
1501 Cairo 399
1501 Hangzhou 250
1501 Tabriz 248
1501 Istanbul 205
1501 Gauda 200
1501 Paris 186
1501 Guangzhou 150
1501 Nanjing 147
<----------------->
1502 Beijing 673
1502 Vijayanagar 499
1502 Cairo 398
1502 Hangzhou 250
1502 Tabriz 246
1502 Istanbul 210
...

更新

我使用 Sedrick 的代码。我没有删除我的数组,而是我给 AtomicInteger 赋值,然后我用这些值创建了一个循环。

 AtomicInteger counter = new AtomicInteger();
        AtomicInteger index = new AtomicInteger();
        index.set(0);
        timeline = new Timeline(
                new KeyFrame(Duration.millis(300), (ActionEvent event) -> {
                    top10.get(counter.getAndIncrement());
                    for (int i = index.get(); i < index.get() + 10; i++) {
                        Group group = (Group) allBars.getChildren().get(i % 10);
                        Rectangle rectangle = (Rectangle) group.getChildren().get(0);
                        Text value = (Text) group.getChildren().get(1);
                        Text name = (Text) group.getChildren().get(2);
                        rectangle.setFill(Color.BLACK);
                        value.setText(String.valueOf(top10.get(i).getValue()));
                        name.setText(top10.get(i).getName());
                        year.setText(top10years.get(i));
                        System.out.println(year.getText() + " " + name.getText() + " " + value.getText());
                        System.out.println("I-> " + i);
                    }
                    index.set(index.get() + 10);
                    System.out.println("Index -> " + index.get());
                    //top10.clear();
                    //top10years.clear();
                }));

        timeline.setCycleCount(bars.size());
        timeline.play();

您的问题是您在读取数据和更新显示时让 FX 应用程序线程一直很忙。该线程负责绘制您的场景并通过输入事件通知您的应用程序。通过使其忙碌 5 秒钟,您的应用程序将在这段时间内无响应。

您可以通过多种方式等待或在后台执行操作,并且只返回 FX 应用程序线程进行与 FX 相关的操作,例如更新您的 UI。

我将在下面向您展示几个示例,其中有一个简单的任务,即每 500 毫秒更新一次 Label 当前日期和时间。不是您的程序,但在要求方面非常接近它。

enableButtons(boolean) 只是一个虚拟方法调用,用于说明您应该在哪里设置 before/after 动画显示。在我的测试中,我禁用了触发动画的按钮,然后重新启用它们。我已经注释掉了这些调用。

  1. 使用动画API。例如,使用 PauseTransition
private void withPauseAnimation() {
  //enableButtons(false);
  DateTimeFormatter fmt = DateTimeFormatter.ISO_DATE_TIME;
  PauseTransition pause = new PauseTransition(Duration.millis(500));
  pause.setOnFinished(new EventHandler<ActionEvent>() {
    int index = 0;
    @Override
    public void handle(ActionEvent event) {
      String disp = fmt.format(LocalDateTime.now());
      System.out.println(disp);
      label.setText(disp);
      if (stage.isShowing() && ++index < 10) {
        pause.play();
      } else {
        //enableButtons(true);
      }
    }
  });
  pause.play();
}

缺点:有点笨拙的程序结构,以及一个动画帧的粒度(60fps = 17ms)

  1. 使用java.util.concurrentAPI。在这里,一个 ScheduledExecutorService
private void withExecutor() {
  //enableButtons(false);
  DateTimeFormatter fmt = DateTimeFormatter.ISO_DATE_TIME;
  ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
  exec.scheduleAtFixedRate(new Runnable() {
    int index = 0;
    public void run() {
      if (stage.isShowing()) {
        String disp = fmt.format(LocalDateTime.now());
        System.out.println(disp);
        Platform.runLater(() -> label.setText(disp));
        if (++index >= 10) {
          exec.shutdownNow();
          //Platform.runLater(() -> enableButtons(true));
        }
      } else {
        exec.shutdownNow();
      }
    }
  }, 0, 500, TimeUnit.MILLISECONDS);
}

缺点:需要使用 Platform.runLater 进行显示更新或任何 FX 内容。不要忘记在关闭您的应用程序之前关闭 Executor,否则它会阻止您的进程终止

  1. 使用javafx.concurrentAPI。在这里,一个 ScheduledService
private class BarsService extends ScheduledService<Void> {
  
  private int index = -1;
  DateTimeFormatter fmt = DateTimeFormatter.ISO_DATE_TIME;
  
  public BarsService() {
    super();
    setDelay(Duration.ZERO);
    setPeriod(Duration.millis(500));
  }
  
  @Override
  public void start() {
    //enableButtons(false);
    super.start();
  }
  
  @Override
  protected Task<Void> createTask() {
    return new Task<Void>() {
      @Override
      protected Void call() throws Exception {
        index++;
        //You're supposed to do background work here, but we only need to increment our counter
        return null;
      }
    };
  }
  
  @Override
  protected void succeeded() {
    super.succeeded();
    String disp = fmt.format(LocalDateTime.now());
    System.out.println(disp);
    label.setText(disp);
    if (index >= 9) {
      super.cancel();
      //enableButtons(true);
    }
  }
  
  @Override
  protected void failed() {
    super.failed();
    getException().printStackTrace();
    //enableButtons(true);
  }
  
}

并开始动画:

new BarsService().start();

缺点:对于这么小的任务来说代码非常冗长

本例使用Timeline。您可以以循环方式使用 Timeline 。注释在代码中。

主要

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 *
 * @author sedj601
 */
public class App extends Application
{

    @Override // Override the start method in the Application class
    public void start(Stage primaryStage)
    {
        List<List<Bar>> barsList = readFileData();//Get data from file.
        
        //Create GUI Nodes.
        Label lblYear = new Label();
        List<Label> lblPlaces = new ArrayList();
        List<Label> lblDataPoints = new ArrayList();
        for(int i = 0; i < barsList.get(0).size(); i++)
        {
            Label lblTempPlaces = new Label();
            lblPlaces.add(lblTempPlaces);
            
            Label lblTempDataPoints = new Label();
            lblDataPoints.add(lblTempDataPoints);
        }
        
        //Use Timeline to loop through the data and update the Labels.
        AtomicInteger counter = new AtomicInteger();
        Timeline timeline = new Timeline(
                 new KeyFrame(Duration.seconds(1), (ActionEvent event) -> {
                 List<Bar> currentBars = barsList.get(counter.getAndIncrement());
                 
                 for(int i = 0; i < currentBars.size(); i++)
                 {
                      lblPlaces.get(i).setText(currentBars.get(i).getPlace());
                      lblDataPoints.get(i).setText(Integer.toString(currentBars.get(i).getDataPoint()));
                      lblYear.setText(currentBars.get(i).getYear());                     
                 }
        }));
        timeline.setCycleCount(barsList.size());
        timeline.play();
        
        //Add Nodes to Parent Nodes. Add Parent Nodes to the Scene
        VBox vbPlaces = new VBox();
        vbPlaces.getChildren().addAll(lblPlaces);
        VBox vbDataPoints = new VBox();
        vbDataPoints.getChildren().addAll(lblDataPoints);        
        HBox hBox = new HBox(vbPlaces, vbDataPoints);
        BorderPane root = new BorderPane();
        root.setTop(lblYear);
        root.setCenter(hBox);
        Scene scene = new Scene(root, 300, 300);
        primaryStage.setTitle("Hello World!"); // Set the stage title
        primaryStage.setScene(scene); // Place the scene in the stage
        primaryStage.show(); // Display the stage
        primaryStage.setResizable(false);
    }

    /**
     * The main method is only needed for the IDE with limited JavaFX support.
     * Not needed for running from the command line.
     */
    public static void main(String[] args)
    {
        launch(args);
    }
    
    //Read the data from the file into a List of List<Bar>.
    public static List<List<Bar>> readFileData()
    {
        List<List<Bar>> barsList = new ArrayList();
        
        List<String> places = new ArrayList();
        places.add("Vijayanagar");
        places.add("Cairo");
        places.add("Hangzhou");
        places.add("Tabriz");
        places.add("Gauda");
        places.add("Istanbul");
        places.add("Paris");
        places.add("Guangzhou");
        places.add("Nanjing");
        
        Random random = new Random();
        for(int i = 0; i < 7; i++)
        {
            List<Bar> bars = new ArrayList();
            for(int t = 0; t < places.size(); t++)
            {
                 bars.add(new Bar("150" + i, places.get(t), random.nextInt(700)));
            }
            Collections.sort(bars);
            barsList.add(bars);
        }
                
        return barsList;
    }
}

酒吧

/**
 *
 * @author sedrick
 */
public class Bar implements Comparable<Bar>{
    private String year;
    private String place;
    private Integer dataPoint;

    public Bar(String year, String place, Integer dataPoint) {
        this.year = year;
        this.place = place;
        this.dataPoint = dataPoint;
    }

    public Integer getDataPoint() {
        return dataPoint;
    }

    public void setDataPoint(Integer dataPoint) {
        this.dataPoint = dataPoint;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public String getPlace() {
        return place;
    }

    public void setPlace(String place) {
        this.place = place;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Bar{year=").append(year);
        sb.append(", place=").append(place);
        sb.append(", dataPoint=").append(dataPoint);
        sb.append('}');
        return sb.toString();
    }    
    
    @Override
    public int compareTo(Bar bar)
    {        
        return bar.getDataPoint().compareTo(this.getDataPoint());
    }
}