Java 8u40 で、 待望のメッセージ ・ ダイアログが JavaFX に追加されました。Alert
クラスとして提供されるメッセージ ・ ダイアログはとても便利なんですが、 少しだけ不満もあったります。
その不満の 1 つが 「いつもスクリーンの中央に表示されてしまうこと」 です。
メッセージ ・ ダイアログには親ウィンドウが存在することがほとんどですから、 スクリーンの中央ではなく親ウィンドウの中央に表示して欲しいものです。
今回は、 JavaFX のメッセージ ・ ダイアログを親ウィンドウの中央に表示するテクニックを紹介します。
メッセージ・ダイアログを表示するサンプルプログラム
はじめに、 メッセージ ・ ダイアログを表示する簡単なプログラムを作ります。
Sample.javapackage com.example;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Sample extends Application {
public static void main(String[] args) {
launch(args);
}
private Stage stage;
@Override
public void start(Stage stage) throws Exception {
this.stage = stage;
Button button = new Button("ダイアログを表示");
button.setOnAction(event -> {
button_Action(event);
});
stage.setWidth(480);
stage.setHeight(320);
stage.setScene(new Scene(new VBox(button)));
stage.show();
}
protected void button_Action(ActionEvent event) {
Alert dialog = new Alert(AlertType.INFORMATION);
dialog.setHeaderText(null);
dialog.setContentText("これはサンプル・メッセージです。");
dialog.showAndWait();
}
}
このプログラムを実行すると、 ウィンドウが 1 つ表示されます。
ダイアログを表示 ボタンをクリックすると、 メッセージ ・ ダイアログが表示されます。
まだ、 なにも対処をしていないので、 親ウィンドウが左上にあっても、 メッセージ ・ ダイアログはかまわずスクリーンの中央に表示されてしまいます。
ダイアログの位置を変更するには
Alert
クラスのインスタンス(サンプルコードの dialog
変数)には、 表示位置を指定するための setX
メソッドと setY
メソッドがあります。
Alert dialog = new Alert(AlertType.INFORMATION);
dialog.setHeaderText(null);
dialog.setContentText("これはサンプル・メッセージです。");
dialog.setX(50);
dialog.setY(50);
dialog.showAndWait();
このように、 setX
メソッドと setY
メソッドを使うことでダイアログの表示位置を変更することは簡単にできます。
ですが、 困ったことに親ウィンドウの中央に表示するための X 座標と Y 座標を求めるにはダイアログ自体の横幅と縦幅が必要になります。
このダイアログのサイズを求めるのが、 少々やっかいなのです。
ダイアログのサイズは表示される直前まで確定できません。そして、 ダイアログは showAndWait
メソッドの呼び出すことで表示されるのですが、 このメソッドはダイアログが閉じられるまで呼び出しもとに復帰しないのです。
Alert dialog = new Alert(AlertType.INFORMATION);
dialog.setHeaderText(null);
dialog.setContentText("これはサンプル・メッセージです。");
dialog.showAndWait();
//ダイアログを表示してからここで位置を指定することはできない!
つまり、 なんとかして showAndWait
の呼び出しから復帰する前に、 ダイアログのサイズを元に位置を決定する処理が必要になります。showAndWait
を呼ばないとダイアログのサイズが確定しないのに、 そんなことができるのかな ・ ・ ・ ?
リスナーを追加してプロパティー変更時に処理をおこなう
ここで役に立つのがリスナーです。他の言語ではイベントハンドラーと呼ばれたりもしますね。
JavaFX では任意のプロパティーに変更リスナーを付けることができるようになりました。これを使ってダイアログのサイズが変わった時 (はじめてサイズが決まった時) に、 位置を決める処理をおこなうことができます。
具体的には getDialogPane()
でペインを取得して、 その layoutBoundsProperty
にリスナーを設定します。
Alert dialog = new Alert(AlertType.INFORMATION);
dialog.setHeaderText(null);
dialog.setContentText("これはサンプル・メッセージです。");
Window owner = this.stage;
dialog.getDialogPane().layoutBoundsProperty().addListener(
new ChangeListener<Bounds>() {
@Override
public void changed(ObservableValue<? extends Bounds> observable,
Bounds oldValue, Bounds newValue) {
if(newValue != null
&& newValue.getWidth() > 0 && newValue.getHeight() > 0) {
double x = owner.getX() + owner.getWidth() / 2;
double y = owner.getY() + owner.getHeight() / 2;
dialog.setX(x - newValue.getWidth() / 2);
dialog.setY(y - newValue.getHeight() / 2);
dialog.getDialogPane().layoutBoundsProperty().removeListener(this);
}
}
});
dialog.showAndWait();
layoutBoundsProperty
に設定した ChangeListener
は何度か呼び出されます。はじめはダイアログのサイズが決まっていない状態で、 newValue
には有意な値が設定されていません。この状態では位置を決定できないので、 newValue.getWidth()
と newValue.getHeight()
が 0 より大きい場合という処理条件を付けています。
一度、 位置を決めたら removeListenr()
メソッドを呼び出してリスナー登録を解除しています。これで繰り返し位置計算されてしまう無駄がなくなります。
プログラムを起動し直してダイアログを表示してみましょう。
今度はちゃんと親ウィンドウの中央にメッセージ ・ ダイアログが表示されました!
最後にソースコードの全体を掲載しておきます。参考にしてください。
Sample.javapackage com.example;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.Window;
public class Sample extends Application {
public static void main(String[] args) {
launch(args);
}
private Stage stage;
@Override
public void start(Stage stage) throws Exception {
this.stage = stage;
Button button = new Button("ダイアログを表示");
button.setOnAction(event -> {
button_Action(event);
});
stage.setWidth(480);
stage.setHeight(320);
stage.setScene(new Scene(new VBox(button)));
stage.show();
}
protected void button_Action(ActionEvent event) {
Alert dialog = new Alert(AlertType.INFORMATION);
dialog.setHeaderText(null);
dialog.setContentText("これはサンプル・メッセージです。");
//ダイアログのサイズが決まったときに位置を設定するリスナーを登録します。
Window owner = this.stage;
dialog.getDialogPane().layoutBoundsProperty().addListener(
new ChangeListener<Bounds>() {
@Override
public void changed(ObservableValue<? extends Bounds> observable,
Bounds oldValue, Bounds newValue) {
if(newValue != null &&
newValue.getWidth() > 0 && newValue.getHeight() > 0){
double x = owner.getX() + owner.getWidth() / 2;
double y = owner.getY() + owner.getHeight() / 2;
dialog.setX(x - newValue.getWidth() / 2);
dialog.setY(y - newValue.getHeight() / 2);
dialog.getDialogPane()
.layoutBoundsProperty().removeListener(this);
}
}
}
);
dialog.showAndWait();
}
}