手続き方式によるGUIの作成
では、作成したソースコードを見てみましょう。ここで行っているのは、ウインドウクラスのインスタンスを作成し、その属性を設定して表示する、といった昔ながらの「手続きを記述してGUIを作成する」方式の処理です。
import javafx.ui.*;
まず、javafx.uiパッケージのクラスをインポートします。JavaFXのGUI関係は、このパッケージに一通り揃えられていますので、これは必須といってよいでしょう。
var window = new Frame();
ここからが実行する処理になります。まず、ウインドウクラスのインスタンスを作成します。これは「Frame」というクラスになります。JavaFXのGUI関連クラスは、基本的にAWTのクラス名と同じものがつけられています。ウインドウであればFrameクラスを利用します。
また、インスタンスを代入するのにvar windowとして変数宣言を行っている点にも注目してください。JavaFXでは、変数を宣言するのに値のタイプを特に指定しません。単にvar windowとするだけで変数windowが用意されます。といって、タイプがないわけではなく、大抵の場合は必要に応じて自動的にキャストされるようになっています。このあたりの感覚はスクリプト言語に近いでしょう。
window.title = "JavaFX!"; window.width = 200; window.height = 100; window.visible = true;
そして、作成したFrameインスタンスの属性に値を設定していきます。JavaFXでは、基本的に「属性に値を代入する」という形で設定を行います。JavaのようにSetter/Getterのメソッドを呼び出す、というやり方ではありません。GUIクラスに用意されている属性に値を代入するだけで設定が行えます。
また、全体を通して分かることですが、JavaFXでは、単純に「実行したい文を書くだけ」でいいことが分かります。Javaのように、クラスを定義して、mainメソッドを用意して……といったことは不要です。やりたいことを、ただ書くだけ。JavaFXは、基本的にそれだけです。
宣言によるGUIの作成
今度は、JavaFXらしい書き方で先ほどのソースコードを書き直してみることにしましょう。宣言によるウインドウの作成は、次のような形で書くことになります。
import javafx.ui.*; var window = Frame { title:"JavaFX!" width:200 height:100 }; window.visible = true;
変数windowにFrameを設定していますが、このFrameには{}によって宣言が記述されています。これによって、このFrameがどのようなFrameかを指定しているわけです。これは、感覚的にはJavaの無名クラスによる実装を思い起こさせますね。が、この宣言は、もっと具体的な構造を記述することができます。
このことは、ウインドウ内にいくつかの部品を配置するとより明確になります。ラベル、入力フィールド、プッシュボタンといったものを組み込んでみると、宣言による記述方法がいかにGUIの構造を分かりやすく記述するのに役立つか、実感できるでしょう。
import javafx.ui.*; var window = Frame { title:"JavaFX!" width:200 height:120 content:BorderPanel { top:Label { text:"Hello" font:new Font("Serif", "BOLD", 18) } center:TextField { value:"type..." font:new Font("Serif", "PLAIN", 12) } bottom:Button { text:"OK" } } }; window.visible = true;

ここでは、Frameに表示されるcontentに「BorderPanel」というパネルを設定し、そこにいくつかの部品を配置しています。BorderPanelというのは、文字通りBorderLayoutが組み込まれているパネルと考えればいいでしょう。ですから、top、center、bottomといった各場所に部品を組み込んでおくことができます。
ここでは、これらの属性に、さらにLabelやTextField、Buttonといった宣言が記述されています。このように記述すれば、明らかに「ここにこの宣言による部品が組み込まれる」ということが分かるわけです。ソースコードを眺めれば、GUIの構造は一目瞭然です。先ほどの手続き式によるソースコードと比べてみれば分かることでしょう。
bindとoperationによるバインディング
では、これらへのイベント処理などはどうすればいいのでしょうか。例えば、ごく単純に「ボタンを押したら入力フィールドの値を利用してラベルにテキストを表示する」というような処理を書きたい場合、どのような方法をとるのでしょう。この種の宣言式では、どうもイベントの組み込みがどうなるか想像がしにくいです。
ボタンクリック時の処理については、Buttonのactionという属性に設定すれば行えます。ただし、組み込んだそれぞれの部品の値をうまく管理するための方法も考えてやらなければいけません。それらの修正を行ったソースコードはこのような形になります。
import javafx.ui.*; class TextData { attribute value:String; attribute input:String; } var textData = new TextData(){ value:"Hello" }; var window = Frame { title:"JavaFX!" width:200 height:120 content:BorderPanel { top:Label { text:bind textData.value font:new Font("Serif", "BOLD", 18) } center:TextField { value:bind textData.input font:new Font("Serif", "PLAIN", 12) } bottom:Button { text:"OK" action:operation(){ textData.value = "You typed \"{textData.input}\" !"; } } } }; window.visible = true;

今回は、classが登場しました。が、見れば分かるようにJavaのクラスと同じものではありません。クラスには、attributeというもので属性を定義してありますね。
ここで作成したTextDataというクラスは、テキストの値を管理するモデルクラスです。JavaFXでは、利用するさまざまな値は「モデル」としてクラスを定義し、それによって管理させます。このモデルは、その後でnew TextDataとしてインスタンスを作成して利用していますね。が、面白いのは、このモデルとの値のやり取りは、何かの処理を実行させることで行うわけではない、という点です。
TextDataは、入力フィールドに書いたテキストをinputに、ラベルに表示するテキストをvalueにそれぞれ収めて管理します。では、これらの値はいつどこで値として設定されるのでしょうか。実をいえば、以下の2つの文でそれが行われていたのです。
- LabelへのTextDataのvalue属性のバインド
- TextFieldへのTextDataのinput属性のバインド
top:Label {
text:bind textData.value
center:TextField {
value:bind textData.input
JavaFXには「バインド」と呼ばれる機能があります。「bind」というものを使うことで、部品の属性などの値とモデルの属性とを結びつけることができるようになります。上記のようにバインドされると、入力フィールドのテキストが修正されれば自動的にtextData.inputの値にそれが反映されるようになります。またtextData.valueの値が変更されれば、バインドされているラベルの表示テキストも自動的に変更されるわけです。
このことを念頭において、ボタンのアクションを見てみましょう。Buttonの属性設定部分では、次のようにしてaction属性を設定しています。
action:operation(){
textData.value = "You typed \"{textData.input}\" !";
}
operationは、文字通り何らかの操作を行うためのものです。これにより、actionが発生した際にはoperationで記述した処理が実行されるようになります。ここで行っているのは、textData.valueの値を変更する処理です。ラベルの表示を操作しているわけではありません。あくまでもモデルの属性を変更する操作だけなのです。モデルを操作すれば、それにバインドされている部品類の表示は自動的に更新されます。
また、テキストの値には{}記号を使ってJavaFXの要素を埋め込むことができます。テキストの値は基本的に1つのテキストリテラルの形で記述します。Javaのように、a = "a" + "b";というように複数のテキストリテラルを演算子で接続するようなことはせず、代わりにa = "{a}{b}";というようにしてリテラル内にさまざまな要素を埋め込んで記述していきます。
Javaクラスとの連携
GUIの作成に関する限り、非常にJavaFXが分かりやすく構造的に記述できるということは分かりました。では、GUI以外の処理の実装はどうでしょうか。「宣言を使い構造的に記述できる」というのは、裏を返せば「手続き的な処理の記述が苦手」ということになりはしないでしょうか。
先にあげたように、JavaFXでは、operationを使うことで長い処理を記述することも可能です。ですから、「JavaFXは難しい処理の記述には向かない」というのは誤りでしょう。何より、JavaFXはJavaと連携して動かすことが可能です。この点を考えれば、「GUIの設計はJavaFX、背後で実行される処理はJava」というように、両者を組み合わせることでより本格的な処理への対応も可能となるはずです。
例として、Javaクラスに定義しておいた処理を使って計算した結果を表示させるというサンプルを挙げておきましょう。ここでは、MyMathというクラスを定義しておきます。
package jp.codezine; public class MyMath { public static String getTotal(String s){ int n = 0; try { n = Integer.parseInt(s); } catch (NumberFormatException e) { e.printStackTrace(); } int total = 0; for(int i = 1;i <= n;i++) total += i; return Integer.toString(total); } }
ここにあるのは、ごく単純な数値の合計を計算して返すメソッドです。では、このメソッドをJavaFX側で呼び出し実行させるように先の例を修正してみましょう。
import javafx.ui.*; import java.lang.System; import jp.codezine.*; class TextData { attribute value:String; attribute input:String; } var textData = new TextData(){ value:"Hello", input:"0" }; var window = Frame { title:"JavaFX!" width:200 height:120 content:BorderPanel { top:Label { text:bind textData.value font:new Font("Serif", "BOLD", 18) } center:TextField { value:bind textData.input font:new Font("Serif", "PLAIN", 12) } bottom:Button { text:"OK" action:operation(){ textData.value = "TOTAL: {getAnswer(textData.input)}"; } } } }; operation getAnswer(n:String):String { return MyMath.getTotal(n); } window.visible = true;

ここでは、Buttonのactionに直接呼び出しを書くのではなく、getAnswerというoperationを用意してその中から呼び出すようにしておきました。getAnswerを修正することで、処理の変更にも比較的簡単に対応できるように、というわけです。
元来、JavaFXの目的は「複雑化したSwingなどのGUI設計の簡略化」だったように思います。この点を考えるなら、「内部処理はJavaにまかせ、GUI関係だけをJavaFXに置き換える」というのは、むしろJavaFX本来の役目からすれば正しい手法かもしれません。
