Issue
I'm using a loading screen in my application while a service is running a task in another thread.
After the service is done, I would like to close the loading screen.
But instead of calling something like window.hide()
every time, I would like to have a binding between the service state and the visibility of the window.
- Service runs --> loading screen visible
- Service runs not --> loading screen invisible
The service has properties like onRunningProperty()
or runningProperty()
and the window has onShownProperty()
or showingProperty()
but I didn't manage to bind them.
How can I bind the visibility of the loading screen with the running state of a service, so that the loading screen is automatically shown, when the service runs and hidden, when the service is done?
Example:
HelloApplication.java
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
HelloController.java
public class HelloController {
@FXML
private Label welcomeText;
private final Service<Void> service = new Service<>() {
@Override
protected Task<Void> createTask() {
return new Task<>() {
@Override
protected Void call() throws Exception {
Thread.sleep(3000);
return null;
}
};
}
};
public HelloController() {
service.setOnSucceeded(e -> {
// now I want to hide the loading screen
WaitController.waitController.waitLabel.getScene().getWindow().hide();
});
}
@FXML
protected void onHelloButtonClick() {
welcomeText.setText("Welcome to JavaFX Application!");
service.restart();
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("wait.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 630, 400);
Stage stage = new Stage();
stage.setTitle("New Window");
stage.setScene(scene);
stage.show();
WaitController.waitController = fxmlLoader.getController();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
WaitController.java
public class WaitController {
@FXML
Label waitLabel;
public static WaitController waitController;
}
wait.fxml
<AnchorPane prefHeight="291.0" prefWidth="428.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.test.WaitController">
<children>
<Label fx:id="waitLabel" layoutX="161.0" layoutY="100.0" prefHeight="92.0" prefWidth="105.0" text="Wait..." textAlignment="CENTER">
<font>
<Font size="18.0" />
</font>
</Label>
</children>
</AnchorPane>
hello-view.fxml
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.example.test.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label fx:id="welcomeText"/>
<Button text="Hello!" onAction="#onHelloButtonClick"/>
</VBox>
Solution
You won't be able to use bindings for this. There are no writable properties of Window
that control whether or not it's showing. There is, of course, the showing
property, but it is read-only. In other words, there's no appropriate property of Window
that you can bind to the service's running
property.
What you can do, however, is listen to the service's running
property and call show()
/ hide()
on the window instance as appropriate. For example:
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
var service = new ServiceImpl();
setupWindowForService(primaryStage, service);
var button = new Button("Start service");
button.disableProperty().bind(service.runningProperty());
button.setOnAction(e -> {
e.consume();
service.restart();
});
primaryStage.setScene(new Scene(new StackPane(button), 600, 400));
primaryStage.show();
}
private void setupWindowForService(Window owner, Service<?> service) {
var window = new Stage();
window.initOwner(owner);
window.initModality(Modality.WINDOW_MODAL);
window.setTitle("Service Window");
window.setScene(new Scene(new StackPane(new Label("Service running...")), 300, 150));
window.setOnCloseRequest(e -> {
if (service.isRunning()) {
// prevents user from closing the window while service is
// running. Perhaps it would make more sense to cancel the
// service?
e.consume();
}
});
// the code that shows and hides the window based on the service's state
service.runningProperty().addListener((obs, wasRunning, isRunning) -> {
if (isRunning) {
window.show();
} else {
window.hide();
}
});
}
private static class ServiceImpl extends Service<Void> {
@Override protected Task<Void> createTask() {
return new Task<>() {
@Override protected Void call() throws Exception {
int max = 3_000;
for (int i = 0; i < max; i++) {
Thread.sleep(1L);
}
return null;
}
};
}
}
}
Answered By - Slaw
Answer Checked By - Timothy Miller (JavaFixing Admin)