KatsuYuzuのブログ

.NET の開発をメインとした日記です。

Windows PhoneアプリのローカリゼーションとApplication BarのBinding #wpjp

この記事はWindows Phone Advent Calendar 2013 - Adventarの18日目の記事です。昨日は@Kera1601さんのPeopleハブの分析と活用 ~Windows Phone Advent Calendar 2013 17日目~ | 高校生のComputer備忘録と考察です。
ドコモメールをWindows Phoneで運用する方法を紹介しようとしたらガラケーユーザーには無理で断念。端末購入にまで至ってる素晴らしい記事があったので紹介。

断念に至ったメモは下記にて。こちらのメモ記事でも上記リンクを結論として紹介させて頂きました。

そして持ってたソースを漁ってひねり出した下記のテーマ(無計画

Windows Phoneアプリのローカリゼーション

いまどきのスマートフォンアプリは多言語対応されていることが多いかと思います。Windows Phoneでも多い話題ですがおさらいです。
プロジェクトテンプレートが色々と準備済みなので、プロジェクトを作成したらMainPage.xamlの指示に従うだけです。

<!-- ローカライズに関する注:
    表示された文字列をローカライズするには、その値を、アプリのニュートラル言語
    リソース ファイル (AppResources.resx) 内の適切な名前のキーにコピーしてから、
    属性の引用符間のハードコーディングされたテキスト値を、パスがその文字列名を
    指しているバインド句と置き換えます。

    例:

        Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"

    このバインドは、テンプレートの "ApplicationTitle" という文字列リソースを指します。

    [プロジェクトのプロパティ] タブでサポートされている言語を追加すると、
    新しい resx ファイルが、UI 文字列の翻訳された値を含む言語ごとに作成
    されます。これらの例にあるバインドにより、属性の値が、実行時に
    アプリの CurrentUICulture と一致する .resx ファイルから描画されます。
    
 -->

まずはプロジェクトのプロパティで言語を追加します。すると、resxファイルが作成されますので、同じキーでそれぞれの言語のリソースを定義します。
f:id:KatsuYuzu:20131218020807p:plain
あとはデータバインドするだけです。簡単!*1
f:id:KatsuYuzu:20131218021344p:plain
実行するとresxで指定した文字列がバインドされています。端末の設定で言語を変えて再起動するとサポートしてる言語に切り替わります。
f:id:KatsuYuzu:20131218022034p:plain

Application BarのBinding

さて唐突にApplication Barの話になりましたが、実はApplication Barはバインドが使えないので実行時にコードビハインドからインスタンスを生成して挿入する必要があり面倒です。バインドが使える時の簡単さはこの記事でもご覧になった通りなので是非バインドさせたいですよね。
やり方は簡単で、バインド出来ないプロパティにはよく使う(?)テクニックです。バインド可能なプロパティを持ったビヘイビアーを作成して、ビヘイビアーから変更を同期させます。
……が、問題があってApplication Barにビヘイビアーを設定できないのでPhoneApplicationPage用のビヘイビアーを作成します。

    public class BindToApplicationBarIconButtonBehavior : Behavior<PhoneApplicationPage>
    {
        /// <summary>
        /// テキストを取得または設定します。
        /// </summary>
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(BindToApplicationBarIconButtonBehavior), new PropertyMetadata(TextChangedHandler));

この時、どのボタンにバインドさせるかを指定する必要があるのでp&p prismを参考にTextで特定させてみます。*2

 void ChangeText()
 {
     if (this.Text == null) throw new InvalidOperationException("Text が設定されていません。");

     var buttons = this.AssociatedObject.ApplicationBar.Buttons
         .OfType<ApplicationBarIconButton>()
         .Where(x => x.Text == this.TargetItemStaticText);

     if (!buttons.Any()) throw new InvalidOperationException("TargetItemStaticText が一致するボタンが見つかりません。");
     if (buttons.Count() > 1) throw new InvalidOperationException("他のアイテムと同じテキストを設定することはできません。");

     buttons.First().Text = this.Text;
     this.TargetItemStaticText = this.Text;
 }

あとはxamlでこんな風にボタンのTextとビヘイビアーの特定用Textを一致させます。

<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
        <shell:ApplicationBar.MenuItems>
            <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
        	<shell:ApplicationBarMenuItem Text="MenuItem 2"/>
        </shell:ApplicationBar.MenuItems>
        <shell:ApplicationBarIconButton IconUri="/Assets/AppBar/add.png" Text="1"/>
        <shell:ApplicationBarIconButton IconUri="/Assets/AppBar/delete.png" Text="2"/>
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

<i:Interaction.Behaviors>
	<local:BindToApplicationBarIconButtonBehavior TargetItemStaticText="1" Text="{Binding LocalizedResources.AppBarButton_Add, Mode=OneWay, Source={StaticResource LocalizedStrings}}"/>
</i:Interaction.Behaviors>

実行すると"1"に設定したボタンがバインディングされ、無事に完了です。
f:id:KatsuYuzu:20131218021406p:plain
ビヘイビアーのソースはgistにあげました。埋め込むと長くなるのでリンク置いときます。

あとがき

prismではコマンドもバインドさせており、本来そこまでやらないとBlendぽとぺた的においしくないのですが、ローカライズバインディングに着目して割愛しました。クリックイベントの参照と解放を内包するinner classでコマンドの発火を管理しているようですね。
nugetさんでprism使いなよーって投げようとしたらWP7で時代が止まってたので、この辺のパーツは移植しておくといいですね。

*1:余談ですがVS2012のデザイナからはデータバインドの作成がBlendチックで簡単になりましたね

*2:ざっと見直していて記事中のlinqはシングルじゃねとか変数でうけろとか思ったけど内容に関係なさげなのでそのままにしておきます。