在下面的示例中,有一个FXML控制器运行一个单独的线程来定期更新组件。
如果用户点击导航离开此控制器并返回此控制器,则后台将运行2个线程。 (第一个从未停止过)。
如何确保只有1个线程并且该线程与控制器的生命周期相关?
<BorderPane xmlns:fx="http://javafx.com/fxml/1"
fx:controller="example.BorderController"
fx:id="rootPane">
<left>
<VBox>
<Button text="Pane1" onAction"#goToPane1"/>
<Button text=Pane2" onAction="goToPane2"/>
</VBox>
</left>
</BorderPane>
public class BorderController implements Initializable {
@FXML private BorderPane rootPane;
@FXML
public void goToPane1(ActionEvent event) {
showPage("fxml/Pane1");
}
@FXML
public void goToPane2(ActionEvent event) {
showPage("fxml/Pane2");
}
private void showPage(final Stringpage) {
try {
rootPane.setCenter((Node) FXMLLoader.load(getClass().getResource(page)));
} catch (final IOException ex) {
LOGGER.log(Level.INFO, "An error occurred loading the page.", ex);
}
}
}
public class Pane1Controller implements Initializable {
@FXML private Label toUpdate;
@Override
public void initialize(URL url, ResourceBundle rb) {
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
Platform.runLater(() -> {
System.out.println("Pane1 " + this);
toUpdate.setText(Instant.now().toString());
});
}, 2, 2, SECONDS);
}
}
在2个窗格之间导航后,输出如下:
Pane1 example.Pane1@45892b4c
Pane1 example.Pane1@6dc7392e
Pane1 example.Pane1@45892b4c
Pane1 example.Pane1@6dc7392e
// Repeats
答案 0 :(得分:1)
您可以在窗格中观察节点的sceneProperty()
,并相应地启动或关闭ScheduledExecutor
:
import java.net.URL;
import java.time.Instant;
import java.util.ResourceBundle;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
public class Pane1Controller implements Initializable {
@FXML
private Label toUpdate;
@FXML
private Pane root;
private ScheduledExecutorService scheduler;
@Override
public void initialize(URL url, ResourceBundle rb) {
root.sceneProperty().addListener((obs, oldScene, newScene) -> {
if (newScene == null && scheduler != null) {
scheduler.shutdown();
}
if (newScene != null) {
scheduler = Executors.newScheduledThreadPool(1, runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t ;
});
scheduler.scheduleAtFixedRate(() -> {
Platform.runLater(() -> {
System.out.println("Pane1 " + this);
toUpdate.setText(Instant.now().toString());
});
} , 2, 2, TimeUnit.SECONDS);
}
});
}
}
窗格中的任何节点通常都可以工作,尽管使用窗格本身(我在这里给出fx:id="root"
)可能是最有意义的。注意我还使执行程序使用的线程成为守护程序线程,因此它不会阻止应用程序退出。
最后,请注意,对于您在示例中使用的功能(其中计划任务中的所有内容都在FX应用程序线程上执行),动画API可能比java.util.concurrent
API更清晰,尽管您的真实申请可能需要后者:
import java.net.URL;
import java.time.Instant;
import java.util.ResourceBundle;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
public class Pane1Controller implements Initializable {
@FXML
private Label toUpdate;
@FXML
private Pane root;
private Timeline timeline ;
@Override
public void initialize(URL url, ResourceBundle rb) {
timeline = new Timeline(new KeyFrame(Duration.seconds(2), e-> {
System.out.println("Pane1 " + this);
toUpdate.setText(Instant.now().toString());
}));
timeline.setCycleCount(Animation.INDEFINITE);
root.sceneProperty().addListener((obs, oldScene, newScene) -> {
if (newScene == null) {
timeline.pause();
} else {
timeline.play();
}
});
}
}