kazuakix の日記

Windows Phone とか好きです

プロ生ちゃんで学ぶ データ バインディング

プロ生ちゃん Advent Calendar 2014 14 日目の記事です。

先日、マスコットアプリ文化祭 2014 向けに超シンプルなアプリを作りました。

キャラクターを選んでホーム画面にタイルを作るだけというシンプルアプリですが、プログラムの方もすごくシンプルで ぶっちゃけ何もしていません。データ バインディングという機能をつかってどれだけ手を抜い楽をしているのかご紹介します。

ビューモデルを作る

このアプリで管理しているデータは、各キャラクターの名前、画像、公式 Web ページの URL だけです。

普通ならこれらのデータを扱うモデルと画面との受け渡しを行うビューモデルを作るのですが、あまりに単純すぎるのでモデルを割愛して いきなりビューモデルを作りました。名前が ProfilePanelViewModel になっているのは 後で ProfilePanel という画面(ビュー)を作るからです。

public class ProfilePanelViewModel
{
    public string Name       { get; set; } // 名前
    public Uri    Image      { get; set; } // 立ち絵
    public Uri    HeadImage  { get; set; } // タイル用の顔画像
    public Uri    WebPageUri { get; set; } // Web ページ
}

サンプルデータの作成

上記のクラスを作ったら Blend に移動します。画面右上のデータタブからちょっと見つけにくいアイコンを押してクラスからサンプルデータを作成します。

f:id:kazuakix:20141213161841j:plain,w360

f:id:kazuakix:20141213162107j:plain,w360

サンプルデータができたら これまた少しわかりにくいアイコンからサンプルデータ値を編集します。

f:id:kazuakix:20141213161904j:plain,w360

とりあえず、名前と立ち絵があればいいので必要なプロパティの値を書き換えました。

<ViewModels:ProfilePanelViewModel xmlns:ViewModels="using:MascotTiles.ViewModels" 
    Name="Pronama-Chan" 
    Image="ms-appx:///Assets/pronama-chan.png" 
/>

尚、画像は前もってコンテストで配布している素材をリサイズしてプロジェクトに追加しています。

f:id:kazuakix:20141213162618j:plain,w360

ビューを作る

続いて画面を作ります。Blend で ProfilePanel というユーザーコントロールを作りました。
とりあえず名前を表示するための TextBlock と立ち絵を表示するための Image だけを貼っています。

f:id:kazuakix:20141213162958j:plain,w500

かなり端折った XAML はこんな感じ

<UserControl x:Class="MascotTiles.Views.ProfilePanel">
    <Grid Background="White">
        <TextBlock Text="TextBlock" />
        <Image  />
    </Grid>
</UserControl>

 
このビューに先ほどのサンプルデータをドラッグしていきます。

まずはサンプルデータ自体をユーザーコントロールに、続けてサンプルデータの Name を TextBlock に、Image を Image にドラッグします。

f:id:kazuakix:20141213163244j:plain,w500
 
サンプルデータを貼り付けるとこんな感じになります。デザイナで実際のデータが表示されるので完成形をイメージしやすいですね。

f:id:kazuakix:20141213163517j:plain,w360

XAML を見るとユーザーコントロールに DataContext が、TextBlock, Image に "{Binding xxx}" が追加されているのがわかると思います。これがデータバインディングです。
コードで "TextBlock.Text =" みたいな事は書く必要はありません。

<UserControl x:Class="MascotTiles.Views.ProfilePanel"
    d:DataContext="{d:DesignData /SampleData/ProfilePanelViewModelSampleData.xaml}">
    <Grid Background="White">
        <TextBlock Text="{Binding Name}" />
        <Image Source="{Binding Image}" />
    </Grid>
</UserControl>


この後 Blend を使ってちょっとだけデザインを直しました。

f:id:kazuakix:20141213164408j:plain,w360

以下 繰り返し

キャラクターを表示するパネルができたのでメインの(そして唯一の)画面を作ります。ここでもやはり ビューモデルとビューだけを作ります。

ビューモデル

メイン画面で必要なのはキャラクターの一覧と、現在表示されているキャラクターです。

キャラクターは先ほど作った ProfilePanelViewModel で管理しているので そのクラスのリスト、現在表示されているキャラクターについても ProfilePanelViewModel で管理するようにしました。

public class MainPageViewModel : ViewModel
{
    public ObservableCollection<ProfilePanelViewModel> Mascots { get; set; }

    private ProfilePanelViewModel _currentProfile;
    public ProfilePanelViewModel CurrentProfile
    {
        get { return _currentProfile; }
        set { SetProperty(ref _currentProfile, value); }
    }
}

今回のビューモデルは、画面が操作されたときやデータが変更されたときに状態が変わったことを通知する必要があるので、ちょっと特殊な事をしています。このために Prism というライブラリの ViewModel を継承して、データを変更するときに SetProperty() を呼んでいます。キャラクター一覧が単純な List<> ではなく ObservableCollection<> という長ったらしい名前になっているのも変更を通知するためです。

変更の通知に関してはこちらもご参照ください。

ふたたび Blend に戻って、サンプルデータを修正します。

<ViewModels:MainPageViewModel xmlns:ViewModels="using:MascotTiles.ViewModels">
    <ViewModels:MainPageViewModel.Mascots>
        <ViewModels:ProfilePanelViewModel 
            Name="Pronama-Chan" 
            Image="ms-appx:///Assets/pronama-chan.png" />
        <ViewModels:ProfilePanelViewModel
            Name="Conoha-Chan"
            Image="ms-appx:///Assets/conoha-chan.png" />
    </ViewModels:MainPageViewModel.Mascots>
</ViewModels:MainPageViewModel>

ここでもデザインに必要なキャラクター一覧だけを変更しました。

ビュー

Windows Phone の特徴である Pivot コントロールを置いただけの画面を作ってサンプルデータをドラッグしていきます。

f:id:kazuakix:20141213171609j:plain,w500

画面の XAML はこんな感じ。標準の Page ではなく Prism の VisualStateAwarePage を使っていますが、それ以外に特殊なところはありません。

<prism:VisualStateAwarePage
    x:Class="MascotTiles.Views.MainPage"
    xmlns:local="using:MascotTiles.Views"
    d:DataContext="{d:DesignData /SampleData/MainPageViewModelSampleData.xaml}">
    
    <prism:VisualStateAwarePage.Resources>
        <DataTemplate x:Key="MascotPanelTemplate">
            <Grid>
                <local:ProfilePanel />
            </Grid>
        </DataTemplate>
    </prism:VisualStateAwarePage.Resources>
    
    <Grid>
        <Pivot ItemsSource="{Binding Mascots}" ItemTemplate="{StaticResource MascotPanelTemplate}" 
            SelectedItem="{Binding CurrentProfile, Mode=TwoWay}"/>
    </Grid>
</prism:VisualStateAwarePage>

ポイントは Pivot の ItemSource にキャラクター一覧 (Mascots) をバインドして、ItemTemplate として先に作った ProfilePanel を指定している点と 同じく Pivot の SelectedItem に現在表示されているキャラクター (CurrentProfile) を Mode=TwoWay としてバインドしている点です。

これでキャラクター一覧の数だけ ProfilePanel が表示され、画面でキャラクターを切り替えたときもビューモデルの CurrentProfile で表示されているキャラクターを知り事ができるようになりました。やはり "hoge = Pivot.SelectedItem" のようなコードを書く必要はありません。

最後にビューモデルのコンストラクタで本番データを突っ込んおきます。

public class MainPageViewModel : ViewModel
{
    public MainPageViewModel()
    {
        Mascots = new ObservableCollection<ProfilePanelViewModel>
        {
            new ProfilePanelViewModel("Pronama-Chan", "http://pronama.azurewebsites.net/pronama/"),
            new ProfilePanelViewModel("Conoha-Chan",  "https://www.conoha.jp/blog/conoha"),
            new ProfilePanelViewModel("Anzu-Chan",    "http://cloud.gmo.jp/anzu/"),
            new ProfilePanelViewModel("Claudia-San",  "http://msdn.microsoft.com/ja-jp/hh508969"),
            new ProfilePanelViewModel("Unity-Chan",   "http://unity-chan.com/"),
            new ProfilePanelViewModel("Query-Chan",   "http://www.query-chan.com/"),
            new ProfilePanelViewModel("daruyanagi",   "http://daruyanagi.net/")
        };
        CurrentProfile = Mascots.First();
    }
}

実行してみるとキャラクター分のページが追加されている事がわかります。

f:id:kazuakix:20141213174613j:plain,w600

まとめ

こんな感じで、データの入れ物であるビューモデル以外ロクなコードも書かずに画面ができあがりました。ありがちな「画面に値をセットする」や「画面の値を読み込む」みたいな処理はデータ バインディングを使って楽をしましょう。

慣れるとすごくお手軽に画面が作れるようになる Windows Phone 、マジ天使なアプリが増えてくれる事を期待しています。

f:id:kazuakix:20141213182520j:plain,w300