簡體   English   中英

如何在不使用任何“ButtonType”控件的情況下在 JavaFx 中創建自定義對話框?

[英]How to create a custom dialog in JavaFx without using any "ButtonType"-controls?

我想創建一個自定義Dialog ,它只顯示選項(見圖 1)。 如果用戶選擇這些選項之一,對話框應關閉並立即返回相應的結果。

到目前為止,我只能通過將任意ButtonType添加到Dialog ,使用setVisible(false)隱藏它並在單擊選項的EventHandler中應用fire()來完成此操作。

這種奇怪的解決方法實際上工作正常,但在我看來非常不專業......
那么,如何在不使用 ButtonType 技巧的情況下以更專業或更適當的方式做到這一點?

圖1

我的解決方法代碼如下所示(Dialog 類):

public class CustomDialog extends Dialog<String> {

    private static final String[] OPTIONS
            = new String[]{"Option1", "Option2", "Option3", "Option4"};
    private String selectedOption = null;
    Button applyButton;

    public CustomDialog() {
        super();
        initStyle(StageStyle.DECORATED);
        VBox vBox = new VBox();
        for (String option : OPTIONS) {
            Button optionButton = new Button(option);
            optionButton.setOnAction((event) -> {
                selectedOption = option;
                applyButton.fire();
            });
            vBox.getChildren().add(optionButton);
        }
        getDialogPane().setContent(vBox);
        getDialogPane().getButtonTypes().add(ButtonType.APPLY);
        applyButton = (Button) getDialogPane().lookupButton(ButtonType.APPLY);
        applyButton.setVisible(false);

        setResultConverter((dialogButton) -> {
            return selectedOption;
        });
    }
}

使用對話框類:

    CustomDialog dialog = new CustomDialog();
    Optional<String> result = dialog.showAndWait();
    String selected = null;
    if (result.isPresent()) {
        selected = result.get();
    } else if (selected == null) {
        System.exit(0);
    }

Dialog只是一個顯示DialogPane的窗口,並且引用了DialogPaneJavadocs

DialogPane基於DialogPane的概念ButtonType ButtonType是單個按鈕的描述符,應在DialogPane直觀地表示。 因此,創建DialogPane開發人員必須指定他們想要顯示的按鈕類型

(我的重點)。 因此,雖然您已經展示了一種可能的解決方法,而在另一個答案中 Slaw 展示了另一種方法,但如果您嘗試使用Dialog而不使用ButtonType及其關聯的結果轉換器,那么您實際上是在使用Dialog類來實現這不是故意的。

您描述的功能可以通過常規模式Stage完美實現。 例如,下面給出了與您描述的相同的基本行為,並且不涉及ButtonType s:

package org.jamesd.examples.dialog;

import java.util.Optional;
import java.util.stream.Stream;

import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;

public class CustomDialog {

    private static final String[] OPTIONS 
        = {"Option 1", "Option 2", "Option 3", "Option 4"};

    private final Stage stage ;

    private String selectedOption = null ;

    public CustomDialog() {
        this(null);
    }

    public CustomDialog(Window parent) {
        var vbox = new VBox();
        // Real app should use an external style sheet:
        vbox.setStyle("-fx-padding: 12px; -fx-spacing: 5px;");
        Stream.of(OPTIONS)
            .map(this::createButton)
            .forEach(vbox.getChildren()::add);
        var scene = new Scene(vbox);
        stage = new Stage();
        stage.initOwner(parent);
        stage.initModality(Modality.WINDOW_MODAL);
        stage.setScene(scene);
    }

    private Button createButton(String text) {
        var button = new Button(text);
        button.setOnAction(e -> {
            selectedOption = text ;
            stage.close();
        });
        return button ;
    }

    public Optional<String> showDialog() {
        selectedOption = null ;
        stage.showAndWait();
        return Optional.ofNullable(selectedOption);
    }
}

這是一個使用此自定義對話框的簡單應用程序類:

package org.jamesd.examples.dialog;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class App extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        var root = new VBox();
        // Real app should use an external style sheet:
        root.setStyle("-fx-padding: 12px; -fx-spacing: 5px;");
        var showDialog = new Button("Show dialog");
        var label = new Label("No option chosen");
        showDialog.setOnAction(e -> 
            new CustomDialog(stage)
                .showDialog()
                .ifPresentOrElse(label::setText, Platform::exit));
        root.getChildren().addAll(showDialog, label);
        stage.setScene(new Scene(root));
        stage.show();
    }

}

正如@Sedrick 和@James_D 所指出的, Dialog API 是圍繞“按鈕類型”的概念構建的。 不使用ButtonType與 API ButtonType ,因此,看起來總是很糟糕/錯誤。 也就是說,您可以對當前代碼稍作改動,以滿足您的“不使用任何 'ButtonType'-controls ” 目標。 它似乎沒有記錄,但如果您手動設置result屬性,它會觸發關閉並返回結果過程。 這意味着您不需要添加任何ButtonType並且可以完全繞過resultConverter 這是一個概念驗證

OptonsDialog.java

import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Dialog;
import javafx.scene.layout.VBox;

public class OptionsDialog<T extends OptionsDialog.Option> extends Dialog<T> {

  public interface Option {

    String getDisplayText();
  }

  @SafeVarargs
  public OptionsDialog(T... options) {
    if (options.length == 0) {
      throw new IllegalArgumentException("must provide at least one option");
    }

    var content = new VBox(10);
    content.setAlignment(Pos.CENTER);
    content.setPadding(new Insets(15, 25, 15, 25));

    for (var option : options) {
      var button = new Button(option.getDisplayText());
      button.setOnAction(
          event -> {
            event.consume();
            setResult(option);
          });
      content.getChildren().add(button);
    }

    getDialogPane().setContent(content);
  }
}

應用程序.java :

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.Window;

public class App extends Application {

  private enum Foo implements OptionsDialog.Option {
    OPTION_1("Option Number 1"),
    OPTION_2("Option Number 2"),
    OPTION_3("Option Number 3"),
    OPTION_4("Option Number 4");

    private final String displayText;

    Foo(String displayText) {
      this.displayText = displayText;
    }

    @Override
    public String getDisplayText() {
      return displayText;
    }
  }

  @Override
  public void start(Stage primaryStage) {
    var button = new Button("Click me!");
    button.setOnAction(
        event -> {
          event.consume();
          showChosenOption(primaryStage, promptUserForOption(primaryStage));
        });
    primaryStage.setScene(new Scene(new StackPane(button), 500, 300));
    primaryStage.show();
  }

  private static Foo promptUserForOption(Window owner) {
    var dialog = new OptionsDialog<>(Foo.values());
    dialog.initOwner(owner);
    dialog.setTitle("Choose Option");
    return dialog.showAndWait().orElseThrow();
  }

  private static void showChosenOption(Window owner, OptionsDialog.Option option) {
    var alert = new Alert(AlertType.INFORMATION);
    alert.initOwner(owner);
    alert.setHeaderText("Chosen Option");
    alert.setContentText(String.format("You chose the following: \"%s\"", option.getDisplayText()));
    alert.show();
  }
}

這與您當前的解決方法沒有什么不同,它仍在針對 API 工作。 這也依賴於未記錄的行為(設置result屬性手動關閉對話框並返回結果)。 底部的ButtonBar仍然占用一些空間,盡管比添加不可見按鈕時少。 但是,可以通過添加以下 CSS 來擺脫這個空白空間:

.options-dialog-pane .button-bar {
  -fx-min-height: 0;
  -fx-pref-height: 0;
  -fx-max-height: 0;
}

請注意,以上假設您已修改代碼以將"options-dialog-pane"樣式類添加到與OptionsDialog DialogPane使用的DialogPane

我認為您應該從Java Docs 中閱讀以下內容:

對話結束規則:

了解關閉對話框時會發生什么以及如何關閉對話框非常重要,尤其是在異常關閉情況下(例如在對話框標題欄中單擊“X”按鈕時,或操作系統特定鍵盤時快捷鍵(例如 Windows 上的 alt-F4))。 幸運的是,在這些情況下結果是明確定義的,可以最好地總結為以下要點:

  • JavaFX 對話框只能在兩種情況下“異常”關閉(如上定義):

    1. 當對話框只有一個按鈕時,或
    2. 當對話框有多個按鈕時,只要其中一個滿足以下要求之一即可:
      1. 該按鈕有一個 ButtonType,其 ButtonBar.ButtonData 的類型為 ButtonBar.ButtonData.CANCEL_CLOSE。
      2. 該按鈕有一個 ButtonType,當調用 ButtonBar.ButtonData.isCancelButton() 時,其 ButtonBar.ButtonData 返回 true。
  • 在所有其他情況下,對話框將拒絕響應所有關閉請求,保持打開狀態,直到用戶單擊對話框的 DialogPane 區域中的可用按鈕之一。

  • 如果對話框異常關閉,並且對話框包含滿足上述兩個條件之一的按鈕,則對話框將嘗試將結果屬性設置為通過調用具有第一個匹配 ButtonType 的結果轉換器返回的任何值。

  • 如果由於某種原因結果轉換器返回 null,或者如果對話框在僅存在一個非取消按鈕時關閉,則結果屬性將為 null,並且 showAndWait() 方法將返回 Optional.empty()。 后面這一點意味着,如果您使用選項 2 或選項 3(如本類文檔前面所述),則永遠不會調用 Optional.ifPresent(java.util.function.Consumer) lambda,代碼將繼續執行就好像對話框根本沒有返回任何值一樣。

如果你不介意Buttons是水平的,你應該使用ButtonTypesetResultConverter根據按下的按鈕返回一個字符串。

import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.stage.StageStyle;
import javafx.util.Callback;

/**
 *
 * @author blj0011
 */
public class CustomDialog extends Dialog<String>
{
    String result = "";

    public CustomDialog()
    {
        super();
        initStyle(StageStyle.DECORATED);

        setContentText(null);
        setHeaderText(null);

        ButtonType buttonOne = new ButtonType("Option1");
        ButtonType buttonTwo = new ButtonType("Option2");
        ButtonType buttonThree = new ButtonType("Option3");
        ButtonType buttonFour = new ButtonType("Option4");

        getDialogPane().getButtonTypes().addAll(buttonOne, buttonTwo, buttonThree, buttonFour);

        setResultConverter(new Callback<ButtonType, String>()
        {
            @Override
            public String call(ButtonType param)
            {
                if (param == buttonOne) {
                    return buttonOne.getText();
                }
                else if (param == buttonTwo) {
                    return buttonTwo.getText();
                }
                else if (param == buttonThree) {
                    return buttonThree.getText();
                }
                else if (param == buttonFour) {
                    return buttonFour.getText();
                }

                return "";
            }
        });
    }
}

更新:正如@Slaw 在評論中所述,您可以將setResultConverter(...)替換為setResultConverter(ButtonType::getText)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM