Issue
I am trying to raise a custom loading dialog in java and then execute some synchronous function which takes a few seconds.
I would like the dialog to be present as long as the function executes and once it finishes I would close the dialog.
public abstract class LoaderControl extends Control implements SimpleDialogInfo {
private static final StyleablePropertyFactory<LoaderControl> FACTORY = new StyleablePropertyFactory<>(Control.getClassCssMetaData());
private LoaderDialogResponse response;
private final DialogInfo dialogInfo;
private final SimpleStringProperty text = new SimpleStringProperty("");
private final SimpleBooleanProperty spinnerVisible = new SimpleBooleanProperty(true);
private UpdaterStates state;
private CloseDialogFunction onClose;
@Override
public void closeDialog(){
onClose.closeDialog();
}
@Override
public void setCloseDialog(CloseDialogFunction onClose){
this.onClose = onClose;
}
}
This is how I create it and show it:
public void createIndependentDialog(SimpleDialogInfo content, EventHandler<MouseEvent> onClose) {
Platform.runLater(() -> {
Stage stage = new Stage();
Parent p = new StackPane();
Scene s = new Scene(p);
stage.setScene(s);
MFXGenericDialog dialogContent = MFXGenericDialogBuilder.build()
.makeScrollable(true)
.setShowAlwaysOnTop(false)
.get();
MFXStageDialog dialog = MFXGenericDialogBuilder.build(dialogContent)
.toStageDialogBuilder()
.initModality(Modality.APPLICATION_MODAL)
.setDraggable(true)
.initOwner(stage)
.setTitle("Dialogs Preview")
.setOwnerNode(grid)
.setScrimPriority(ScrimPriority.WINDOW)
.setScrimOwner(true)
.get();
dialogContent.setMinSize(350, 200);
MFXFontIcon infoIcon = new MFXFontIcon(content.getDialogInfo().getIcon(), 18);
dialogContent.setHeaderIcon(infoIcon);
dialogContent.setHeaderText(content.getDialogInfo().getHeader());
dialogContent.setContent((Node) content);
MFXGenericDialog finalDialogContent = dialogContent;
MFXStageDialog finalDialog = dialog;
content.setCloseDialog(dialog::close);
convertDialogTo(String.format("mfx-%s-dialog", content.getDialogInfo().getDialogType()));
if(onClose != null)
dialogContent.setOnClose(onClose);
dialog.showAndWait();
});
}
This is how it looks like in the calling class:
DialogLoaderControlImpl preloader = new DialogLoaderControlImpl(new LoaderDialogInfo("Searching For New Versions"));
DialogsController.getInstance().createIndependentDialog(preloader);
someSynchronousMethod();
preloader.closeDialog();
The issue is that when I get to the "preloader.closeDialog()" line, the closeDialog function which should close the dialog is null (the onClose field is null).
In short:
The createIndependentDialog() method should raise a dialog and I would like to proceed to execute the method "someSynchronousMethod()" while the dialog is still shown and close it once the method finishes.
Please note that I use a Skin for the dialog which is not shown here but it works if I remove the Platform.runLater, but then it is stuck in the showAndWait() without advancing which is expected
Is there a way or a known design of some sort that will help to run tasks/methods with custom dialogs?
Solution
This can be done, but as pointed out in the comments, it is probably better to use some type of progress node. I used Alert
in this example but Dialog
should be very similar.
The key is closing the Alert/Dialog after the task is complete using the task's setOnSucceeded
.
longRunningTask.setOnSucceeded((t) -> {
System.out.println("Task Done!");
alert.close();
});
Full Code
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class App extends Application
{
public static void main(String[] args)
{
launch();
}
@Override
public void start(Stage stage)
{
Scene scene = new Scene(new StackPane(new Label("Hello World!")), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
Task<Integer> longRunningTask = new Task<Integer>() {
@Override protected Integer call() throws Exception {
int iterations;
for (iterations = 0; iterations < 100000; iterations++) {
if (isCancelled()) {
break;
}
System.out.println("Iteration " + iterations);
}
return iterations;
}
};
Alert alert = new Alert(Alert.AlertType.INFORMATION);
Button okButton = (Button)alert.getDialogPane().lookupButton(ButtonType.OK);
okButton.setDisable(true);
longRunningTask.setOnSucceeded((t) -> {
System.out.println("Task Done!");
alert.close();
});
new Thread(longRunningTask).start();
alert.setTitle("Hello World");
alert.setHeaderText("Hello");
alert.setContentText("I will close when the long running task ends!");
alert.showAndWait();
}
}
Altered code from https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm.
One pitfall I can see is someone closing the Alert/Dialog
before the task finishes.
Answered By - SedJ601
Answer Checked By - Terry (JavaFixing Volunteer)