2012-12-20
JavaFXでLambdaを使ってみる(ProgressBar)
このブログはJavaFX Advent Calendar 2012 : ATNDの21日目として書きました。
先日は@cocoatomoさん、明日はまだ未定のようです、、、
id:hakuraiさんの記事JavaFX Advent Calendar 2012 2日目 javafx.concurrent.Task - 壷ラボを読んで、JavaFXで非同期のTaskをProgressBarに表示する実装に挑戦したのですが、一部の実装をLambdaで書くことができました。
環境は、Mac OS X 10.7.4でJavaのバージョンは以下の通りです。
現時点で、すでにJavaFXでLambda式を使えることに驚いています。
JavaFXで、バックグラウンドで別スレッドで処理を実行し、その進捗率を画面のProgressBarとTextに表示するサンプルを作成。
- 画面(起動時)
画面に2つのスタートボタン、ProgressBar、Text(ready)、を表示。
- 画面(タスク実行中)
各startボタンをクリックするとTask1、Task2を実行し、その進捗率が画面のProgressBarとText(running 8/10)に表示。
- 画面(タスク終了)

この処理の中でLambda式を使っています。記述は以下の2カ所です。
- 1. Task1のstartボタンのsetOnAction部分
EventHandler<ActionEvent> btnHandler = btnEvent -> { // Lambda式1
btn1.setDisable(true);
Task<String> task1 = getTask1(); // 1-1 Task1を作成
bar1.progressProperty().bind(task1.progressProperty()); // 1-2 Task1とProgressBar1のProgressPropertyをbind
text1.textProperty().bind(task1.messageProperty()); // 1-3 Task1のMessageProperytyとText1のtextPropertyをbind
ExecutorService exe = Executors.newSingleThreadExecutor();
exe.submit(task1); // 1-4 Task1実行
EventHandler<WorkerStateEvent> taskHandler = // Lambda式2
taskEvent -> { exe.shutdown();
btn1.setDisable(false);
};
task1.addEventHandler(
WorkerStateEvent.WORKER_STATE_SUCCEEDED, taskHandler); // 上記のLambda式2を引数に指定
};
btn1.setOnAction(btnHandler); // 上記のLambda式1を引数に指定
- 2. Task2のstartボタンのsetOnAction部分
1と同じですが、Lambda式を引数の中で記述しています。
btn2.setOnAction(e -> {
btn2.setDisable(true);
final Task<String> task2 = getTask2();
bar2.progressProperty().bind(task2.progressProperty());
text2.textProperty().bind(task2.messageProperty());
final ExecutorService exe = Executors.newSingleThreadExecutor();
exe.submit(task2);
task2.addEventHandler(
WorkerStateEvent.WORKER_STATE_SUCCEEDED,
w -> {exe.shutdown(); btn2.setDisable(false);});
}
);
- Task1の作成
JavaFXでは、非同期の処理用に「javafx.concurrent.Task」を使えます。
画面のProgressBar1に進捗率を表示させるために「updateProgress」でTask1のProgressPropertyの値を更新します。
また画面のText1のTextに進捗率の文言を表示させるために「updateMessage」でMessagePropertyの値を更新します。
画面のコントロールとbindしているpropertyの値を上記メソッドを使って更新するだけで、画面のbind先のコントロールのpropertyの値に反映されます。
このへんは、id:skrbさんがバインドのサンプル - JavaFX in the Boxで書いていらっしゃいます。
private Task<String> getTask1() {
return new Task<String>() {
@Override
protected String call() throws Exception {
updateMessage("start task1"); // 設定したメッセージがbind先のText1に表示
int i;
for (i = 1; i <= 10; i++) {
updateProgress(i, 10); // 設定した進捗率がbind先のProgressBarに表示
TimeUnit.SECONDS.sleep(1);
updateMessage(String.format("running %d/%d", i, 10)); // 設定したメッセージがbind先のText1に表示
}
updateMessage("task1 done"); // 設定したメッセージがbind先のText1に表示
return "Done";
}
};
}
Taskの作成もLambda式で書いてみたのですが、「エラー the target type must be a Functional interface」のエラーになりました。
追記:id:skrbさんからTaskがLmbdaで書けないのは、インターフェースではなく、クラスだからです。とTwitterで教えていただきました。
画面にcancelボタンも表示してTaskを途中でキャンセルするとかは、また次回に挑戦したいと思います。
- 実装のすべて
package test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javafx.application.Application; import javafx.concurrent.Task; import javafx.concurrent.WorkerStateEvent; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ProgressBar; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.stage.Stage; public class ProgressSample extends Application { @Override public void start(Stage primaryStage) { VBox vBox = new VBox(); vBox.setSpacing(5); vBox.setAlignment(Pos.CENTER); final Scene scene = new Scene(vBox, 300, 250); // Task1 HBox hBox1 = new HBox(); hBox1.setSpacing(5); Text title1 = new Text("Task1"); Button btn1 = new Button("start"); final ProgressBar bar1 = new ProgressBar(0); final Text text1 = new Text("ready"); hBox1.setAlignment(Pos.CENTER); hBox1.getChildren().addAll(title1, btn1, bar1, text1); EventHandler<ActionEvent> btnHandler = btnEvent -> { btn1.setDisable(true); Task<String> task1 = getTask1(); bar1.progressProperty().bind(task1.progressProperty()); text1.textProperty().bind(task1.messageProperty()); ExecutorService exe = Executors.newSingleThreadExecutor(); exe.submit(task1); EventHandler<WorkerStateEvent> taskHandler = taskEvent -> { exe.shutdown(); btn1.setDisable(false); }; task1.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, taskHandler); }; btn1.setOnAction(btnHandler); // Task2 HBox hBox2 = new HBox(); hBox2.setSpacing(5); Text title2 = new Text("Task2"); Button btn2 = new Button("start"); final ProgressBar bar2 = new ProgressBar(0); final Text text2 = new Text("ready"); hBox2.setAlignment(Pos.CENTER); hBox2.getChildren().addAll(title2, btn2, bar2, text2); btn2.setOnAction(e -> { btn2.setDisable(true); final Task<String> task2 = getTask2(); bar2.progressProperty().bind(task2.progressProperty()); text2.textProperty().bind(task2.messageProperty()); final ExecutorService exe = Executors.newSingleThreadExecutor(); exe.submit(task2); task2.addEventHandler( WorkerStateEvent.WORKER_STATE_SUCCEEDED, w -> {exe.shutdown(); btn2.setDisable(false);}); } ); vBox.getChildren().add(hBox1); vBox.getChildren().add(hBox2); primaryStage.setTitle("Progress Smaple"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String... args) { launch(args); } private Task<String> getTask1() { return new Task<String>() { @Override protected String call() throws Exception { updateMessage("start task1"); int i; for (i = 1; i <= 10; i++) { updateProgress(i, 10); TimeUnit.SECONDS.sleep(1); updateMessage(String.format("running %d/%d", i, 10)); } updateMessage("task1 done"); return "Done"; } }; } private Task<String> getTask2() { return new Task<String>() { @Override protected String call() throws Exception { updateMessage("start task2"); int i; for (i = 1; i <= 20; i++) { updateProgress(i, 20); TimeUnit.SECONDS.sleep(1); updateMessage(String.format("running %d/%d", i, 20)); } updateMessage("task2 done"); return "Done"; } }; } }
- 9 http://t.co/WMEFYggL
- 4 http://atnd.org/events/33874
- 4 http://t.co/TrnJjHYZ
- 3 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CDgQFjAA&url=http://d.hatena.ne.jp/tomoTaka/20120122&ei=8izUUM-RDuKJmQX1_oCgDg&usg=AFQjCNENcratGCkfyh0Kl6V5QzgcYq2YtA&sig2=d4LTH7RsNBYzJmzMSfhIvA&bvm=bv.1355534169,d.dGY
- 3 http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=5&cad=rja&ved=0CFYQFjAE&url=http://d.hatena.ne.jp/tomoTaka/20121117/1353114625&ei=0bfTUOe5AomviQfL_4HwCw&usg=AFQjCNEvVeNjThKpQAiDynJGwJITsJCqiQ&sig2=NesgXQ7vJJy-IyZ1-RSR0Q&bvm=bv.1
- 3 https://www.google.co.jp/
- 2 http://news.google.com/
- 2 http://www.google.co.jp/url?sa=t&rct=j&q=webdriverwait+junit&source=web&cd=1&ved=0CDMQFjAA&url=http://d.hatena.ne.jp/tomoTaka/20121027/1351342145&ei=_hPUUP_2BtGUmQXazoCIBg&usg=AFQjCNHyp7QGl7gGB778M500R4W-Lv9u1A&bvm=bv.1355534169,d.dGY
- 1 http://api.twitter.com/1/statuses/show/281771657448783872.json
- 1 http://api.twitter.com/1/statuses/show/281785791288598528.json