Положение оповещения в JavaFx меняется на значение по умолчанию после повторного нажатия кнопки - PullRequest
0 голосов
/ 12 октября 2019

Я показываю предупреждение при нажатии кнопки в определенной позиции.

Я сделал, но положение меняется на положение по умолчанию, когда я нажимаю button во второй раз.

MessageController.java

 public class MessageController implements Initializable {

    Alert alert = new Alert(Alert.AlertType.INFORMATION);

    @FXML
    private void handleButtonAction(ActionEvent event) {
        alert.setTitle("Message");
        alert.setHeaderText("You clicked button");
        alert.show();
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        ((Stage) alert.getDialogPane().getScene().getWindow()).setX(50);
        ((Stage) alert.getDialogPane().getScene().getWindow()).setY(50);
    }

Message.fxml

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" fx:controller="message.MessageController">
    <children>
        <Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
    </children>
</AnchorPane>  

Message.java (mainкласс)

public class Message extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("Message.fxml"));

        Scene scene = new Scene(root);

        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}

То же поведение, если я использую setX и setY, где я показываю предупреждение внутри метода

private void handleButtonAction(ActionEvent event) {
    alert.setTitle("Message");
    alert.setHeaderText("You clicked button");
    ((Stage) alert.getDialogPane().getScene().getWindow()).setX(50);
    ((Stage) alert.getDialogPane().getScene().getWindow()).setY(50);
    alert.show();
}  

Выше кода также выбрасывает NullPointerExceptionпосле повторного нажатия кнопки.

Как установить фиксированное положение Alert?

Ответы [ 2 ]

2 голосов
/ 14 октября 2019

Короткий ответ

Если вы назначите Alert владельца Window, проблема исчезнет. И вам не следует использовать:

((Stage) alert.getDialogPane().getScene().getWindow()).setX(50);
((Stage) alert.getDialogPane().getScene().getWindow()).setY(50);

Для установки свойств x и y. Вместо этого используйте следующее:

alert.setX(50);
alert.setY(50);

Примечание: См. Документацию по Dialog, суперклассу Alert, для получения дополнительной информации.

Например:

private void handleButtonAction(ActionEvent event) {
    alert.initOwner(((Node) event.getSource()).getScene().getWindow());
    alert.setTitle("Message");
    alert.setHeaderText("You clicked button");
    alert.setX(50);
    alert.setY(50);
    alert.show();
}

Другой вариант, если вы не хотите назначать владельца, - это каждый раз использовать новый экземпляр Alert вместо повторного использованияодин экземпляр. Ответ Ахмеда Эмада показывает пример этого.


Длинный ответ

Обратите внимание, что все это связано с деталями реализации.

ПричинаAlert или, в более общем смысле, Dialog центрируется на экране, когда вы показываете его во второй раз, как причуду (возможно, ошибку?) реализации. Код, ответственный за поведение, находится в пакете private javafx.scene.control.HeavyweightDialog class. При отображении диалогового окна вызывается следующий метод:

@Override public void show() {
    scene.setRoot(dialogPane);
    stage.centerOnScreen();
    stage.show();
}

Точно такая же процедура применяется для showAndWait(), за исключением того, что она вызывает stage.showAndWait() вместо stage.show(). Как видите, метод centerOnScreen() вызывается до показа Stage. Однако Stage, используемый диалоговым окном, является пользовательским анонимным классом, который переопределяет centerOnScreen():

final Stage stage = new Stage() {
    @Override public void centerOnScreen() {
        Window owner = HeavyweightDialog.this.getOwner();
        if (owner != null) {
            positionStage();
        } else {
            if (getWidth() > 0 && getHeight() > 0) {
                super.centerOnScreen();
            }
        }
    }
};

Вы не указываете владельца, поэтому путь elseказнены. Это где причуды вступают в игру. Перед отображением диалогового окна вызовы к getWidth() и getHeight() возвращают NaN, который не считается больше 0, что означает, что супер реализация centerOnScreen() не вызывается и Stageиспользует значения x и y, которые вы явно задали. Но затем во второй раз, когда вы выводите диалоговое окно, оба значения getWidth() и getHeight() возвращают числа, превышающие 0, что означает, что вызывается super.centerOnScreen(), и этот метод переопределяет любые явно установленные значения x или y.

Однако если вы назначите диалоговое окно владельцем, метод super.centerOnScreen() никогда не будет вызван;вместо этого используется метод positionStage(). Этот метод используется для центрирования диалога над его владельцем, но, в отличие от centerOnScreen(), он учитывает любые явные значения, установленные для свойств x и y.

Это означает, что одно из решений - просто назначить своего Alert владельца, и он всегда будет отображаться в том положении, в котором вы хотите. К сожалению, я не верю, что вы можете решить проблему элегантно, если у вашего Alert нет владельца. Один из вариантов - установить x и y после того, как он был показан, но это может или не может привести к отображению диалога в центре экрана и последующему переходу на заданную вами позицию - не совсем идеальный опыт дляваши конечные пользователи. Это предполагает, что вы должны продолжать использовать тот же экземпляр Alert. Если использовать новый экземпляр Alert каждый раз, когда это приемлемо, это также решит проблему.

Также вам не нужно использовать:

((Stage) alert.getDialogPane().getScene().getWindow()).setX(50);
((Stage) alert.getDialogPane().getScene().getWindow()).setY(50);

Для того, чтобы установитьx и y значения. Например, методы setX и setY объявлены в классе Window, поэтому приведение к Stage не требуется. Но что более важно, класс Dialog также объявляет свойства x и y. Реализация даже делегирует реализации FXDialog, которая, в случае HeavyweightDialog, просто устанавливает значение для внутреннего использования Stage;другими словами, вызов:

alert.setX(50);
alert.setY(50);

Делает то же самое (в основном, см. ниже).

Кроме того, использование getDialogPane().getScene().getWindow() является причиной вашего NullPointerException,Когда вы создаете Dialog, он имеет начальный DialogPane, который автоматически добавляется к Scene, который, в свою очередь, автоматически добавляется к внутреннему Stage. Так что в первый раз вы не получите NPE. Однако при закрытии диалогового окна вызывается следующее (снова из HeavyweightDialog):

@Override public void close() {
    if (stage.isShowing()) {
        stage.hide();
    }

    // Refer to RT-40687 for more context
    if (scene != null) {
        scene.setRoot(DUMMY_ROOT);
    }
}

Как видите, он заменяет корень Scene, что означает, что у DialogPane больше нет Scene и, следовательно, NPE. Это еще одна причина для использования Dialog#setX и Dialog#setY. Обратите внимание, что DialogPane снова становится корнем в методах show() и showAndWait().

Имейте в виду, что использование Stage является подробностью реализации. Из документации :

Диалог в JavaFX оборачивает DialogPane и предоставляет необходимый API для представления его конечным пользователям. В JavaFX 8u40 это по существу означает, что DialogPane показывается пользователям внутри Stage, но в будущих выпусках могут быть предложены альтернативные варианты (такие как «облегченные» или «внутренние» диалоги). Поэтому этот API намеренно игнорирует базовую реализацию и пытается представить общий API для всех возможных реализаций.

Вы должны использовать только API, предоставленный Dialog и DialogPane, и не предполагатьчто-нибудь о реализации.

2 голосов
/ 13 октября 2019

Вы можете достичь того, чего хотите, заставив кнопку создать диалоговое окно, когда вы щелкаете по ней не как член класса, поэтому ваш контроллер будет выглядеть так

public class MessageController implements Initializable {

    @FXML
    private void handleButtonAction(ActionEvent event) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle("Message");
        alert.setHeaderText("You clicked button");
        alert.setX(50);
        alert.setY(50);
        alert.show();
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
    }
}

И причина, по которой вы получили NullPointerExceptionранее, потому что вы пытались получить этап диалога, который не существует до alert.show() в функции нажатия кнопки

...