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

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

Webカメラのデバイスの種類が選択された時の処理

メンバ変数cameraNoにcameraComboBoxより選択されたインデックス番号を格納しておきます。
AppBar1を表示状態にします。

MediaCaptureの新しいインスタンスmyMediaCaptureオブジェクトを作成します。

InitializeAsyncメソッドで、以下の内容でMediaCaptureオブジェクトを初期化します。
新しいMediaCaptureInitializationSettingsクラスのインスタンスを作成します。MediaCaptureInitializationSettingsクラスは、MediaCaptureオブジェクトの初期化設定をするクラスです。VideoDevideIdプロパティにcameraComboBoxより選択されたインデックスに対応する、コレクション変数myCameraのデバイスIDを指定します。

CaptureElement1のSourceプロパティにデバイスIDで初期化されたmyMediaCaptureを指定します。StartPreviewAsyncメソッドでプレビューを開始します。これで、Webカメラの画像が表示されます。
マウスの右クリックで表示されるshutterButton(Attach Cameraアイコン)の使用を可能にします。非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Sub cameraComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles cameraComboBox.SelectionChanged
    Try
      cameraNo = cameraComboBox.SelectedIndex
      AppBar1.Visibility = Xaml.Visibility.Visible
      myMediaCapture = New MediaCapture
      Await myMediaCapture.InitializeAsync(New MediaCaptureInitializationSettings With {.VideoDeviceId = myCamera(cameraComboBox.SelectedIndex).Id})
      CaptureElement1.Source = myMediaCapture
      Await myMediaCapture.StartPreviewAsync
      shutterButton.IsEnabled = True
    Catch
      Exit Sub
    End Try
  End Sub

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

MediaElement1を再生します。つまりシャッター音が鳴ります。

ピクチャライブラリにアクセスし、CreateFolderAsyncメソッドで、ピクチャフォルダ内にImageDiaryというサブフォルダを作成します。その際、CreationCollisionOption.OpenIfExistsと指定しておくと、同名フォルダやファイルがある場合は、そのフォルダやファイル名を返し、ない場合は新規に作成してくれます。

CreateFileAsyncメソッドで現在の年月日時間分秒.jpgファイルを作成します。メンバ変数saveImageFileNameに、このjpgファイル名を格納しておきます。

イメージストリームの書式を表す新しい、ImageEncodingPropertiesクラスのインスタンス、myImageEncodingPropertyオブジェクトを作成します。書式のサブタイプを表すSubtypeプロパティにjpegを指定し、Widthに640、Heightに480と指定します。

CapturePhotoToStorageFileAsyncメソッドで、ストレージファイルにフォトをキャプチャします。書式は以下の通りです。

CapturePhotoToStorageFileAsync(ImageEncodingProperties,IStorageFile)

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

コレクション変数myPictureFilesが格納しているファイルの個数を、Countプロパティで取得して、メンバ変数Indexに格納します。

新しいBitmapImageクラスのインスタンスsaveBmpを作成します。SetSourceメソッドに
Await myPictureFiles(Index - 1).OpenReadAsync
と指定して、コレクション変数内のIndexに対応するファイルをOpenReadAsyncメソッドで開き、ソースイメージに指定します。Indexを-1しているのはコレクション変数myPictureFilesが格納しているファイルのインデックスが0から始まるためです。「Save」アイコンの使用を可能にします。

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

  Private Async Sub shutterButton_Click(sender As Object, e As RoutedEventArgs) Handles shutterButton.Click

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

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

コメント欄が未入力の場合は、警告メッセージを発して処理を抜けます。

それ以外の場合は、ピクチャライブラリのImageDiaryXMLサブフォルダにアクセスします。

XMLを作成して変数saveXmlに格納します。作成するXMLは、ルート要素がで、その子要素として、があり、 要素の子要素として要素があります。
各要素の内容は、Visual Basicの埋め込み式を用いています。埋め込み式の構文はで、ASP.NET で使用される構文と同じです。要素の内容には埋め込み式で、saveImageFileNameの値を、要素には、commentTextBox.Textの値を、には、myLatitudeの値を、には、myLongitudeの値を指定します。

XElement.Parseメソッドで、作成したXMLを文字列として読み込みます。読み込んだXMLを変数resultXmlに格納します。

ImageDiaryXMLフォルダに、CreateFileAsyncメソッドで、saveImageFileName変数が格納している画像ファイル名から、20文字分取りだして、文字列「.xml」と連結したXMLファイルを作成し、変数myXmlFileで参照しておきます。

OpenAsyncメソッドで、読み込みと書きこみモードでmyXmlFileが参照しているファイルを開き、myStreamで参照します。

データをストリームに書きこむ、myStreamで初期化された新しいインスタンス、writerを作成します。
エンコードにUTF-8を指定し、WriteStringメソッドで、出力ストリームにresultXmlの内容を書きこみます。
StoreAsyncメソッドで、ストアにバッファのデータをコミットします。

文字列型の新しいリストである、xmlFileListオブジェクトを作成します。ImageDiaryXml内のファイルを、GetFilesAsyncで取得して、コレクション変数myXmlFileNameに格納します。

XMLファイルが存在する場合は、コレクション変数myXmlFileName内を、変数resultで反復しながら、以下の処理を繰り返します。

Addメソッドで、取得したファイル名から、パスと拡張子を除いたファイル名を取り出し、それに、文字列「.xml」を連結して、xmlFileListオブジェクトに追加していきます。
fileListBoxのItemsSourceプロパティにxmlFileListオブジェクトを指定します。ListBoxに保存したXMLファイルが追加されます。
Folderアイコンの使用を可能にし、Saveアイコンの使用を不可とします。

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

  Private Async Sub saveButton_Click(sender As Object, e As RoutedEventArgs) Handles saveButton.Click
    If commentTextBox.Text = String.Empty Then
      Dim message As New MessageDialog("何かコメントを入力してください。")
      Await message.ShowAsync
      Exit Sub
    Else
      Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
      Dim mySubFolder = Await myFolder.CreateFolderAsync("ImageDiaryXML", CreationCollisionOption.OpenIfExists)
      Dim saveXml As XElement = <ImageDiary><Info><ImageName><%= saveImageFileName %></ImageName><Comment><%= commentTextBox.Text %></Comment><Latitude><%= myLatitude %></Latitude><Longitude><%= myLongitude %></Longitude></Info></ImageDiary>
      Dim saveXmlDoc As XElement = XElement.Parse(saveXml.ToString)
      Dim resultXml As String = saveXmlDoc.ToString
      Dim myXmlFile As StorageFile = Await mySubFolder.CreateFileAsync(saveImageFileName.Substring(0, 20) & ".xml", CreationCollisionOption.ReplaceExisting)
 
      Using myStream As IRandomAccessStream = Await myXmlFile.OpenAsync(FileAccessMode.ReadWrite)
        Dim writer As DataWriter = New DataWriter(myStream)
        writer.UnicodeEncoding = UnicodeEncoding.Utf8
        writer.WriteString(resultXml)
        Await writer.StoreAsync
      End Using
 
      Dim xmlFileList As New List(Of String)
      Dim myXmlFileName = Await mySubFolder.GetFilesAsync
      If myXmlFileName.Count > 0 Then
        For Each result In myXmlFileName
          xmlFileList.Add(Path.GetFileNameWithoutExtension(result.Path) & ".xml")
        Next
      Else
        fileListBox.Items.Clear()
      End If
      fileListBox.ItemsSource = xmlFileList
      ichiranButton.IsEnabled = True
      saveButton.IsEnabled = False
    End If
  End Sub

ListBoxからXMLファイルが選択された時の処理

AppBarを非表示にし、ブール型メンバ変数をTrueで初期化します。この値を元に戻る(←)アイコンがタップされた時、どこに戻るかを決定します。

Frameを表示状態にし、ListBoxから選択されたXMLファイル名を変数mySelectedFileに格納します。
Image1を非表示にします。
FrameのNavigateメソッドで、mySelectedFileを引数にDataShowPageに遷移します。

エラーが発生した場合は、Frameを非表示にし、AppBarとImageを表示状態にします。

  Private Sub fileListBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles fileListBox.SelectionChanged
    AppBar1.Visibility = Xaml.Visibility.Collapsed
    Try
      flag = True
      myFrame.Visibility = Xaml.Visibility.Visible
      Dim mySelectedFile As String = fileListBox.SelectedItem.ToString
      Image1.Visibility = Xaml.Visibility.Collapsed
      myFrame.Navigate(GetType(DataShowPage), mySelectedFile)
    Catch
      myFrame.Visibility = Xaml.Visibility.Collapsed
      AppBar1.Visibility = Xaml.Visibility.Visible
      Image1.Visibility = Xaml.Visibility.Visible
      Exit Sub
    End Try
  End Sub

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

各表示を以下の状態にします。

  • CaptureElementを表示状態にします。
  • cameraComboBoxを表示状態にします。
  • AppBarを表示状態にします。
  • myFrameを非表示にします。
  • Image1を表示状態にします。
  • CommentTextBox内を空にします。

ブール型メンバ変数がTrueの場合は、DataShowプロシージャを実行して、flagをFalseで初期化します。それ以外は、flagをFalseで初期化し、処理を抜けます。

  Private Sub backButton_Click(sender As Object, e As RoutedEventArgs) Handles backButton.Click
    CaptureElement1.Visibility = Xaml.Visibility.Visible
    cameraComboBox.Visibility = Xaml.Visibility.Visible
    AppBar1.Visibility = Xaml.Visibility.Visible
    AppBar1.IsEnabled = True
    myFrame.Visibility = Xaml.Visibility.Collapsed
    Image1.Visibility = Xaml.Visibility.Visible
    Image1.Source = Nothing
    commentTextBox.Text = String.Empty
    If flag = True Then
      DataShow()
      flag = False
    Else
      flag = False
      Exit Sub
    End If
  End Sub

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

CaptureElementを非表示に、cameraComboBoxを非表示に、myFrameを表示状態に、AppBarを非表示とします。

myFrameのNavigateメソッドでDataIchiranShowPageに遷移します。何も値は渡さないのでNothingを指定しておきます。

  Private Sub ichiranButton_Click(sender As Object, e As RoutedEventArgs) Handles ichiranButton.Click
    CaptureElement1.Visibility = Xaml.Visibility.Collapsed
    cameraComboBox.Visibility = Xaml.Visibility.Collapsed
    myFrame.Visibility = Xaml.Visibility.Visible
    AppBar1.Visibility = Xaml.Visibility.Collapsed
    myFrame.Navigate(GetType(DataIchiranShowPage), Nothing)
  End Sub

Geolocatorの機能が変更された時に発生する処理

Geolocatorオブジェクトの更新後の状態を、変数positionStatusで参照します。

Select Case文で条件分岐を行います。場所データを提供するGeolocatorオブジェクトがDisabledであった場合、つまり、場所プロバイダーが無効であった場合は、RunAsyncメソッドでディスパッチされたイベントの結果を非同期に返します。ディスパッチャの優先順位は標準のNormalを指定しています。

この場合は、「位置情報を取得できません。GPSおよび位置情報取得可能なPCでお試しください。」というメッセージボックスを表示させ、「Attach Camera」アイコンの使用を不可として、処理を抜けます。それ以外の場合は、「Attch Camera」アイコンの使用を可能にします。

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

  Private Async Sub myGeolocator_StatusChanged(sender As Geolocator, args As StatusChangedEventArgs)
    Dim positionStatus = args.Status
    Select Case positionStatus
      Case Windows.Devices.Geolocation.PositionStatus.Disabled
        Await myCoreDispacher.RunAsync(CoreDispatcherPriority.Normal, Async Sub()
  
          Dim message As New MessageDialog("位置情報を取得できません。GPSおよび位置情報取得可能なPCでお試しください。")
          Await message.ShowAsync
          shutterButton.IsEnabled = False
          Exit Sub
                                    End Sub)
      Case Else
        Await myCoreDispacher.RunAsync(CoreDispatcherPriority.Normal, Sub()
          shutterButton.IsEnabled = True
                                                                   End Sub)
    End Select
  End Sub
End Class

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

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

リスト5 (DataShowPage.xaml.vb)

ファイル、フォルダおよびアプリケーションの設定を管理するクラスの含まれる、Windows.Storage名前空間をインポートします。

Imports Windows.Storage

Bing Mapsに関するクラスの含まれるBing.Maps名前空間をインポートします。

Imports Bing.Maps

図形を定義するクラスの含まれる、Windows.UI.Xaml.Shapes名前空間をインポートします。

Imports Windows.UI.Xaml.Shapes

オブジェクトのUIをサポートする機能の含まれる、Windows.UI名前空間をインポートします。

Imports Windows.UI

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

Imports System.Net.Http

Imports Windows.UI.Popups
Imports System.IO

シーケンシャルアクセスストリームおよびランダムアクセスストリームに対する読み取りと書きこみのサポートを提供するクラスの含まれる、Windows.Storage.Streams名前空間をインポートします。

Imports Windows.Storage.Streams

Public NotInheritable Class DataShowPage
  Inherits Page

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

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

  Dim myLatitude As String
  Dim myLongitude As String
  Dim myImageFile As String
  Dim myComment As String
  Dim delXmlFile As String
薬師寺国安事務所

薬師寺国安事務所代表。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メルマガ会員のサービス内容を見る

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