最終更新日: 2017年5月1日
FXMLは、Javaオブジェクト・グラフを構築するためのスクリプト可能なXMLベースのマークアップ言語です。 そのようなグラフを手続き型のコードで構築する方法の便利な代替方法であり、JavaFXアプリケーションのユーザー・インタフェースの定義に最適です。これは、XMLドキュメントの階層構造が、JavaFXシーン・グラフの構造と密接に対応しているためです。
このドキュメントでは、FXMLマークアップ言語の概要を示し、これを使用してJavaFXアプリケーションの開発をどのように簡素化できるかについて説明します。
FXMLでは、XML要素は次のいずれかを表します。
クラス・インスタンス、インスタンス・プロパティ、静的プロパティおよびdefineブロックについては、この項で後述します。 スクリプトについては、後の項で説明します。
クラス・インスタンスは、FXMLで複数の方法で構築できます。 最も一般的には、インスタンス宣言要素を使用します。この場合、単純に名前でクラスの新しいインスタンスが作成されます。 クラス・インスタンスを作成するその他の方法には、既存の値の参照、既存の値のコピー、外部FXMLファイルの読込みなどがあります。 それぞれの方法については、次に詳しく説明します。
タグが大文字の(クラスをインポート)で始まる場合、またはJavaの場合と同様に、クラスの完全修飾された(パッケージ名を含む)名を示す場合、要素タグがインスタンス宣言と見なされます。 FXMLローダー(後述)は、このような要素を検出すると、そのクラスのインスタンスを作成します。
クラスのインポートは、"import"処理命令(PI)を使用して行います。 たとえば、次のPIでは、javafx.scene.control.Labelクラスが現在のFXMLドキュメントのネームスペースにインポートされます。
<?import javafx.scene.control.Label?>
このPIでは、javafx.scene.controlパッケージからすべてのクラスが現在のネームスペースにインポートされます。
<?import javafx.scene.control.*?>
JavaBeanのコンストラクタとプロパティの命名規則に従うクラスは、FXMLを使用して簡単にインスタンス化して構成できます。 次の単純ですが完全な例では、javafx.scene.control.Labelのインスタンスを作成し、そのtextプロパティをHello, World!に設定しています。
<?import javafx.scene.control.Label?> <Label text="Hello, World!"/>
この例で、Labelのtextプロパティは、XML属性を使用して設定されます。 プロパティは、ネストされたプロパティ要素を使用して設定することもできます。 プロパティ要素については、この項の後半で詳しく説明します。 プロパティ属性については、後の項で説明します。
Bean規則に準拠しないクラスも、ビルダーと呼ばれるオブジェクトを使用してFXMLで構築できます。 ビルダーについては、後で詳しく説明します。
内部的に、FXMLローダーはcom.sun.javafx.fxml.BeanAdapterのインスタンスを使用して、インスタンス化されたオブジェクトをラップし、そのセッター・メソッドを呼び出します。 このprivate (現在)クラスはjava.util.Mapインタフェースを実装し、コール元がBeanプロパティ値をキー/値ペアとして取得および設定できるようにします。
要素がすでにMap (java.util.HashMapなど)を実装している型を表す場合、要素はラップされず、そのget()およびput()メソッドが直接呼び出されます。 たとえば、次のFXMLではHashMapのインスタンスが作成され、そのfooおよびbarの値が123と456にそれぞれ設定されます。
<HashMap foo="123" bar="456"/>
「fx:値」属性は、デフォルトのコンストラクタを持たないが静的なvalueOf(String)メソッドを提供する型のインスタンスを初期化するために使用できます。 たとえば、java.lang.Stringおよび各プリミティブ・ラッパー型はvalueOf()メソッドを定義し、FXMLで次のように構築できます。
<String fx:value="Hello, World!"/> <Double fx:value="1.0"/> <Boolean fx:value="false"/>
static valueOf(String)メソッドを定義するカスタム・クラスもこの方法で構築できます。
「FX:ファクトリ」属性は、クラスにデフォルト・コンストラクタがないオブジェクトを作成するもう1つの手段です。 属性の値は、クラス・インスタンスを生成するための、静的で引数のないファクトリ・メソッドの名前です。 たとえば、次のマークアップは、3つの文字列値が移入された監視可能な配列リストのインスタンスを作成します。
<FXCollections fx:factory="observableArrayList"> <String fx:value="A"/> <String fx:value="B"/> <String fx:value="C"/> </FXCollections>
Beanの規則に準拠しないクラスのインスタンスを作成する第3の手段(不変の値を表すもの)は"builder"です。 ビルダーのデザイン・パターンは、不変型のインスタンスを生成する可変ヘルパー・クラス(ビルダーと呼ばれる)にオブジェクトの構築を委譲します。
FXMLにおけるビルダーのサポートは、2つのインタフェースによって提供されます。 javafx.util.Builderインタフェースは、実際のオブジェクトを構築するbuild()という名前の単一のメソッドを定義します。
public interface Builder<T> { public T build(); }
javafx.util.BuilderFactoryは、指定された型をインスタンス化できるビルダーを生成します。
public interface BuilderFactory { public Builder<?> getBuilder(Class<?> type); }
デフォルト・ビルダー・ファクトリのJavaFXBuilderFactoryは、javafx.fxmlパッケージで提供されます。 このファクトリは、ほとんどの不変JavaFX型を作成および構成できます。 たとえば、次のマークアップは、デフォルト・ビルダーを使用して、不変javafx.scene.paint.Colorクラスのインスタンスを作成します。
<Color red="1.0" green="0.0" blue="0.0"/>
要素の開始タグの処理時に作成されるBeanタイプとは異なり、ビルダーによって作成されるオブジェクトは、要素の終了タグに到達するまでインスタンス化されません。 これは、要素が完全に処理されるまで、必要なすべての引数が利用できない場合があるためです。 たとえば、前の例のColorオブジェクトは、次のように記述することもできます。
<Color> <red>1.0</red> <green>0.0</green> <blue>0.0</blue> </Color>
Colorインスタンスは、3つの色の構成要素がすべて認識されるまで完全には作成されません。
ビルダーによって作成されるオブジェクトのマークアップを処理する場合、Builderインスタンスは値オブジェクトとして処理され、BuilderがMapインタフェースを実装する場合、put()メソッドを使用してビルダーの属性値が設定されます。 それ以外の場合、ビルダーはBeanAdapterでラップされ、そのプロパティは標準Beanセッターを介して公開されると想定されます。
<fx:include>タグは、別のファイルで定義されたFXMLマークアップからオブジェクトを作成します。 次のように使用されます。
<fx:include source="filename"/>
filenameは、読み込むFXMLファイルの名前です。 先頭のスラッシュ文字で始まる値は、クラスパスからの相対パスとして扱われます。 スラッシュのない値は、現在のドキュメントのパスを基準にして考慮されます。
たとえば、次のマークアップを考えてみます。
<?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <VBox xmlns:fx="http://javafx.com/fxml"> <children> <fx:include source="my_button.fxml"/> </children> </VBox>
my_button.fxmlに次が含まれるとします。
<?import javafx.scene.control.*?> <Button text="My Button"/>
この場合、生成されるシーン・グラフには、子ノードとして単一のButtonを持つルート・オブジェクトとしてVBoxが含まれます。
fxネームスペース接頭辞が使用されていることに注意してください。 これは、FXMLソース・ファイルの内部処理に使用される要素と属性の数を定義する予約済接頭辞です。 通常、これはFXMLドキュメントのルート要素で宣言されます。 fxネームスペースによって提供されるその他の機能については、以降の項で説明します。
<fx:include>は、読み込まれるコンテンツのローカライズに使用されるリソース・バンドルの名前を指定する属性、およびソース・ファイルのエンコードに使用される文字セットもサポートします。 リソース解決については、後の項で説明します。
<fx:include source="filename" resources="resource_file" charset="utf-8"/>
<fx:constant>要素は、クラス定数への参照を作成します。 たとえば、次のマークアップは、ButtonインスタンスのminWidthプロパティの値を、java.lang.Doubleクラスで定義されたNEGATIVE_INFINITY定数の値に設定します。
<Button> <minHeight><Double fx:constant="NEGATIVE_INFINITY"/></minHeight> </Button>
<fx:reference>要素は、既存の要素への新しい参照を作成します。 このタグが出現する場所では、要素は実際には必ず名前付き要素の値に置換されます。 これはfx:id属性またはスクリプト変数とともに使用されます。どちらについても後の項で詳しく説明します。 <fx:reference>要素のsource属性は、新しい要素が参照するオブジェクトの名前を指定します。
たとえば、次のマークアップは、前に定義したmyImageという名前のImageインスタンスをImageViewコントロールのimageプロパティに割り当てます。
<ImageView> <image> <fx:reference source="myImage"/> </image> </ImageView>
属性変数解決演算子を使用して変数を逆参照することもできますが(後で「属性」の項で説明します)、fx:referenceは、通常、コレクションに参照を追加する場合など、参照値を要素として指定する必要がある場合にのみ使用されます。
<ArrayList> <fx:reference source="element1"/> <fx:reference source="element2"/> <fx:reference source="element3"/> </ArrayList>
他のほとんどの場合は、属性を使用する方がより簡単で簡潔です。
<fx:copy>要素は、既存の要素のコピーを作成します。 <fx:reference>と同様に、fx:id属性またはスクリプト変数とともに使用されます。 要素のsource属性は、コピー元オブジェクトの名前を指定します。 ソース・タイプは、ソース値からコピーを作成するために使用されるコピー・コンストラクタを定義する必要があります。
現在、このようなコピー・コンストラクタを提供するJavaFXプラットフォーム・クラスは存在しないため、この要素は主にアプリケーション開発者向けに提供されています。 これは将来のリリースで変更される可能性があります。
<fx:root>要素は、以前に定義されたルート要素への参照を作成します。 これはFXML文書のルート・ノードとしてのみ有効です。<fx:root>は、主にFXMLマークアップによってサポートされているカスタム・コントロールを作成するときに使用されます。 詳細は、FXMLLoaderの項で説明します。
タグ名が小文字で始まる要素は、オブジェクト・プロパティを表します。 プロパティ要素は次のいずれかを表します。
要素がプロパティ・セッターを表す場合、要素(テキスト・ノードまたはネストされたクラスのインスタンス要素でなければなりません)の内容はプロパティのセッターに値として渡されます。
たとえば、次のFXMLは、Labelクラスのインスタンスを作成し、ラベルのtextプロパティの値をHello, World!に設定します。
<?import javafx.scene.control.Label?> <Label> <text>Hello, World!</text> </Label>
これにより、結果は、属性を使用してtextプロパティを設定した以前の例と同じになります。
<?import javafx.scene.control.Label?> <Label text="Hello, World!"/>
通常、プロパティ要素は、プロパティ値が単純な文字列ベースの属性値を使用して表すことができない複合型の場合、または値の文字列長が非常に長いため、属性として指定すると読みにくくなる場合に使用されます。
FXMLは"型強制"を使用して、必要に応じてプロパティ値を適切な型に変換します。 XMLでサポートされているデータ型は要素、テキストおよび(値がテキストの)属性のみであるため、型の強制が必要となります。 ただし、Javaでは、組込みプリミティブ値型および拡張可能参照型を含む多数の異なるデータ型がサポートされています。
FXMLローダーは、BeanAdapterのcoerce()メソッドを使用して必要な型変換を実行します。 このメソッドは、Stringからbooleanまたはintからdoubleなどの基本のプリミティブ型変換を実行でき、StringからClassまたはStringからEnumへの変換も行います。 ターゲット型でstatic valueOf()メソッドを定義して、追加の変換を実装できます。
読み取り専用のリスト・プロパティは、getterがjava.util.Listのインスタンスを戻し、対応するセッター・メソッドを持たないBeanプロパティです。 読み取り専用リスト要素の内容は、処理されると自動的にリストに追加されます。
たとえば、javafx.scene.Groupのchildrenプロパティは、グループの子ノードを表す読取り専用リスト・プロパティです。
<?import javafx.scene.*?> <?import javafx.scene.shape.*?> <Group xmlns:fx="http://javafx.com/fxml"> <children> <Rectangle fx:id="rectangle" x="10" y="10" width="320" height="240" fill="#ff0000"/> ... </children> </Group>
<children>要素の各サブ要素は、読み取られるたびにGroup#getChildren()によって返されるリストに追加されます。
読み取り専用のマップ・プロパティは、getterがjava.util.Mapのインスタンスを戻し、対応するセッター・メソッドを持たないBeanプロパティです。 読み取り専用マップ要素の属性は、終了タグが処理されるときに地図に適用されます。
javafx.scene.Nodeのpropertiesプロパティは、読取り専用マップ・プロパティの例です。 次のマークアップは、Labelインスタンスのfooおよびbarプロパティを、123と456にそれぞれ設定します。
<?import javafx.scene.control.*?> <Button> <properties foo="123" bar="456"/> </Button>
タイプがListでもMapでもない読み取り専用プロパティは、読み取り専用マップであるかのように扱われます。 getterメソッドの戻り値は、BeanAdapterにラップされ、他の読み取り専用マップと同じ方法で使用できます。
クラスは、javafx.beansパッケージで定義された@DefaultProperty注釈を使用して"デフォルト・プロパティ"を定義することができます。 存在する場合、デフォルト・プロパティを表すサブ要素をマークアップから省略することができます。
たとえば、javafx.scene.layout.Pane (javafx.scene.layout.VBoxのスーパークラス)では子のデフォルト・プロパティが定義されるため<children>要素は不要で、ローダーによってVBoxのサブ要素がコンテナの子コレクションに自動的に追加されます。
<?import javafx.scene.*?> <?import javafx.scene.shape.*?> <VBox xmlns:fx="http://javafx.com/fxml"> <Button text="Click Me!"/> ... </VBox>
デフォルト・プロパティは、コレクションに限定されません。 要素のデフォルト・プロパティがスカラー値を参照する場合、その要素のサブ要素はプロパティの値として設定されます。
たとえば、javafx.scene.control.ScrollPaneではコンテンツのデフォルト・プロパティが定義されるため、コンテンツとしてTextAreaを含むスクロール・ペインを次のように指定できます。
<ScrollPane> <TextArea text="Once upon a time..."/> </ScrollPane>
デフォルト・プロパティを利用すると、FXMLマークアップの冗長度を大幅に低下させることができます。
要素は、"static"プロパティ(ときどき"付属プロパティ"と呼ばれる)を表すこともできます。 静的プロパティは、特定のコンテキストでのみ意味を持つプロパティです。 それらは、それらが適用されるクラスに固有のものではなく、別のクラス(多くの場合、コントロールの親コンテナ)によって定義されます。
静的プロパティには、静的プロパティを定義するクラス名の接頭辞が付けられます。 たとえば、次のFXMLは、GridPaneのrowIndexおよびcolumnIndexプロパティの静的セッターを呼び出します。
<GridPane> <children> <Label text="My Label"> <GridPane.rowIndex>0</GridPane.rowIndex> <GridPane.columnIndex>0</GridPane.columnIndex> </Label> </children> </TabPane>
これは、Javaでおおむね次のように変換されます。
GridPane gridPane = new GridPane(); Label label = new Label(); label.setText("My Label"); GridPane.setRowIndex(label, 0); GridPane.setColumnIndex(label, 0); gridPane.getChildren().add(label);
GridPane#setRowIndex()およびGridPane#setColumnIndex()へのコールは、Labelインスタンスに索引データをアタッチします。 GridPaneでは、レイアウト時にこれらを使用して子を適切に配置します。 AnchorPane、BorderPane、StackPaneなどの他のコンテナは、同様のプロパティを定義します。
インスタンス・プロパティと同様に、静的プロパティ要素は、通常、プロパティ値を属性値で効率的に表すことができない場合に使用されます。 それ以外の場合は、一般に、静的プロパティ属性(後の項で説明します)によってより簡潔で読みやすいマークアップが生成されます。
<fx:define>要素は、オブジェクト階層外に存在するオブジェクトを作成するために使用されますが、他の場所で参照する必要がある場合があります。
たとえば、ラジオ・ボタンを使用する場合は、一般的に、ボタンの選択状態を管理するToggleGroupを定義します。 このグループはシーン・グラフ自体の一部ではないため、ボタンの親に追加することはできません。 定義ブロックを使用すると、ドキュメントの構造全体に影響を与えることなくボタン・グループを作成できます。
<VBox> <fx:define> <ToggleGroup fx:id="myToggleGroup"/> </fx:define> <children> <RadioButton text="A" toggleGroup="$myToggleGroup"/> <RadioButton text="B" toggleGroup="$myToggleGroup"/> <RadioButton text="C" toggleGroup="$myToggleGroup"/> </children> </VBox>
定義ブロック内の要素は、通常、後で要素の値を参照するために使用できるIDが割り当てられます。 IDについては、後の項で詳しく説明します。
FXMLで属性は次のいずれかを表します。
それぞれについて、以降の項で詳しく説明します。
プロパティ要素と同様に、属性を使用してクラス・インスタンスのプロパティを構成することもできます。 たとえば、次のマークアップは、Click Me!というテキストが表示されるボタンを作成します。
<?import javafx.scene.control.*?> <Button text="Click Me!"/>
プロパティ要素と同様に、プロパティ属性は型強制をサポートします。 次のマークアップが処理される場合、x、y、widthおよびheightの値はdoubleに変換され、fill値はColorに変換されます。
<Rectangle fx:id="rectangle" x="10" y="10" width="320" height="240" fill="#ff0000"/>
処理時に適用されるプロパティ要素とは異なり、プロパティ属性は、それぞれの要素の終了タグに到達するまで適用されません。 これは主に、要素のコンテンツが完全に処理されるまで利用できない情報に属性値が依存するケースを容易にするためです(たとえば、すべてのタブが追加されるまで設定できないTabPaneコントロールの選択された索引など)。
FXMLのプロパティ属性とプロパティ要素間のもう1つの重要な相違点は、属性が、その機能を拡張する多数の解決演算子をサポートしていることです。 サポートされている演算子とその説明を次に示します。
文字列として、XML属性は、URLなどの型指定されたロケーション情報をネイティブに表すことはできません。 ただし、マークアップでこのような場所(たとえば、イメージ・リソースのソースなど)を指定することが必要な場合がよくあります。 ロケーション解決演算子(属性値のプレフィクス"@"で表される)は、属性値が単純な文字列ではなく現在のファイルに相対的なロケーションとして扱われるように指定するために使用されます。
たとえば、次のマークアップでは、ImageViewが作成され、現在のFXMLファイルに対する相対パスにあると想定されるmy_image.pngからイメージ・データが移入されます。
<ImageView> <image> <Image url="@my_image.png"/> </image> </ImageView>
Imageは不変オブジェクトであるため、これを作成するにはビルダーが必要です。 または、ImageがvalueOf(URL)ファクトリ・メソッドを定義する場合は、イメージ・ビューを次のように移入できます。
<ImageView image="@my_image.png"/>
image属性の値はFXMLローダーによってURLに変換され、次にvalueOf()メソッドを使用してImageに強制変換されます。
URL内の空白値はエンコードする必要があり、たとえば、My Image.pngという名前のファイルを参照するには、FXMLドキュメントに次を含める必要があります。
<Image url="@My%20Image.png"/>
次のようにはなりません。
<Image url="@My Image.png"/>
FXMLでは、ローカリゼーションの目的でロード時にリソース置換を実行できます。 java.util.ResourceBundleのインスタンスが提供された場合、FXMLローダーはリソース名のインスタンスをロケール固有の値に置き換えます。 リソース名は、次に示すように、%接頭辞によって識別されます。
<Label text="%myText"/>
次のように定義されたリソース・バンドルがローダーに指定されるとします。
myText = This is the text!
この場合、FXMLローダーの出力は、This is the text!というテキストを含むLabelインスタンスになります。
FXMLドキュメントは、名前付き要素とスクリプト変数が一意に識別される可変のネームスペースを定義します。 変数解決演算子を使用すると、コール元は、対応するセッター・メソッドが呼び出される前に、属性値を名前付きオブジェクトのインスタンスに置き換えることができます。 変数参照は、以下に示すように、プレフィクス"$"によって識別されます:
<fx:define> <ToggleGroup fx:id="myToggleGroup"/> </fx:define> ... <RadioButton text="A" toggleGroup="$myToggleGroup"/> <RadioButton text="B" toggleGroup="$myToggleGroup"/> <RadioButton text="C" toggleGroup="$myToggleGroup"/>
要素にfx:id値を代入すると、後で説明する"toggleGroup"属性やスクリプト・コードなどの変数の参照解除属性によって後で参照できる変数がドキュメントのネームスペースに作成されます。 さらに、オブジェクト型が"id"プロパティを定義する場合、この値はobjects setId()メソッドにも渡されます。
属性の値がリソース解決プレフィクスのうちの1つで始まる場合、先頭にバックスラッシュの("")文字を付加することで、その文字をエスケープできます。 たとえば、次のマークアップは、$10.00というテキストが表示されるLabelインスタンスを作成します。
<Label text="\$10.00"/>
前述の属性変数は、ロード時に1回解決されます。 変数値に対する以降の更新は、値の割当て先のプロパティに自動的に反映されません。 多くの場合、これは十分な動作ですが、変数に対する変更がターゲット・プロパティに自動的に伝播されるようにプロパティ値を変数または式にバインドすると便利な場合がよくあります。 この目的で、式バインディングを使用できます。
式バインディングは、可変解像度演算子でも始まりますが、式の値を囲む中括弧のセットが続きます。 たとえば、次のマークアップは、テキスト入力のtextプロパティの値をLabelインスタンスのtextプロパティにバインドします。
<TextField fx:id="textField"/> <Label text="${textField.text}"/>
ユーザーがテキスト入力を入力すると、ラベルのテキスト・コンテンツが自動的に更新されます。
より複雑な式もサポートされています。 サポートされている定数と演算子のリストは次のとおりです。
定数 / 演算子 | 説明 |
---|---|
"string" 'string' | 文字列定数 |
true false | ブール定数 |
null | null値の定数 |
50.0 3e5 42 | 数値定数 |
- (単項演算子) | 数値に適用される、単項マイナス演算子 |
! (単項演算子) | ブールの単項マイナス |
+ - * / % | 数値バイナリ演算子 |
& & || | ブール・バイナリ演算子 |
> >= < <= == != |
比較のバイナリ演算子 両方の引数がComparable型である必要があります |
静的プロパティを表す属性は、静的プロパティ要素と同様に扱われ、同様の構文が使用されます。 たとえば、以前に静的プロパティ要素を説明するために示したGridPaneマークアップは、次のように書き換えることができます。
<GridPane> <children> <Label text="My Label" GridPane.rowIndex="0" GridPane.columnIndex="0"/> </children> </TabPane>
より簡潔であることに加えて、インスタンス・プロパティ属性、サポート・ロケーション、リソース、および変数解決演算子のような静的プロパティ属性は、静的プロパティに式バインディングを作成することができないという唯一の制限です。
イベント・ハンドラ属性は、ドキュメント要素に動作をアタッチする便利な方法です。 setOnEvent()メソッドを定義するクラスにマークアップでイベント・ハンドラを割り当てることができます。
FXMLでは、スクリプト・イベント・ハンドラ、コントローラ・メソッド・イベント・ハンドラおよび式の3つのタイプのイベント・ハンドラ属性がサポートされています。 それぞれについて次に説明します。
スクリプト・イベント・ハンドラは、HTMLのイベント・ハンドラと同様に、イベントが発生したときにスクリプト・コードを実行するイベント・ハンドラです。 たとえば、ボタンのonActionイベントに関する次のスクリプトベースのハンドラは、ユーザーがボタンを押すと、JavaScriptを使用してYou clicked me!というテキストをコンソールに書き込みます。
<?language javascript?> ... <VBox> <children> <Button text="Click Me!" onAction="java.lang.System.out.println('You clicked me!');"/> </children> </VBox>
コード・スニペットの先頭にある言語処理命令の使用に注意してください。 このPIは、イベント・ハンドラの実行にどのスクリプト言語を使用するかをFXMLローダーに指示します。 FXMLドキュメントでインライン・スクリプトが使用される場合は、常にページ言語を指定する必要があり、ドキュメントにつき1回のみ指定できます。 ただし、サポートされている任意の数のスクリプト言語を使用して実装できる外部スクリプトには適用されません。 スクリプトについては、次の項で詳しく説明します。
コントローラ・メソッド・イベント・ハンドラは、ドキュメントのコントローラによって定義されるメソッドです。 コントローラは、FXMLドキュメントのデシリアライズされたコンテンツに関連付けられるオブジェクトで、ドキュメントで定義されるオブジェクト(多くの場合ユーザー・インタフェース要素)の動作を調整します。
コントローラ・メソッド・イベント・ハンドラは、先頭のハッシュ記号の後にハンドラ・メソッドの名前を続けて指定します。 次に例を示します。
<VBox fx:controller="com.foo.MyController" xmlns:fx="http://javafx.com/fxml"> <children> <Button text="Click Me!" onAction="#handleButtonAction"/> </children> </VBox>
ルート要素に「fx:コントローラ」属性を使用することに注意してください。 この属性は、コントローラ・クラスをドキュメントに関連付けるために使用されます。 MyControllerが次のように定義されるとします。
package com.foo; public class MyController { public void handleButtonAction(ActionEvent event) { System.out.println("You clicked me!"); } }
この場合、ユーザーがボタンを押すと、handleButtonAction()がコールされ、You clicked me!というテキストがコンソールに書き込まれます。
一般的に、ハンドラ・メソッドは、標準のイベント・ハンドラのシグネチャに準拠する必要があります。つまり、javafx.event.Eventを拡張し、void (C#のイベント・デリゲートに似ています)を返す型の単一の引数を取る必要があります。 イベントの議論は、イベントの性質に関する重要かつ有用な情報を運ぶことが多い。ただし、オプションであり、必要に応じて省略することができます。 したがって、次も有効なハンドラです。
package com.foo; public class MyController { public void handleButtonAction() { System.out.println("You clicked me!"); } }
コントローラについては、後の項で詳しく説明します。
javafx.event.EventHandlerタイプの変数を指す式を式ハンドラとして使用できます。
式ハンドラを使用した前述の例:
<VBox fx:controller="com.foo.MyController" xmlns:fx="http://javafx.com/fxml"> <children> <Button text="Click Me!" onAction="$controller.onActionHandler"/> </children> </VBox>
次のようなフィールドを含むコントローラを使用:
public class MyController { @FXML public EventHandler<ActionEvent> onActionHandler = new EventHandler<>() { ... } ... }
バインディング式のような他の種類の式は、このコンテキストではサポートされません。
コレクションおよびオブジェクト・プロパティは、setOnEvent()メソッドを使用してリスニングできません。 そのため、特殊なハンドラ・メソッドを使用する必要があります。 ObservableList、ObservableMapまたはObservableSetは、それぞれListChangeListner.Change、MapChangeListener.ChangeまたはSetChangeListener.Changeパラメータを持つハンドラ・メソッドを指す、特殊なonChange属性を使用します。
<VBox fx:controller="com.foo.MyController" xmlns:fx="http://javafx.com/fxml"> <children onChange="#handleChildrenChange"/> </VBox>ここで、ハンドラ・メソッドは次のようになります。
package com.foo; import javafx.collections.ListChangeListener.Change; public class MyController { public void handleChildrenChange(ListChangeListener.Change c) { System.out.println("Children changed!"); } }
同様に、プロパティ・ハンドラは、ChangeListenerの変更されたメソッドと同じパラメータを持つメソッドです。
changed(ObservableValue<? extends T> observable, T oldValue, T newValue)
親プロパティのハンドラは次のようになります。
public class MyController { public void handleParentChange(ObservableValue value, Parent oldValue, Parent newValue) { System.out.println("Parent changed!"); } }
便宜上、最初のパラメータをObservableValueのサブクラスにできます(例: Property)
プロパティに登録するために、特殊なon<propertyName>Change属性を使用する必要があります。
<VBox fx:controller="com.foo.MyController" xmlns:fx="http://javafx.com/fxml" onParentChange="#handleParentChange"/>
コレクションおよびプロパティは、現在スクリプト・ハンドラをサポートしていません。
<fx:script>タグを使用すると、呼び出し元がスクリプト・コードをFXMLファイルにインポートしたり、スクリプトを埋め込んだりすることができます。 任意のJVMスクリプト言語を使用できます(特にJavaScript、Groovy、Clojureなど)。 イベント・ハンドラは、Javaなどの静的型言語よりも疎結合型のスクリプト言語でより簡潔に記述することができるため、スクリプト・コードは頻繁にマークアップまたは関連ソース・ファイルでイベント・ハンドラを定義するために使用されます。
たとえば、次のマークアップは、Button要素にアタッチされたアクション・ハンドラによって呼び出されるhandleButtonAction()という関数を定義します。
<?language javascript?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <VBox xmlns:fx="http://javafx.com/fxml"> <fx:script> function handleButtonAction(event) { java.lang.System.out.println('You clicked me!'); } </fx:script> <children> <Button text="Click Me!" onAction="handleButtonAction(event);"/> </children> </VBox>
ボタンをクリックすると、関数を呼び出すイベント・ハンドラがトリガーされ、前述の例と同じ出力が生成されます。
スクリプト・コードは、外部ファイルでも定義できます。 前の例は、機能に差異がないFXMLファイルとJavaScriptソース・ファイルに分割できます:
<?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <VBox xmlns:fx="http://javafx.com/fxml"> <fx:script source="example.js" charset="cp1252"/> <children> <Button text="Click Me!" onAction="handleButtonAction(event);"/> </children> </VBox>
function handleButtonAction(event) { java.lang.System.out.println('You clicked me!'); }
多くのテキスト・エディタでは、JVMでサポートされている各種スクリプト言語の構文の強調表示が可能であるため、多くの場合、このようにマークアップからコードを分けることが適切です。 これは、ソース・コードとマークアップを読みやすくするためにも役立ちます。
スクリプト・ブロックは、イベント・ハンドラ関数の定義に限定されません。 スクリプト・コードは処理時に実行されるため、結果の出力の構造を動的に構成するために使用することもできます。 簡単な例として、次のFXMLにはlabelTextという名前の変数を定義するスクリプト・ブロックが含まれています。 この変数の値は、Labelインスタンスのテキスト・プロパティを移入するために使用されます。
<fx:script> var myText = "This is the text of my label."; </fx:script> ... <Label text="$myText"/>
警告: JavaFX 8では、importClass() javascript関数はサポートされなくなりました。 前述の例では完全修飾名を使用するか、nashorn互換スクリプトをロードする必要があります。
load("nashorn:mozilla_compat.js"); importClass(java.lang.System); function handleButtonAction(event) { System.out.println('You clicked me!'); }
単純なイベント・ハンドラをスクリプトでインラインまたは外部ファイルで定義して記述することが便利な場合もありますが、多くの場合、より複雑なアプリケーション・ロジックをコンパイル済のJavaなどの強い型指定の言語で定義することが適切です。 前述のように、fx:controller属性を使用すると、コール元はコントローラ・クラスをFXMLドキュメントに関連付けることができます。 コントローラは、ドキュメントで定義されたオブジェクト階層の背後にコードを実装するコンパイル済クラスです。
前述のように、コントローラは多くの場合、マークアップで定義されているユーザー・インタフェース要素のイベント・ハンドラを実装するために使用されます。
<VBox fx:controller="com.foo.MyController" xmlns:fx="http://javafx.com/fxml"> <children> <Button text="Click Me!" onAction="#handleButtonAction"/> </children> </VBox>
package com.foo; public class MyController { public void handleButtonAction(ActionEvent event) { System.out.println("You clicked me!"); } }
多くの場合、この方法でイベント・ハンドラを宣言するだけで十分です。 ただし、コントローラとそれが管理する要素の動作をより詳細に制御する必要がある場合、コントローラはinitialize()メソッドを定義できます。これは、関連する文書の内容が完全にロードされたときに:
public void initialize();
これにより、実装クラスはコンテンツに対して必要な後処理を実行できます。 また、コントローラには、ドキュメントのロードに使用されたリソースおよびドキュメント内の相対パスの解決に使用された場所(通常、ドキュメント自体の場所と同じ)へのアクセスも提供されます。
たとえば、次のコードは、前述の例で行ったようにイベント・ハンドラ属性を使用するのではなく、コードでボタンにアクション・ハンドラをアタッチするinitialize()メソッドを定義します。 ボタン・インスタンス変数は、ドキュメントの読取り時にローダーによって注入されます。 結果のアプリケーション動作は同じです。
<VBox fx:controller="com.foo.MyController" xmlns:fx="http://javafx.com/fxml"> <children> <Button fx:id="button" text="Click Me!"/> </children> </VBox>
package com.foo; public class MyController implements Initializable { public Button button; @Override public void initialize(URL location, Resources resources) button.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("You clicked me!"); } }); } }
前の例では、コントローラ・メンバー・フィールドとイベント・ハンドラ・メソッドはpublicとして宣言されていたので、ローダーによって設定または呼び出すことができます。 実際は、コントローラは通常コントローラを作成するFXMLローダーに対してのみ可視であるため、このことは多くの場合問題とはなりません。 ただし、コントローラ・フィールドやハンドラ・メソッドの可視性がより制限されている開発者にとっては、javafx.fxml.FXML注釈を使用することができます。 この注釈は、保護されたクラス・メンバーまたはプライベート・クラス・メンバーをFXMLにアクセス可能とマークします。 アノテートされているクラスが名前付きモジュールに含まれている場合、そのクラスを含むモジュールは、パッケージを少なくともjavafx.fxmlモジュールにopenする必要があります。
たとえば、前述の例のコントローラは次のように書き換えることができます。
package com.foo; public class MyController { @FXML private void handleButtonAction(ActionEvent event) { System.out.println("You clicked me!"); } }
package com.foo; public class MyController implements Initializable { @FXML private Button button; @FXML protected void initialize() button.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { System.out.println("You clicked me!"); } }); } }
最初の例で、handleButtonAction()は、コントローラのドキュメントで定義されているマークアップで呼び出すことができるように、@FXMLがタグ付けされています。 2番目の例で、ボタン・フィールドはローダーが値を設定できるように注釈付けされています。 initialize()メソッドは同様に注釈付けされます。
<fx:include>要素を介してロードされたネストされたFXMLドキュメントのコントローラ・インスタンスは、インクルード・コントローラのメンバー・フィールドに直接マップされます。 これにより、開発者は、includeによって定義される機能(アプリケーションのメイン・ウィンドウ・コントローラによって提示されるダイアログ・ウィンドウなど)に容易にアクセスできます。 たとえば、次のコードを考えてみます。
<VBox fx:controller="com.foo.MainController"> <fx:define> <fx:include fx:id="dialog" source="dialog.fxml"/> </fx:define> ... </VBox>
public class MainController extends Controller { @FXML private Window dialog; @FXML private DialogController dialogController; ... }
コントローラのinitialize()メソッドがコールされると、dialogフィールドに"dialog.fxml" includeからロードされたルート要素が含まれ、dialogControllerフィールドにincludeのコントローラが含まれます。 その後、たとえば、ダイアログを移入および表示するために、メイン・コントローラは含まれるコントローラ上でメソッドを呼び出すことができます。 それ以外では、fx:includeで参照されるファイルのコンテンツは、main_window_content.fxmlから広がるシーン・グラフの一部になるため、fx:includeをfx:defineでラップして、両方のウィンドウのシーン・グラフを切り離す必要があることに留意してください。
FXMLLoaderクラスは、FXMLソース・ファイルを実際にロードして結果のオブジェクト・グラフを返します。 たとえば、次のコードは、ロードするクラスに対する相対クラスパス上の場所からFXMLファイルをロードし、com.foo.exampleという名前のリソース・バンドルを使用してローカライズします。 ルート要素のタイプはjavafx.scene.layout.Paneのサブクラスであると想定され、ドキュメントはタイプMyControllerのコントローラを定義すると想定されます。
URL location = getClass().getResource("example.fxml"); ResourceBundle resources = ResourceBundle.getBundle("com.foo.example"); FXMLLoader fxmlLoader = new FXMLLoader(location, resources); Pane root = (Pane)fxmlLoader.load(); MyController controller = (MyController)fxmlLoader.getController();
FXMLLoader#load()操作の出力は、これらのクラスを表すorg.w3c.domノードではなく、ドキュメント内の実際の名前付きクラスを反映するインスタンス階層です。 内部的に、FXMLLoaderはjavax.xml.stream API (Streaming API for XML、つまりStAXとも呼ばれる)を使用してFXMLドキュメントをロードします。 StAXは、非常に効率的なイベントベースのXML解析APIで、W3Cでの先行APIであるSAXに概念的に類似しています。 これにより、FXMLドキュメントを中間DOM構造にロードしてから後処理するのではなく、単一のパスで処理できます。
FXMLLoaderのsetRoot()およびsetController()メソッドは、FXMLLoader自体にこれらの値の作成を委任するのではなく、ドキュメントのネームスペースにそれぞれドキュメント・ルートとコントローラ値を挿入することができます。 これにより、開発者はマークアップを使用して内部で実装されているが、(APIの観点からは)プログラムで実装されたコントロールと同一に見える再利用可能なコントロールを簡単に作成できます。
たとえば、次のマークアップは、TextFieldおよびButtonインスタンスを含む単純なカスタム・コントロールの構造を定義します。 ルート・コンテナは、javafx.scene.layout.VBoxのインスタンスとして定義されます。
<?import javafx.scene.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <fx:root type="javafx.scene.layout.VBox" xmlns:fx="http://javafx.com/fxml"> <TextField fx:id="textField"/> <Button text="Click Me" onAction="#doSomething"/> </fx:root>
前述のように、<fx:root>タグは、以前に定義されたルート要素への参照を作成します。 この要素の値を取得するには、FXMLLoaderのgetRoot()メソッドをコールします。 load()を呼び出す前に、呼び出し側はsetRoot()を呼び出してこの値を指定する必要があります。 呼び出し側は、同様に、setController()を呼び出すことによってドキュメント・コントローラの値を提供します。setController()は、ドキュメントの読み込み時にドキュメント・コントローラとして使用される値を設定します。 一般的に、これらの2つのメソッドは、カスタムFXMLベースのコンポーネントを作成するときに同時に使用されます。
次の例では、CustomControlクラスがVBox(<fx:root>要素で宣言された型)を拡張し、それ自体をそのコンストラクタ内のFXMLドキュメントのルートとコントローラの両方として設定します。 ドキュメントがロードされると、CustomControlの内容に前のFXMLドキュメントのコンテンツが移入されます。
package fxml; import java.io.IOException; import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.control.TextField; import javafx.scene.layout.VBox; public class CustomControl extends VBox { @FXML private TextField textField; public CustomControl() { FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("custom_control.fxml")); fxmlLoader.setRoot(this); fxmlLoader.setController(this); try { fxmlLoader.load(); } catch (IOException exception) { throw new RuntimeException(exception); } } public String getText() { return textProperty().get(); } public void setText(String value) { textProperty().set(value); } public StringProperty textProperty() { return textField.textProperty(); } @FXML protected void doSomething() { System.out.println("The button was clicked!"); } }
コール元は、コードまたはマークアップでこのコントロールのインスタンスを他のコントロールと同様に使用できるようになりました。例:
HBox hbox = new HBox(); CustomControl customControl = new CustomControl(); customControl.setText("Hello World!"); hbox.getChildren().add(customControl);
<HBox> <CustomControl text="Hello World!"/> </HBox>
FXMLLoaderを使用して名前付きモジュールで型をロードする場合、アプリケーションは、コントローラ・クラスおよびカスタムNodeクラスを含むFXMLファイルで参照されるすべての型が、javafx.fxmlモジュールからリフレクションによりアクセスが可能であることを保証する必要があります。 モジュールopensが含まれているパッケージを少なくともjavafx.fxmlモジュールに渡すと、型はリフレクションによりアクセス可能です。
たとえば、com.foo.MyControllerがfoo.appモジュールにある場合、module-info.javaは次のようになります:
module foo.app {
opens com.foo to javafx.fxml;
}
あるいは、モジュールexportsが含まれているパッケージを無条件に禁止すると、型にリフレクションによりアクセスできます。
Report a bug or suggest an enhancement
Copyright © 2008, 2018, Oracle and/or its affiliates. All rights reserved.