torutkのブログ

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

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

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

javafx.concurrent.Task(またはService)を使った別スレッド実行の結果を
GUIに反映させるには、Workerオブジェクト*1の状態変化をイベントとして取得します。

Workerオブジェクトの状態遷移

Workerオブジェクトは次の状態遷移を取ります。

    ●
    ↓
   READY ---------------+
    ↓                  ↓
 SCHEDULED --------→ CANCELED
    ↓    ______________↑
  RUNNING -------+
  ↓           ↓
 SUCCEEDED      FAILED

状態遷移が発生したタイミングで通知を受ける機能がServiceクラスに用意されています。

メソッド名 引数 通知が発生する遷移
setOnCancelled EventHandler CANCELED
setOnFailed FAILED
setOnReady READY
setOnRunning RUNNING
setOnScheduled SCHEDULED
setOnSucceeded SUCCEEDED

今回は、Semaphoreのacquireメソッド呼び出しが完了したタイミングを取得できればよいので、setOnSucceededで通知を受け取る設定をします。

追加コード

startメソッドに追記します。

	acquireService = new AcquireService(semaphore);
	acquireService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
		@Override
		public void handle(WorkerStateEvent event) {
		    updatePermites();
		}
	    });

これで一通りの動作を実装することができました。

ソースコード全体

ここまでのソースコードをまるごと次に示します。

SemaphoreView.java
import java.util.concurrent.Semaphore;
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.concurrent.WorkerStateEvent;
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.ProgressBar;
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;

public class SemaphoreView extends Application {
    private Semaphore semaphore = new Semaphore(1);
    private TextField permitsField;
    private Text resultText;
    private ProgressBar progressBar;
    private AcquireService acquireService;

    @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();
	BorderPane.setMargin(semaphorePane, new Insets(4, 12, 4, 12));
	borderPane.setCenter(semaphorePane);
	semaphorePane.setHgap(10);
	semaphorePane.setVgap(16);

	Label permitsLabel = new Label("利用可能な許可数:");
	semaphorePane.add(permitsLabel, 0, 0);
	permitsField = new TextField();
	semaphorePane.setMargin(permitsField, new Insets(12, 6, 12, 6));
	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);
		    updatePermites();
		}
	    });
	semaphorePane.add(tryButton, 0, 1);

	resultText = new Text();
	semaphorePane.setMargin(resultText, new Insets(12, 6, 12, 6));
	semaphorePane.add(resultText, 1, 1);

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

	acquireService = new AcquireService(semaphore);
	acquireService.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
		@Override
		public void handle(WorkerStateEvent event) {
		    updatePermites();
		}
	    });

	Button acquireButton = new Button("Acquire");
	acquireButton.setOnAction(new EventHandler<ActionEvent>() {
		@Override
		public void handle(ActionEvent event) {
		    acquireService.restart();
		}
	    });
	acquireButton.disableProperty().bind(acquireService.runningProperty());
	acquireButton.setMaxWidth(Double.MAX_VALUE);
	semaphorePane.add(acquireButton, 0, 3);

	progressBar = new ProgressBar(0);
	progressBar.progressProperty().bind(acquireService.progressProperty());
	progressBar.visibleProperty().bind(acquireService.runningProperty());
	progressBar.setMaxWidth(Double.MAX_VALUE);
	semaphorePane.setMargin(progressBar, new Insets(12, 6, 12, 6));
	semaphorePane.add(progressBar, 1, 3);

	borderPane.setCenter(semaphorePane);

	updatePermites();

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

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

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

    public static void main(String[] args) {
	launch(args);
    }
}
AcquireService.java
import java.util.concurrent.Semaphore;
import javafx.concurrent.Task;
import javafx.concurrent.Service;

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;
    }
}

次回は

CSSファイルによる見栄えの設定を行う予定です。

*1:TaskおよびServiceクラスはWorkerインタフェースを実装する