[WinRT] 改変しやすい GridView テンプレートを考える

[WinRT] メディアサーバーを立てて、動画を Surface RT で表示させる の派生として、GridView のテンプレートバリエーションを考えます。つーか、Visual Studio 2012 の吐き出すグリッドアプリケーションの GridView テンプレートは改変がしづらいです。今、書いている本は、そのグリッドアプリケーションを元にしてアプリを作ろうというスタイルになっているので、この言及自体矛盾しているのですが…まあ、ちと高機能すぎて使いづらい。モノとしては、従来の DataGridView コントロールっぽい形で使えたらいいのですが、どうも WPF の GridView を踏襲しているらしく、ItemTemplate(だっけ?)を「自由に作れる」ってところが強調されすぎてるきらいがあるのです。まあ、それはそれで自由度があっていいのですが、最初の取っ掛かりとしては難しいかなと。かつ、最後まで難しいって雰囲気もあります。

方法としては、従来の DataGridView コントロールの扱い位(エクスプローラーの ListView ぐらい)チープな扱い方に戻してしまってもいいのですが、それはそれで退化している感じがして面白くありません。iOS の UITableView ぐらいにするものアリはアリです。なので、タイトルとかサブタイトルとかのデータ部分だけ残して、UI 的なごちゃごちゃしたところをばっさりと隠蔽してしまうのが「コンポーネント」的で良いかなと思っています。

■ビデオ再生専用の GridView を考える

Windows 7/8 は、ホームグループやメディアサーバーを簡単に立てられるようになったので、Surface のようなタブレットPCで、ストリーミング的に動画を流すことが簡単にできます。そんな訳で、私家版ひだまり鑑賞アプリを作ってみました。で、あれはボタンを固定で配置しているので、データが増えるとプログラムを手直ししないといけません。ひだまりの場合は放映が終了しているので、DVD の数は増えたりしないし、話数やタイトルも今後変わりません。なので、ファイル名などはハードコーディングでよいのです。
が、私家版とはいえ先行き、他の動画再生とかする場合にいちいちプログラミングをしているのも面倒ですよね。なので、ビデオ再生のランチャーを目的とした GridView コンポーネントを作っておきますってのが主旨です。

作成途中ですが、プログラミングパターンとしては、MVVM とコンポーネント技法と遅延ロードの組み合わせになります。

  • VideoGridView へは Source プロパティで Model のコレクションを渡す
  • Model は INotifyPropertyChanged を持つ。
  • Model の Image プロパティは、遅延ロードを行う
  • VideoGridView の Item は適当なスタイルをいくつか準備しておく

ってところです。

グリッドアプリケーションのテンプレートを使ったときの SampleDataSource.cs のまずいところは、はっきり言って2点あります。

  • SampleDataItem, SampleDataGroup をそのままの状態でシリアライズできない。
  • SampleDataGroup クラスの ItemsCollectionChanged メソッドの実装がまずい/不要

「サンプル」なのでか書き換えてくれ、ってのもそうなのですが、書き換えるにしても実装まるごと書き換えるスタイルになってしまうので、この2点は直してほしいかなと思ってます(今更直せない気もするけど)。

なので、VideoGridView では、このあたりをばっさりと削ってコンポーネント化します。

■Model クラス(SampleDataItem, SampleDataGroup)

ほぼ、データ構造はグリッドアプリケーションのものを使いますが、コンストラクタと ItemsCollectionChanged を外します。アイテムコレクションの増減に対する処理は、本来 UI で行うべきこと(データの仮想化、遅延ロードなど)なので、モデルに含めるのは妙な感じかと。直接シリアライズができないのも、ここが原因になっています。

Image プロパティに関しては、直接 BitmapImage オブジェクトを生成することになっていますが、これを遅延化させるため別な方法を考えます。というのも、WinRT ではファイル読みが非同期になっているので、new BitmapImage( パス名 ) という同期型で画像ファイルをロードするのは、スタイルとして矛盾がでてきます。実際、書籍のほうで矛盾がでてきたので回避コードを書いています。
MVVM パターンを使うと、バインドという形で UI に接続しているのですが、Image プロパティのように非同期で設定されるプロパティに関しては、バインドが即時実行を期待されているのに反して、画像のデータ(BitmapImageオブジェクト)の生成は時間がかかる(場合によっては読み込めない)という矛盾がでてきます。プロパティには async/await がつけられないというのもこれが元になっています。— バインド自体を少し修正して、非同期の GetImage メソッドを呼び出すように改変するとどうなのかは、試していません。— そんな訳で、同期/非同期とは離れた形で、モデルを実装します。

■データストアクラス SampleDataSource

アプリケーションが唯一持つデータクラス SampleDataSource には、2つの役目があって、デザイン時にテスト的なデータを提供する役割と、実行時にアプリのデータストア的な役割があります。デザイン時のデータは、VS2010 の Blend で使っていたサンプルデータのバインドのやり方で、デザイナさんが UI のイメージが作りやすいようにする技です。グリッドアプリケーションのテンプレートには、これが入っているために、単にビルドしてもなんらかの画面が表示される、ってのが「売り」あんですが、逆に仇になってしまっていて修正するときにはどう修正すればいいのかわからない、という状態のブログが多々みつかります。商業コードの場合は、ばっさりと削ってしまって別の Model を作るのですが、なんかせっかくのテンプレートがもったいないですよね。なので、デザイン時のコードは短めに記述、実行時のシリアライズのコードを事前に組み込んでおきます。

ちなみに SampleDataSource クラスが static を持っているのは、MFC の App スタイルです。ただし、「唯一の App」スタイルは、実行時の生成順序が分かりづらい(変更しずらい)ので、複雑な構成になる場合は app.xaml.cs の OnLaunched 時に明示的にオブジェクト生成を行うほうがいいと思います。起動時の時間とかが予測できなくなるので。

■Image プロパティの遅延ロード

先に書いたように Image プロパティは ImageSource オブジェクトになるのですが、画像ファイルのロードの仕方によっては非同期になります。むしろ、new BitmapImage(path) のほうが例外的で、アプリローカルの画像ファイルやマイドキュメントのファイルをロードするときは必ず非同期処理になります。特に SkyDrive やホームグループ先の画像ファイル(ピクチャフォルダ)を扱う場合には、読み込めない場合も多々あります。
そうなると、Model クラスで Image プロパティに画像オブジェクトを設定するパターンだと、画像ファイルのロード処理をどこでおこなうのか?遅延する場合はどうするか?エラーになった場合はどうするのか?遅延前はどう表示したらよいのか?という課題がでてきます。これらの課題は、いままでは Model の外側で行っていたのですが — なんらかのロードが終わったら Image プロパティに設定するというコーディングスタイル — せっかく async/await の非同期処理の構文が導入されたのですから、UI コントロール内でこの遅延処理を実行しましょう、ってのが発端です。
おそらく、パスかスキームを渡して ImagePath を指定するか、Image プロパティを設定するかというところです。バインドなどで従来の書き方を残しておくために Image プロパティは ImageSource 型になるので、そのあたりをどうコントロールで実装するかってことですね。いわゆるブラウザが画像表示をさせようとしているとき(jQuery.ui とか)と同じです。

  • 画像を表示する前
  • 画像をロードしているとき
  • 画像のロードが完了したとき
  • 画像がロードできなかったとき

の4種類を一括してコントロールのほうで受け持ちます。Model としては Image プロパティあるいは ImagePath プロパティに任意のタイミングで設定し、SetPropertyChanged を行ったタイミングで画像をロードし始めるというパターンです(場合によっては、スタートも遅延させます)。そして画像のロードが終わったタイミングをコントロール自身が取得して画像を更新させる、というコードスタイルですね。このあたりが、コンポーネントの技法になります。

■Item のスタイルをいくつか用意しておく

これはグリッドアプリケーションもやっている方法で、StandardStyles.xaml の中に Standard250x250ItemTemplate などのスタイルが記述してあります。ですが、これを VideoGridView 内部リソースとして持つようにします。DataTemplate の書き方が難しいのと、そのままではデザインできないので、ってところです。

ちなみに、StandardStyles.xaml にバグがありますね。

            <ToolTipService.Placement>マウス</ToolTipService.Placement>

ではなくて、

            <ToolTipService.Placement>Mouse</ToolTipService.Placement>

のように書きます。たぶん「訳し過ぎ」ってやつです。後で Connect にでも。
<a href=”http://msdn.microsoft.com/ja-jp/library/system.windows.controls.tooltipservice.placement.aspx”>ToolTipService.Placement アタッチされるプロパティ (System.Windows.Controls)</a>

こんな風に、GridView を継承した/改変したコンポーネントが揃ってくるといいかなと思っています。インターフェースは GridView そのままで。UI とか内部動作がちょっとずつ違うというパターンですね。Model はストレージに近いので、共有化して使えるほうが便利なのです。

カテゴリー: C#, WinRT パーマリンク