JavaFX ダイアログを親ウインドウの中央に表示する
  Java, プログラミング

Java 8u40で、待望のメッセージ・ダイアログがJavaFXに追加されました。 Alertクラスとして提供されるメッセージ・ダイアログはとても便利なんですが、少しだけ不満もあったります。

その不満の1つが「いつもスクリーンの中央に表示されてしまうこと」です。

メッセージ・ダイアログには親ウィンドウが存在することがほとんどですから、スクリーンの中央ではなく親ウィンドウの中央に表示して欲しいものです。

今回は、JavaFXのメッセージ・ダイアログを親ウィンドウの中央に表示するテクニックを紹介します。

メッセージ・ダイアログを表示するサンプルプログラム

はじめに、メッセージ・ダイアログを表示する簡単なプログラムを作ります。

Sample.java
package 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(dialog.getWidth() > 0 && dialog.getHeight() > 0) {
			double x = owner.getX() + owner.getWidth() / 2;
			double y = owner.getY() + owner.getHeight() / 2;
			dialog.setX(x - dialog.getWidth() / 2);
			dialog.setY(y - dialog.getHeight() / 2);
			dialog.getDialogPane().layoutBoundsProperty().removeListener(this);
		}
	}
});

dialog.showAndWait();

layoutBoundsPropertyに設定したChangeListenerは何度か呼び出されます。はじめはダイアログのサイズが決まっていない状態で、dialog.getWidth()dialog.getHeight()の値がDouble.NaNになっています。この状態では位置を決定できないので、dialog.getWidth()dialog.getHeight()が 0 より大きい場合という処理条件を付けています。

一度、位置を決めたらremoveListenr()メソッドを呼び出してリスナー登録を解除しています。これで、繰り返し位置計算されてしまう無駄がなくなります。

プログラムを起動し直してダイアログを表示してみましょう。

今度はちゃんと親ウィンドウの中央にメッセージ・ダイアログが表示されました!

最後にソースコードの全体を掲載しておきます。参考にしてください。

Sample.java
package 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(dialog.getWidth() > 0 && dialog.getHeight() > 0) { double x = owner.getX() + owner.getWidth() / 2; double y = owner.getY() + owner.getHeight() / 2; dialog.setX(x - dialog.getWidth() / 2); dialog.setY(y - dialog.getHeight() / 2); dialog.getDialogPane().layoutBoundsProperty().removeListener(this); } } } ); dialog.showAndWait(); } }