Hatena::ブログ(Diary)

tomoTakaの日記

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式を使えることに驚いています。
f:id:tomoTaka:20121219080840p:image

JavaFXで、バックグラウンドで別スレッドで処理を実行し、その進捗率を画面のProgressBarとTextに表示するサンプルを作成。

  • 画面(起動時)

画面に2つのスタートボタン、ProgressBar、Text(ready)、を表示。

f:id:tomoTaka:20121218083403p:image

  • 画面(タスク実行中)

各startボタンをクリックするとTask1、Task2を実行し、その進捗率が画面のProgressBarとText(running 8/10)に表示。

f:id:tomoTaka:20121218083423p:image

  • 画面(タスク終了)

f:id:tomoTaka:20121218083442p:image

この処理の中で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";
            }
        };
    }
}

MartinMartin 2013/01/21 01:32 Hey man,
when using a Task, use this to update buttons:

Platform.runLater(
new Runnable(){
public void run(){
// Update your buttons in here!
}});

Because when you run code from a task, it is not inside JavaFX thread. That's very wrong.

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/tomoTaka/20121220/1356013679