クラスの作成、ViewとModelの分離、増分評価、テキストフィールド
- GuessNumberサンプル

次に紹介するサンプルは、単純な数値当てアプリケーションだ。

1から100までの間の数値の中からプログラムがランダムで選びだした数値を、ユーザは推測して回答する。ユーザが入力した数値が間違っていた場合、正解の数値と比較して、大きいか小さいかをユーザに通知する。

GuessNumberサンプル

入力した数値が間違っていた場合に表示されるダイアログ

入力した数値が正解だった場合に表示されるダイアログ

このサンプルのポイントは、アプリケーションが持つデータを格納するためのクラスを別途作成することにより、ユーザが目にするビューと、アプリケーション内のデータを明確に分離している点だ。

つまり、UIプログラミングの常道とも言えるMVCパターンをJavaFXで実践しているわけだ。

ビュー⇔モデルの間のデータのやり取りは、JavaFXのランタイムによってほぼ自動化されるという点にも注意が必要だ。

このアプリケーションのソースコードは以下のようになる。少々長いが、内容は非常に簡単だ。(1)、(2)……などのポイントはきっちり押さえておきたい。

リスト2: examples/guessnumber/GuessNumber.fx

package examples.guessnumber;

import javafx.ui.*;
import java.lang.System;
import javax.swing.JOptionPane;

// java.util.Randomのインスタンスを作成しておく
var random = <<java.util.Random>>;

// (1) アプリケーションが持つデータを格納しておくモデルクラス
class GuessNumberModel {
    attribute count:Integer;  // ユーザの回答回数
    attribute result:Integer; // 今回の正解
    attribute guess:String;   // ユーザが入力した数値
}
// モデルをインスタンス化しておく
var model = GuessNumberModel {
    count: 0, result: random.nextInt(100), guess: ""
};
// (2) 正解をコンソールに出力しておく。文字列内で式を使用している。
System.out.println("結果は{model.result}");

// メインウィンドウの作成
Frame {
    title: "数値当てゲーム!", width: 200, height: 90,

    // (3) フローレイアウトで複数のコンポーネントを配置
    content: FlowPanel {
        content:[
            // (4) ラベルの指定。bind属性を使用
            Label {
                text: bind "1から100までの数値を入力してね!: 現在{model.count}回目"
          },
          // テキストフィールドの作成
          TextField {
              columns: 3  // テキストフィールドの長さ
              value: bind model.guess // 属性値バインドを使用

              // (5) テキストフィールドのアクションを指定
              action: operation() {
                  // (6) 文字列を数値に変換し、正解と比較
                  var guessNum = new <<java.util.Scanner>>(model.guess).nextInt();
                    if (guessNum == model.result) {
                        JOptionPane.showMessageDialog(null, "正解!");
                        model.count = 0;
                        model.result = random.nextInt(100);
                    } else  if (guessNum > model.result) {
                        JOptionPane.showMessageDialog(null, "{guessNum}は大きすぎる値です。");
                        model.count++;
                    } else {
                        JOptionPane.showMessageDialog(null, "{guessNum}は小さすぎる値です。");
                        model.count++;
                    }
                    // ユーザの入力値をクリア
                    model.guess = "";
                }
            },
        ]
    }
    centerOnScreen: true, visible: true
};

今回新たに学ぶべきポイントを以下に解説する。

(1) アプリケーションが取り扱うデータを保持するクラス、GuessNumberModelを作成している。クラスの作成方法は、後でもう少し詳しく解説する。

(2) System.out.println()で、正解をコンソールに出力している。注目すべきは文字列の内容("結果は{model.result}")だ。見ておわかりのように、ダブルクォーテーションで囲んだ文字列の内部で中括弧({})を使用すると、JavaFX Scriptの式を記述することができる。これはJavaFX Scriptの文字列リテラルが持つ能力だ。

(3) メインウィンドウ内にコンポーネントを配置するために、フローパネル(javafx.ui.FlowPanel)を使用している。フローパネルは、content属性で指定したコンポーネント(複数)を左から右、上から下に自動的に配置する。このように、JavaFXではレイアウトマネージャをとても簡単に利用することが可能で、他にも以下のようなレイアウトマネージャがJavaFXのコンポーネントに対応している。

JavaFXコンポーネント レイアウトマネージャ
GridPanel GridLayout
GridBagPanel GridBagLayout
FlowPanel FlowLayout
BorderPanel BorderLayout
Box Box
StackPanel スタックレイアウト
CardPanel CardLayout
GroupPanel org.jdesktop.layout.GroupLayout

(4) テキスト表示用コンポーネントであるラベルを使用している。ラベルについてはこの後のサンプルでさらに詳しく紹介するが、ここで重要なのは、ラベルのtext属性において属性バインディングを使用している以下の部分だ。

text: bind "1から100までの数値を入力してね!: 現在{model.count}回目"

このコードは、属性値の前にbindキーワードを付与し、属性値の内部で他のインスタンスの属性(model.count)を参照している。このようにしておくことで、model.countが変更されるたびに、自動的にラベルのtext属性を再評価させ、表示を更新することができる。つまり、ビュー⇔モデル間でのデータのやり取りを、プログラマ自身が実装する必要がないということだ。こうした、参照先の属性が変更されるたびに、属性値の再評価を自動的に行う仕組みを増分評価(incremental evaluation)と呼ぶ。

(5) テキストフィールドのaction属性にオペレーションを指定している。このアクションが実行されるのは、テキストフィールド内でEnterキーを押された瞬間だ。

(6) テキストフィールドに入力された文字列を、java.util.Scannerを用いて数値に変換している。ここで重要なのは、model.guessに対して数値変換を行っている点だ。テキストフィールドのvalue属性を見てみると、やはりbindキーワードによるmodel.guessとの紐付けが行われている。そのため、テキストフィールドに入力された値は自動的にモデルクラスの属性値へと反映される。