Everyday JavaFX

2012-02-11

[] JavaFX で Hello, World!

JavaFX シリーズ目次



今日から、JavaFXプログラミングに入っていきます。

その前に、おととい JavaFX 2.1 の build 12 が公開されました。今日から、この build 12 を使っていきます。

さて、一番はじめのプログラムといえば、やっぱり Hello, World! ですね。

とりあえず、見ていただきましょう。

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class Hello extends Application {
   
    @Override
    public void start(Stage stage) {
        // ステージのタイトルを設定
        stage.setTitle("Hello World!");
        
        // ルートのコンテナ
        StackPane root = new StackPane();
        
        // シーンの生成
        Scene scene = new Scene(root, 300, 250);

        // シーンをステージに貼る
        stage.setScene(scene);
        
        // ラベル
        Label label = new Label("Hello, World!");

        // コンテナにラベルを貼る
        root.getChildren().add(label);
        
        // ステージの表示
        stage.show();
    }

    public static void main(String[] args) {
        // JavaFX のスレッドを起動
        launch(args);
    }
}

JavaFXアプリケーションjavafx.application.Application クラスのサブクラスとして定義します。

Application クラスは、かつての Applet と同じようなクラスです。

Applet オブジェクトはそのライフサイクルを外側からコントロールされていて、起動時には start メソッド、終了時には stop メソッドがコールされます。

Application オブジェクトも同じようにライフサイクルを管理されており、起動時には start メソッド、終了時には stop メソッドがコールされます。

start メソッドだけは abstract メソッドになっているので、サブクラスでオーバーライドする必要があります。

ようするに、start メソッドの中で GUI を構築しろということですね。

Application オブジェクトのライフサイクルは外側から管理されると書きましたが、その外側の人を作る必要があります。それが、main メソッドの launch メソッドです。

launch メソッドの中で GUI を描画するスレッドを生成し、その後、GUI 用のスレッドから start メソッドをコールするようになっています。

    public static void main(String[] args) {
        // JavaFX のスレッドを起動
        launch(args);
    }

さて、start メソッドに戻りましょう。

前述したように start メソッドでは GUI の構築を行います。

Swing でもそうでしたが、JavaFX でも GUI の構成というのはあらかた決まっています。ウィンドウに相当するのが javafx.stage.Stage クラスです。

そこからのクラスの関連は次のようになります。

    [Stage] - [Scene] - [Root Container] - [GUI のコンポーネントなど]

Stage オブジェクトに貼るのが javafx.scene.Scene オブジェクトです。Scene クラスは Swing でいうところの JRootPane クラスのような役割です。

そして、Scene にすべての描画要素のルートとなるコンテナを貼ります。Swing でいうところの ContentPane のようなものです。

Swing ではコンテナとレイアウトは別々のクラスとして表現されていましたが、JavaFX ではコンテナはレイアウトを含んでいます。

ここで使用した javafx.scene.layout.StackPane クラスはスタック上に描画要素を積み重ねてレイアウトするコンテナです。

        // ルートのコンテナ
        StackPane root = new StackPane();
        
        // シーンの生成
        Scene scene = new Scene(root, 300, 250);

        // シーンをステージに貼る
        stage.setScene(scene);

後は自由に GUI の描画要素をルートコンテナに貼っていきます。

ここでは Hello, World! という文字列を表示するので、javafx.scene.control.Label クラスを使用しました。

Swing ではコンポーネントと呼ばれていたものは、JavaFX ではコントロールと呼び、javafx.scene.control パッケージで定義されています。

        // ラベル
        Label label = new Label("Hello, World!");

        // コンテナにラベルを貼る
        root.getChildren().add(label);

ルートコンテナに貼る時に、わざわざ getChildren をコールしてから add しているのは、そういうものだと思ってください。これについても、いつか説明したいと思います。

最後にステージを表示するため stage.show メソッドをコールします。

では実行してみましょう。

f:id:skrb:20120210094700p:image

ビルダー

ここまではクラス名などはことなるものの、Swingアプリケーションと大差ありません。逆にいえば、JavaFX という新しい技術を導入しても、結局は旧態依然とした書き方しかできないということになります。

詳しくは次回以降に説明しますが、GUI の構造はツリー構造で表すことができます。

でも、Javaツリー構造を書くと、そのツリーがプログラムからはとても読み取りにくいのです。まぁ、当たり前といえば当たり前なのですが...

Swing や上の JavaFX の書き方というのは、ツリー構造をそのままベタに書いているわけです。当然読みにくくなります。

そこで、JavaFX ではツリーをベタに書く以外に 2 種類の方法でツリーを書くことができるようになっています。

  • ビルダー
  • FXML

ビルダーというのはいわゆる流れるようなインタフェース (Fluent Interface) を採用したファクトリのクラスです。このビルダを使用すると、JavaFX 1.x の頃の JavaFX Script にちょっとだけ近い書き方ができます。

もう 1 つの FXML は XML です。ようするに、XMLGUI の構造を書いてしまおうということです。

XMLツリー構造を表すのがXMLGUI の構造を書くというのは、Adobe FlashMicrosoftXAMLAndroid などで使われています。

まずは、ビルダーからいきましょう。

ビルダーは static メソッドの create でビルダーを生成して、そこにプロパティを設定し、最後に build メソッドで、ターゲットとしているオブジェクトを作ります。

たとえば、X を作る XBuilder があって、X には num と text というプロパティがあったとします。このとき X オブジェクトを作るには次のようになります。

    X x = XBuilder.create()
                  .num(100)
                  .text("Foo Bar!")
                  .build();

さて、ビルダーを使って、先ほどの Hello, World! を作ってみましょう。

    @Override
    public void start(Stage stage) {
        // ステージのタイトルを設定
        stage.setTitle("Hello World!");
     
        // ビルダーを使ってツリーを生成
        stage.setScene(
            SceneBuilder.create()
                .root(
                    StackPaneBuilder.create()
                        .children(
                            LabelBuilder.create()
                                        .text("Hello, World!")
                                        .build()
                        )
                        .build()
                )
                .width(300).height(250)
                .build()
            );
        
        // ステージの表示
        stage.show();
    }

この書き方だと、Stage が Scene を持っていて、Scene が StackPane を持ち、StackPane が Label を持っているという関係性が分かりやすくなります。

でも、その分、書き方はちょっと冗長かもしれません。

シンプルな GUI であればいいのですが、すべてをこの形式で書くと帰ってわかりにくいかもしれないですね。

なお、このビルダーの考え方をより洗練させたのが、GroovyFX です。Groovy ではビルダーを DSL としてよく使用するのですが、それを JavaFX適用させたライブラリです。

JavaFX 1.x のころの JavaFX Script を使っていた方には GroovyFX が一番違和感がないかもしれません。

FXML

最後の FXML が本命です。

とりあえず、FXML の例を見てもらいましょう。

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<StackPane>
    <children>
        <Label text="Hello, World!" />
    </children>
</StackPane>

もう一目瞭然ですね。StackPane の子供 (children) として Label があるということがすぐに分かります。

クラスのプロパティは Label の text のように XML の属性として記述してもかまいませんし、StackPane の children のように子要素として記述してもかまいませんん。

驚くことに FXML にはスキーマがありません。要素はそのままクラス名を表しています。クラスを使用できるようにするには import を書く必要があります。

任意のクラスを記述できるので、スキーマを設定できないというのがほんとのところのような気がします。

ただし、今回は使っていないのですが、Javaアプリケーションのやりとりを行うための要素があり、その部分のスキーマは決まっています。

さて、先走りして FXML を出してしまいましたが、NetBeans で FXML を扱ってみましょう。

前々回、NetBeansJavaFX のカテゴリには 3 つのアプリケーションの種類があるといいました。もう一度出すと、

FXML を使用する場合は一番最後の [JavaFX FXML アプリケーション] を使うのですが、これが現状はとてもできが悪い ><

ということで、今回は[JavaFX アプリケーション] を使用して FXML を追加していきます。

今回は使用しませんが、今後 NB の JavaFX Plug-in がアップデートされれば [JavaFX FXML アプリケーション] も使えるようになるかもしれません。

ということで、まずはプロジェクトを作るところから。

メニューバーの [ファイル] -> [新規プロジェクト] を選択します。プロジェクトのカテゴリは [JavaFX]、そして前述したように [JavaFX アプリケーション] を選びます。プロジェクト名などは適当に決めてください。ここでは hello にしました。

まずは、メインクラスから作成します。

public class Hello extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        stage.setTitle("Hello World!");

        // FXML ファイルをロードして、GUI を構築する
        StackPane root = FXMLLoader.load(getClass().getResource("HelloView.fxml"));
        
        stage.setScene(new Scene(root, 300, 250));
        stage.show();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}

javafx.fxml.FXMLoader クラスが FXML ファイルをロードするクラスです。load メソッド引数URL なので、クラスローダーから URL を取得しています。

こうやって FXML ロードしているということは、Adobe FlashMXML などと扱いが違うということが分かります。

Flash では MXMLコンパイルして ActionScript に変換します。JavaJSP みたいな感じですね。これに対し、FXML はコンパイルせずに JavaFX の実行時にパースします。

では、FXML ファイルを作成します。

プロジェクトもしくはパッケージを右クリックして、ポップアップメニューから [新規] を選択します。そして、サブメニューの中から [その他] を選択します。

f:id:skrb:20120211173240p:image



表示されたダイアログの中から、カテゴリ [JavaFX]、ファイルの種類 [FXML テンプレート] を選択して、[次へ] をクリックします。

f:id:skrb:20120211173509p:image



続いて、ファイル名を入力します (クラス名となっているのはご愛敬 ^ ^;;)。

FXML ファイルとメインクラスの名前が同じだと、警告が出るので違う名前にします。ここでは、Hello クラスに記述したように HelloView にしましょう。

f:id:skrb:20120211173802p:image



HelloView.fxml ファイルを作成すると、同時に HelloView.java ファイルも一緒に作成してしまいます。先ほど、メインクラスと同じファイル名にしようとして警告が出たのは、FXML ファイルと同じ Java のファイルを生成してしまうからでした。

MS の blend が XAML ファイルを作成すると、自動的に同じファイル名の C# のファイルを作るのと同じような理由です。

f:id:skrb:20120211174142p:image


この FXML ファイルと同じ名前の Java のクラスは、FXML ファイルに記述したコントロールなどを Javaプログラム中で扱いたい場合や、イベント処理などに使用します。

でも、今回は使いません。FXML についてのもう少し詳しい解説は、また別途おこないたいと思います。

FXML ファイルにはテンプレートというか、ボタンとラベルが並んでいるような FXML が記述されているのですが、それをすべて消して、上述した FXML に書き換えてください。

これで完成です。

実行すると、Java ですべて書いたのとまったく同じステージが表示されます。

2012-02-09

[] JavaFX 2.1 のインストール (Linux 編)

JavaFX シリーズ目次


さて、今回は Linux です。

JavaFXLinux 版は JavaFX 2.1 がリリースされる時に Developer Preview が公開されて、今年の年末にリリース予定の JavaFX 2.2 で正式に採用されるはずだったのですが....

うれしいことに予定が早まって、すでに JavaFX 2.1 Developer Preview で Linux 版が公開されています。そこで、JavaFX 2.1 をインストールしていきましょう。

ただし、現在の 2.1b11 では 32bit 版しか公開されていません。Linux を使う人の多くは 64bit を使っていると思うので、そこは注意が必要です。

なお、今回は Ubutu 11.10 に JavaFXインストールしました。他のディストリビューションだとコマンドなどが異なるかもしれませんが、ご了承ください。

事前準備

JavaFX が 32bit 版しか公開されていないので、64bit Linux を使っている人は事前の準備が必要です。

もし、64bit Linux でも JDK 7 or 6 の 32bit が動作しているのであれば、飛ばしてください。

まず、32bit のバイナリを動かすために、必要なパッケージをインストールしておきます。

$ sudo apt-get install ia32-libs

ia32-libs は 64bit 環境で 32bit のバイナリを動作させるためのランタイムライブラリを集めたパッケージです。

これで 32bit の JDKインストールできます。

JDK は OpenJDK でもいいと思いますが、ここでは OracleJDK を使用しました。

JDK のダウンロードページ から JDKダウンロードしてください。

ここでは、これを書いている時点での最新版である JDK 7u2 を使用しました。Ubuntu なので RPM ではなく、jdk-7u2-linux-i586.tar.gz ファイルをダウンロードします。

ダウンロードしたら、展開しましょう。

$ sudo tar zxvf jdk-7u2-linux-i586.tar.gz

展開すると、jdk1.7.0_02 ディレクトリが作成されます。ところが、このディレクトリが 32bit 用なのか、64bit 用なのか分からない。櫻庭は 64bit も必要なので、区別できるようにディレクトリ名を変更しています。

もちろん、64bit が必要ない方はそのままでも全然かまわないです。

$ sudo mv jdk1.7.0_02 jdk1.7.0_02_32
$ sudo mv jdk1.7.0_02_32 /usr/lib/jvm

後は update-alternatives で起動する java を設定します。

$ sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk1.7.0_02_32/bin/java 21
$ sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk1.7.0_02_32/bin/javac 21
$ sudo update-alternatives --install /usr/bin/javaws javaws /usr/lib/jvm/jdk1.7.0_02_32/bin/javaws 21

最後の数字は優先順位なので、適当でいいです。

櫻庭が使用している PC にはすでに 64bit の Javaインストールしてあるので、32bit の Java を起動するように切り替えます。

$ sudo update-alternatives --config java
There are 2 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                  Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/jdk1.7.0_02_64/bin/java   23        auto mode
  1            /usr/lib/jvm/jdk1.7.0_02_32/bin/java   21        manual mode
  2            /usr/lib/jvm/jdk1.7.0_02_64/bin/java   23        manual mode

Press enter to keep the current choice[*], or type selection number: 

ここで 1 を入力すれば、32bit の java が起動できるようになります。

$ java -version
java version "1.7.0_02"
Java(TM) SE Runtime Environment (build 1.7.0_02-b13)
Java HotSpot(TM) Client VM (build 22.0-b10, mixed mode)

なお、ここでは必要最低限の java, javac, javaws だけ設定しました。必要であれば jconsole や javap などのコマンドも設定してください。

JavaFX 2.1 のインストール

JavaFX 2.1 のインストールといっても、Windows の時とは違って単に展開するだけです。

まずはダウンロードしましょう。JavaFX 2.1 のダウンロードWindows の場合と同じように、JavaFX 2.0.2 のダウンロードページ にあるリンクをたどっていきます。

f:id:skrb:20120207204244p:image

ダウンロードページの Linux 32-bit (zip) のリンクから SDKダウンロードを行います。このファイルをダウンロードする時も、Windows の場合と同じく Oracle の認証が必要です。

ついでに JavaFX Samples も Linux 版をダウンロードしておきましょう。

$ ls
javafx_samples-2_1_0-beta-b11-linux-i586-31_jan_2012.zip
javafx_sdk-2_1_0-beta-b11-linux-i586-31_jan_2012.zip

それでは展開しましょう。

$ unzip javafx_sdk-2_1_0-beta-b11-linux-i586-31_jan_2012.zip

展開すると、javafx-sdk2.1.0-beta ディレクトリが作成されます。ディレクトリ構成は Windows 版の SDK と同じで、JavaFX の本体の jfxrt.jar は rt/lib ディレクトリにあります。なので、JavaFX アプリを実行する場合は、ここにクラスパスをはります。

しかし、ランタイムは入っていないので、アプレットを実行することはできません。サンプルにも JAR ファイルがあるだけで、JNLP ファイルや HTML ファイルは含まれていません。

実際に展開してみると...

$ unzip javafx_samples-2_1_0-beta-b11-linux-i586-31_jan_2012.zip
     <<省略>>
$ ls -l javafx-samples-2.1.0-beta/
total 8664
-rw-r--r-- 1 sakuraba sakuraba  723842 2012-01-31 18:02 BrickBreaker.jar
-rw-r--r-- 1 sakuraba sakuraba 8079415 2012-01-31 18:02 Ensemble.jar
-rw-r--r-- 1 sakuraba sakuraba   24607 2012-01-31 18:02 FXML-LoginDemo.jar
drwxr-xr-x 7 sakuraba sakuraba    4096 2012-01-31 18:02 src
-rw-r--r-- 1 sakuraba sakuraba   26863 2012-01-31 18:02 SwingInterop.jar
drwxr-xr-x 2 sakuraba sakuraba    4096 2012-01-31 18:02 web-files

このように JAR ファイルしかありません。まぁ、正式版が出るまでには、どうにかなるでしょう。

ということで、サンプルを実行してみます。

$ java -cp javafx-sdk2.1.0-beta/rt/lib/jfxrt.jar:javafx-samples-2.1.0-beta/Ensemble.jar ensemble.Ensemble2

この Ensemble は JavaFX のグラフィックスの機能を紹介するアプリケーションなので、機能のチェックに使えます。

f:id:skrb:20120210002540p:image



NetBeansJavafX

ついでに NetBeans でも JavaFX を使えるようにしておきましょう。

ちなみに、櫻庭は JavaFX SDKディレクトリを /usr/lib/jvm にコピーしておいてあります。

NetBeans の設定は Windows の時と同じです。まず JavaFX が使える Java プラットフォームを追加しましょう。Java プラットフォームの設定はツールバーの [Tools] -> [Java Platforms] で行いましょう。

f:id:skrb:20120210004409p:image



プラットフォームの追加には左下の [Add Platforme] ボタンをクリックします。すると、ファイルチューザーが表示されるので、JavaFX を使用する JDK を選択します。

f:id:skrb:20120210010203p:image



[Next] をクリックするとプラットフォーム名を入力する画面に移ります。とりあえず、ここではそのままにしておきます。[Finish] でプラットフォームが登録されます。

[Java Platform Manager] ダイアログに戻って、今作成したプラットフォームの [JavaFX] タブに移動します。

f:id:skrb:20120210010614p:image



[Enable JavaFX] をチェックして、JavaFX SDK の場所を指定します。

f:id:skrb:20120210010759p:image



これで JavaFX を使う準備は整いました。

では、JavaFX のプロジェクトを作成してみましょう。プロジェクトの作成は Windows の場合と同じです。

ここではプロジェクトの種類を [JavaFX Application] として作成します。プロジェクト名などを入力する画面では、[JavaFX Platform] が先ほど作成した JavaFX を使用できる Java プラットフォームであるか確認してください。

f:id:skrb:20120210011339p:image



プロジェクトが生成できたら、実行します。

f:id:skrb:20120210011854p:image



無事、動かすことができました。

では、次回から JavaFXプログラムの作り方を紹介していきます。

2012-02-08

[] NetBeansJavaFX (Windows 編)

JavaFX シリーズ目次



前回は JavaFXSDKインストールしたので、今回は NetBeans IDEJavaFX の開発環境を整備していきます。

ところで、IDEEclipse を使いたいという方もいらっしゃるかもしれませんが、JavaFX の場合はおすすめできません。というのも、現状 JavaFXEclipse Plug-in が提供されていないからです。

また、Oracle の方針からしても、NetBeans が優先で、Eclipse は後回しになっているため、たとえ Eclipse の Plug-in が出たとしてもちゃんとメンテナンスされるかどうかは疑問符がつきます。

もちろん、Plug-in なしでも JAR ファイルをクラスパスに含めればいいだけなので、開発は可能です。でも、わざわざそこまでして Eclipse で開発する意義は感じないです。

NetBean の環境設定

今回使用する NetBeans のバージョンは 7.1 です。

すでに NetBeans 7.1 はインストールされているものとします。ただ、このままでは JavaFX は使えません。

NetBeans では複数の Java のバージョンを同時に扱うため、Java プラットフォームというのを設定できます。JavaFX もこの Java プラットフォームを設定する必要があります。

Java プラットフォームを設定するには、メニューバーの [ツール]-[Java プラットフォーム] から行います。

f:id:skrb:20120208001616p:image


すると、[Java プラットフォームマネージャ] ダイアログが表示されます。通常はデフォルトのプラットフォームしか表示されていないはずです。下の図の場合、JDK 7がデフォルトになっています。

問題なのは、このデフォルトJava プラットフォームでは JavaFX が使えないということです。そのため、プラットフォームを追加する必要があります。

f:id:skrb:20120208002149p:image


プラットフォームを追加するには、上手の赤四角で囲った [プラットフォームを追加] ボタンから行います。ボタンをクリックすると、JDK を選択するためのファイルチューザーが表示されます。

f:id:skrb:20120208002535p:image


ここで追加する JDK を選択します。選択するのは、デフォルトで表示されていた JDK でも大丈夫です。ここでもデフォルトJDK である JDK 7u2 を選択しています。

JDK を選択して、[次へ] ボタンをクリックすると、プラットフォーム名を入力する画面に移ります。

f:id:skrb:20120208002800p:image


プラットフォーム名は適当なものでかまいません。JavaFX が使えるということを明示するのであれば、JavaFX という文字列を入れておけばいいかもしれません。でも、ここではデフォルトのまま JDK 1.7 にしておきます。

入力が終了したら、[完了] ボタンをクリックします。すると、再び Java プラットフォームマネージャーに戻ります。プラットフォームが追加されていることが分かるはずです。

ところが、先ほどのデフォルトのプラットフォームとは違うタブが!!

f:id:skrb:20120208003351p:image


では、この [JavaFX] のタブを開いてみましょう。

f:id:skrb:20120208003712p:image


JavaFX を使えるようにするため、左上の [JavaFX を有効にする] をチェックし、JavaFX SDK の場所を入力します。右側の [参照] ボタンをクリックすると、ファイルチューザーが開くので、そこで選択しても大丈夫です。

JavaFX SDK の場所だけ入力すれば、ランタイムJavadoc は自動的に入力されます。一番下のソースは現状提供されていないので、空欄のままで大丈夫です。

f:id:skrb:20120208004015p:image


最後に [閉じる] で、Java プラットフォームの設定を終了させます。

これで、JavaFX を使う準備は完了です。

サンプルの実行

JavaFX を使う準備ができたので、NetBeans に付属している JavaFX のサンプルを実行してみましょう。

メニューバーの [ファイル] -> [新規プロジェクト]、もしくはプロジェクトペインで右クリックして表示されたポップアップメニューの [新規プロジェクト] を選択します。すると、[新規プロジェクト] ダイアログが表示されます。

左側のカテゴリの中から、[サンプル] -> [JavaFX] を選択すると、右側にサンプルが列挙されます。今回はこの中から、[ColorfulCircles] を選びます。

f:id:skrb:20120208071445p:image


[次へ] でプロジェクトの名前と場所を設定します。デフォルトのままでもかまいませんし、編集してしまってもかまいません。とりあえず、ここでは名前はデフォルトのまま ColorfulCircles にしておきます。

重要なのがその下の [JavaFX プラットフォーム] です。ここが先ほど設定した JavaFX が使用できる Java プラットフォームになっているか確認してください。

[完了] をクリックすれば、プロジェクトが作成されます。

f:id:skrb:20120208071935p:image


ちなみに、Java プラットフォームはメニューバーの [ツール] -> [Java プラットフォーム] から設定しましたが、このダイアログの [プラットフォームを管理] からでも可能です。

プロジェクトはできたので、次は実行です。

実行にはプロジェクト名を右クリックし、[実行] を選択します。

f:id:skrb:20120208072540p:image


もし、プロジェクトがシュプロジェクトになっているのであれば、ツールバーの [実行] -> [主プログラムを実行]、もしくは F6 キーからでも実行できます。

実行すると、下の図のようにカラフルな円が表示されます。

f:id:skrb:20120208072804p:image


ほかにもいろいろサンプルがあるので、ぜひ試してみてください。

JavaFX プロジェクトの作成

サンプルが実行できたら、今度は自分で JavaFX のプロジェクトを作成してみましょう。

サンプルのプロジェクトを作成した時と同じように、ツールバーの [ファイル] -> [新規プロジェクト]、もしくはプロジェクトペインを右クリックでポップアップメニューの [新規プロジェクト] を選択します。[新規プロジェクト] ダイアログが表示されたら、カテゴリの [JavaFX] を選択します。

f:id:skrb:20120208073331p:image


[JavaFX] カテゴリには、次の3つのプロジェクトの種類があります。

[JavaFX アプリケーション] が通常の JavaFX を使用したアプリケーションの作成に使用します。

[JavaFX プレローダー] は、JavaFX プリケーションの前に起動させる、小さいなアプリケーションです。起動の待ち時間を見かけ上減らすために使用します。特にアプレットなどクラスのロードに時間がかかるようなアプリケーションに有効です。

いわゆる起動時のスプラッシュイメージのアプリケーション版みたいなものです。

最後の [JavaFX FXML アプリケーション] は、GUI の構造を FXML という XML ドキュメントで表したアプリケーションです。

大きめなアプリケーションであれば FXML を使うのが普通ですが、ここでは単なる [JavaFX アプリケーション] を使用します。

[次へ] をクリックすると、サンプルの時と同様にプロジェクトの名前と場所を設定する画面に映ります。

デフォルトだと JavaFXApplication1 という名前 (数字の部分は違うかもしれません) になっているはずです。ここではこのままにしておきましょう。

サンプルプロジェクトを作成した時と同様、ここでも [JavaFX プラットフォーム] が JavaFX が使用できる Java プラットフォームになっていることを確認してください。

後は、[アプリケーションクラスを作成] をチェックし、クラス名を記入してください。デフォルトのままでもかまいません。

f:id:skrb:20120208074248p:image


[完了] をクリックして、プロジェクトを作成しましょう。

プロジェクトを作成すると、先ほど設定したメインクラス (アプリケーションクラス) がエディターに表示されます。

このクラスだけで、実行できる状態にあるのでとりあえず実行してみましょう。

先ほどと同じように、プロジェクト名を右クリックして [実行] を選択します。

f:id:skrb:20120208074738p:image


実行すると、[Say 'Hello World'] というボタンが表示されます。このボタンをクリックすると、NetBeans の下側の [出力] ペインに Hello World! と出力されるはずです (赤線は櫻庭が追加したものです)。

f:id:skrb:20120208075148p:image


今回はここまでで、プログラムの解説は次回以降行いますが、プログラムだけは示しておきます。

package javafxapplication1;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class JavaFXApplication1 extends Application {

    public static void main(String[] args) {
        launch(args);
    }
    
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");
            }
        });
         
        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

他で作られた JavaFX プロジェクトを開く

最後に、他で作られた JavaFX プロジェクトを開くことをしてみましょう。そんなの簡単じゃないと思うかもしれないのですが、JavaFX を使用するための Java プロジェクトのせいで、ちょっと面倒になっています。

ここでは、Getting Started with FXML のサンプルを開いてみます。

Getting Started with FXML の右側のサイドバーに Download Source Code とあり、その下に FXMLExample.zip のリンクがあるので、ここからダウンロードしてください。

ZIP ファイルは展開して、適当な場所に配置してください。ここでは C:\temp\FXMLExample に配置しました。

では、このプロジェクトを開いてみましょう。

プロジェクトを開くにはツールバーの [ファイル] -> [プロジェクトを開く]、もしくはプロジェクトペインで右クリックし [プロジェクトを開く] を選択します。

すると、ファイルチューザーが表示されます。そこで、先ほどの FXMLExample を指定します。NetBeans で読み込めるプロジェクトはディレクトリのアイコンがコーヒーカップになっているので、すぐに判別がつきます。

f:id:skrb:20120208192046p:image


ディレクトリを指定して、[プロジェクトを開く] ボタンを選択します。すると、下のような警告ダイアログが表示されます。

f:id:skrb:20120208192238p:image


この警告が Java プロジェクトに起因しているのです。

[問題を解決] というボタンがあるのでクリックしてみます。すると、問題の説明を表したダイアログが表示されます。

f:id:skrb:20120208192501p:image


ダイアログの下部に表示されている説明には下のように書いてあります。

問題: プロジェクトで "Default_JavaFX_Platform" という Java プラットフォームが使用されていますが、このプラットフォームが見つかりませんでした。

対処法: 「解決」をクリックして、"Default_JavaFX_Platform" というプラットフォームを新規作成してください。

ようするに Java プラットフォームが見つからないということなのです。

先ほど、JavaFX が使える Java プラットフォームを登録しましたが、そのときに名前を任意に決められるということを思い出してください。

ようするに、ここに書かれている "Default_JavaFX_Platform" というのは、このサンプルを作った人が JavaFX を使うために作った Java プラットフォームなのです。

もちろん、そんなプラットフォームは登録していないので、警告が表示されているのです。

ここで、[解決] ボタンがあるので、これをクリックして解決を.... 図ることはできません。

とりあえず、ここでは [閉じる] ボタンをクリックして、ダイアログを閉じます。

警告が出たままなので、プロジェクトペインでも FXMLExample は赤く表示されたままです。

f:id:skrb:20120208193632p:image


ここで、プロジェクトペインで FXMLExample を右クリックして、ポップアップメニューから [プロパティ] を選択します。

f:id:skrb:20120208193959p:image


すると、[プロジェクトプロパティー] ダイアログが表示されるので、[カテゴリ] の中から [ライブラリ] を選択します。

f:id:skrb:20120208194201p:image


[ライブラリ] の一番上部には Java プラットフォームを選択できるコンボボックスがあります。ここには先ほど存在しないとされていたプラットフォームが選ばれた状態になっているはずです。

そこで、コンボボックスの選択肢の中から JavaFX が使用できるプラットフォームを選択します。もちろん、ここでは [JDK 1.7] です。

f:id:skrb:20120208194437p:image


[JDK 1.7] を選択しても、ダイアログの下部には [一部のライブラリ項目が見つかりません。「参照の問題を解決」ダイアログを使用してください] と黄色で表示されています。でも、これは無視して大丈夫です。

最後に [了解] ボタンをクリックして、ダイアログを閉じましょう。

これで、このプロジェクトを使えるようになりました。プロジェクトペインでも、赤字ではなく、通常表示に戻っているのを確認してください。

f:id:skrb:20120208194951p:image


では、実行してみましょう。プロジェクト名を右クリックし、[実行] を選択します。すると、下のようなウィンドウが表示されます。

f:id:skrb:20120208195137p:image


ここで示したように、他の人が作成したプロジェクトは Java プラットフォーム名が異なっている場合がほとんどです。これを修正しないと、プロジェクトを使うことができないので注意しましょう!!

NetBeans 7.1beta で作られたプロジェクトを読み込む

NetBeans 7.1beta の頃も JavaFX のプロジェクトを作成できたのですが、残念ながらそのままでは NetBeans 7.1 では読み込むことができません。

というか、プロジェクトとして認識できません。

また、読み込めたとしても、プロジェクトの実行時に

Target "jfxsa-run" does not exist in the project

というエラーコードが表示されて、実行できません。

これは 7.1beta と 7.1 で Antビルドファイルの仕様が微妙に変わったからのようです。

こういう場合は、しかたないので同じ名前のプロジェクトを作成して、ソースだけ古いプロジェクトからコピーするようにします。

2012-02-07

[] JavaFX 2.1のインストール (Windows 編)

JavaFX シリーズ目次



JavaFX も InfoQ で記事を書いてからずいぶんたってしまったので、ちょっとまとめようと思います。InfoQの記事JavaFX 2.0betaだったので、APIも変更されたものも多いし。

ということで、まずはインストールから。

現在の最新バージョンは JavaFX 2.1b11 なので、それを使っていきます。

もちろん、事前に JDKインストールしておく必要があります。JDK 6 でも JDK 7 でもかまいませんが、正式にサポートしているのは JDK 6u29 もしくは JDK 7u2 以降のバージョンです。


では、まずはダウンロードから。Oracle の OTN の JavaFXのダウンロードページ は正式にリリースされたバージョンである JavaFX 2.0.2 がダウンロードできます。

JavaFX 2.1 はタイトルの下に小さくあるリンクから行います。下の図の赤線で示したところです。

f:id:skrb:20120207204244p:image

すると、Developer Preview Download のページに移動します。まずは [Accept License Agreement] をチェックします。

SDKWindows では 32bit と 64bit の両方があります。また、exe ファイルと zip ファイルの両方があります。ここでは 32bit の exe ファイルをダウンロードすることにします。

[Windows 32-bit (exe)] をクリックすると、認証ページに移ります。

f:id:skrb:20120207205513p:image

ベータ版なので、オラクルアカウントが必要なのですが、これはちょっとうざいですね。もし、アカウントがなければ赤字のリンクからユーザ登録を行ってください。

認証ができるとファイルをダウンロードできます。今回、ダウンロードしたファイルは javafx_sdk-2_1_0-beta-b11-windows-i586-31_jan_2012.exe ですが、ビルドが進むとファイル名は変化するはずです。

SDK 以外に、同じページにある JavaFX Samples もダウンロードしておきましょう。サンプルもプラットフォーム別にあるので、Windows 版のダウンロードします。

さて、ダウンロードした exe ファイルを実行して、JavaFXインストールを行いましょう。exe ファイルを実行すると、お決まりのユーザアカウント制御のダイアログが表示されます。了承すると、インストールダイアログが表示されます。

f:id:skrb:20120207203217p:image

[次へ] をクリックすると、インストールする場所を設定する画面に進みます。必要であれば

f:id:skrb:20120207210528p:image

デフォルトでは C:\Program Files\Oracle\JavaFX 2.1 SDK\ になっています。もし変更したい場合は、[参照]ボタンから変更できます。ここでは、デフォルトのままで進めます。

[次へ] をクリックすると、インストールが開始されます。問題がなければ、セットアップが完了します。

サンプルの実行

JavaFXインストールできたはずなので、さきほど一緒にダウンロードしたサンプルを実行してみましょう。

サンプルのファイルは ZIP ファイルなので、適当な場所で展開します。展開すると、javafx-samples-2.1.0-betaというディレクトリが作成され、そこに HTML ファイルと、JAR ファイル、JNLP ファイルが配置されます。

では、HTML ファイルをブラウザで表示させてみてください。下の図は Chrome で BrickBreaker.html を表示させた場合です。

f:id:skrb:20120207212259p:image

何かキーを押すと、ブロック崩しがはじまります。

f:id:skrb:20120207212300p:image

ブラウザで実行できるというのは、ようするにアプレットとして実行しているということです。では、次にデスクトップアプリとして実行してみましょう。

デスクトップアプリとして実行するには JavaFXJAR ファイルをクラスパスに通す必要があります。JAR ファイルは先ほど SDKインストールした C:\Program Files\Oracle\JavaFX 2.1 SDK ディレクトリの下の、rt\lib ディレクトリにある jfxrt.jar ファイルです。

この JAR ファイルをクラスパスに通して実行します。

c:\javfx-samples-2.1.1-beta>java -cp "c:\Program Files\Oracle\JavaFX 2.1 SDK\rt\lib\jfxrt.jar";BrickBreaker.jar brickbreaker.Main

すると、先ほどブラウザで実行していたブロック崩しデスクトップアプリとして実行できます。

f:id:skrb:20120207213457p:image

ところで、jfxrt.jar ファイルを他の場所にコピーしてしまうと実行できません。というのも、jfxrt.jarDLL ファイルを参照しており、その場所が相対パスで記述されているため、jfxrt.jar を移動させてしまうと DLL ファイルを探せなくなってしまうからです。

せっかくですから、ソースからコンパイルしてみましょう。JavaFX Sample は NetBeans のプロジェクトが一緒に添付されているのですが、今回は NetBeans を使わずにコンパイルします。

なお、NetBeans に関しては次回触れる予定です。

では JavaFX Sample を展開したディレクトリsrc\BrickBreaker\src に移動します。NetBeans のプロジェクトなので、Ant を使ってもコンパイルできるのですが、今回は使いません。素の javac を使用します。

コンパイルも実行時と同じく jfxrt.jar をクラスパスに通して行います。

c:\javfx-samples-2.1.1-beta\src\BrickBreaker\src>javac -cp "c:\Program Files\Oracle\JavaFX 2.1 SDK\rt\lib\jfxrt.jar" brickbreaker\*.java

c:\javfx-samples-2.1.1-beta\src\BrickBreaker\src>java -cp "c:\Program Files\Oracle\JavaFX 2.1 SDK\rt\lib\jfxrt.jar";. brickbreaker.Main

これで先ほどと同じく、デスクトップアプリとしてブロック崩しが起動します。

今日はここまで。次回は NetBeans を使います。

2011-12-28

[] FXEyes その 2

昨日のエントリーの FXEyes の続き。

昨日気になっていた部分を作り直しました。今回はプロパティやバインドを多用してみたので、バインドはかなり分かってきましたww

ドラッグしてステージを移動することや、タイトルバーを出すのをポップアップメニューで選択できるようにしました。タイトルバーを表示している時には、ステージのサイズを変更することもできます。

そういえば、ポップアップメニュー (JavaFX でいうところの ContextMenu) のバグも発見しました。

シーングラフに Control が 1 つも貼っていないと、ContextMenu が表示されないというバグです。以前、プレゼンツールを作っている時に、ContextMenu が表示されないという問題があったのですが、Control が貼っていないからのようです。

ここでは、空文字を表示するラベルをダミーで貼ってあります。

ソースは Gist にアップしました。

FXEyes - https://gist.github.com/1523258

少しだけ解説しておきます。まず、Eyes クラスの方から。

Eyes クラスは位置とサイズに関するプロパティを 4 つ保持しています。

public class Eyes extends Parent {

    // Eye Position
    private DoubleProperty locationX = new SimpleDoubleProperty();
    private DoubleProperty locationY = new SimpleDoubleProperty();
    private DoubleProperty width = new SimpleDoubleProperty();
    private DoubleProperty height = new SimpleDoubleProperty();

これらのプロパティはすべて double の値を表しているプロパティです。DoubleProperty クラスはアブストラクトなので、Read と Write の両方ができるのであれば SimpleDoubleProperty クラスを使用します。ちなみに、Read のみであれば ReadOnlyDoubleProperty クラスを使用します。

さて、これらのプロパティを返すためのメソッドも用意します。

    public DoubleProperty locationXProperty() {
        return locationX;
    }
 
    public DoubleProperty locationYProperty() {
        return locationY;
    }

    public DoubleProperty widthProperty() {
        return width;
    }

    public DoubleProperty heightProperty() {
        return height;
    }

後はステージを表示する時にこれらのプロパティにバインドをするようにします。これは FXEyes クラスで行っています。

        eyes = new Eyes();
        eyes.widthProperty().bind(scene.widthProperty());
        eyes.heightProperty().bind(scene.heightProperty());
        eyes.locationXProperty().bind(stage.xProperty());
        eyes.locationYProperty().bind(stage.yProperty());

幅と高さは Scene の幅と高さにバインドし、位置は Stage の位置にバインドするようにしています。こうすることで、ステージが移動した時やサイズが変更した時でも、Eyes クラスの方は自動的にそれに追従するようになります。

これで Eyes クラスの位置とサイズは自動的に変更されるようになりました。ついでにサイズに自動的に追従したいものに、目を描く時の線の太さがあります。

しかし、単にサイズに追従してしまうと、ステージのサイズが小さい時に、線の太さが 1 以下になってしまうかもしれません。このため、線の太さを決めるには少なからずロジックが必要になります。

このロジックは低レベルのバインド API を使用して実現しました。

低レベルのバインドを使用するには、javafx.beans.binding パッケージで定義されている XBinding クラスを使用します。X にはプリミティブ型、String もしくは Object が入ります。ここでは double を使用するので、DoubleBinding クラスを使用しました。

    // Eye Stroke Width is determined by scene size.
    private DoubleBinding strokeWidth = new DoubleBinding() {

        {
            super.bind(width, height);
        }

        @Override
        protected double computeValue() {
            // Strok Width is dicided by screen size.
            double stroke;
            if (width.get() < height.get()) {
                stroke = width.get() / SIZE_RATIO;
            } else {
                stroke = height.get() / SIZE_RATIO;
            }
            return (stroke < MINIMAM_STROKE) ? MINIMAM_STROKE : stroke;
        }
    };

イニシャライザでは、このオブジェクトがバインドするプロパティを bind しておきます。これらのプロパティが変更されると、computeValue メソッドがコールされます (本当はちょっと違うのですが、ちょっと単純化しています)。

computeValue メソッドではバインドしたプロパティに応じた新たな値を得るためのロジックを記述します。ここでは幅と高さに応じて太さを変化させ、細すぎる場合は最小値を設定するというロジックを記述してあります。

これでサイズが変化すると、線の太さも変わるようになりました。

では、目を描いてみましょう。目は楕円で描いてます。ここでは左の白目の部分を示しました。

    private void createEyes() {
        // Left Eye
        Ellipse left = new Ellipse();
        left.centerXProperty().bind(width.divide(4.0));
        left.centerYProperty().bind(height.divide(2.0));
        left.radiusXProperty().bind(width.divide(4.0).subtract(strokeWidth.divide(2.0)));
        left.radiusYProperty().bind(height.divide(2.0).subtract(strokeWidth.divide(2.0)));
        left.setStroke(Color.BLACK);
        left.strokeWidthProperty().bind(strokeWidth);
        left.setFill(Color.WHITE);

左目の楕円の中心は幅の 1/4、高さの 1/2 のところです。このため、単に width や height にバインドするわけにはいきません。とはいえば、先ほどの低レベル API を使うほどではないようです。

そこで、DoubleProperty クラスのスーパークラスである DoubleExpression クラスで定義している四則演算メソッドを使用します。たとえば、 width.divide(4.0) というのは、プロパティ width を 4 で割った値を持つ DoubleBinding オブジェクトを生成します。

そして、楕円の中心はこの DoubleBinding オブジェクトとバインドするようにします。

ちなみに Bindings クラスというユーティリティクラスがあって、このクラスを使用してもプロパティ同士の四則演算を実現することができます。

こうやって描画すると、シーンのサイズに応じて楕円の位置が決まります。

サイズを変化させた FXEyes を 2 つ並べたのが、以下の図です。

f:id:skrb:20111227204817j:image

次は目玉の位置です。

目玉の位置は AWT の MouseInfo クラスで定期的に取得します。タイマーには Swing のタイマーを使っています。AWT/Swing で実行しているので、この処理は AWT/Swing のイベントディスパッチスレッド (EDT) で行う必要があります。そのために SwingUtilities クラスの invokeLater メソッドを使用します。


    private void startSwingEDT() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                timer = new Timer(50, new ActionListener() {
                    @Override
                    public void actionPerformed(java.awt.event.ActionEvent event) {
                        final PointerInfo info = MouseInfo.getPointerInfo();

                        Platform.runLater(new Runnable() {
                            @Override
                            public void run() {
                                eyes.setMouseLocation(info.getLocation().getX() - scene.getX(),
                                                      info.getLocation().getY() - scene.getY());
                            }
                        });
                    }
                });
                timer.start();
            }
        });
    }

Swing の EDT で実行しているのですから、直接 JavaFXアクセスすることはできません。そこで、Platform クラスの runLater メソッドを使用して、値を更新しています。runLater メソッドは SwingUtilities クラスの inovokeLater メソッドJavaFX 版ですね。

JavaFX 側ではマウスの位置の更新されたら、目玉の位置を更新します。

    public void setMouseLocation(double mouseX, double mouseY) {
        double x = mouseX - locationX.get();
        double y = -mouseY + locationY.get() + height.get();
        double cx = width.get() / 4;
        double cy = height.get() / 2;

        // Update Left Black Eye Position
        Point2D left = calculateEyePosition(x, y, cx, cy); 
        leftX.set(left.getX());
        leftY.set(left.getY());

        // Update Right Black Eye Position
        cx = width.get() / 4 * 3;
        Point2D right = calculateEyePosition(x, y, cx, cy);
        rightX.set(right.getX());
        rightY.set(right.getY());
    }

leftX, leftY, rightX, rightY が目玉の中心の位置を示しています。calculateEyePosition メソッドで目玉の位置を計算しているのですが、ここは単なる計算なので省略。

目玉は円で描いています。もちろん、leftX などにバインドするようにしてあります。

        // Left Black Eye
        Circle leftBlackEye = new Circle();
        leftBlackEye.centerXProperty().bind(leftX);
        leftBlackEye.centerYProperty().bind(leftY);
        leftBlackEye.radiusProperty().bind(strokeWidth);
        leftBlackEye.setFill(Color.BLACK);

これでできあがりです。

[] FXEyes

FXEyes is a kind of xeyes. Two eyes follow the mouse cursor movements on the screen.

https://gist.github.com/1523258:titile=FXEyes source]

To make FXEyes the location of mouse cursor is needed, but JavaFX doesn't provide a class like MouseInfo class in AWT. Therefore, I used MouseInfo class.

MouseInfo class must work on Swing Event Dispatch Thread (EDT), JavaFX works on JavaFX EDT. Platform.runLater method is used for updating the location of mouse cursor.

Eyes class has properties: size and location et al. These properties value are changed in response to the stage size and location.

public class Eyes extends Parent {
    // Eye Position
    private DoubleProperty locationX = new SimpleDoubleProperty();
    private DoubleProperty locationY = new SimpleDoubleProperty();
    private DoubleProperty width = new SimpleDoubleProperty();
    private DoubleProperty height = new SimpleDoubleProperty();

    public DoubleProperty locationXProperty() {
        return locationX;
    }

    public DoubleProperty locationYProperty() {
        return locationY;
    }

    public DoubleProperty widthProperty() {
        return width;
    }

    public DoubleProperty heightProperty() {
        return height;
    }

In FXEyes class, these properties are binded to the properties of the stage and the scene.

        eyes = new Eyes();
        eyes.widthProperty().bind(scene.widthProperty());
        eyes.heightProperty().bind(scene.heightProperty());
        eyes.locationXProperty().bind(stage.xProperty());
        eyes.locationYProperty().bind(stage.yProperty());

The other property, strokeWidth, is also binded to width and height with some logics. So, strokeWidth is implemented by low-level bind API.

    private DoubleBinding strokeWidth = new DoubleBinding() {

        {
            super.bind(width, height);
        }

        @Override
        protected double computeValue() {
            // Strok Width is dicided by screen size.
            double stroke;
            if (width.get() < height.get()) {
                stroke = width.get() / SIZE_RATIO;
            } else {
                stroke = height.get() / SIZE_RATIO;
            }
            return (stroke < MINIMAM_STROKE) ? MINIMAM_STROKE : stroke;
        }
    };

And then, Eyes class draw ellipse based on the properties.

For example, the center of left eye is (width/4, height/2). To calculate center of eye, divide method of DoubleExpression class is used.

    private void createEyes() {
        // Left Eye
        Ellipse left = new Ellipse();
        left.centerXProperty().bind(width.divide(4.0));
        left.centerYProperty().bind(height.divide(2.0));
        left.radiusXProperty().bind(width.divide(4.0).subtract(strokeWidth.divide(2.0)));
        left.radiusYProperty().bind(height.divide(2.0).subtract(strokeWidth.divide(2.0)));
        left.setStroke(Color.BLACK);
        left.strokeWidthProperty().bind(strokeWidth);
        left.setFill(Color.WHITE);

Next, I'll show udpating black eye positions in response to the location of mouse cursor.

As I have described previously, the location of mouse cursor is get by MouseInfo class. MouseInfo class is worked on Swing EDT and udpating the black eye positions is work on JavaFX EDT. Therefore, I use SwingUtilities.invokeLater method and Platform.runLater method.

    private void startSwingEDT() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                timer = new Timer(50, new ActionListener() {
                    @Override
                    public void actionPerformed(java.awt.event.ActionEvent event) {
                        final PointerInfo info = MouseInfo.getPointerInfo();

                        Platform.runLater(new Runnable() {
                            @Override
                            public void run() {
                                eyes.setMouseLocation(info.getLocation().getX() - scene.getX(),
                                                      info.getLocation().getY() - scene.getY());
                            }
                        });
                    }
                });
                timer.start();
            }
        });
    }

setMouseLocation method shows on below:

    public void setMouseLocation(double mouseX, double mouseY) {
        double x = mouseX - locationX.get();
        double y = -mouseY + locationY.get() + height.get();
        double cx = width.get() / 4;
        double cy = height.get() / 2;

        // Update Left Black Eye Position
        Point2D left = calculateEyePosition(x, y, cx, cy); 
        leftX.set(left.getX());
        leftY.set(left.getY());

        // Update Right Black Eye Position
        cx = width.get() / 4 * 3;
        Point2D right = calculateEyePosition(x, y, cx, cy);
        rightX.set(right.getX());
        rightY.set(right.getY());
    }

    private Point2D calculateEyePosition(double x, double y, double cx, double cy) {
        double theta = Math.atan2(y - cy, x - cx);
        double hr = width.get() / 4 - 2 * strokeWidth.get();

        double eyeX;
        double eyeY;

        if (Math.abs(x - cx) > Math.abs(hr * Math.cos(theta))) {
            eyeX = cx + hr * Math.cos(theta);
        } else {
            eyeX = x;
        }

        double vr = height.get() / 2 - 2 * strokeWidth.get();
        if (Math.abs(y - cy) > Math.abs(vr * Math.sin(theta))) {
            eyeY = height.get() - cy - vr * Math.sin(theta);
        } else {
            eyeY = height.get() - y;
        }

        return new Point2D(eyeX, eyeY);
    }

leftX, leftY, rightX, rightY are properties binded to black eye positions.

        // Left Black Eye
        Circle leftBlackEye = new Circle();
        leftBlackEye.centerXProperty().bind(leftX);
        leftBlackEye.centerYProperty().bind(leftY);
        leftBlackEye.radiusProperty().bind(strokeWidth);
        leftBlackEye.setFill(Color.BLACK);

The following picture shows two FXEyes ;-)

f:id:skrb:20111227204817j:image