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

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を追加します。

01Private Async Sub cameraComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles cameraComboBox.SelectionChanged
02  Try
03    cameraNo = cameraComboBox.SelectedIndex
04    AppBar1.Visibility = Xaml.Visibility.Visible
05    myMediaCapture = New MediaCapture
06    Await myMediaCapture.InitializeAsync(New MediaCaptureInitializationSettings With {.VideoDeviceId = myCamera(cameraComboBox.SelectedIndex).Id})
07    CaptureElement1.Source = myMediaCapture
08    Await myMediaCapture.StartPreviewAsync
09    shutterButton.IsEnabled = True
10  Catch
11    Exit Sub
12  End Try
13End 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を追加します。

01Private Async Sub shutterButton_Click(sender As Object, e As RoutedEventArgs) Handles shutterButton.Click
02 
03  MediaElement1.Play()
04  Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
05  Dim mySubFolder = Await myFolder.CreateFolderAsync("ImageDiary", CreationCollisionOption.OpenIfExists)
06  Dim myFile As StorageFile = Await mySubFolder.CreateFileAsync(DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒") & ".jpg")
07  saveImageFileName = DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒") & ".jpg"
08  Dim myImageEncodingProperty As New ImageEncodingProperties
09  myImageEncodingProperty.Subtype = "jpeg"
10  myImageEncodingProperty.Width = 640
11  myImageEncodingProperty.Height = 480
12 
13  AwaitmyMediaCapture.CapturePhotoToStorageFileAsync(myImageEncodingProperty, myFile)
14myPictureFiles = Await mySubFolder.GetFilesAsync()
15  Index = myPictureFiles.Count
16  saveBmp = New BitmapImage
17  saveBmp.SetSource(Await myPictureFiles(Index - 1).OpenReadAsync)
18  Image1.Source = saveBmp
19  saveButton.IsEnabled = True
20End 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を追加します。

01Private Async Sub saveButton_Click(sender As Object, e As RoutedEventArgs) Handles saveButton.Click
02  If commentTextBox.Text = String.Empty Then
03    Dim message As New MessageDialog("何かコメントを入力してください。")
04    Await message.ShowAsync
05    Exit Sub
06  Else
07    Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
08    Dim mySubFolder = Await myFolder.CreateFolderAsync("ImageDiaryXML", CreationCollisionOption.OpenIfExists)
09    Dim saveXml As XElement = <ImageDiary><Info><ImageName><%= saveImageFileName %></ImageName><Comment><%= commentTextBox.Text %></Comment><Latitude><%= myLatitude %></Latitude><Longitude><%= myLongitude %></Longitude></Info></ImageDiary>
10    Dim saveXmlDoc As XElement = XElement.Parse(saveXml.ToString)
11    Dim resultXml As String = saveXmlDoc.ToString
12    Dim myXmlFile As StorageFile = Await mySubFolder.CreateFileAsync(saveImageFileName.Substring(0, 20) & ".xml", CreationCollisionOption.ReplaceExisting)
13 
14    Using myStream As IRandomAccessStream = Await myXmlFile.OpenAsync(FileAccessMode.ReadWrite)
15      Dim writer As DataWriter = New DataWriter(myStream)
16      writer.UnicodeEncoding = UnicodeEncoding.Utf8
17      writer.WriteString(resultXml)
18      Await writer.StoreAsync
19    End Using
20 
21    Dim xmlFileList As New List(Of String)
22    Dim myXmlFileName = Await mySubFolder.GetFilesAsync
23    If myXmlFileName.Count > 0 Then
24      For Each result In myXmlFileName
25        xmlFileList.Add(Path.GetFileNameWithoutExtension(result.Path) & ".xml")
26      Next
27    Else
28      fileListBox.Items.Clear()
29    End If
30    fileListBox.ItemsSource = xmlFileList
31    ichiranButton.IsEnabled = True
32    saveButton.IsEnabled = False
33  End If
34End Sub

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

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

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

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

01Private Sub fileListBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles fileListBox.SelectionChanged
02  AppBar1.Visibility = Xaml.Visibility.Collapsed
03  Try
04    flag = True
05    myFrame.Visibility = Xaml.Visibility.Visible
06    Dim mySelectedFile As String = fileListBox.SelectedItem.ToString
07    Image1.Visibility = Xaml.Visibility.Collapsed
08    myFrame.Navigate(GetType(DataShowPage), mySelectedFile)
09  Catch
10    myFrame.Visibility = Xaml.Visibility.Collapsed
11    AppBar1.Visibility = Xaml.Visibility.Visible
12    Image1.Visibility = Xaml.Visibility.Visible
13    Exit Sub
14  End Try
15End Sub

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

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

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

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

01Private Sub backButton_Click(sender As Object, e As RoutedEventArgs) Handles backButton.Click
02  CaptureElement1.Visibility = Xaml.Visibility.Visible
03  cameraComboBox.Visibility = Xaml.Visibility.Visible
04  AppBar1.Visibility = Xaml.Visibility.Visible
05  AppBar1.IsEnabled = True
06  myFrame.Visibility = Xaml.Visibility.Collapsed
07  Image1.Visibility = Xaml.Visibility.Visible
08  Image1.Source = Nothing
09  commentTextBox.Text = String.Empty
10  If flag = True Then
11    DataShow()
12    flag = False
13  Else
14    flag = False
15    Exit Sub
16  End If
17End Sub

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

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

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

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

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

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

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

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

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

01  Private Async Sub myGeolocator_StatusChanged(sender As Geolocator, args As StatusChangedEventArgs)
02    Dim positionStatus = args.Status
03    Select Case positionStatus
04      Case Windows.Devices.Geolocation.PositionStatus.Disabled
05        Await myCoreDispacher.RunAsync(CoreDispatcherPriority.Normal, Async Sub()
06   
07          Dim message As New MessageDialog("位置情報を取得できません。GPSおよび位置情報取得可能なPCでお試しください。")
08          Await message.ShowAsync
09          shutterButton.IsEnabled = False
10          Exit Sub
11                                    End Sub)
12      Case Else
13        Await myCoreDispacher.RunAsync(CoreDispatcherPriority.Normal, Sub()
14          shutterButton.IsEnabled = True
15                                                                   End Sub)
16    End Select
17  End Sub
18End Class

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

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

リスト5 (DataShowPage.xaml.vb)

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

1Imports Windows.Storage

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

1Imports Bing.Maps

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

1Imports Windows.UI.Xaml.Shapes

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

1Imports Windows.UI

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

1Imports System.Net.Http
2 
3Imports Windows.UI.Popups
4Imports System.IO

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

1Imports Windows.Storage.Streams
2 
3Public NotInheritable Class DataShowPage
4  Inherits Page

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

1Const AppID As String = "YahooのアプリケーションID"
2 
3Dim myLatitude As String
4Dim myLongitude As String
5Dim myImageFile As String
6Dim myComment As String
7Dim 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メルマガ会員のサービス内容を見る

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