JavaFX 2でアプリケーション作成(その11) - torutkのブログの続きです。
JavaFX 2アプリケーションのGUIをFXML(XMLファイル)で定義します。
今回は、FXMLで画面レイアウトを実現するところまで記述し、ボタンを押した時のコントロールは次回のネタとします。
FXMLをロードする処理を記述、画面配置はJavaプログラムから削除
まず、今までJavaプログラムで記述した画面配置はFXMLに記述するので、プログラムではFXMLファイルのロード処理に変えます。
import java.io.IOException; import java.util.ResourceBundle; import java.util.concurrent.Semaphore; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class SemaphoreView extends Application { private Semaphore semaphore = new Semaphore(1); private AcquireService acquireService; @Override public void start(Stage primaryStage) { primaryStage.setTitle("Semaphoreの動きを理解する"); Parent root = createRootFromFxml(); Scene scene = new Scene(root, 360 , 250); String style = SemaphoreView.class.getResource("stylesheet.css").toExternalForm(); scene.getStylesheets().add(style); primaryStage.setScene(scene); primaryStage.show(); } private Parent createRootFromFxml() { Parent root = null; try { root = FXMLLoader.load(getClass().getResource("semaphore.fxml")); } catch (IOException ex) { // エラーダイアログを出したい } return root; } public static void main(String[] args) { launch(args); } }
FXMLLoaderクラスのloadメソッドでFXMLファイルを指定してロードさせています。
また、FXMLのロードで例外発生時にエラー処理をさぼっています。
そういえば、CSSファイルのロードでもエラー処理が必要なのかもしれませんが、こちらはチェック例外ではないので、実行時例外(CSSファイルを削除して実行したら)が発生しました。
FXMLファイルの記述
実行時にクラスファイル SemaphoreView.class と同じディレクトリに置きます。
- semaphore.fxml
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.ProgressBar?> <?import javafx.scene.control.TextField?> <?import javafx.scene.layout.BorderPane?> <?import javafx.scene.layout.GridPane?> <?import javafx.scene.text.Text?> <?import javafx.geometry.Insets?> <BorderPane> <top> <Label text="Semaphoreへの操作とその結果" id="topLabel" BorderPane.alignment="CENTER"> <BorderPane.margin> <Insets top="6" right="6" bottom="6" left="6"/> </BorderPane.margin> </Label> </top> <center> <GridPane styleClass="grid" hgap="10" vgap="16"> <BorderPane.margin> <Insets top="4" right="12" bottom="4" left="12"/> </BorderPane.margin> <children> <Label text="利用可能な許可数:" GridPane.columnIndex="0" GridPane.rowIndex="0"/> <TextField GridPane.columnIndex="1" GridPane.rowIndex="0"> <GridPane.margin> <Insets top="12" right="6" bottom="12" left="6"/> </GridPane.margin> </TextField> <Button text="Try Acquire" maxWidth="Infinity" GridPane.columnIndex="0" GridPane.rowIndex="1"/> <Text text="result" GridPane.columnIndex="1" GridPane.rowIndex="1"> <GridPane.margin> <Insets top="12" right="6" bottom="12" left="6"/> </GridPane.margin> </Text> <Button text="Release" maxWidth="Infinity" GridPane.columnIndex="0" GridPane.rowIndex="2"/> <Button text="Acquire" maxWidth="Infinity" GridPane.columnIndex="0" GridPane.rowIndex="3"/> <ProgressBar maxWidth="Infinity" GridPane.columnIndex="1" GridPane.rowIndex="3"> <GridPane.margin> <Insets top="12" right="6" bottom="12" left="6"/> </GridPane.margin> </ProgressBar> </children> </GridPane> </center> </BorderPane>
import
まず、importが必要です。
使用するタグはimportしたものである必要があります。
<?import javafx.scene.control.Button?>
のように、XMLではなく独自構文となります。このimport文を書くと、
ワイルドカードの使用もOKです。
<?import javafx.scene.control.*?>
ワイルドカードはJavaと同じく再帰的には適用されないので、
<?import javafx.*?>
としてもButtonはimportされません。
BorderPaneのTOPに貼るLabel
今回FXMLのレイアウトで苦労した一つがInsetsの設定です。ドキュメントには説明や例が見当たらず、試行錯誤の連続でした。今回示したFXMLの記述が最適かどうかも分かってはいませんが、とりあえず見た目にAPI版とほぼ同じ配置になったので、一応よしとしました。
BorderPaneのTOPに貼るLabelのレイアウトについて、APIで書いていた次のコード
Label topLabel = new Label("Semaphoreへの操作とその結果"); topLabel.setId("topLabel"); BorderPane.setAlignment(topLabel, Pos.CENTER); BorderPane.setMargin(topLabel, new Insets(6));
は、FXMLでは次の記述となります。
<Label text="Semaphoreへの操作とその結果" id="topLabel" BorderPane.alignment="CENTER"> <BorderPane.margin> <Insets top="6" right="6" bottom="6" left="6"/> </BorderPane.margin> </Label>
textとidは、Labelのプロパティなので、FXMLでもLabelの属性で指定します。
alignmentは、Labelのプロパティではなく、BorderPaneのstaticな(?)定義なので、指定方法が分からず四苦八苦してましたが、BorderPane.alignment、BorderPane.marginという指定になるようです。なお、marginで指定するInsetsは単一項では指定できないので、Labelの属性ではなく子要素としてネストして指定しています。
BorderPaneのCENTERに貼るGridPane
BorderPaneのCENTERに貼るGridPaneのレイアウトについて、APIで書いていた次のコード
GridPane semaphorePane = new GridPane(); semaphorePane.getStyleClass().add("grid"); BorderPane.setMargin(semaphorePane, new Insets(4, 12, 4, 12)); borderPane.setCenter(semaphorePane); semaphorePane.setHgap(10); semaphorePane.setVgap(16);
は、FXMLでは次の記述となります。
<center> <GridPane styleClass="grid" hgap="10" vgap="16"> <BorderPane.margin> <Insets top="4" right="12" bottom="4" left="12"/> </BorderPane.margin> <children> :(中略) </children> </GridPane> </center>
styleClassの指定は属性にしてみました。適用するスタイルクラスが1つのときはこれでよいかと思いますが、2つ以上あるときは、別の書き方が必要と思います。子要素で複数列挙することができるかは未調査です。
BorderPaneのCENTERに貼るGridPaneの余白をBorderPane.marginで指定します。
GridPaneに貼るLabel
GridPaneに貼るLabelのレイアウトについて、APIで書いていた次のコード
Label permitsLabel = new Label("利用可能な許可数:"); semaphorePane.add(permitsLabel, 0, 0);
は、FXMLでは次の記述となります。
<Label text="利用可能な許可数:" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
GridPaneに貼るTextField
GridPaneに貼るTextFieldのレイアウトについて、APIで書いていた次のコード
permitsField = new TextField(); semaphorePane.setMargin(permitsField, new Insets(12, 6, 12, 6)); semaphorePane.add(permitsField, 1, 0);
は、FXMLでは次の記述となります。
<TextField GridPane.columnIndex="1" GridPane.rowIndex="0"> <GridPane.margin> <Insets top="12" right="6" bottom="12" left="6"/> </GridPane.margin> </TextField>
GridPaneに貼るButton
GridPaneに貼るButtonのレイアウトについて、APIで書いていた次のコード
Button tryButton = new Button("Try acquire"); tryButton.setMaxWidth(Double.MAX_VALUE); :(中略) semaphorePane.add(tryButton, 0, 1);
は、FXMLでは次の記述となります。
<Button text="Try Acquire" maxWidth="Infinity" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
maxWidthの指定は、数値のときはそのまま数値を書きますが、Double.MAX_VALUEのときは、"Infinity"と記述しました。Double.MIN_VALUEは、"-Infinity"になるようです。
なお、ボタンを押した時のイベントハンドラの指定は今回は省略しています。
GridPaneに貼るText
GridPaneに貼るTextのレイアウトについて、APIで書いていた次のコード
resultText = new Text(); semaphorePane.setMargin(resultText, new Insets(12, 6, 12, 6)); semaphorePane.add(resultText, 1, 1);
は、FXMLでは次の記述となります。
<Text text="result" GridPane.columnIndex="1" GridPane.rowIndex="1"> <GridPane.margin> <Insets top="12" right="6" bottom="12" left="6"/> </GridPane.margin> </Text>
GridPaneに貼るProgressBar
GridPaneに貼るProgressBarのレイアウトについて、APIで書いていた次のコード
progressBar = new ProgressBar(0); : (bindに関しては中略) progressBar.setMaxWidth(Double.MAX_VALUE); semaphorePane.setMargin(progressBar, new Insets(12, 6, 12, 6)); semaphorePane.add(progressBar, 1, 3);
は、FXMLでは次の記述となります。
<ProgressBar maxWidth="Infinity" GridPane.columnIndex="1" GridPane.rowIndex="3"> <GridPane.margin> <Insets top="12" right="6" bottom="12" left="6"/> </GridPane.margin> </ProgressBar>
なお、bind設定は次回以降とします。
FXML雑感
APIで書いたInsetsの設定がFXMLではなかなか実現できず、SceneBuilderで吐かせたFXMLを参考に書きました。FXMLはスキーマもなく、ドキュメントにmarginまでの解説/サンプルが見当たらず、ここまで至るのに前回の日記から10日近くかかってしまいました。
APIで書くときは、呼び出せるメソッドはJavadoc APIで分かるし、IDEが間違いを書いているその場で指摘してくれるのですが、FXMLではJavadoc APIに相当する網羅的な情報がないのと記述時にエラーチェックがないため、試行錯誤が多く、大変苦労します。
SceneBuilderで丸ごと配置を作ろうともしましたが、BorderPaneがなかったので、生成されるFXMLを参考にする程度でした。
なお、SceneBuilderは、ビジュアルにGUIコントロールを配置しFXMLファイルを生成するツールで、以下からダウンロードできます(本日現在、プレビュー b37版)。
記事一覧
- JavaFX 2.0最初の一歩 - torutkのブログ
- JavaFX 2.0でアプリケーション作成(その1) - torutkのブログ
- JavaFX 2.0でアプリケーション作成(その2) - torutkのブログ
- JavaFX 2.0でアプリケーション作成(その3) - torutkのブログ
- JavaFX 2.0でアプリケーション作成(その4) - torutkのブログ
- JavaFX 2.0でアプリケーション作成(その5) - torutkのブログ
- JavaFX 2.0でアプリケーション作成(その6) - torutkのブログ
- JavaFX 2.0でアプリケーション作成(その7) - torutkのブログ
- JavaFX 2でアプリケーション作成(その8) - torutkのブログ
- JavaFX 2でアプリケーション作成(その9) - torutkのブログ
- JavaFX 2でアプリケーション作成(その10) - torutkのブログ
- JavaFX 2でアプリケーション作成(その11) - torutkのブログ