場所と写真を記録するプログラムを作って思い出のシーンを保存しよう

2013年6月3日(月)
薬師寺 国安

[シャッター]ボタンがタップされた時の処理

CountdownControlの秒数を設定するSecondプロパティに5を指定し、カウントダウンを5秒とします。

  Private Sub shutterButton_Click(sender As Object, e As RoutedEventArgs) Handles shutterButton.Click
    CountdownControl1.Visibility = Windows.UI.Xaml.Visibility.Visible
    messageTextBlock.Text = String.Empty
    CountdownControl1.Seconds = 5
  End Sub

カウントダウンが終わった時の処理。

MediaElementのPlayメソッドでシャッター音を再生します。
messageTextBlock内に保存した旨のメッセージを表示します。
CountdownControlを非表示にします。[写した場所を表示]ボタンの使用を可能にします。
画像を保存するPhotoSaveプロシージャを実行します。

  Private Sub CountdownControl1_CountdownComplete(sender As Object, e As RoutedEventArgs) Handles CountdownControl1.CountdownComplete
    MediaElement1.Play()
    messageTextBlock.Text = "ピクチャライブラリのMemoryImageフォルダに保存しました。"
    CountdownControl1.Visibility = Windows.UI.Xaml.Visibility.Collapsed
    positionButton.IsEnabled = True
    PhotoSave()
  End Sub

Webカメラで撮った画像を保存する処理

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

CreateFileAsyncで「年月日時間秒分」に「緯度」と「経度」の値を「-」で区切って追加し、”.jpg”という文字列を追加したファイルを作成し、myFile変数で参照しておきます。例えば2012年10月10日10時10分10秒-00.0000-123.3333.jpgといったファイル形式になります。

イメージストリームの書式を表すImageEncodingPropertiesクラスの新しいインスタンス、myImageEncodingPropertyオブジェクトを作成します。

フォーマットのサブタイプを設定するSubTypeプロパティに「Jpeg」と指定します。

WidthとHeightの値を指定します。

CapturePhotoToStorageFileAsyncメソッドで、出力イメージのエンコードプロパティ(Jpgで640×480)と、保存する画像ファイル名、とを指定して、ストレージファイルにフォトをキャプチャします。

メンバ変数Indexに、コレクション変数myPictureFiles内のファイルの個数をCountプロパティで取得し、格納します。
新しいBitmapImageクラスのインスタンスsaveBmpオブジェクトを作成します。
SetSourceメソッドに、Await myPictureFiles(Index - 1).OpenReadAsyncと指定して、ランダムアクセスストリームを開きます。
Image1にsaveBmpオブジェクトを指定します。これで、640×480サイズの画像が表示されます。
[写した場所を表示]ボタンの使用を可能にします。

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

  Private Async Sub PhotoSave()
    Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim mySubFolder = Await myFolder.CreateFolderAsync("MemoryImage", CreationCollisionOption.OpenIfExists)
  
    Dim myFile As StorageFile = Await mySubFolder.CreateFileAsync(DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒") & "-" & myLatitude & "-" & myLongitude & ".jpg")
    Dim myImageEncodingProperty As New ImageEncodingProperties
    myImageEncodingProperty.Subtype = "jpeg"
    myImageEncodingProperty.Width = 640
    myImageEncodingProperty.Height = 480
    Await myMediaCapture.CapturePhotoToStorageFileAsync(myImageEncodingProperty, myFile)
 
    myPictureFiles = Await mySubFolder.GetFilesAsync()
    Index = myPictureFiles.Count
    saveBmp = New BitmapImage
    saveBmp.SetSource(Await myPictureFiles(Index - 1).OpenReadAsync)
    Image1.Source = saveBmp
    positionButton.IsEnabled = True
  End Sub

シャッター音の再生が終わった時の処理

messageTextBoxにメッセージを表示し、メディアの再生の経過時間を示す現在の位置を0とします。
GridView内をクリアし、AddPhotoプロシージャを実行します。

  Private Sub MediaElement1_MediaEnded(sender As Object, e As RoutedEventArgs) Handles MediaElement1.MediaEnded
    Dim myPos As New TimeSpan(0, 0, 0)
    messageTextBlock.Text = "ピクチャライブラリのMemoryImageフォルダに保存しました。"
    MediaElement1.Position = myPos
    GridView1.Items.Clear()
    AddPhoto()
  End Sub

GridViewから画像が選択された時の処理

GridViewから選択された画像のインデックスを変数myIndexに格納しておきます。
ピクチャライブラリのMemoryImageサブフォルダにアクセスします。CreationCollisionOption列挙体については下記URLを参照してください。
→ CreationCollisionOption列挙体

GetFilesAsyncメソッドでMemoryImageフォルダ内のファイルを取得し、ファイルのコレクションを表すメンバ変数myPictureFilesに格納しておきます。

新しいBitmapImageのインスタンスsaveBmpオブジェクトを作成し、SetSourceメソッドに、Await myPictureFiles(Index).OpenReadAsyncと指定して、メンバ変数Indexに該当するコレクション変数myPictureFilesに格納されている画像ファイルを読み取って開きます。
ImageのSourceプロパティにsaveBmpオブジェクトを指定します。実寸の画像が表示されます。

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

  Private Async Sub GridView1_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles GridView1.SelectionChanged
    myIndex = GridView1.SelectedIndex
    If myIndex < 0 Then
      Exit Sub
    Else
      Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
      Dim mySubFolder = Await myFolder.CreateFolderAsync("MemoryImage", CreationCollisionOption.OpenIfExists)
      myPictureFiles = Await mySubFolder.GetFilesAsync()
      saveBmp = New BitmapImage
      saveBmp.SetSource(Await myPictureFiles(myIndex).OpenReadAsync)
      Image1.Source = saveBmp
    End If
  End Sub

戻る(←)アイコンがタップされた時の処理

GridViewを表示し、myFrameを非表示にします。

  Private Sub backButton_Click(sender As Object, e As RoutedEventArgs) Handles backButton.Click
    GridView1.Visibility = Xaml.Visibility.Visible
    myFrame.Visibility = Xaml.Visibility.Collapsed
  End Sub

[写した場所を表示]ボタンをタップした時の処理

ピクチャライブラリのMemoryImageサブフォルダにアクセスします。CreateFolderAsyncメソッドで、ピクチャライブラリ内にMemoryImage というサブフォルダを作成します。CreationCollisionOption.OpenIfExistsと指定すると、同名フォルダがある場合はフォルダ名を返し、ない場合は新規に作成します。CreationCollisionOption列挙体については下記URLを参照してください。
→ CreationCollisionOption列挙体

GetFilesAsyncメソッドでMemoryImageフォルダ内のファイルを取得し、ファイルのコレクションを表すメンバ変数myImageFilesに格納しておきます。
myImageFiles内のGridViewから選択された画像のインデックスに該当するファイルを変数myImageNameに格納します。この変数を引数にNavigateメソッドでBingMapsShowPageに遷移します。

  Private Async Sub positionButton_Click(sender As Object, e As RoutedEventArgs) Handles positionButton.Click
    myFrame.Visibility = Xaml.Visibility.Visible
    GridView1.Visibility = Xaml.Visibility.Collapsed
    Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim mySubFolder = Await myFolder.CreateFolderAsync("MemoryImage", CreationCollisionOption.OpenIfExists)
    Dim xmlFile = Await mySubFolder.GetFilesAsync
    Dim myImageName = xmlFile(GridView1.SelectedIndex)
    myFrame.Navigate(GetType(BingMapsShowPage), myImageName)
  End Sub
End Class

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

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

リスト5 (BingMapsShowPage.xaml.vb)

Option Strict On

Imports Windows.Storage

Bing Maps APIを提供するクラスを含むBimg.Maps名前空間をインポートします。

Imports Bing.Maps

Imports Windows.UI.Popups

最新の HTTP アプリケーション用のプログラミングインターフェイスを提供するクラスの含まれる、System.Net.Http名前空間をインポートします。

Imports System.Net.Http

色の指定に関係するクラスの含まれるWindows.UI名前空間をインポートします。

Imports Windows.UI

Public NotInheritable Class BingMapsShowPage
  Inherits Page

文字列型の定数メンバ変数として宣言した、AppIDに「Yahooで取得したアプリケーションID」を指定します。YahooのIDは下記のURLの「Yahoo!JAPAN IDを新規取得」から取得してください。
→ Yahoo!JAPANログイン・新規ID取得

  Const AppID As String = "Yahooで取得したアプリケーションID"

ファイルを表すStorageFile型のメンバ変数myImageNameを宣言します。

  Dim myImageName As StorageFile

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

MainPage.xamlから渡された値は、e.Parameterで取得できます。これはObject型であるため、DirectCastでStorageFile型にキャストして、メンバ変数myImageNameで参照します。

Pathプロパティで絶対パス付きファイル名を取得し、変数resultに格納します。

IO.Path.GetFileNameWithoutExtension(result)

で、パスと拡張子を除いた部分のファイル名を取得し、再度、拡張子”.jpg”を連結して変数myResultに格納します。
JPGのファイル名からSubString関数を使って、緯度と経度の部分を抜き出し、変数myLatitudeとmyLongitudeに格納します。ファイル名は

2012年10月21日11時28分22秒-33.835091-132.774902.jpg

という形式になっています。「33.835091」が緯度、「132.774902」が経度になります。この部分をSubString関数で取得します。

緯度、経度を格納した変数、myLatitudeとmyLongitudeを指定して、下記のURL文字列を作成し、変数myUriに格納しておきます。

Dim myUri As String = String.Format("http://reverse.search.olp.yahooapis.jp/OpenLocalPlatform/V1/reverseGeoCoder?lat={0}&lon={1}&appid={2}", myLatitude, myLongitude, AppID)

appidにはYahooで取得したIDを指定します。

このURIで取得できるXMLの構造は、図18のような構造です。赤枠で囲った

要素の値を取得します。
図18:返される結果XML(クリックで拡大)

新しいHttpClientのオブジェクトmyHttpClientを作成します。
GetStringAsyncメソッドで、指定したURIにGET要求を送信し、非同期操作で応答本体を文字列として受け取り、変数resultAddressに格納します。

ルート要素に属性や、名前空間が付いていますので、Replace関数でというルート要素に置換します。

置換したXMLをXElement.Parseメソッドで文字列として読み込みます。

要素の値を変数myAddressに格納しておきます。

ピクチャライブラリのMemoryImageサブフォルダにアクセスします。
GetFileAsyncメソッドで、myResult変数に格納されているファイルを取得します。
OpenReadAsyncメソッドで、読み込んだファイルからアクセスストリームを開きます。
新しいBitmapImageのインスタンスmyBmpオブジェクトを作成し、SetSourceメソッドで、読み込んだファイル名の画像にアクセスして、BitmapSourceのソースイメージを設定します。

新しいPushPinのインスタンスmyPinオブジェクトを作成します。背景色をCrimsonとします。

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

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

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

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

MapLayerクラスのSetPositionメソッドで、マップレイヤー内に要素の位置を設定します。この場合、myLatitudeとmyLongitudeの位置にmyPinオブジェクトをセットします。MapLayerクラスは、地図上の要素の位置を保持しているマップレイヤーを表すクラスです。
MapにAddメソッドでmyPinオブジェクトを追加します。同様に、MapLayerクラスのSetPositionメソッドで、マップレイヤー内に要素の位置を設定します。この場合、myLatitudeとmyLongitudeの位置にmyStackPanelオブジェクトをセットします。MapLayerクラスは、地図上の要素の位置を保持しているマップレイヤーを表すクラスです。
MapにAddメソッドでmyStackPanelオブジェクトを追加します。

AddHandlerメソッドで、PushPinがタップされた時のイベントハンドラを追加します。
myStackPanelオブジェクトを表示状態にします。おおまかな住所とそこで撮影した画像が表示されます。

表示されたmyStackPanelオブジェクトをタップした際には、myStackPanelオブジェクトを非表示にします。

このサンプルはGPSか位置情報取得機能が装備されていないとエラーになりますので、例外処理を追加しています。例外が発生した際にはErrorShowプロシージャを実行します。

  Protected Overrides Async Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
    Try
      myImageName = DirectCast(e.Parameter, StorageFile)
      Dim result As String = myImageName.Path
      Dim myResult = IO.Path.GetFileNameWithoutExtension(result) & ".jpg"
 
      Dim myLatitude = myResult.Substring(21, 9)
      Dim myLongitude = myResult.Substring(31, 10)
      
      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("MemoryImage", CreationCollisionOption.OpenIfExists)
      Dim myPictureFile = Await mySubFolder.GetFileAsync(myResult)
      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 myImage As New Image
      With myImage
        .Width = 160
        .Height = 120
        .Source = myBmp
      End With
 
      myStackPanel.Children.Add(myTextBlock)
      myStackPanel.Children.Add(myImage)
      MapLayer.SetPosition(myPin, New Location(CDbl(myLatitude), CDbl(myLongitude)))
      myMap.Children.Add(myPin)
   
      MapLayer.SetPosition(myStackPanel, New Location(CDbl(myLatitude), CDbl(myLongitude)))
      myMap.Children.Add(myStackPanel)
      
      AddHandler myPin.Tapped, Sub()
                   myStackPanel.Visibility = Windows.UI.Xaml.Visibility.Visible
                 End Sub
 
      AddHandler myStackPanel.Tapped, Sub()
                   myStackPanel.Visibility = Windows.UI.Xaml.Visibility.Collapsed
                 End Sub
    Catch
      ErrorShow()
    End Try
  End Sub

エラーが発生した時の処理

警告メッセージを表示して、処理を抜けます。

  Private Async Sub ErrorShow()
    Dim myMessage As New MessageDialog("GPSまたは位置情報取得機能が付いておりません。" & vbCrLf & "写真を撮った位置の表示はできません。" & vbCrLf & "GPSまたは位置情報取得機能の付いたPCでお試しください。")
    Await myMessage.ShowAsync
    Exit Sub
  End Sub
End Class

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

筆者からのお知らせ

筆者はWindowsストアでアプリを公開しています。チャームの検索からWindowsストアを選択して、検索欄に、kuniyasuまたはYakushijiKuniyasuと入力すると、公開されているアプリの一覧が表示されます。上記はどちらも私のアカウントですので、興味のある方は是非ダウンロードして使ってみてください。

  • 思い出の写真を記録するサンプルプログラム

薬師寺国安事務所

薬師寺国安事務所代表。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 Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

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