主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

スポンサーサイト

                
tags:
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

WPFで非同期のスレッド処理でGUIパーツを加える

                
To Add GUI Controls on a WPF Window with Threading.

参考:http://msdn.microsoft.com/ja-jp/magazine/cc163328.aspx#S4


 GUIに求められるものはなんだろう。直観的なデザイン、誰にでも容易に扱えること。そういったこともあるだろうが、即応性も同等に求められているだろう。クリックやなにかアクションを与えてから数秒して急に画面が切り替わる。そういうGUIの変化までの数秒の空白に人は退屈を感じてしまう。

 いくら処理を効率化したところで、処理自体に数秒以上の時間がかかってしまうのは仕方のないことだ。この空白の数秒を埋めるため、iPhoneではGUIの変化をアニメーションにして画面表示に変化を与えておき、別スレッドで処理をすることがあるらしい。これをごまかしと捉えることもできるが、ユーザーの大半はこれによって待たされるストレスを軽減されている。


 音楽プレーヤーアプリを作っていたのだけど、メディアライブラリにある数百、数千の曲のタグ情報を読み込もうとすると、数十秒待たされた。その数十秒ののちにGUIを構成してアプリを画面表示させるとすると、ユーザーはストレスを感じるだろう。だからこのアプリの即応性を高める必要がある。最初はライブラリのうちの何曲かだけ表示しておいて、読み込みが完了したら曲を追加していくというふうに。

 だから実現したい処理は以下のとおりだ。
1.GUIを立ち上げ、GUIにライブラリのうちの何曲かを表示して、ユーザーのアクションに対応できるようにしておく
2.バックグラウンドでライブラリに残った曲の読み込みを処理して、表示されているGUIに追加していく


 最初のGUI表示まではなんら難しいことはないはずだ。だからそれは省略して、バックグラウンド処理の開始から説明する。

 処理に時間のかかる読み込み処理を、別スレッドで開始する。別スレッドを開始するにはBackgroundWorkerというクラスが用意されているが、これは処理自身をマルチスレッド化するものだ。stackpanel.Children.Clear()などのような複数のGUIを対象にする操作を実行しようとすると、「シングルスレッドで処理してくれ」と例外を投げてくる。だから複数のGUIをいじりたければ、以下のようにしてシングルスレッド処理を開始すればいい。
参考:http://msdn.microsoft.com/ja-jp/library/system.threading(v=vs.100)
from System import Threading
"""
省略
"""
thread = Threading.Thread(System.Threading.ThreadStart(self.work))
thread.SetApartmentState(System.Threading.ApartmentState.STA)
thread.Start()


 self.work関数を定義して、ここに曲のタグ情報の読み込み処理一連を書く。読み込みをすべて処理できたら、読み込んだ情報からアルバムアートやタイトルを使ってButtonをつくればいいだろう。
120911_1.jpg


 作成したButtonを、表示されているGUIに追加すれば今回の目的は達成される。表示されているGUIにStackPanelコンテナがあるとしよう。ここに作成したButtonを追加していく。GUIは通常、UIスレッド上からしか呼び出せないようになっている。だから以下のようなことをしようとすると、エラーが投げられる。ソノUIハ別スレッドデツクラレタモノダヨ、と言われる。
self.stackPanel.AddChild(button)


だからUIスレッド外でボタンやスタックパネルのようなコントロールを呼び出して操作したければ、Dispatcherを使って以下のように書かねばならない。
self.Dispatcher.Invoke(Action[list](self.add_buttons), buttons)


 "Action[list](self.add_buttons)"←これなんだろう? "Action"を使って"デリゲート"を定義している。DipatcherのBeginInvokeプロパティ、もしくはInvokeプロパティには"デリゲート"を渡さなければならない。デリゲートって何? 関数への参照と、変数への参照を同時に渡すものだ。どこに端を発するもので、どういうものかは他でさんざん説明が見つけられるのでここには書かない。逆に、IronPythonでデリゲートをどう作ればいいかを見つけるのが困難だったために、デリゲートの定義の仕方をこれから書く。
buttonsはPythonの通常のリストオブジェクトで、これまでですでに作成されたButtonコントロールが詰め込まれたリストだとしよう。self.add_buttonsは通常のプロパティで、buttonsを引数として受け取り、キューのように利用して定義済みのButtonをそこから取り出し、StackPanelコンテナに詰め込んでいく関数として定義されているとする。デリゲート渡しに必要な変数buttonsと、関数self.add_buttonsが揃った。ではActionを使ってデリゲートを定義する。まずActionをSystemから読み込む。
from System import Action


そしたらば、デリゲートは以下のようにすれば定義できる。
Action[list](self.add_buttons)

Actionの直後にある[]内にself.add_buttonsに必要な引数の型を明記している。これはデリゲートがC#由来のもので、型宣言をやっておかなければならないからだ。まあlistでなくobjectと表記してもいいんだけども。これでデリゲートが定義できた。デリゲート作成にはActionでなくFuncを使うという手もあるが、今回はActionの方が適していたのでFuncは使っていない。ActionとFuncの違いはその言葉のとおりで、なにかしらの動作だけをやりたければAction、なにかしらの関数として返り値まで要求したければFuncを使う。

 デリゲートが定義できたらば、あとはDispatcher.Invokeにデリゲートと、そのデリゲートが欲する引数を渡すだけだ。これで、デリゲート内でGUIをいじれるようになる。これで、バックグラウンドプロセスでGUIをいじることが可能になった。ユーザーにGUIを提示しておきつつ、そのGUIをいじることが可能ということだ。


 バックグラウンドスレッドを使うことで、応答性の高いアプリを作れるようになる。GUIはデザインだけでなく、応答性も重要なもので、バックグラウンドスレッドは応用価値の高いものだろう。

 
            

コメントの投稿

非公開コメント

プロフィール

hMatoba

Author:hMatoba
Github

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。