Как создать собственный диалог в JavaFx без использования «ButtonType» -контроля? - PullRequest
0 голосов
/ 06 февраля 2020

Я хочу создать пользовательский Dialog, который просто отображает параметры (см. Рисунок 1). Если пользователь выбирает один из этих параметров, диалог должен закрыться и немедленно вернуть соответствующий результат.

Пока что я могу выполнить sh только добавив произвольный ButtonType к Dialog, скрыв его с помощью setVisible(false) и применив fire() в EventHandler нажатых вариант.

Этот странный обходной путь на самом деле работает нормально, но мне кажется очень непрофессиональным ...
Итак, как сделать более профессиональным или правильным способом без использования трюка ButtonType?

figure 1

Мой обходной код выглядит следующим образом (класс диалога):

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

Ответы [ 3 ]

3 голосов
/ 07 февраля 2020

A Dialog - это просто окно, отображающее DialogPane, и, цитируя Javadocs для DialogPane:

DialogPane, работает по концепции ButtonType. ButtonType - дескриптор отдельной кнопки, который должен быть визуально представлен в DialogPane. Разработчики, которые создают DialogPane, поэтому должны указать типы кнопок, которые они хотят отображать

(мой акцент). Поэтому, хотя вы показали один возможный обходной путь, а в другом ответе Слав показал другой, если вы пытаетесь использовать Dialog без использования ButtonType и связанного с ним преобразователя результатов, вы действительно используете Dialog класс для чего-то, для чего он не предназначен.

Функциональность, которую вы описываете, вполне достижима с обычным модальным Stage. Например, следующее дает то же самое базовое c поведение, которое вы описали, и не включает 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();
    }

}
2 голосов
/ 07 февраля 2020

Как указали @Sedrick и @James_D, API Dialog построен на концепции «типов кнопок». Неиспользование ButtonType идет вразрез с API, и поэтому всегда будет казаться хакерским / неправильным. Тем не менее, вы можете внести небольшое изменение в ваш текущий код, который удовлетворяет вашей цели " без использования цели 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" в DialogPane используется с OptionsDialog.

1 голос
/ 07 февраля 2020

Я думаю, вам следует прочитать следующее из Java Документов :

Правила закрытия диалога:

Это важно понимать, что происходит, когда диалог закрывается, а также как его можно закрыть, особенно в случаях ненормального закрытия (например, когда кнопка «X» нажата в строке заголовка диалога или когда операционная система указывает c вводятся сочетания клавиш (например, alt-F4 на Windows). К счастью, результат в этих ситуациях четко определен, и его лучше всего суммировать в следующих пунктах:

  • Диалоги JavaFX могут быть закрыты «ненормально» (как определено выше) в две ситуации:

    1. Когда в диалоговом окне имеется только одна кнопка, или
    2. Когда в диалоговом окне имеется несколько кнопок, если одна из них удовлетворяет одному из следующих требований:
      1. У кнопки есть ButtonType, у которого ButtonBar.ButtonData имеет тип ButtonBar.ButtonData.CANCEL_CLOSE.
      2. У кнопки есть ButtonType, у которого ButtonBar.ButtonData возвращает true, когда вызывается ButtonBar.ButtonData.isCancelButton ().
  • Во всех других ситуациях диалоговое окно будет отказываться отвечать на все запросы на закрытие, оставаясь открытым до тех пор, пока пользователь не нажмет одну из доступных кнопок в Область диалогового окна диалогового окна.

  • Если диалоговое окно закрыто ненормально, и если диалоговое окно содержит кнопку, которая соответствует одному из При двух вышеупомянутых критериях диалоговое окно будет пытаться установить для свойства результата любое значение, возвращаемое при вызове преобразователя результатов с первым совпадающим типом ButtonType.

  • Если по какой-либо причине преобразователь результатов возвращает ноль, или если диалоговое окно закрывается, когда присутствует только одна кнопка «не отменить», свойство result будет иметь значение null, а метод showAndWait () вернет Optional.empty (). Эта более поздняя точка означает, что, если вы используете либо опцию 2, либо опцию 3 (как представлено ранее в документации этого класса), лямбда-выражение Optional.ifPresent (java .util.function.Consumer) никогда не будет вызываться, и код будет продолжить выполнение, как если бы диалог вообще не возвращал никакого значения.

Если вы не возражаете против горизонтального положения 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).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...