torutkのブログ

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

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

少し間が空きましたが、torutk.hatenablog.jpの続きです。

次は前回の画面です。tryAcquireボタンを押すと、ボタン右横にtryAcquire結果としてtrueまたはfalseの文字列を表示します。tryAcquireボタンを繰り返し押し、tryAcquire結果が前と変わらないとき、結果の文字列が変化しないため、tryAcquire処理が実際に実行されたかどうかが見た目には分かりません。

ユーザーインタフェースとして、ユーザー操作に対する直接的なフィードバックがないことになり、好ましい設計ではありませんでした。

そこで、tryAcquireボタンを押すときに、いったん前回の結果(trueまたはfalse)を消去し、再度表示することにします。
しかし、前回のコードにもあるように、ボタンが押されたイベントハンドラーの中でclear()するだけでは、消去した状態が見た目に分からず、修正前と同じになってしまいます。

        tryButton.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                resultField.clear();
                boolean acquired = semaphore.tryAcquire();
                resultField.setText(String.valueOf(acquired));
                updatePermits();
            }
        });

そこで、SwingにはないJavaFXの新しい概念であるTransitionを使って、tryAcquire結果をフェードイン表示することにします。フェードインまたはフェードアウト効果を実現するFadeTransitionクラスを使用します。

tryAcquire結果表示部品をTextFieldからTextに変更

前回までに作ったアプリケーションでは、tryAcquireの結果を表示するのにTextFieldを使用していますが、TextFieldをFadeTransitionに喰わせると、枠線もフェードしてしまいます。TextFieldの中のテキストだけフェードさせたかったのですが、実装方法が分からなかったので、TextFieldをやめてTextを使うことにしました。枠線なしでテキスト表示だけになります。

  • フィールドの定義
    private Text resultText;
  • 初期化
        resultText = new Text();
        semaphorePane.add(resultText, 1, 1);
        releaseButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                semaphore.release();
                resultText.setText("");
                updatePermits();
            }
        });

clear()メソッドがないので、空文字列をセットしています。

        tryButton.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                boolean acquired = semaphore.tryAcquire();
                updateTryResult(acquired);
                updatePermits();
            }
        });

新たにメソッド void updateTryResult(boolean) を定義し、これを呼び出すようにしています。

FadeTransitionの実装

tryAcquireボタンが押された際に呼び出されるイベントハンドラの中で、結果テキスト(resultText)にフェードイン効果を付けて文字列を表示する処理を、updateTryResultメソッドに記述します。

    private void updateTryResult(final boolean acquired) {
        resultText.setText(String.valueOf(acquired));
        FadeTransition fader = new FadeTransition(Duration.millis(1000), resultText);
        fader.setFromValue(0.0);
        fader.setToValue(1.0);
        fader.play();
    }

まず、結果テキスト(resultText)に新しい文字列をセットします。
次に、FadeTransitionインスタンスを生成し、フェード効果を行う時間、フェード効果対象Nodeを指定します。
フェード効果の開始時点の透過度をsetFromValueメソッドで指定します。
フェード効果の終了時点の透過度をsetToValueメソッドで指定します。
フェードイン効果の場合は、開始時点の透過度を0.0(完全に透過するので表示が見えない)を、終了時点の透過を1.0(完全に不透過なので表示が指定した色で見える)を指定します。フェードアウト効果の場合はこの逆にします。
FadeTransitionインスタンスのplayメソッドを呼び、フェード効果を発動します。

画面動作

以下にFlashで画面の操作を録画した動画を置いています。

ソースコード全体

今回のソースコード全体を次に示します。

package semaphoreview;

import java.util.concurrent.Semaphore;
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * セマフォの動きを理解するための可視化プログラムメイン。
 * @author toru
 */
public class SemaphoreView extends Application {
    private Semaphore semaphore = new Semaphore(1);
    private TextField permitsField;
    private Text resultText;

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Semaphoreの動きを理解する");

        BorderPane borderPane = new BorderPane();
        Label topLabel = new Label("Semaphoreへの操作とその結果");
        topLabel.setStyle("-fx-background-color: mistyrose");

        BorderPane.setAlignment(topLabel, Pos.CENTER);
        BorderPane.setMargin(topLabel, new Insets(6));
        borderPane.setTop(topLabel);

        GridPane semaphorePane = new GridPane();
        //semaphorePane.setGridLinesVisible(true);
        semaphorePane.setHgap(10);
        semaphorePane.setVgap(16);
        Label permitsLabel = new Label("利用可能な許可数:");
        semaphorePane.add(permitsLabel, 0, 0);

        permitsField = new TextField();
        semaphorePane.add(permitsField, 1, 0);

        Button tryButton = new Button("Try acquire");
        tryButton.setMaxWidth(Double.MAX_VALUE);
        tryButton.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                boolean acquired = semaphore.tryAcquire();
                updateTryResult(acquired);
                updatePermits();
            }
        });
        semaphorePane.add(tryButton, 0, 1);

        resultText = new Text();
        semaphorePane.add(resultText, 1, 1);

        Button releaseButton = new Button("Release");
        releaseButton.setMaxWidth(Double.MAX_VALUE);
        releaseButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                semaphore.release();
                resultText.setText("");
                updatePermits();
            }
        });
        semaphorePane.add(releaseButton, 0, 2);

        BorderPane.setMargin(semaphorePane, new Insets(4, 12, 4, 12));
        borderPane.setCenter(semaphorePane);

        updatePermits();

        primaryStage.setScene(new Scene(borderPane, 300, 200));
        primaryStage.show();
    }

    private void updateTryResult(final boolean acquired) {
        resultText.setText(String.valueOf(acquired));
        FadeTransition fader = new FadeTransition(Duration.millis(1000), resultText);
        fader.setFromValue(0.0);
        fader.setToValue(1.0);
        fader.play();
    }

    private void updatePermits() {
        permitsField.setText(String.valueOf(semaphore.availablePermits()));
    }
}