PR

写真と現在位置を入れた画像日記アプリを作る

2013年8月29日(木)
薬師寺 国安

ページがアクティブになった時の処理

MainPageから送られた値はe.Parameterで取得できます。これはObject型であるため、DirectCastで文字列にキャストして、変数readXmlFileに格納しておきます。

ピクチャライブラリのImageDiaryXMLサブフォルダにアクセスします。
GetFileAsyncメソッドで、readXmlFileに格納されているXMLファイルを取得し、myFileで参照します。
読み込み専用モードで、OpenAsyncメソッドでmyFileを開き、myStream変数で参照します。
UTF-8でエンコードされ、myStreamで初期化された、新しいStreamReaderのインスタンスreaderオブジェクトを作成します。
ReadToEndメソッドでファイルの最後まで読み取り、変数resultXmlに格納します。

XElemet.ParseメソッドでresultXmlの値を文字列として読み込みます。

Descendantsメソッドで、全ての子孫要素に対して、その内容を変数resultに格納しながら、以下の処理を行います。

メンバ変数myImageFileに、要素の値を取得して、パスと拡張子を除いたファイル名に、文字列「.jpg」を連結して格納します。

メンバ変数myLatitudeに要素の値を、myLongitudeにの値を格納します。
メンバ変数myCommentには、要素の値を格納します。
メンバ変数delXmlFileにはmyImageFile変数が格納している値から20文字を取り出し、文字列「.xml」と連結して格納しておきます。
メンバ変数myUriに「Yahoo!ローカルサーチAPI」のアドレスを格納します。引数latにメンバ変数myLatitudeが格納している緯度の値を指定して、lonにメンバ変数myLongitudeが格納している経度の値を指定します。
AppidにはYahooのアプリケーションIDを格納しているメンバ定数変数AppIDを指定します。

新しいHttpClientクラスのインスタンスmyHttpClientオブジェクトを作成します。HttpClientクラスは、URIで識別されるリソースにHTTP要求を送信し、そのリソースからHTTP応答を受信するための基本クラスが含まれています。

GetStringAsyncメソッドでmyUriの結果を文字列として返し、変数resultAddressに格納します。GetStringAsyncメソッドは、指定URIにGET要求を送信し、非同期操作で応答本体を文字列として返すメソッドです。

返される文字列はXML形式になっています。このXMLのルート要素には、名前空間や不要な属性が定義されていて、内容を取得する際の邪魔(-_-;)になりますので、Replace関数で、ルート要素()だけに置換しています。

XElement.Parseメソッドで置換された結果のXMLが格納されている、resultAddressを文字列として読み込みます。

読み込んだXMLから

要素の内容を取得して、変数myAddressに格納しておきます。

ピクチャライブラリのサブフォルダImageDiaryにアクセスします。GetFileAsyncメソッドで、メンバ変数myImageFileに格納されている画像ファイルを取得してmyPictureFileで参照します。
OpenReadAsyncメソッドでmyPictureFileのランダムアクセスストリームを開き、myPictureで参照します。

新しいBitmapImageのインスタンスmyBmpオブジェクトを作成します。
SetSourceメソッドにBitmapSourceのソースイメージにmyPictureを指定しておきます。

Locationをメンバ変数myLatitudeが格納している緯度と、myLongitudeが格納している経度で初期化し、myMapのCenterプロパティに指定します。

新しいPushpinクラスのインスタンスmyPinオブジェクトを作成します。
ピンの背景色をCrimsonに指定します。

新しいStackPanelのインスタンスmyStackPanelオブジェクトを作成します。
Marginプロパティに5を指定して余白を設けます。背景色をNavyに指定します。
このStackPanelオブジェクトのインスタンスを、非表示としておきます。

新しいTextBlockのインスタンスmyTextBlockオブジェクトを作成します。文字色をRedに指定します。
文字サイズに24を指定し、パディングに10を指定します。
Textプロパティに住所を格納している変数myAddressと、文字列「辺り」を連結して指定します。

新しいTextBlockのインスタンスmyCommentTextBlockオブジェクトを作成します。
文字色をGoldに指定します。文字サイズに20を指定し、パディングに5を指定します。Textプロパティにメンバ変数myCommentを指定します。

新しいImageのインスタンスmyImageオブジェクトを作成します。Widthに160、Heightに120を指定し、Sourceプロパティに、先に作成しておいたmyBmpオブジェクトを指定します。

myStackPanelオブジェクトにAddメソッドで住所の設定された、myTextBlockを、画像の指定されたmyImageを、コメントの指定された、myCommentTextBlockを追加します。

Locationを、メンバ変数myLatitudeが格納している緯度と、myLongitudeが格納している経度で初期化します。

MapLayerクラスのSetPositionメソッドで、マップレイヤー内にピンの位置を設定します。
myMapにAddメソッドでピンを追加します。
SetViewメソッドで、指定された中心部に位置、ズーム レベル、方位、およびピッチにマップビューを設定します。この場合myLocationの位置に15レベルでズームインします。

MapLayerクラスのSetPositionメソッドで、マップレイヤー内にmyStackPanelの位置を設定します。
myMapにAddメソッドでmyStackPanelを追加します。

AddHandlerステートメントでピンをタップした時のイベントハンドラを追加します。
イベントハンドラ内では、myStackPanelオブジェクトを表示状態にします。

AddHandlerステートメントでmyStackPanelをタップした時のイベントハンドラを追加します。イベントハンドラ内では、myStackPanelオブジェクトを非表示状態にします。

非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Protected Overrides Async Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
    Dim readXmlFile = DirectCast(e.Parameter, String)
    Dim myStorageFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim myXmlSubFolder = Await myStorageFolder.CreateFolderAsync("ImageDiaryXML", CreationCollisionOption.OpenIfExists)
    Dim myFile = Await myXmlSubFolder.GetFileAsync(readXmlFile)
    Dim resultXml As String
    Using myStream As IRandomAccessStream = Await myFile.OpenAsync(FileAccessMode.Read)
      Using reader As StreamReader = New StreamReader(myStream.AsStream, System.Text.Encoding.UTF8)
        resultXml = reader.ReadToEnd
      End Using
    End Using
      Dim xmldoc As XElement = XElement.Parse(resultXml)
      For Each result In From c In xmldoc.Descendants("Info") Select c
        myImageFile = IO.Path.GetFileNameWithoutExtension(result.Element("ImageName").Value) & ".jpg"
        myLatitude = result.Element("Latitude").Value
        myLongitude = result.Element("Longitude").Value
        myComment = result.Element("Comment").Value
        delXmlFile = myImageFile.Substring(0, 20) & ".xml"
      Next
 
      Dim myUri As String = String.Format("http://reverse.search.olp.yahooapis.jp/OpenLocalPlatform/V1/reverseGeoCoder?lat={0}&lon={1}&appid={2}", myLatitude, myLongitude, AppID)
      Dim myHttpClient As New HttpClient
      Dim resultAddress = Await myHttpClient.GetStringAsync(myUri)
      resultAddress = resultAddress.Replace("<YDF firstResultPosition=" & ChrW(34) & "1" & ChrW(34) & " totalResultsAvailable=" & ChrW(34) & "1" & ChrW(34) & " totalResultsReturned=" & ChrW(34) & "1" & ChrW(34) & " xmlns=" & ChrW(34) & "http://olp.yahooapis.jp/ydf/1.0" & ChrW(34) & ">", "<YDF>")
      Dim httpDoc As XElement = XElement.Parse(resultAddress)
      Dim myAddress = httpDoc.Descendants("Address").Value
      Dim myFolder = KnownFolders.PicturesLibrary
      Dim mySubFolder = Await myFolder.CreateFolderAsync("ImageDiary", CreationCollisionOption.OpenIfExists)
      Dim myPictureFile = Await mySubFolder.GetFileAsync(myImageFile)
      Dim myPicture = Await myPictureFile.OpenReadAsync
      Dim myBmp As New BitmapImage
      myBmp.SetSource(myPicture)
 
      myMap.Center = New Location(CDbl(myLatitude), CDbl(myLongitude))
 
      Dim myPin As New Pushpin
      myPin.Background = New SolidColorBrush(Colors.Crimson)
      Dim myStackPanel As New StackPanel
      myStackPanel.Margin = New Thickness(5)
      myStackPanel.Background = New SolidColorBrush(Colors.Navy)
      myStackPanel.Visibility = Xaml.Visibility.Collapsed
      
      Dim myTextBlock As New TextBlock
      myTextBlock.Foreground = New SolidColorBrush(Colors.Red)
      myTextBlock.FontSize = 24
      myTextBlock.Padding = New Thickness(10)
      myTextBlock.Text = myAddress & " 辺り"
 
      Dim myCommentTextBlock As New TextBlock
      myCommentTextBlock.Foreground = New SolidColorBrush(Colors.Gold)
      myCommentTextBlock.FontSize = 20
      myCommentTextBlock.Text = myComment
      myCommentTextBlock.Padding = New Thickness(5)
 
      Dim myImage As New Image
      With myImage
        .Width = 160
        .Height = 120
        .Source = myBmp
      End With
 
      myStackPanel.Children.Add(myTextBlock)
      myStackPanel.Children.Add(myImage)
      myStackPanel.Children.Add(myCommentTextBlock)
 
      Dim myLocation = New Location(CDbl(myLatitude), CDbl(myLongitude))
      MapLayer.SetPosition(myPin, myLocation)
      myMap.Children.Add(myPin)
      myMap.SetView(myLocation, 15)
 
      MapLayer.SetPosition(myStackPanel, New Location(CDbl(myLatitude), CDbl(myLongitude)))
      myMap.Children.Add(myStackPanel)
 
      AddHandler myPin.Tapped, Sub()
                               myStackPanel.Visibility = Xaml.Visibility.Visible
                           End Sub
 
      AddHandler myStackPanel.Tapped, Sub()
                               myStackPanel.Visibility = Xaml.Visibility.Collapsed
                                  End Sub
    End Sub

「Delete」アイコンがタップされた時の処理

ピクチャライブラリのサブフォルダImageDiaryXMLにアクセスします。

GetFileAsyncメソッドで、メンバ変数myImageFileの格納している画像を取得して、変数myDelImageFileで参照します。
DeleteAsyncメソッドで、画像ファイルを削除します。

GetFileAsyncメソッドで、メンバ変数delXmlが格納しているXMLファイルを、変数myDelXmlFileで参照します。

DeleteAsyncメソッドでXMLファイルを削除します。

削除した旨のメッセージを表示し、myMap内をクリアします。

非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Sub delButton_Click(sender As Object, e As RoutedEventArgs) Handles delButton.Click
    Dim myStorageFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim myXmlSubFolder = Await myStorageFolder.CreateFolderAsync("ImageDiaryXML", CreationCollisionOption.OpenIfExists)
    Dim delImageSubFolder = Await myStorageFolder.CreateFolderAsync("ImageDiary", CreationCollisionOption.OpenIfExists)
    Dim myDelImageFile = Await delImageSubFolder.GetFileAsync(myImageFile)
    Await myDelImageFile.DeleteAsync()
    Dim myDelXmlFile = Await myXmlSubFolder.GetFileAsync(delXmlFile)
    Await myDelXmlFile.DeleteAsync()
 
    Dim message As New MessageDialog("削除しました!")
    Await message.ShowAsync
    myMap.Children.Clear()
  End Sub

次に、ソリューションエクスプローラー内のDataIchiranShowPage.xamlを展開して表示される、DataIchiranShowPage.xaml.vbをダブルクリックしてリスト6のコードを記述します。

ロジックコードを記述する

リスト6 (DataIchiranShowPage.xaml.vb)

Option Strict On
Imports Windows.Storage
Imports Windows.Storage.Streams
Imports System.Net.Http
Imports Windows.UI
Imports Windows.UI.Popups

Public NotInheritable Class DataIchiranShowPage
  Inherits Page

「YahooのアプリケーションID」で初期化された、文字列型の定数メンバ変数を宣言します。

Const AppID As String = "YahooのアプリケーションID"

文字列型の新しいリストである、dateListメンバ変数を宣言します。

  Dim dateList As New List(Of String)

ページがアクティブになった時の処理

ピクチャライブラリのImageDiaryXMLサブフォルダにアクセスします。
GetFileAsyncメソッドで、ImageDiaryXMLサブフォルダ内に格納されているXMLファイルを取得し、コレクション変数myFileに格納します。

コレクション変数myFile内を変数resultで反復処理しながら、以下の処理を行います。

OpenReadAsyncメソッドでmyFileコレクション変数内のファイルを読み込み、変数myStreamで参照します。

UTF-8でエンコードされ、myStreamで初期化された、新しいStreamReaderのインスタンスreaderオブジェクトを作成します。ReadToEndメソッドでファイルの最後まで読み取り、変数resultXmlに格納します。

XElemet.ParseメソッドでresultXmlの値を文字列として読み込みます。

Descendantsメソッドで、全ての子孫要素に対して、その内容を変数myContentsに格納しながら、以下の処理を行います。

メンバ変数myImageFileに、要素の値を取得して、パスと拡張子を除いたファイル名に、文字列「.jpg」を連結して格納します。

メンバ変数myLatitudeに要素の値を、myLongitudeにの値を格納します。

メンバ変数myCommentには、要素の値を格納します。

ピクチャライブラリ内のImageDiaryサブフォルダにアクセスします。GetFileAsyncメソッドで、変数myImageFileが格納しているJPG画像を取得し、変数myPictureFileで参照します。
myPictureFileをOpenReadAsyncメソッドで、ランダムアクセスストリームを開き、myPictureで参照します。

新しいBitmapImageのインスタンスmyBmpオブジェクトを作成し、SetSourceメソッドで、myPictureオブジェクトをソースイメージに指定します。

変数myUriに「Yahoo!ローカルサーチAPI」のアドレスを格納します。
引数latに変数myLatitudeが格納している緯度の値を指定して、lonに変数myLongitudeが格納している経度の値を指定します。
AppidにはYahooのアプリケーションIDを格納しているメンバ定数変数AppIDを指定します。

新しいHttpClientクラスのインスタンスmyHttpClientオブジェクトを作成します。HttpClientクラスは、URIで識別されるリソースにHTTP要求を送信し、そのリソースからHTTP応答を受信するための基本クラスが含まれています。

GetStringAsyncメソッドでmyUriの結果を文字列として返し、変数resultAddressに格納します。GetStringAsyncメソッドは、指定URIにGET要求を送信し、非同期操作で応答本体を文字列として返すメソッドです。
返される文字列はXML形式になっています。このXMLのルート要素には、名前空間や不要な属性が定義されていて、内容を取得する際の邪魔(-_-;)になりますので、Replace関数で、ルート要素()だけに置換しています。

XElement.Parseメソッドで置換された結果のXMLが格納されている、resultAddressを文字列として読み込みます。

読み込んだXMLから

要素の内容を取得して、変数myAddressに格納しておきます。

コレクション変数myFile内のXMLファイルの、完全なファイルシステムパスをResultプロパティで取得し、IO.Path.GetFileNameWithoutExtensionメソッドで、パスと拡張子を除いたファイル名を取得し、変数myDateに格納します。
dateListメンバ変数に、変数myDateから11文字分取りだした値を、Addメソッドで追加します。2013年01月01日という11文字の文字列が追加されます。

新しいTextBlockのインスタンスmyDateTextBlockを作成します。文字色にCrimson、文字サイズに28、Textプロパティに変数myDateの値を指定します。

新しいTextBlockのインスタンスmyAddressTextBlockを作成します。文字色にNavy、文字サイズに20、文字の回り込みを可、Textプロパティに変数myAddressの値と文字列「辺り」を連結して指定します。
新しいImageのインスタンスmyImageオブジェクトを作成します。Widthに384、Heightに288、SourceプロパティにmyBmpオブジェクトを指定します。

新しいListBoxのインスタンスmyListBoxオブジェクトを作成します。WidthとHeightを指定し、AddメソッドでmyComment変数の値を追加します。

新しいStackPanelのインスタンスmyStackPanelオブジェクトを作成します。Widthを指定し、背景色にPink、Marginに10を指定して余白を設けます。

AddメソッドでmyStackPanelオブジェクトに、myDateTextBlock、myAddressTextBlock、myImage、myListBoxオブジェクトを追加します。

最後に、myStackPanelオブジェクトをGridViewに追加します。これで、日付、住所、画像、コメント付きのStackPanelがGridView内に一覧で表示されます。

非同期処理で行われるためメソッドの先頭にAsyncを追加します。

  Protected Overrides Async Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
    Dim xmlFolder As StorageFolder = KnownFolders.PicturesLibrary
    Dim myXmlSubFolder As StorageFolder = Await xmlFolder.CreateFolderAsync("ImageDiaryXML", CreationCollisionOption.OpenIfExists)
 
    Dim myFile = Await myXmlSubFolder.GetFilesAsync
    Dim resultXml As String
      For Each result In myFile
        Using myStream As IRandomAccessStream = Await result.OpenReadAsync
          Using reader As StreamReader = New StreamReader(myStream.AsStream, System.Text.Encoding.UTF8)
            resultXml = reader.ReadToEnd
            Dim xmldoc As XElement = XElement.Parse(resultXml)
            For Each myContents In From c In xmldoc.Descendants("Info") Select c
              Dim myImageFile = IO.Path.GetFileNameWithoutExtension(myContents.Element("ImageName").Value) & ".jpg"
              Dim myLatitude = myContents.Element("Latitude").Value
              Dim myLongitude = myContents.Element("Longitude").Value
              Dim myComment = myContents.Element("Comment").Value
 
              Dim mySubFolder = Await xmlFolder.CreateFolderAsync("ImageDiary", CreationCollisionOption.OpenIfExists)
              Dim myPictureFile = Await mySubFolder.GetFileAsync(myImageFile)
              Dim myPicture = Await myPictureFile.OpenReadAsync
              Dim myBmp As New BitmapImage
              myBmp.SetSource(myPicture)
 
              Dim myUri As String = String.Format("http://reverse.search.olp.yahooapis.jp/OpenLocalPlatform/V1/reverseGeoCoder?lat={0}&lon={1}&appid={2}", myLatitude, myLongitude, AppID)
              Dim myHttpClient As New HttpClient
              Dim resultAddress = Await myHttpClient.GetStringAsync(myUri)
              resultAddress = resultAddress.Replace("<YDF firstResultPosition=" & ChrW(34) & "1" & ChrW(34) & " totalResultsAvailable=" & ChrW(34) & "1" & ChrW(34) & " totalResultsReturned=" & ChrW(34) & "1" & ChrW(34) & " xmlns=" & ChrW(34) & "http://olp.yahooapis.jp/ydf/1.0" & ChrW(34) & ">", "<YDF>")
 
              Dim httpDoc As XElement = XElement.Parse(resultAddress)
              Dim myAddress = httpDoc.Descendants("Address").Value
 
              Dim myDate = IO.Path.GetFileNameWithoutExtension(result.Path)
              dateList.Add(myDate.Substring(0, 11))
 
              Dim myDateTextBlock As New TextBlock
              With myDateTextBlock
                .Foreground = New SolidColorBrush(Colors.Crimson)
                .FontSize = 28
                .Text = myDate
              End With
 
              Dim myAddressTextBlock As New TextBlock
              With myAddressTextBlock
                .Width = 384
                .Foreground = New SolidColorBrush(Colors.Navy)
                .FontSize = 20
                .TextWrapping = TextWrapping.Wrap
                .Text = myAddress & "辺り"
              End With
 
              Dim myImage As New Image
              With myImage
                .Width = 384
                .Height = 288
                .Source = myBmp
              End With
 
              Dim myListBox As New ListBox
              myListBox.Width = 384
              myListBox.Height = 150
              myListBox.Items.Add(myComment)
 
              Dim myStackPanel As New StackPanel
              With myStackPanel
                .Width = 384
                .Background = New SolidColorBrush(Colors.Pink)
                .Margin = New Thickness(10)
              End With
 
              myStackPanel.Children.Add(myDateTextBlock)
              myStackPanel.Children.Add(myAddressTextBlock)
              myStackPanel.Children.Add(myImage)
              myStackPanel.Children.Add(myListBox)
              GridView1.Items.Add(myStackPanel)
            Next
          End Using
        End Using
      Next
  End Sub

「Yes」ボタンがタップされた時の処理

日付を入力するsearchTextBoxに何も入力されていなければ、処理を抜けます。日付が入力された時は、日付を格納しているメンバ変数dateListオブジェクトのCountプロパティで、格納されている日付の個数分、変数iで反復処理を行います。
変数iに該当するdateListの値が、searchTextBoxに入力された値と同じなら、ScrollIntoViewメソッドで、指定された項目が表示されるよう、リストをスクロールします。同じ日付が見つかった場合は、その位置を表示し、反復処理を抜けます。

  Private Sub okButton_Click(sender As Object, e As RoutedEventArgs) Handles okButton.Click
    If searchTextBox.Text = String.Empty Then
      Exit Sub
    Else
      GridView1.SelectedIndex = -1
      For i As Integer = 0 To dateList.Count - 1
        If dateList(i) = searchTextBox.Text Then
          GridView1.SelectedIndex = i
          GridView1.ScrollIntoView(GridView1.SelectedItem)
          Exit For
        End If
      Next
    End If
  End Sub
End Class
End Class

アイコンの作成

ソリューションエクスプローラーのAssetsフォルダ内には、4つのpngファイルが入っています(表1)。

表1:Assetsフォルダ内に入っているpngファイルの種類

ファイル名 サイズ
Logo.png 150×150
SmallLogo.png 30×30
SplashScreen.png 620×300
StoreLogo.png 50×50

表1の画像はデフォルトでは、□に×の画像になっています。ストアの審査では、このままの画像では審査に受かりませんので、4種類のアイコンを作る必要があります。このサンプルは実際にストアで審査の通ったアプリですので、Assetsフォルダ内には筆者の作成したアイコンが収められていますので、見てみてください。

SplashScreen.pngはアプリを起動した際に、一瞬最初に表示される画像です。スタート画面にピン止めされる画像はデフォルトでは150×150のLogo.pngが使用されます。これを長方形の画像にしたい場合は、310×150サイズのpng画像を作成し、WideLogo.png(任意の名前でOK)としてAssetsフォルダに追加しておきます。
詳細については、「自分の現在位置を取得して表示するサンプルプログラム」の記事を参照してください。

今回はここまでです。ありがとうございました。

Think IT会員限定特典
  • 画像日記を作るプログラム

薬師寺国安事務所

薬師寺国安事務所代表。Visual Basic プログラミングと、マイクロソフト系の技術をテーマとした、書籍や記事の執筆を行う。
1950年生まれ。事務系のサラリーマンだった40歳から趣味でプログラミングを始め、1996年より独学でActiveXに取り組む。1997年に薬師寺聖とコラボレーション・ユニット PROJECT KySS を結成。2003年よりフリーになり、PROJECT KySS の活動に本格的に参加、.NETやRIAに関する書籍や記事を多数執筆する傍ら、受託案件のプログラミングも手掛ける。Windows Phoneアプリ開発を経て、現在はWindows ストア アプリを多数公開中

Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。Microsoft MVP for Development Platforms - Windows Phone Development(Oct 2012-Sep 2013)。Microsoft MVP for Development Platforms - Client Development(Oct 2013-Sep 2014)。Microsoft MVP for Development Platforms-Windows Platform Development (Oct 2014-Sep 2015)。

連載バックナンバー

Think IT会員サービス無料登録受付中

Think ITでは、より付加価値の高いコンテンツを会員サービスとして提供しています。会員登録を済ませてThink ITのWebサイトにログインすることでさまざまな限定特典を入手できるようになります。

Think IT会員サービスの概要とメリットをチェック

他にもこの記事が読まれています

写真と現在位置を入れた画像日記アプリを作る | Think IT(シンクイット)

Think IT(シンクイット)

サイトに予期せぬエラーが起こりました。しばらくたってから再度お試しください。