[英]How to create a custom dialog in JavaFx without using any "ButtonType"-controls?
我想創建一個自定義Dialog
,它只顯示選項(見圖 1)。 如果用戶選擇這些選項之一,對話框應關閉並立即返回相應的結果。
到目前為止,我只能通過將任意ButtonType
添加到Dialog
,使用setVisible(false)
隱藏它並在單擊選項的EventHandler
中應用fire()
來完成此操作。
這種奇怪的解決方法實際上工作正常,但在我看來非常不專業......
那么,如何在不使用 ButtonType 技巧的情況下以更專業或更適當的方式做到這一點?
我的解決方法代碼如下所示(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
的窗口,並且引用了DialogPane
的Javadocs :
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 對話框只能在兩種情況下“異常”關閉(如上定義):
- 當對話框只有一個按鈕時,或
- 當對話框有多個按鈕時,只要其中一個滿足以下要求之一即可:
- 該按鈕有一個 ButtonType,其 ButtonBar.ButtonData 的類型為 ButtonBar.ButtonData.CANCEL_CLOSE。
- 該按鈕有一個 ButtonType,當調用 ButtonBar.ButtonData.isCancelButton() 時,其 ButtonBar.ButtonData 返回 true。
在所有其他情況下,對話框將拒絕響應所有關閉請求,保持打開狀態,直到用戶單擊對話框的 DialogPane 區域中的可用按鈕之一。
如果對話框異常關閉,並且對話框包含滿足上述兩個條件之一的按鈕,則對話框將嘗試將結果屬性設置為通過調用具有第一個匹配 ButtonType 的結果轉換器返回的任何值。
- 如果由於某種原因結果轉換器返回 null,或者如果對話框在僅存在一個非取消按鈕時關閉,則結果屬性將為 null,並且 showAndWait() 方法將返回 Optional.empty()。 后面這一點意味着,如果您使用選項 2 或選項 3(如本類文檔前面所述),則永遠不會調用 Optional.ifPresent(java.util.function.Consumer) lambda,代碼將繼續執行就好像對話框根本沒有返回任何值一樣。
如果你不介意Buttons
是水平的,你應該使用ButtonType
和setResultConverter
根據按下的按鈕返回一個字符串。
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.