使ってみよう! Bing API/SDK

第7回Hello, Bing Map App!─⁠─Silverlightで作るBing Mapsアプリケーション(7)

My Photo Map Appの開発

前回はBing Map Appのコンテストで受賞・応募作品を紹介しました。Bing公式のコンテストは終了してしまいましたが、Mashup Awards 6が開催されています。こちらの対象APIには、Map App SDKや、その他のBing API/SDKがありますので、腕に自信のある方は応募されてみるといいかもしれませんね。

さて今回と次回は、これまでの内容を(すべてではありませんが)踏まえつつ、簡単なMap Appを作成します。それがMy Photo Map Appです図1⁠。このアプリは、ユーザーが写真をBing Maps上に表示でき、次のような機能を持っています。

  • ユーザーの写真を地図上とパネルに表示
  • 写真のサムネイルと経緯度情報を取得して使用
  • パネルの写真を選択するとその場所に移動
  • プッシュピンをクリックすると写真をポップアップ表示
  • 追加した写真情報の保存
    • 図1 My Photo Map App
      図1 My Photo Map App

      ユーザーのPCからの写真の読み込みや、ブラウザーへのファイルのドラッグ&ドロップ操作など、Silverlightの機能も活用していきます。また、見た目も図のようにBingのスタイルに合うように作成します。写真のWebアップロードは行わず、ユーザーのPC上でのみ楽しめるものとします。

      プラグインの作成

      それでは、これまでと同様にSilverlightクラスプロジェクトから、プラグイン クラスを作成しましょう。また、プッシュピンなどのエンティティを表示するレイヤー クラスも用意しておきます。各クラス ファイルの内容は次の通りです。

      プラグイン クラス(MyPhotoPlugin.cs)
      namespace MyPhotoMapApp
      {
          using Microsoft.Maps.Core;
          using Microsoft.Maps.Plugins;
          using Microsoft.Maps.MapControl;
      
          public class MyPhotoPlugin : Plugin
          {
              [ImportSingle("Microsoft/MapContract", ImportLoadPolicy.Synchronous)]
              public MapContract DefaultMap { get; set; }
      
              [ImportSingle("Microsoft/LayerManagerContract", ImportLoadPolicy.Synchronous)]
              public LayerManagerContract LayerManagerContract { get; set; }
      
              [ImportSingle("Microsoft/PushpinFactoryContract", ImportLoadPolicy.Synchronous)]
              public PushpinFactoryContract PushpinFactoryContract { get; set; }
      
              [ImportSingle("Microsoft/PopupContract", ImportLoadPolicy.Synchronous)]
              public PopupContract PopupContract { get; set; }
      
              public MyPhotoLayer MainLayer { get; set; }
              
              public override void Initialize()
              {            
                  base.Initialize();
                  this.MainLayer = new MyPhotoLayer(this.Token, this);
              }
      
              public override void Activate(System.Collections.Generic.IDictionary<string, string> activationParameters)
              {
                  if (LayerManagerContract.ContainsLayer(this.MainLayer))
                  {
                      LayerManagerContract.BringToFront(this.MainLayer);
                  }
                  else
                  {
                      LayerManagerContract.AddLayer(this.MainLayer);
                  }
              }
          }
      }
      レイヤー クラス(MyPhotoLayer.cs)
      namespace MyPhotoMapApp
      {
          using Microsoft.Maps.Core;
          using Microsoft.Maps.Plugins;
          using System;
       
          public class MyPhotoLayer : Layer
          {
              private MyPhotoPlugin plugin;
       
              public MyPhotoLayer(PluginToken pluginToken, MyPhotoPlugin plugin)
                  : base(pluginToken)
              {
                  this.plugin = plugin;
                  this.Title = "My Photo";
       
                  var panel = new MyPhotoPanel(plugin);
                  this.Panel = panel;
              }
          }
      }

      パネルとなるSilverlightユーザーコントロールもプロジェクトに追加します。今回のアプリの方針として、ほとんどの処理をこのパネルコントロールのコードビハインドに記述(MyPhotoPanel.xaml.csに記述)します。

      地図の参照やプッシュピンの追加などContractによって提供されている機能や、レイヤーの参照などは、プラグイン クラスのプロパティから参照します。そのため、レイヤー クラスを通してパネルでもプラグイン クラス(MyPhotoPlugin)を参照できるように記述します。

      パネル コントロール(MyPhotoPanel.xaml.cs)
      namespace MyPhotoMapApp
      {
          using Microsoft.Maps.Core;
          using Microsoft.Maps.Plugins;
          using Microsoft.Maps.Network;
          using Microsoft.Maps.MapControl;
          using System.Collections.Generic;
          using System.Collections.ObjectModel;
          using System;
          using System.IO;
          using System.Windows;
          using System.Windows.Controls;
          using System.Windows.Input;
          using System.Windows.Media;
          using System.Windows.Media.Imaging;
      
          public partial class MyPhotoPanel : UserControl
          {
              private MyPhotoPlugin plugin;
      
              public MyPhotoPanel(MyPhotoPlugin plugin)
              {
                  InitializeComponent();
      
                  this.plugin = plugin;
              }
          }
      }

      MyPhotoPanel.xamlのほうは、後で編集します。以上がプラグインを構成する基本となるクラスです。

      PhotoEntityクラス

      次に、写真を表すクラスを作成してプラグインで使用します。Entityクラスを継承し、レイヤーには追加して、パネル上のリスト用にはコレクションとして使用します。最低限な情報として次のプロパティを追加します。

      • ID(文字列)
      • 名前(文字列)
      • 写真(BitmapImgeクラス)

      位置情報は、Entityクラス自身が持っているため追加する必要はありません。名前には今回、ファイル名を設定して使います。プロジェクトにクラスを追加して次のように記述します。

      namespace MyPhotoMapApp
      {
          using Microsoft.Maps.Core;
          using System.Windows.Media.Imaging;
          using System;
      
          public class PhotoEntity : Entity
          {
              public string Id { get; set; }
              public BitmapImage BitmapImage { get; set; }
              
              public string Name
              {
                  get
                  {
                      return (string)base.GetValue(RetrieveProperty("Core.Name", typeof(string)));
                  }
                  set
                  {
                      base.SetValue(RetrieveProperty("Core.Name", typeof(string)), value);
                  }
              }
          }
      }

      上記コードではNameプロパティで少し特殊なことをしているので説明します。エンティティの名前というのは、今回の写真を表すものだけでなく、すべてのエンティティでも使用されるような一般的な情報です。実際にEntityクラスはそう設計されており、名前のプロパティを持っています。ただし、XAMLの機能である依存プロパティとして実装されています。依存プロパティについての説明は割愛しますが、上記コードでは通常のプロパティからアクセスできるようにしています。また、Bing Map SDKでは上記のようにEntityのRetrievePropertyメソッドを使用します。

      Map Appにおいて、この依存プロパティを使用することは、Contractで提供されるルート探索などを使用した場合に、プロパティの値が使用され正しい動作が期待できます。今回のアプリでは特に関係がありません。Core.Nameのほかにも、Core.Address、Core.Phone、Core.Description、Core.PluginTokenがあります。

      このPhotoEntityクラスは、パネルクラス内でコレクションとして保持するようにします。MyPhotoPanelクラス内を次のように編集します。

      private MyPhotoPlugin plugin;
      private ObservableCollection<PhotoEntity> PhotoItems; // 追加
      
      public MyPhotoPanel(MyPhotoPlugin plugin)
      {
          InitializeComponent();
      
          this.plugin = plugin;
          this.PhotoItems = new ObservableCollection<PhotoEntity>(); // 追加
      }

      パネルのデザイン

      次にパネルの見た目の部分を作成していきましょう。パネル上には、写真の項目を表示するListBoxと写真追加のためのButtonがあります図2⁠。スクロールバーやボタンのスタイルやBing Mapsのスタイルを適用しています。また、ListBoxの項目は図のように写真とリンクから構成し、この部分もデザインします。

      図2 パネルのデザイン
      図2 パネルのデザイン

      XAMLの編集

      順にMyPhotoPanel.xamlを編集していきましょう。まずListBoxとButtonです。MyPhotoPanel.xamlの内容を次のように書き換えます。

      <UserControl x:Class="MyPhotoMapApp.MyPhotoPanel"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      
          <UserControl.Resources>
          </UserControl.Resources>
      
          <Grid x:Name="LayoutRoot">
              <Grid.RowDefinitions>
                  <RowDefinition />
                  <RowDefinition Height="Auto" />
              </Grid.RowDefinitions>
              <ListBox x:Name="PhotoListBox" 
                       ItemContainerStyle="{StaticResource ListBoxItemStyle}"
                       ItemTemplate="{StaticResource ListBoxItemTemplate}"                 
                       AllowDrop="True" Drop="PhotoListBox_Drop">
                  <ListBox.Template>
                      <ControlTemplate>
                          <ScrollViewer Style="{StaticResource App.ScrollViewer}">
                              <ItemsPresenter  />
                          </ScrollViewer>
                      </ControlTemplate>
                  </ListBox.Template>
                  <ListBox.ItemsPanel>
                      <ItemsPanelTemplate>
                          <StackPanel />
                      </ItemsPanelTemplate>
                  </ListBox.ItemsPanel>
              </ListBox>
              <Button Grid.Row="1" Content="Add photo" Width="120" Margin="5"
                      Style="{StaticResource App.Button1}"
                      Click="Button_Click" />
          </Grid>
      </UserControl>

      StaticResource部分で指定している、App.ScrollViewerApp.Button1はBing Maps SDKで用意されているスタイルです。Bing Maps上で実行したときにデザインが反映されます。スタイルについては第3回でも紹介しています。そのほかのStaticResourceは、<UserControl.Resources>要素内に、これから定義します。上記コードにまだ定義されていない内容は次の3個です。

      • ListBoxの項目のスタイル
      • ListBoxの項目内のリンクのスタイル
      • ListBoxの項目のコントロールの構成

      内容は特に気にせず次のコードをコピーしていきましょう。それぞれ、<UserControl.Resources>要素内に追記します。

      ListBoxの項目のスタイル
      <Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
          <Setter Property="Template">
              <Setter.Value>
                  <ControlTemplate TargetType="ListBoxItem">
                      <Grid Background="{TemplateBinding Background}">
                          <VisualStateManager.VisualStateGroups>
                              <VisualStateGroup x:Name="CommonStates">
                                  <VisualState x:Name="Normal"/>
                                  <VisualState x:Name="MouseOver">
                                      <Storyboard>
                                          <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="selectedItem"/>
                                      </Storyboard>
                                  </VisualState>
                                  <VisualState x:Name="Disabled" />
                              </VisualStateGroup>
                              <VisualStateGroup x:Name="SelectionStates">
                                  <VisualState x:Name="Unselected"/>
                                  <VisualState x:Name="Selected" />
                              </VisualStateGroup>
                              <VisualStateGroup x:Name="FocusStates">
                                  <VisualState x:Name="Focused" />
                                  <VisualState x:Name="Unfocused"/>
                              </VisualStateGroup>
                          </VisualStateManager.VisualStateGroups>
                          <Border x:Name="selectedItem" Style="{StaticResource App.Border.Rollover}" Opacity="0">
                              <Rectangle Fill="{StaticResource App.Color.ItemRollover}" IsHitTestVisible="False" RadiusY="1" RadiusX="1"/>
                          </Border>
                          <ContentPresenter x:Name="contentPresenter" 
                                            ContentTemplate="{TemplateBinding ContentTemplate}" 
                                            Content="{TemplateBinding Content}" 
                                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}"/>
                      </Grid>
                  </ControlTemplate>
              </Setter.Value>
          </Setter>
      </Style>
      ListBoxの項目内のリンクのスタイル:
      <Style x:Key="HyperlinkButtonStyle" TargetType="HyperlinkButton" BasedOn="{StaticResource App.P1.Hyperlink}" >
          <Setter Property="Template">
              <Setter.Value>
                  <ControlTemplate TargetType="HyperlinkButton">
                      <Grid Background="{TemplateBinding Background}" Cursor="{TemplateBinding Cursor}">
                          <VisualStateManager.VisualStateGroups>
                              <VisualStateGroup x:Name="CommonStates">
                                  <VisualState x:Name="Normal"/>
                                  <VisualState x:Name="MouseOver">
                                      <Storyboard>
                                          <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Visibility" Storyboard.TargetName="UnderlineTextBlock">
                                              <DiscreteObjectKeyFrame KeyTime="0">
                                                  <DiscreteObjectKeyFrame.Value>
                                                      <Visibility>Visible</Visibility>
                                                  </DiscreteObjectKeyFrame.Value>
                                              </DiscreteObjectKeyFrame>
                                          </ObjectAnimationUsingKeyFrames>
                                          <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Visibility" Storyboard.TargetName="contentPresenter">
                                              <DiscreteObjectKeyFrame KeyTime="0">
                                                  <DiscreteObjectKeyFrame.Value>
                                                      <Visibility>Collapsed</Visibility>
                                                  </DiscreteObjectKeyFrame.Value>
                                              </DiscreteObjectKeyFrame>
                                          </ObjectAnimationUsingKeyFrames>
                                      </Storyboard>
                                  </VisualState>
                                  <VisualState x:Name="Pressed">
                                      <Storyboard>
                                          <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Visibility" Storyboard.TargetName="UnderlineTextBlock">
                                              <DiscreteObjectKeyFrame KeyTime="0">
                                                  <DiscreteObjectKeyFrame.Value>
                                                      <Visibility>Visible</Visibility>
                                                  </DiscreteObjectKeyFrame.Value>
                                              </DiscreteObjectKeyFrame>
                                          </ObjectAnimationUsingKeyFrames>
                                          <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Visibility" Storyboard.TargetName="contentPresenter">
                                              <DiscreteObjectKeyFrame KeyTime="0">
                                                  <DiscreteObjectKeyFrame.Value>
                                                      <Visibility>Collapsed</Visibility>
                                                  </DiscreteObjectKeyFrame.Value>
                                              </DiscreteObjectKeyFrame>
                                          </ObjectAnimationUsingKeyFrames>
                                      </Storyboard>
                                  </VisualState>
                                  <VisualState x:Name="Disabled" />
                              </VisualStateGroup>
                              <VisualStateGroup x:Name="FocusStates">
                                  <VisualState x:Name="Focused" />
                                  <VisualState x:Name="Unfocused" />
                              </VisualStateGroup>
                          </VisualStateManager.VisualStateGroups>
                          <TextBlock x:Name="UnderlineTextBlock" 
                                     TextDecorations="Underline" 
                                     Visibility="Collapsed" 
                                     HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                     Margin="{TemplateBinding Padding}" 
                                     Text="{TemplateBinding Content}" 
                                     VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                         <ContentPresenter x:Name="contentPresenter" 
                                            ContentTemplate="{TemplateBinding ContentTemplate}" 
                                            Content="{TemplateBinding Content}" 
                                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                            Margin="{TemplateBinding Padding}" 
                                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                      </Grid>
                  </ControlTemplate>
              </Setter.Value>
          </Setter>
      </Style>
      ListBoxの項目のコントロールの構成:
      <DataTemplate x:Key="ListBoxItemTemplate">
          <ListBoxItem Style="{StaticResource ListBoxItemStyle}">
              <Grid>
                  <Grid.ColumnDefinitions>
                      <ColumnDefinition Width="Auto" />
                      <ColumnDefinition />
                  </Grid.ColumnDefinitions>
                  <Border Width="60" BorderBrush="LightGray" BorderThickness="1" Background="White" Padding="2" RenderTransformOrigin="0.5,0.5" Margin="10">
                      <Border.RenderTransform>
                          <CompositeTransform Rotation="-10"/>
                      </Border.RenderTransform>
                      <Border.Effect>
                          <DropShadowEffect BlurRadius="5" ShadowDepth="1" Color="Gray" Direction="250" />
                      </Border.Effect>
                      <Image Source="{Binding BitmapImage}" />
                  </Border>
                  <StackPanel Grid.Column="1" VerticalAlignment="Center">
                      <HyperlinkButton Content="{Binding Name}" Style="{StaticResource HyperlinkButtonStyle}" 
                                       Click="HyperlinkButton_Click" />
                  </StackPanel>
              </Grid>
          </ListBoxItem>
      </DataTemplate>

      デザイン部分は以上です。見た目を少しよくするためにコードが長くなっています。上記部分にもSDKで用意されいてるスタイルApp.Border.Rolloverなど)が使用されています。マウスポインターをListBoxの項目に重ねたとき見た目を切り替えるなどの処理が記述されています。

      これらをすべてVisual Studioで記載したわけではなく、実際にはデザイナツールであるExpression Blend 4を使用して編集しています。ListBoxのデフォルトのスタイルをコピーして書き換えています。デフォルトのスタイルはMSDNライブラリにも記載されています。

      ListBoxの項目の構成を記述しているListBoxItemTemplateでは、PhotoEntityのプロパティの内容を表示するため、データバンディング({Binding Name}と{Binding BitmapImage}部分)も記述しています。説明は省略しますが、データバインディングもXAMLの大きな特徴のひとつです。

      項目を構成するコントロールの中のHyperlinkButtonについても、スタイルを定義しています(HyperlinkButtonStyle部分⁠⁠。SDKにはHyperlinkButton用にスタイルが用意されているのですが、見た目がBing Mapsと異なるため、そのスタイルを参照しつつ別に用意しました。

      イベントの仮処理

      上記のXAMLのコードには、次のイベント記述の部分が含まれています。

      • Click="HyperlinkButton_Click"
      • Drop="PhotoListBox_Drop"
      • Click="Button_Click"

      コピーした場合はこれらに対応する処理がないため実行時にエラーになってしまいます。今回は何もしない空のメソッドをコードビハインドに記述しておきましょう。記述するといっても自分で書くのではなくVisual Studioの機能を利用してください。デザイナのメニュー図3またはプロパティウィンドウなどを使って自動でメソッドをコードに挿入するようにします。

      図3 イベントハンドラーの追加
      図3 イベントハンドラーの追加

      正しく追加された場合は、以下のコードが、MyPhotoPanel.xaml.cs内に記述されています。

      private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
      {
      }
      
      private void PhotoListBox_Drop(object sender, DragEventArgs e)
      {
      }
      
      private void Button_Click(object sender, RoutedEventArgs e)
      {
      }

      デザインの確認

      実際の処理部分は次回に続きますが、現時点までの内容を確認できるように一時的なコードを書いてプラグインを実行してみましょう。たとえばパネル クラスのコードを次のように記述すると動作します。

      MyPhotoPanel.xaml.cs
      public MyPhotoPanel(MyPhotoPlugin plugin)
      {
          InitializeComponent();
      
          this.plugin = plugin;
          this.PhotoItems = new ObservableCollection<PhotoEntity>();
          this.PhotoListBox.ItemsSource = this.PhotoItems;
      
          // 一時的なコード
          for (var i = 0; i < 10; ++i)
          {
              var item = new PhotoEntity()
              {
                  Name = "写真" + i.ToString(),
                  BitmapImage = new BitmapImage(new Uri("http://image.gihyo.co.jp/assets/images/ICON/2010/thumb/TH64_641_bing-sdk.png"))
              };
              this.PhotoItems.Add(item);
          }
      }

      実行結果は図4の通りです。Bing Mapsのようなデザインになったでしょうか?

      図4 実行結果
      図4 実行結果

      今回はここまでです。次回はアプリの動作を記述していきます。お楽しみに。

おすすめ記事

記事・ニュース一覧