Короткий ответ
Если вы назначите 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
, и не предполагатьчто-нибудь о реализации.