少し間が空きましたが、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);
- releaseボタンを押した際のイベントハンドラ
releaseButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { semaphore.release(); resultText.setText(""); updatePermits(); } });
clear()メソッドがないので、空文字列をセットしています。
- tryAcquireボタンを押した際のイベントハンドラ
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())); } }