torutkのブログ

ソフトウェア・エンジニアのブログ

JavaFX 2でアプリケーション作成(その8)

JavaFX 2.0でアプリケーション作成(その7) - torutkのブログの続きです。

JavaFX 2.1リリースとNetBeansプロジェクト設定変更

JavaFX 2.1がリリースされ、Java SE Development Kit 7u4をインストールすると、JavaFX 2.1がインストールされるようになります。JavaFX 2.1の新機能については日本オラクルのブログに紹介があります。

NetBeansJavaFXアプリケーションプロジェクトでプログラミングをしていた場合、Java SE 7のアップデートによりJavaFX 2.0からJavaFX 2.1に更新されるため、設定の変更が必要になります。

[ツール]メニュー>[Javaプラットフォーム]を選択すると、「Javaプラットフォームマネージャ」ダイアログが表示されます。[JavaFX]タブをクリックすると、JavaFXへのパスが記載されていますが、前のバージョンの2.0となっているため、Java SE 7 Update4にした結果エラーとなってしまったものです。

JavaFX 2.1を指すように修正します。

画面レイアウト追加

セマフォの獲得(ブロッキング呼び出し)を行うSemaphore.acquireメソッドを別スレッドで実行する機能を追加します。GUIとして、ボタンの追加、別スレッドが実行している間はProgressBarを表示させます。

セマフォの獲得を実行するボタンを追加した画面を次に示します。

利用可能な許可数が0のときに[Acquire]ボタンを押すと、セマフォのacquireメソッドを呼び出します。acquireメソッドは許可数が1以上になるまでブロックするので、別スレッドで待ち続け、ProgressBarを表示します。


Acquireボタン

まず、ボタンのビューを追加します。ボタンを押した時の振る舞いは後程記述します。

    public void start(Stage primaryStage) {
        :
        Button acquireButton = new Button("Acquire");
        acquireButton.setMaxWidth(Double.MAX_VALUE);
        semaphorePane.add(acquireButton, 0, 3);
        :
    }
ProgressBar

Acquireボタンを押した時、セマフォ獲得待ちであれば獲得されるまでの間表示するプログレスバーを追加します。表示に関する振る舞いは後程記述します。

public class SemaphoreView extends Application {
    :
    private ProgressBar progressBar;
    :
    public void start(Stage primaryStage) {
        :
        progressBar = new ProgressBar(0);
        semaphorePane.add(progressBar, 1, 3);
        :
    }
    :
}

スレッドの導入

Acquireボタンを押したときに、セマフォ獲得するスレッドを起動します。
スレッドは、javafx.concurrent.Serviceクラスを継承して作成することにします。Taskクラスの場合、一度実行すると、Taskインスタンスは破棄して新たにTaskインスタンスを作り直す必要があるため、何回も実行可能なボタンやプログレスバーに結び付ける場合は、Serviceの方が適しているからです。

AcquireServiceクラスのコード
import java.util.concurrent.Semaphore;
import javafx.concurrent.Service;
import javafx.concurrent.Task;

public class AcquireService extends Service {
    private Semaphore semaphore;

    public AcquireService(Semaphore semaphore) {
        this.semaphore = semaphore;
    }

    @Override
    protected Task createTask() {
        Task<Void> task = new Task<Void>() {

            @Override
            protected Void call() throws Exception {
                updateProgress(-1, -1);
                semaphore.acquire();
                updateProgress(1, 1);
                return null;
            }
        };
        return task;
    }
}

Serviceクラスの実装は、createTaskをオーバーライドし、スレッドで実行したい処理を記述することです。結局Serviceクラスを実装することは、Taskクラスを実装することでもあるのですが・・・。

別スレッドで実行したいコードは次です。

                semaphore.acquire();

このメソッドが長期間ブロックされる可能性があるためです。このメソッドの呼び出し前後でプログレスバーの制御を記述します。

                updateProgress(-1, -1);
                semaphore.acquire();
                updateProgress(1, 1);

updateProgressメソッドは、javafx.concurrent.Taskクラスに定義済みのメソッドで、プログレスバー等の進捗表示を制御します。

ボタンを押すとこのタスクを実行する処理をstartメソッドに追記します。

        acquireService = new AcquireService(semaphore);

        acquireButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                acquireService.restart();
            }
        });

プログレスバーをサービスに結び付け、updateProgressメソッドが実行できるお膳立てをしておきます。startメソッドに追記します。

        progressBar.progressProperty().bind(acquireService.progressProperty());

セマフォ獲得呼び出しをしていないときはプログレスバーを非表示にしたいため、次の処理をstartメソッドに追記します。

        progressBar.visibleProperty().bind(acquireService.runningProperty());

ついでに、セマフォ獲得スレッド実行中のときはAcquireボタンを非活性化します。startメソッドに追記します。

       acquireButton.disableProperty().bind(acquireService.runningProperty());

基本的な振る舞いはできましたが、セマフォ獲得メソッド呼び出しから復帰後、利用可能な許可数の欄を最新に更新する必要があります。次回、この処理を追加します。