設定チャームを使う

Widowsストアアプリにおいて、アプリケーションの設定は設定チャームを通じて行います。実はFeedroでも設定チャームを通じたアプリケーションの設定画面の実装は行われています。試しにFeedroを起動し、Windows+[I](アルファベットのアイ)を押して設定チャームを開いてみてください。【一般】というFeedroが提供するメニュー項目が追加されています。この【一般】を選択すると【フィードを保持する日数】というコンボボックスがひとつ表示されているだけの設定画面が表示されます。

settingsCharm generalSettings

この設定項目により、RSSフィードから読み取ったブログ記事をローカルに保存する期間を選択できます。

DaysToKeepOptions

この設定画面を実現するための、モデル、ビューモデルおよびビューについてそれぞれ見ていきましょう。

モデル

第6章で触れたようにアプリケーションの設定情報を保存するには、Windows.Storage.ApplicationDataクラスのオブジェクトのLocalSettingsプロパティを用います。このLocalSettingsプロパティは永続化機能をもつオブジェクト辞書で、名前 を付けてオブジェクトを保存、読み出しする機能を提供します。以下のような点が異なりますが、 System.Collections.Generics.Dictionaryクラスと似たようなものだと考えるとわかりやすいでしょう。

  • 自動的にファイルに保存される
  • キーは文字列に固定されている
  • 値となるオブジェクトの型はWindows Runtimeがサポートする標準型に限定される

【注】Windows Runtimeがサポートする標準型の一覧についてはこちらを参考にしてください。

アプリケーションのコードからApplicationDataのLocalSettingsプロパティを直接操作しても実用上は問題ありませんが、ここではSettingsHelperというユーティリティクラスを定義しておきましょう。

このようなユーティリティークラスを介して設定情報を取り扱うことで、のちのち、設定情報を端末間で同期させたい、といった要件がでてきた時に対応しやすくなります。さらにその上で、設定値そのものをモデルStoreクラスのプロパティDaysToKeepとしてアクセスできるようにしておきましょう。

ビューモデル

ビューモデルは以下のふたつのプロパティを実装しています。

DaysToKeepOptions コンボボックスで表示する選択可能な日数の一覧一週間(7)、二週間(14)、一か月(30)、二か月(60)、三か月(90)、半年(180)、一年(365)
SelectedDaysToKeep コンボボックスで選択された日数

SelectedDaysToKeepの実装は、Store.DaysToKeepの値を読み書きしているだけです。

ビュー

ビューに配置されているオブジェクトは、日数を選ぶためのComboBoxおよびそのラベルであるTextBlock(CaptionDaysToKeep)のみです。

ComboBoxのItemsSource(選択可能なオプションの一覧)をビューモデルのDaysToKeepOptionsに、SelectedValue(選択された値)をビューモデルのSelectedDaysToKeepにバインドしておきます。SelectedDaysToKeepへのバインドは双方向になっているため、コンボボックスで日数を選択すると、その値が自動的にビューモデルのSelectedDaysToKeepプロパティに反映され、最終的にSettingsHelperを通じてアプリケーションの設定情報として保存されます。

設定チャームとの接続

設定画面ができたので、最後にこれを実際に設定チャームと接続してみます。設定チャームとして表示されるビューはWindows.UI.ApplicationSettings.SettingsPaneクラスのインスタンスです。まず、このビューに表示されるメニュー項目【一般】を追加します。SettingsPaneがビューの初期化を行う際、CommandsReuqestedイベントを発生させるので、そのタイミングでメニュー項目を追加します。

“General”というメニューの識別子、メニューラベルの文字列およびメニューが選択された場合の処理(showSettingsPanel)を登録しています。メニューが選択された場合の処理そのものは非常に簡単で、先ほど定義したビューのインスタンスを生成し、Showで表示しているだけです。

異なるサイズのライブタイルの同時更新

第10章で述べたとおり、スタート画面に表示できるアプリのタイルには4種類のサイズがあり、小サイズのタイル以外はライブタイルとして用いることができました。

いろいろなサイズ

おさらいになりますが、以下はライブタイルを更新するコードの例です。

ここではTileWide310x150Text01というテンプレートを用いて、横長のライブタイルの情報を更新しています。

ワイド

ここで、このライブタイルのサイズを中および大のサイズに変更してみると確認できますが、中および大サイズのタイルには、アプリのアイコンが表示されるのみです。使用したテンプレートは、横長のタイル用のものなので、横長のタイルのみ表示情報が更新された形なっているのです。

もちろん、ここで違うテンプレート、例えばTileSquare150x150Text01(中サイズ)やTlieSquare310x310Text01(大サイズ)を用いると、それぞれのサイズのタイルに表示されている情報を更新できます。しかし、それ以外のサイズのタイルはやはり変更されません。すべてのサイズのタイルに表示されている情報を同時に変更するにはどうすればよいのでしょうか。

ここで、テンプレートの仕組みについてもう少しく詳しく見てみましょう。書籍でも触れましたが、テンプレートの実体はXmlDocument形式のオブジェクト、すなわちXMLです。例えば、TileWide310x150Text01のテンプレートを等価なXMLとして表現すると以下のようなものになります。

bindingタグおよぼその子要素に注目していください。bindingの子要素であるtextタグが実際の更新情報を表しており、bindingタグのtemplate属性がどのテンプレート(レイアウト)を用いるか指定しています。ここでピンと来た方もいるかもしれませんが、bindingは複数指定することができるのです。

この例では以下の3種類のテンプレートを用い、中、大、横長サイズのそれぞれのライブタイルに対する更新情報を指定しています。

  • TileSquare150x150Text01
  • TlieSquare310x310Text01
  • TileWide310x150Text01

このXMLを用いてTileUpdateManagerのUpdateを呼び出すと、ワイド以外の2種類のサイズのライブタイルにおいても表示される情報が更新されるようになります。

中 大

コーディング上、少し面倒なのがこのXMLを表すXmlDocumentのオブジェクトをどのように準備するのか、という点です。この場合、TileUpdateManagerのGetTemplateContentメソッドは使えないので、XmlDocumentに用意されているDOM(Document Object Model)操作のメソッドを利用して、XmlDocumentのオブジェクトをいちから生成するのが正攻法になります。

ただし、これは実際にやってみるとわかりますが、大変煩雑かつ面倒なコーディングになります。そこでここでは、System.Xml.Linqに用意されているXDocumentを用いる方法を紹介しておきます。

XDocumentもXmlDocumentと同じく、XMLをオブジェクトとして操作するためのものです。LINQ to XMLでの利用を想定して新たにデザインされており、XmlDocumentと比較して使い勝手がかなり改善されています。ここでは、この使いやすいXDocumentを使用してXMLのオブジェクトを構築し、それをXmlDocumentに変換するというアプローチを用いました。メモリ消費およびパフォーマンスという観点からは必ずしもベストなやり方ではないのですが、ここでは、コードの理解しやすさという観点からこの方法を紹介しています。

NOTE: この方法を用いたとしても以下のような条件下であれば特に問題にならないかもしれません。

  • ライブタイルの更新頻度がそれほど頻繁でない
  • 構築するXMLのサイズがそれほど極端に大きくはない

実際に使用される場合は十分にテストしてみてください。

サンプルコードはgithubから入手可能です。

非同期処理におけるセマフォを用いた排他制御

第5章で書いたように lock 節の中では await の使用が禁止されています。

「ロックを取得する」とよく言いますが、これは「実行スレッドがロックを取得する」という意味です。スレッドプールを通じてスレッドを共有する可能性のある Task 処理の間では、「ロックを取得する」ことでは排他制御を行えないのです。lock 節中での await の使用は、そもそも期待通りの排他制御が行われないため禁止されているともいえます。このような理由のため、lock 節を用いずに Mutex のオブジェクトを直接使用したとしても、排他処理はうまく意図したとおりに機能しません。なぜなら、Mutex もスレッドが「取得する」ものだからです。

これでは排他制御ができないじゃないか、という話になってしまいます。アプリのアーキテクチャを設計する上で、ロックを使わないように努力するにしても、どこかで最低限の排他制御は必ず必要になりえます。

スレッドと紐づかないで排他制御をする仕組みはないものでしょうか。

そこで登場するのが「セマフォ」というものです。セマフォという名前は、黎明期の鉄道における交通整理に用いられた手旗信号に由来しています。この手旗信号がどのように使われたか、鉄道マニアをのぞけばピンと来る方は少ないと思われるので、このあたりの由来はあまり気にしないほうがよいでしょう。

ここでは、加減算のできる単なる「カウンター」がセマフォだと思ってください。ただし、このカウンターには以下のような性質があります。

  • カウンターの最大値を指定できる
  • カウンターの加算および減算を実行できるのは、ある一時点でひとつのスレッドに限られる
  • カウンターの値が最大値の時に加算をしようとすると、カウンターの値が減算されるまで待機させられる

たとえば、このカウンターの最大値が3に設定されていると仮定してください。このカウンターを4つのスレッドから同時に加算しようとした場合、何がおこるでしょうか。カウンターの加算を同時に実行できるのは、ひとつのスレッドのみです。なので、4つのスレッドは、あるランダムな順番で順にカウンターの加算を実行していきます。ただし、最初の3つのスレッドが加算を実行した時点で、カウンターは最大値の3に到達するので、4番目のスレッドが加算をしようとすると、その時点で待機することになります。なにがしかの事象によりカウンターが減算されてはじめて、4番目のスレッドは加算を実行し、処理を継続することができるようになります。

ここで、カウンターの最大値を1に設定したと考えてください。ある処理Aの前にカウンターを加算し、処理後にカウンターを減算するようにしておけば、この処理Aが同時に複数実行されることはなくなります。つまり、排他制御を実現できるわけです。

実際のコードでどのように使えるのか確認しておきましょう。

System.Threading.SemaphoreSlimクラスのオブジェクトがセマフォであり、以下のように用います。

  • コンストラクターの引数でカウンターの初期値および最大値を指定
  • カウンターの加算はWaitもしくはWaitAsyncで行う
  • カウンターの減算はReleaseで行う

上記のコードではコレクションに要素を追加すると同時にファイルに書き出していますが、この前後でセマフォを用いて排他制御を行っています。ここで、ファイルの書き出しが非同期処理である点に注意してください。また、セマフォのカウンターの加算を非同期に行っている点も重要です。

サンプルコードはgithubから入手可能です。

NOTE: ところで SemaphoreSlim クラスという名前が示すように、このクラスはSemaphoreクラスの「軽量版」です。SlimがつかないSemaphoreでも同等の排他制御は可能ですが、少し処理が重くなるので、通常は SemaphoreSlim クラスを用いるとよいでしょう。

スレッド間でなくプロセス間でセマフォを利用したい場合は名前付きセマフォを利用する必要があります。このような場合は、SemaphoreSlimでなくSemaphoreクラスを用いる必要がでてきます。ただし、単にプロセス間でのロックが必要なだけであれば、セマフォでなく名前付きミューテックスを利用すればよいでしょう。

参考資料

より詳しく調べてみたい方は以下の資料などを参考にしてみてください。

日本語資料ということであれば、山本康彦(@biac)さんの資料付きデモアプリがわかりやすいです。

双方向データバインディングの利用

第4章で取り上げたように、データバインディングを用いることで、ビューモデルのプロパティの値を、ビュー上のコントロールのプロパティに簡単に反映させることができます。例として、書籍のタイトルと価格を保持するBookクラスを考えてみます。

これを以下のようなビューを用いて表示します。

ビューモデルの初期化は以下のようにMainPageのコンストラクタで行っておきましょう。

アプリをデバッグモードで起動すると、書籍情報が以下のように表示されます。

書籍情報を表示する

ここまでは第4章でみたとおりです。

さて、このビューをユーザーインターフェイスとして用い、ビューモデルの値を編集することを考えてみます。ビューモデルのそれぞれのプロパティがTextBoxのTexstプロパティにバインドされているのだから、テキストを編集すればTextBoxのプロパティが変更されて、それが自動的にビューモデルのプロパティに反映されるのでは?と思った方がいるかもしれません。もし、データバインディングがそのような挙動を示すのであれば大変うれしいのですが、そのような振る舞いをするのでしょうか?

実験で確かめてみましょう。ビューモデルのTitleプロパティのSetメソッドの先頭の箇所に、デバッガーでブレークポイントを設定します。

 2

デバッグ起動したのち、TextBoxに表示されている書籍の題名を編集し、TABキーを押して変更を確定させてください。ここでもし、TexstBoxのTextプロパティの変更がビューモデルに書き戻されているのであれば、ビューモデルのプロパティのSetメソッドが呼ばれるので、先ほど設定したブレークポイントでアプリケーションの実行が一時中断するはずです。

中断しましたか?しませんでしたね。特別に指定しなければ、データバインディングは一方通行で作用します。このため、期待したいような振る舞いにはならないのです。

ところで、「特別に指定しなければ」とあるように、実はデータバインディングを双方向で作用するように、指定することもできます。

上記のようにMode=TwoWayの指定を追加した上で、もう一度、書籍の題名を変更してみてください。今度はTitleプロパティのSetメソッドに設定したブレークポイントで実行が中断されるはずです。

さて、TwoWayモードを指定した双方向バインディングを利用する上で注意すべきことが一点あります。書籍の価格を表示しているTextBoxについてもTwoWayを指定し、編集した金額がビューモデルのプロパティPriceに反映されるか同様に実験してみてください。

どうもデバッガーで設定したブレークポイントがヒットしていない(=変更が反映されていない)模様です。何が起こっているのでしょうか。ここで、Visual StudioのOutputウィンドウに目を向けてください。 以下のようなエラーメッセージに気がつくでしょう。

このエラーは、ビューモデルとビューのプロパティの型が異なるために発生しています。ここまで明示的には書いていませんでしたが、バインド元(ソース)のプロパティとバインド先(ターゲット)のプロパティの型が異なる場合、基本的になんらかの形でその変換方法を指定する必要があります。唯一の例外はターゲットの型が文字列(string)型の場合です。この場合、ソースのオブジェクトのToStringメソッドが呼び出され、文字列に変換されます。

双方向バインディングを用いる場合、ターゲットからソースへの書き戻しが発生しますが、この時にも同様の原則があてはまります。上記の例ではPriceプロパティがdecimal型のため、文字列のTextプロパティの値をどのように書き戻せばよいのかを指定しない限り、エラーとなってしまいます。

このような場合、双方向の変換を明示的に行うコンバーターを実装し、それをバインディング時に指定する必要があります。ここでは以下のようなコンバーターを実装してみましょう。

このコンバーターを静的リソースとして定義し、バインディング時に使用します。

この状態で再度実験してみてください。今度はPriceプロパティへの書き戻しが行われていることを確認できることかと思います。

サンプルコードはgithubから入手可能です。

Windows 8プログラミング本格入門

拙著「Windows 8プログラミング本格入門」の記述に関する訂正、注釈、記載できなかった技術情報などはこちらの一覧に記載しています。