再次按下按钮后,JavaFx 中的警报位置更改为默认值
Alert position in JavaFx changing to default after I'm pressing button again
我在点击特定位置的按钮时显示警告消息。
我已经完成了,但是当我第二次按下 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 class)
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();
}
Above code also throws NullPointerException
after clicked button for second time.
如何设置Alert
的固定位置?
您可以通过在单击按钮时创建对话框来实现您想要的效果,而不是作为 class 成员,这样您的控制器将看起来像这样
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()
之前不存在的对话框阶段
简答
如果您为 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
的超class,了解更多信息。
例如:
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
在您第二次显示时位于屏幕中心的原因是实现的一个怪癖(可能是一个错误?)。负责该行为的代码在 package-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
是自定义匿名 class,它覆盖了 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()
return NaN
不被认为大于 0
,这意味着超级实现centerOnScreen()
未被调用,Stage
使用您明确设置的 x
和 y
值。但是第二次显示对话框时 getWidth()
和 getHeight()
return 数字都大于 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
class 中声明,因此不需要强制转换为 Stage
。但更重要的是 Dialog
class 还声明了 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
是一个实现细节。来自 the documentation:
A Dialog in JavaFX wraps a DialogPane
and provides the necessary API to present it to end users. In JavaFX 8u40, this essentially means that the DialogPane
is shown to users inside a Stage
, but future releases may offer alternative options (such as 'lightweight' or 'internal' dialogs). This API therefore is intentionally ignorant of the underlying implementation, and attempts to present a common API for all possible implementations.
您应该只使用 Dialog
和 DialogPane
提供的 API,不要对实现做任何假设。
我在点击特定位置的按钮时显示警告消息。
我已经完成了,但是当我第二次按下 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 class)
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();
}
Above code also throws
NullPointerException
after clicked button for second time.
如何设置Alert
的固定位置?
您可以通过在单击按钮时创建对话框来实现您想要的效果,而不是作为 class 成员,这样您的控制器将看起来像这样
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()
之前不存在的对话框阶段
简答
如果您为 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
的超class,了解更多信息。
例如:
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
在您第二次显示时位于屏幕中心的原因是实现的一个怪癖(可能是一个错误?)。负责该行为的代码在 package-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
是自定义匿名 class,它覆盖了 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()
return NaN
不被认为大于 0
,这意味着超级实现centerOnScreen()
未被调用,Stage
使用您明确设置的 x
和 y
值。但是第二次显示对话框时 getWidth()
和 getHeight()
return 数字都大于 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
class 中声明,因此不需要强制转换为 Stage
。但更重要的是 Dialog
class 还声明了 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
是一个实现细节。来自 the documentation:
A Dialog in JavaFX wraps a
DialogPane
and provides the necessary API to present it to end users. In JavaFX 8u40, this essentially means that theDialogPane
is shown to users inside aStage
, but future releases may offer alternative options (such as 'lightweight' or 'internal' dialogs). This API therefore is intentionally ignorant of the underlying implementation, and attempts to present a common API for all possible implementations.
您应该只使用 Dialog
和 DialogPane
提供的 API,不要对实现做任何假设。