所以我可以看到几种不同的方式来做我需要的事情,我已经做了很多Google / Stack溢出搜索,但是找不到我真正想要的东西。我需要运行多个“倒数计时器”。我需要在不同的时间一次运行大约6个可能多达10个倒数计时器。我的主程序上有一个选项卡窗格,其中包括FXML并将控制器注入其中。计时器选项卡具有与主程序不同的控制器。
所以我的第一个问题是。由于此“选项卡”在单独的控制器上运行,但包含在主程序中,因此它是否在单独的应用程序线程上运行?
以下是包含的选项卡FXML的示例...
当我按下每个开始按钮时。我可以为每个计时器创建一个Timeline
和KeyFrame
。但是,我并不是真的认为这是最好的方法。特别是一次您最多同时运行10条时间轴,并且确定是否没有在与主程序分开的应用程序线程上运行。
我考虑过将每个启动请求发送到ExecutorService
和newCacheThreadPool
,但是我需要能够使用当前剩余时间更新GUI上的标签,并且我知道您不应该这样做具有后台服务。 Platform.runLater()
也许吗?
另一个想法是使用Timer
类中的java.util.Timer
。但是,当我需要更新GUI标签时,我认为这与ExecutorService
存在相同的问题。我也了解Timer
类仅创建一个线程并按顺序执行其任务。所以,那是行不通的。
或者,我是否应该拥有另一个完整的“ CountDown”类,我可以为每个类创建新的实例,然后在其中启动新线程。但是,如果这样做,我将如何不断更新GUI。我仍然需要使用timeline
来轮询CountDown类吗?因此,这将破坏整个事情的目的。
我只是一直坚持最好的方法,希望我不会被否决或删除主题,因为我没有包含任何代码,也没有太开放供讨论。
谢谢
答案 0 :(得分:3)
所以我的第一个问题是。由于此“选项卡”在单独的控制器上运行,但包含在主程序中,因此它是否在单独的应用程序线程上运行?
不,每个JVM只能有一个 个JavaFX应用程序实例,每个JVM只能有一个一个 JavaFX应用程序线程。
关于如何更新计时器,可以使用Timeline
-每个计时器一个。 Timeline
不在单独的线程上运行-它由底层的“场景图呈现脉冲”触发,该脉冲负责定期更新JavaFX GUI。拥有更多Timeline
实例基本上只是意味着有更多的侦听器订阅“ pulse”事件。
public class TimerController {
private final Timeline timer;
private final ObjectProperty<java.time.Duration> timeLeft;
@FXML private Label timeLabel;
public TimerController() {
timer = new Timeline();
timer.getKeyFrames().add(new KeyFrame(Duration.seconds(1), ae -> updateTimer()));
timer.setCycleCount(Timeline.INDEFINITE);
timeLeft = new SimpleObjectProperty<>();
}
public void initialize() {
timeLabel.textProperty().bind(Bindings.createStringBinding(() -> getTimeStringFromDuration(timeLeft.get()), timeLeft));
}
@FXML
private void startTimer(ActionEvent ae) {
timeLeft.set(Duration.ofMinutes(5)); // For example timer of 5 minutes
timer.playFromStart();
}
private void updateTimer() {
timeLeft.set(timeLeft.get().minusSeconds(1));
}
private static String getTimeStringFromDuration(Duration duration) {
// Do the conversion here...
}
}
当然,您还可以使用Executor
和其他线程方法,只要您通过Label
更新Platform.runLater()
。或者,您可以使用Task
。
这是使用后台线程时的一般示例:
final Duration countdownDuration = Duration.ofSeconds(5);
Thread timer = new Thread(() -> {
LocalTime start = LocalTime.now();
LocalTime current = LocalTime.now();
LocalTime end = start.plus(countDownDuration);
while (end.isAfter(current)) {
current = LocalTime.now();
final Duration elapsed = Duration.between(current, end);
Platform.runLater(() -> timeLeft.set(current)); // As the label is bound to timeLeft, this line must be inside Platform.runLater()
Thread.sleep(1000);
}
});
答案 1 :(得分:1)
要添加到Jai发布的good answer中,您可以测试不同的实现以提高性能,并通过简单的打印输出找出它们是否使用单独的线程:
import java.io.IOException;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.PauseTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TimersTest extends Application {
@Override public void start(Stage stage) throws IOException {
System.out.println("Fx thread id "+ Thread.currentThread().getId());
VBox root = new VBox(new TimeLineCounter(), new PauseTransitionCounter(), new TaskCounter());
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) { launch(args); }
}
abstract class Counter extends Label {
protected int count = 0;
public Counter() {
setAlignment(Pos.CENTER); setPrefSize(25, 25);
count();
}
abstract void count();
}
class TimeLineCounter extends Counter {
@Override
void count() {
Timeline timeline = new Timeline();
timeline.setCycleCount(Animation.INDEFINITE);
KeyFrame keyFrame = new KeyFrame(
Duration.seconds(1),
event -> { setText(String.valueOf(count++) ); }
);
timeline.getKeyFrames().add(keyFrame);
System.out.println("TimeLine thread id "+ Thread.currentThread().getId());
timeline.play();
}
}
class PauseTransitionCounter extends Counter {
@Override
void count() {
PauseTransition pauseTransition = new PauseTransition(Duration.seconds(1));
pauseTransition.setOnFinished(event ->{
setText(String.valueOf(count++) );
pauseTransition.play();
});
System.out.println("PauseTransition thread id "+ Thread.currentThread().getId());
pauseTransition.play();
}
}
class TaskCounter extends Counter {
@Override
void count() { count(this); }
void count(final Label label) {
Task<Void> counterTask = new Task<>() {
@Override
protected Void call() throws Exception {
try {
System.out.println("Task counter thread id "+ Thread.currentThread().getId());
while(true){
Platform.runLater(() -> label.setText(String.valueOf(count++)));
Thread.sleep(1000);
}
} catch (InterruptedException e) {e.printStackTrace(); }
return null;
}
};
Thread th = new Thread(counterTask); th.setDaemon(true); th.start();
}
}
打印输出按预期显示Timeline
和PauseTransition
在FX线程上,而Task
不在:
Fx线程ID 15
TimeLine线程ID 15
PauseTransition 线程ID 15
任务计数器线程ID 19
答案 2 :(得分:0)
您正在寻找的是RxJava及其与JavaFx的桥梁,即RxJavaFx。 导入依赖项:
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjavafx</artifactId>
<version>2.2.2</version>
</dependency>
然后运行
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.rxjavafx.observables.JavaFxObservable;
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
import io.reactivex.schedulers.Schedulers;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TimersApp extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
VBox vBox = new VBox();
for (int i = 0; i < 4; i++) {
ToggleButton button = new ToggleButton("Start");
Label label = new Label("0");
HBox hBox = new HBox(button, label, new Label("seconds"));
vBox.getChildren().add(hBox);
JavaFxObservable.valuesOf(button.selectedProperty())
.switchMap(selected -> {
if (selected) {
button.setText("Stop");
return Observable.interval(1, TimeUnit.SECONDS, Schedulers.computation()).map(next -> ++next);
} else {
button.setText("Start");
return Observable.empty();
}
})
.map(String::valueOf)
.observeOn(JavaFxScheduler.platform())
.subscribe(label::setText);
}
stage.setScene(new Scene(vBox));
stage.show();
}
}
如果您对此解决方案感兴趣,请告诉我。我将为您提供一些学习材料。
答案 3 :(得分:0)
这是标准Java的另一种方式。根据倒计时运行的时间长短,可能需要在关闭GUI时停止这些执行程序。我还使用ScheduledExecutorService进行多次倒计时。
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class CountDownExecutor extends Application{
private final int i= 15;
private final DateTimeFormatter HH_MM_SS = DateTimeFormatter.ofPattern("HH:mm:ss");
private final Label l1=new Label("00:00:00");
private final Insets insets = new Insets(3,5,3,5);
private final Button button = new Button("Start");
private ScheduledExecutorService executor=null;
private AtomicInteger atomicInteger = new AtomicInteger();
public static void main(String[] args) {
Application.launch(CountDownExecutor.class, args);
}
@Override
public void start(Stage stage) {
HBox hb = new HBox();
button.setOnMouseClicked(a-> countDown());
button.setPadding(insets);
l1.setPadding(insets);
hb.getChildren().addAll(button,l1);
Scene scene = new Scene(hb);
stage.setOnCloseRequest((ev)-> {if(executor!=null) executor.shutdownNow();});
stage.setScene(scene);
stage.show();
}
public void countDown() {
Platform.runLater( () -> button.setDisable(true));
atomicInteger.set(i);
setCountDown(LocalTime.ofSecondOfDay(atomicInteger.get()));
executor = Executors.newScheduledThreadPool(1);
Runnable r = ()->{
int j = atomicInteger.decrementAndGet();
if(j<1 ){
executor.shutdown();
Platform.runLater( () ->{
button.setDisable(false);
});
setCountDown(LocalTime.ofSecondOfDay(0));
}else {
setCountDown(LocalTime.ofSecondOfDay(j));
}
};
executor.scheduleAtFixedRate(r, 1, 1, TimeUnit.SECONDS);
}
public void setCountDown(LocalTime lt) { Platform.runLater(() -> l1.setText(lt.format(HH_MM_SS))); }
}