XojoでスレッドからUIを更新するプログラムの書き方
最近、Xojoでプログラミングを始めたのですが、いきなりスレッドを使ってUIを更新するところでつまづきました。ThreadAccessingUIException が発生して、画面上のリストに項目追加ができないのです。
「メインのスレッドからしかUI は更新できないよ」と言われ、Java言語の経験から納得はするも、「じゃあ、どう書くの?」という状態だったのですが、ようやく動くところまでこぎ着けました。
目次
日本語の情報がなかなか無い!!
トライアンドエラーでいろいろ試してみたのですが、ThreadAccessingUIException が発生する状況は変わりませんでした。
気分新たに翌朝、通勤中にネットで調べていたところ、役立ちそうな英語のサイトを見つけました。
Accessing the User Interface from a Thread – Xojo Blog
参考にしたページです。
私の英語力では9割方読むことはできず別の記事を探そうかと思ったのですが、記事タイトルが「Accessing the User Interface from a Thread」で『スレッドからUIへのアクセス方法』に書かれていることと、以下の一文が発見でした。
An example of this technique is included with the Xojo example projects: Desktop/Threading/Threaded UI Update.
訳すと「このテクニック(スレッドからUIへのアクセス方法)はXojoのサンプルプロジェクトに含まれてます。」とのこと。
まさに灯台もと暗し・・・。
Xojoサンプルプロジェクト
Xojoのサンプルプロジェクトは、Xojoを起動して「新規プロジェクト…」を開こうとすると下の方にあります。日本語だと「サンプルアプリケーション」と書かれた中に並べられています。
すごくたくさん並んでいますね。
今回のスレッドからUIを更新するサンプルは、Desktop の中の UpdatingUIFromThread の中にある、 UIThreadingWithTask.xojo_binary_project が該当します。
Xojoプロジェクトを開くとどんなサンプルで、どうやって使うのかが簡単に書かれていました。親切ですね。
UIThreadingWithTask.xojo_binary_project
サンプルプロジェクトには、そのまま使える Task クラスが付属しています。TaskクラスはThreadクラスにUIを更新するための機能を持たせたクラスです。
サンプルでは、このTaskクラスを使って、スレッドからUIを更新しています。
さっそくサンプルプロジェクトを使ってみて分かった注意すべき点を織り交ぜて解説していきます。
「開始」ボタンがサンプルの始まり
このサンプルプロジェクトは、「開始」ボタンを押すとスレッドが立ち上がって、スレッドからプログレスバーを更新してくれるサンプルです。
では、開始ボタンに何が書かれているかを見てみましょう。
MainWindowにある「開始(StartButton)」ボタン、このボタンアクション(Action)には、次の1行が書いてあります。
UITask.Run
これだけです。
UITask とは何か?
先ほどのコード、UITaskというクラスのRunメソッドを呼び出しているわけですが、この1行でスレッド実行が開始されます。
このUITask、開始ボタンと同じ MainWindowの中に定義されています。
UITask をクリックすると右側のSuperには”Task”と書かれています。先ほど紹介したサンプルプログラムに付属する「そのまま使える Task クラス」のことです。
「そのまま使える Task クラス」を拡張して、自分がやりたい動作(サンプルの場合はプログレスバーを進める)を実装したのがUITaskクラスです。もちろん、UITaskクラスという名前は好きな名前で大丈夫です。
ここまでのポイントです。
- サンプル付属のTaskクラスは継承(拡張)してそのまま使える。
- Taskクラスには手を入れず、継承したクラスに実装する。
- 継承したクラスは描画したいWindow(サンプルではMainWindow)内に置く。
- スレッドはTaskを継承したクラスのRunメソッドで実行する。
サンプルで実装している UITaskのメソッド
サンプルプロジェクトのUITaskを開いてみると、RunとUpdateUIという2つのメソッドが定義されていることがわかります。
UITaskを右クリック(Ctrl + クリック)してイベントハンドラを追加しようとすると、他にもBeginやFinished、Killed もあることが分かります。
これらは何かというと、先ほどから出てきているサンプルプログラムに付属している「そのまま使える Task クラス」に定義されているイベントです。Taskクラスを開いてみるとこれらが並んでいることがわかります。
次はUITaskで実装している2つのメソッド(イベント)について見ていきましょう。
サンプルUITaskのRunメソッドの実装内容
開始ボタンにも実装があったUITaskのRunメソッド、まずはここから見ていくことにします。
Runメソッドの中には、たくさんコードが書いてありますが、丁寧に日本語でもコメントが書いてあるので、わかりやすいと思います。
重要なのは最後の方の1行、Me.UpdateUI(“UIProgress”:progressValue) だけです。その他は、プログレスバーを一定間隔で100回更新するためのコードです。
更新するときにMe.UpdateUIを呼んでいます。
サンプルUITaskのUpdateUIメソッドの実装内容
いま出てきました。Me.UpdateUIの実装内容についてです。Meというのはコードが実装されたのと同じオブジェクトのことです。つまりはUITaskです。
Me.UpdateUI は UITask.UpdateUI のことです。
引数がDictionaryなので少々難しい実装になっていますが、UIProgressという値が引数に渡されてきていたら、渡されたUIProgressの値をプログレスバー(UIProgress)の値に設定するという実装です。
ここで重要なのは、UpdateUIというメソッドがその名の通りUIを更新する実装だということのみです。
スレッド Run から UpdateUI を呼び出せて、UpdateUI の中でUIを描画できるというのがポイントであり、この記事で一番やりたかったことです。
Taskを継承して処理を書く。
サンプルだとあまりスレッドで処理している感じはしませんが、ここまで見てきた様な方法でスレッドを使ってプログレスバーの表示を進めています。
ここまで分かれば自分の好きな処理が書けると思います。
Taskクラスを継承したクラスを作成して、その作成したクラスのRunメソッドに別スレッドで実行したい処理を記述して、スレッドの中でUIを更新したい時に、UpdateUIにUIの更新処理を実装して、スレッドのRunから呼び出してやれば良いというわけです。
私は、ここまで理解するのにだいぶ時間がかかってしまいました。この記事が参考になれば幸いです。
以前のXojoではスレッドからUIのアクセスができたのですが、いずれのバージョンからかアクセス出来なくなり、メインスレッドからしかUI操作出来なくなりました。別途Timerを使ったりサンプルのUITaskを使用(実際は内部で単発のtimerを使っている)で逃げるしかなくなりました。タイマーはメインスレッドでスレッドのごとく動いているので可能なんだと思います。
自分的には過去のアプリの修正で一番手間取るところなので、いまだに修正が追いつかないでいます。