【问题标题】:Java - custom dialogs with background threadsJava - 带有后台线程的自定义对话框
【发布时间】:2022-07-07 13:25:45
【问题描述】:

我正在尝试在 java 中引发一个自定义加载对话框,然后执行一些需要几秒钟的同步函数。

只要函数执行,我希望对话框一直存在,一旦完成,我会关闭对话框。

我的对话框如下所示:

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;
    }
}

这就是我创建它并展示它的方式:

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();
        });
    }

这是它在调用类中的样子:

DialogLoaderControlImpl preloader = new DialogLoaderControlImpl(new LoaderDialogInfo("Searching For New Versions"));
DialogsController.getInstance().createIndependentDialog(preloader);
someSynchronousMethod();
preloader.closeDialog();

问题是当我到达“preloader.closeDialog()”行时,应该关闭对话框的 closeDialog 函数为空(onClose 字段为空)。

简而言之:

createIndependentDialog() 方法应该引发一个对话框,我想在对话框仍然显示时继续执行方法“someSynchronousMethod()”,并在方法完成后关闭它。

请注意,我在此处未显示的对话框中使用了皮肤,但如果我删除 Platform.runLater,它会起作用,但随后它会卡在 showAndWait() 中而没有按预期推进

是否有某种方式或已知设计有助于使用自定义对话框运行任务/方法?

【问题讨论】:

  • 创建并发布minimal reproducible example;即一个完整的示例,我们可以复制、粘贴和运行,而无需引用其他类。使用Thread.sleep() 模拟后台长时间运行的任务。这里的代码太多了,我们不知道它是做什么的。
  • 并且:“执行一些需要几秒钟的同步函数”。你的意思是这里的“异步”,确定吗?如果需要几秒钟,您将无法在 FX 应用程序线程上同步执行它,因为 UI 会冻结。
  • 听起来你需要一个进度温度计,而不是一个对话框。对话框用于与用户进行交互,一个对话框。
  • ControlsFX 有一个ProgressDialog。使用它或研究它的implementation。也学习了解concurrency in JavaFX

标签: java multithreading user-interface javafx


【解决方案1】:

这可以做到,但正如 cmets 中所指出的,使用某种类型的进度节点可能会更好。我在这个例子中使用了Alert,但Dialog 应该非常相似。

关键是在任务完成后使用任务的setOnSucceeded关闭警报/对话框。

longRunningTask.setOnSucceeded((t) -> {
    System.out.println("Task Done!");            
    alert.close();
});

完整代码

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();
    }
}

来自https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm的更改代码。

我看到的一个陷阱是有人在任务完成之前关闭了Alert/Dialog

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多