Windows ストアアプリから Windows Azure へアクセスする(その3)

本エントリーは前回説明しました、「Windows ストアアプリから Windows Azure へアクセスする(その2)」の続きになります。前回までに作成した Azure サービスへ Windows 8 ストアアプリからアクセスし、データを表示します。

  1. Windows ストアアプリ プロジェクトの追加

    Windows 8 ストアアプリの作成はスクラッチから行うのではなく、日本マイクロソフトが公開している「Windows 8 アプリ開発体験テンプレート」を利用します。こちらの URL から 「Basic Photo テンプレート XAML/C# 用 Version 1.0」 をダウンロードして [ご利用方法] に従い設定します。
    01    

    ソリューションエクスプローラーでソリューション フォルダを右クリックして、[追加] – [新しいプロジェクト] を選択します。
    02

    [新しいプロジェクトの追加] ダイアログで [Visual C#] – [Basic Photo for Win8] を選択し、プロジェクト名を ”PicMgrClient” とします。.NET Framework のバージョンは 4.5 であることを確認し、[OK] ボタンを押します。
    03

    ウィザードから生成された PicMgrClient プロジェクト下の [DataModel] フォルダを右クリックして、[追加] – [既存の項目] を選択します。
     04

    [PicMgr.WebApi] フォルダ下の [Models] フォルダから “Picture.cs” を選択し、[追加] ボタンの右にある下矢印(▼)をクリックし、[リンクとして追加] を選択します。これは Azure サービスと同じ Picture モデルクラスを使用して、データコントラクトを一致させるための処置です。
    05

  2. Azure サービスとの連携ロジックの追加

    ソリューションエクスプローラーから [PicMgrClient] プロジェクト下の [DataModel] フォルダから “SampleDataSource.cs” を開きます。
    SampleDataSource() コンストラクタをコメントアウトします。これはハードコーディングされたコンテンツを使用せず、グループおよびアイテムのコレクションを Azure サービスから取得して作成するためです。

     //public SampleDataSource()
    //{
    ・・・(省略)
    //}
    

    System.Collections.Specialized に続き、以下の名前空間の宣言を追加します。(黄色に塗られた部分です。)

     using System.Collections.Specialized;
    using System.IO;
    using System.Net.Http;
    using Windows.Data.Json;
    using PicMgr.Models;
    using System.Threading.Tasks;
    

    SampleDataItem() デフォルトコンストラクタを追加します。

     public SampleDataItem()
        : base(String.Empty, String.Empty, String.Empty, String.Empty, 
    String.Empty)
    {
    }
    

    SampleDataSource クラスに Azure サービスからデータを取得するメソッドを追加します。これは Azure 側で実装した Web API に対して非同期で HTTP GET 呼び出しを行います(client.GetAsync)。その後、HTTP コンテンツを文字列として読み込み(response.Content.ReadAsStringAsync)、文字列を JSON 配列として扱えるようパースします(JsonArray.Parse)。serviceURI の “websites-name” には「Windows ストアアプリから Windows Azure へアクセスする(その1)」で作成した、Web Site の名前(本ブログの例では”claudia-picmgr”としました)で置き換えます。

     public static async Task LoadRemoteDataAsync()
    {
        var serviceURI = 
    "https://websites-name.azurewebsites.net/api/Pictures";
    
        using (var client = new System.Net.Http.HttpClient())
        {
            client.MaxResponseContentBufferSize = 1024 * 1024;
    
            HttpResponseMessage response = 
    await client.GetAsync(serviceURI as string);
    
            response.EnsureSuccessStatusCode();
    
            var result = await response.Content.ReadAsStringAsync();
            var pictures = JsonArray.Parse(result);
        }
    }
    

    この JSON 配列からキー / 値の情報を取り出し、Windows 8 ストアアプリのグリッドビューにグループを固定で1つとそのグループに属する項目を画像の数だけ作成するメソッドを追加します。(ご自分の Azure 環境でお試しになる際は、下記コード内の BLOB URL から画像を入手し、ご自分の Azure Storage BLOB へアップロードした上で URL を置き換えてください。)

     private static void CreatePicturesAndPictureGroups(JsonArray array)
    {
        SampleDataGroup group = new SampleDataGroup("Group-1",
    "クラウディア",
    "クラウディア カレンダー画像",
    "https://claudiapicmgr.blob.core.windows.net/pictures/Claudia_pen.png",
    "Group Description: Lorem ipsum dolor sit amet, 
    consectetur adipiscing elit. Vivamus tempor scelerisque lorem in 
    vehicula. Aliquam tincidunt, lacus ut sagittis tristique, 
    turpis massa volutpat augue, eu rutrum ligula ante a ante"
        );
    
        foreach (var item in array)
        {
            var obj = item.GetObject();
            SampleDataItem picture = new SampleDataItem();
    
            foreach (var key in obj.Keys)
            {
                IJsonValue val;
                if (!obj.TryGetValue(key, out val))
                    continue;
    
                switch (key)
                {
                    case "UniqueId":
                        picture.UniqueId = val.GetString();
                        break;
                    case "Title":
                        picture.Title = val.GetString();
                        break;
                    case "SubTitle":
                        picture.Subtitle = val.GetString();
                        break;
                    case "Description":
                        picture.Description = val.GetString();
                        break;
                    case "ImagePath":
                        picture.SetImage(val.GetString());
                        break;
                }
            }
    
            if (group != null)
            {
                picture.Content = picture.Description;
                picture.Group = group;
                group.Items.Add(picture);
            }
        }
        _sampleDataSource.AllGroups.Add(group);
    
    }
    

    LoadRemoteDataAsync メソッドから今のメソッドを呼び出す処理を追加します。(黄色に塗られた部分です。)

     var pictures = JsonArray.Parse(result);
    
    CreatePicturesAndPictureGroups(pictures);
    

    PicMgrClient プロジェクトの “App.xaml.cs” を開き、OnLaunched ハンドラ内でここまで実装しました LoadRemoteDataAsync メソッド呼び出しを追加します。(黄色に塗られた部分です。また、ソースコード内のコメントは削除しています。) 

     protected override async void OnLaunched(LaunchActivatedEventArgs args)
    {
        Frame rootFrame = Window.Current.Content as Frame;
    
        if (rootFrame == null)
        {
            rootFrame = new Frame();
            SuspensionManager.RegisterFrame(rootFrame, "AppFrame");
    
            if (args.PreviousExecutionState == 
    ApplicationExecutionState.Terminated)
            {
                try
                {
                    await SuspensionManager.RestoreAsync();
                }
                catch (SuspensionManagerException)
                {
                }
            }
    
            await SampleDataSource.LoadRemoteDataAsync();
    
            Window.Current.Content = rootFrame;
        }
        if (rootFrame.Content == null)
        {
            if (!rootFrame.Navigate(typeof(GroupedItemsPage), "AllGroups"))
            {
                throw new Exception("Failed to create initial page");
            }
        }
        SettingsLoad();
    
        Window.Current.Activate();
    }
    

    以下の名前空間の宣言を追加します。(黄色に塗られた部分です。)

     using PicMgrClient.Common;
    using PicMgrClient.Data;
    

    [Shift] + [Ctrl] + [B] キーを押し、PicMgrClient プロジェクトをビルドします。正常にビルドできたら、デバッグモードで起動してみてください。デフォルトのスプラッシュ画面が表示された後、以下のようなグリッドビューが表示されると思います。(画像の読み込みには若干時間がかかるかも知れません。)

    06

    Windows 8 ストアアプリから Azure サービスへのアクセス自体は正常に動作していますが、画像が切れているようです。グリッドビューのスタイルを調整して、正しく表示されるようにします。

  3. グリッドビューのスタイル調整

    PicMgrClient プロジェクトの “GroupedItemsPage.xaml” を開き、以下のように "Grouped Items" という名前の<GridView> タグを探します。

     <!-- ほとんどのビューステートで使用される水平スクロール グリッド-->
    <SemanticZoom x:Name="itemGridView"
                  Grid.RowSpan="2"
                  Padding="116,137,40,46">
        <SemanticZoom.ZoomedInView>
            <GridView
        AutomationProperties.AutomationId="ItemGridView"
        AutomationProperties.Name="Grouped Items"
        ItemsSource="{Binding Source=
    {StaticResource groupedItemsViewSource}}"
        ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
        SelectionMode="None"
        IsSwipeEnabled="false"
        IsItemClickEnabled="True"
        ItemClick="ItemView_ItemClick">
    

    PicMgrClient プロジェクト下の [Common] フォルダにある “StandardStyles.xaml” を開き、上の黄色で塗られた「Standard250x250ItemTemplate」 を探し、定義を確認します。

     <!-- GroupedItemsPage と ItemsPage に表示される、
    グリッドに適した 250 ピクセルの四角形のアイテム テンプレート -->
    <DataTemplate x:Key="Standard250x250ItemTemplate">
        <Grid HorizontalAlignment="Left" Width="250" Height="250">
            <Border Background=
    "{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
                <Image Source="{Binding Image}" Stretch="UniformToFill" 
    AutomationProperties.Name="{Binding Title}"/>
            </Border>
            <StackPanel VerticalAlignment="Bottom" 
    Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                <TextBlock Text="{Binding Title}" 
    Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" 
    Style="{StaticResource TitleTextStyle}" Height="60" 
    Margin="15,0,15,0"/>
                <TextBlock Text="{Binding Subtitle}" 
    Foreground="{StaticResource 
    ListViewItemOverlaySecondaryForegroundThemeBrush}" 
    Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" 
    Margin="15,0,15,10"/>
            </StackPanel>
        </Grid>
    </DataTemplate>
    

    これを見ると、「Standard250x250ItemTemplate」 の定義を修正すればよいようです。ただし、直接 “StandardStyles.xaml” の内容は変更しません。代わりにカスタマイズされたアイテムテンプレートを定義し、そちらを適用します。

    ソリューションエクスプローラーから PicMgrClient プロジェクトを右クリックして、[追加] – [新しい項目] を選択します。[新しい項目の追加] ダイアログから “リソース ディクショナリ” を選択し、ファイル名を “MyResourceDictionary.xaml” とし、[追加] ボタンを押します。

    07

    “Standard250x250ItemTemplate” の定義をコピーし、<ResourceDictionary> タグの中に以下のテンプレート定義として追加します。修正した個所は黄色に塗られた部分です。Width と Height は元の画像が 16:9 のためにうまく適合するように、Stretch も縦横比を維持しながら、全体のイメージの表示に必要な領域を 100% 使えるように指定しています。

     <DataTemplate x:Key="MyGroupedItemTemplate">
        <Grid HorizontalAlignment="Left" Width="384" Height="216">
            <Border Background=
    "{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
                <Image Source="{Binding Image}" Stretch="Uniform"  
    AutomationProperties.Name="{Binding Title}"/>
            </Border>
            <StackPanel VerticalAlignment="Bottom" 
    Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                <TextBlock Text="{Binding Title}" 
    Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" 
    Style="{StaticResource TitleTextStyle}" Height="60" 
    Margin="15,0,15,0"/>
                <TextBlock Text="{Binding Subtitle}" 
    Foreground="{StaticResource 
    ListViewItemOverlaySecondaryForegroundThemeBrush}" 
    Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" 
    Margin="15,0,15,10"/>
            </StackPanel>
        </Grid>
    </DataTemplate>
    

    “GroupedItemsPage.xaml” へ戻り、以下のように "Grouped Items" という名前の<GridView> タグの “ItemTemplate” 属性の値を以下のように変更します。(黄色に塗られた部分です。)

     <!-- ほとんどのビューステートで使用される水平スクロール グリッド-->
    <SemanticZoom x:Name="itemGridView"
                  Grid.RowSpan="2"
                  Padding="116,137,40,46">
        <SemanticZoom.ZoomedInView>
            <GridView
        AutomationProperties.AutomationId="ItemGridView"
        AutomationProperties.Name="Grouped Items"
        ItemsSource="{Binding Source=
    {StaticResource groupedItemsViewSource}}"
        ItemTemplate="{StaticResource MyGroupedItemTemplate}"
        SelectionMode="None"
        IsSwipeEnabled="false"
        IsItemClickEnabled="True"
        ItemClick="ItemView_ItemClick">
    

    “StandardStyles.xaml” とカスタマイズされたアイテムテンプレートとをマージするには、”App.xaml” 内に以下のように定義を追加します。

     <ResourceDictionary.MergedDictionaries>
    
        <!-- 
            プラットフォームの外観の共通の要素を定義するスタイル
            Visual Studio プロジェクトおよびアイテム テンプレートで必要です
         -->
        <ResourceDictionary Source="Common/StandardStyles.xaml"/>
        <ResourceDictionary Source="MyResourceDictionary.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    

    これで再びビルドし、デバッグモードで実行してみてください。

    08

    今度はうまく表示されたはずです。

    では、他の部分も調整してみましょう。

    以下は “MyResourceDictionary.xaml” です。黄色で塗られた部分を追加します。

     <ResourceDictionary
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:PicMgrClient2">
    
        <DataTemplate x:Key="MyGroupedItemTemplate">
            <Grid HorizontalAlignment="Left" Width="384" Height="216">
                <Border Background=
    "{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
                    <Image Source="{Binding Image}" Stretch="Uniform"  
    AutomationProperties.Name="{Binding Title}"/>
                </Border>
                <StackPanel VerticalAlignment="Bottom" 
    Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
                    <TextBlock Text="{Binding Title}" 
    Foreground="{StaticResource 
    ListViewItemOverlayForegroundThemeBrush}" 
    Style="{StaticResource TitleTextStyle}" Height="60" 
    Margin="15,0,15,0"/>
                    <TextBlock Text="{Binding Subtitle}" 
    Foreground="{StaticResource 
    ListViewItemOverlaySecondaryForegroundThemeBrush}" 
    Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" 
    Margin="15,0,15,10"/>
                </StackPanel>
            </Grid>
        </DataTemplate>
    
        <DataTemplate x:Key="MyGroupDetailItemTemplate">
            <Grid Height="110" Width="480" Margin="10">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Border Background=
    "{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" 
    Width="110" Height="110">
                    <Image Source="{Binding Image}" Stretch="Uniform" 
    AutomationProperties.Name="{Binding Title}"/>
                </Border>
                <StackPanel Grid.Column="1" VerticalAlignment="Top" 
    Margin="10,0,0,0">
                    <TextBlock Text="{Binding Title}" 
    Style="{StaticResource TitleTextStyle}" TextWrapping="NoWrap"/>
                    <TextBlock Text="{Binding Subtitle}" 
    Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap"/>
                    <TextBlock Text="{Binding Description}" 
    Style="{StaticResource BodyTextStyle}" MaxHeight="60"/>
                </StackPanel>
            </Grid>
        </DataTemplate>
    
        <DataTemplate x:Key="MyStandard80ItemTemplate">
            <Grid Margin="6">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Border Background=
    "{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}" 
    Width="60" Height="60">
                    <Image Source="{Binding Image}" Stretch="Uniform"/>
                </Border>
                <StackPanel Grid.Column="1" Margin="10,0,0,0">
                    <TextBlock Text="{Binding Title}" 
    Style="{StaticResource ItemTextStyle}" MaxHeight="40"/>
                    <TextBlock Text="{Binding Subtitle}" 
    Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap"/>
                </StackPanel>
            </Grid>
        </DataTemplate>
    
    </ResourceDictionary>
    

    以下は “GroupedItemsPage.xaml” です。黄色で塗られた部分を置き換えます。

     <!-- スナップの場合のみ使用される垂直スクロール リスト -->
    <ListView
        x:Name="itemListView"
        AutomationProperties.AutomationId="ItemListView"
        AutomationProperties.Name="Grouped Items"
        Grid.Row="1"
        Visibility="Collapsed"
        Margin="0,-10,0,0"
        Padding="10,0,0,60"
        ItemsSource="{Binding Source=
    {StaticResource groupedItemsViewSource}}"
        ItemTemplate="{StaticResource MyStandard80ItemTemplate}"
        SelectionMode="None"
        IsSwipeEnabled="false"
        IsItemClickEnabled="True"
        ItemClick="ItemView_ItemClick">
    

    以下は “GroupDetailPage.xaml” です。黄色で塗られた部分を置き換えます。

     <!-- ほとんどのビューステートで使用される水平スクロール グリッド-->
    <GridView
        x:Name="itemGridView"
        AutomationProperties.AutomationId="ItemGridView"
        AutomationProperties.Name="Items In Group"
        TabIndex="1"
        Grid.RowSpan="2"
        Padding="120,126,120,50"
        ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
        ItemTemplate="{StaticResource MyGroupDetailItemTemplate}"
        SelectionMode="None"
        IsSwipeEnabled="false"
        IsItemClickEnabled="True"
        ItemClick="ItemView_ItemClick">
    
        <GridView.Header>
            <StackPanel Width="480" Margin="0,4,14,0">
                <TextBlock Text="{Binding Subtitle}" Margin="0,0,18,20" 
    Style="{StaticResource SubheaderTextStyle}" MaxHeight="60"/>
                <Image Source="{Binding Image}" Height="400" 
    Margin="0,0,18,20" Stretch="Uniform" 
    AutomationProperties.Name="{Binding Title}"/>
                <TextBlock Text="{Binding Description}" Margin="0,0,18,0" 
    Style="{StaticResource BodyTextStyle}"/>
            </StackPanel>
        </GridView.Header>
    

    同じく “GroupDetailPage.xaml” です。黄色で塗られた部分を置き換えます。

     <!-- スナップの場合のみ使用される垂直スクロール リスト -->
    <ListView
        x:Name="itemListView"
        AutomationProperties.AutomationId="ItemListView"
        AutomationProperties.Name="Items In Group"
        TabIndex="1"
        Grid.Row="1"
        Visibility="Collapsed"
        Padding="10,0,0,60"
        ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
        ItemTemplate="{StaticResource Standard80ItemTemplate}"
        SelectionMode="None"
        IsSwipeEnabled="false"
        IsItemClickEnabled="True"
        ItemClick="ItemView_ItemClick">
    

                             
    こちらはオプションですが、スプラッシュ画面を変更するにはソリューションエクスプローラーから “Package.appxmanifest” をダブルクリックして開き、[アプリケーション UI] タブから [スプラッシュ スクリーン]のパスと [背景色] を適当に変更します。

    09 

    こちらの URL から画像を取得し置き換えると以下のようなスプラッシュ画面に変更できます。

    10

  4. まとめ

    これまで3回に渡って、Windows ストアアプリから Windows Azure へアクセスするためのサンプル アプリをステップ・バイ・ステップ形式にて説明してきました。コードやデータモデルがなるべくシンプルになるよう、何ヶ所か固定で定義してきましたが、どこを修正すればよいか大体当たりがつくようになったかと思われます。

    Windows 8 の一般発売まであと3日になりました。これからも多くの Windows ストアアプリが作成されるかと思いますが、バックエンドに Windows Azure をご利用いただき、 Windows 8・パブリッククラウドサービス双方の強みを活かしたアプリケーションの開発に、今回の説明が少しでもお役に立てば幸いです。