お気に入りの写真に登場する、仲良しクラウディアちゃん

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

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

変数claudiaWidthに、クラウディアの幅にX軸の拡大縮小の値myTrans.ScaleXを乗算して指定します。

変数claudiaHeightに、クラウディアの高さにY軸の拡大縮小の値myTrans.ScaleYを乗算して指定します。

ピクチャライブラリにアクセスします。
CreateFolderAsyncメソッドで、ピクチャライブラリ内にClaudiaData2というサブフォルダを作成します。CreationCollisionOption.OpenIfExistsを指定すると、既に同名フォルダが存在する場合はフォルダ名を返し、ない場合は新規に作成します。

Visual Basic の埋め込み式を用いて、ルート要素、その子要素としてを作成し、の子要素として要素を作成します。
属性”Width”にclaudiaWidth変数の値を指定します。
属性”Height”にclaudiaHeightの値を指定します。
属性”Top”にはmyTrans.TranslateY.ToStringの値を、属性”Left”にはmyTrans.TranslateX.ToStringの値を指定します。
属性”Wav”にはメンバ変数SoundFileの値を指定します。
要素自体にはメンバ変数claudiaImageFileNameの値を指定します。

次に要素を作成し、Width属性に640、Height属性に480と指定します。
To属性には167、Left属性には303と指定しています。
要素自体の値には変数myFileNameの値を指定します。

埋め込み式の構文であるを用いています。
これは ASP.NET で使用される構文と同じです。作成したXML文書を変数saveXmlに格納します。

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

CreateFileAsyncメソッドで、ピクチャライブラリのClaudiaData2サブフォルダ内に現在の年月日時分秒.xmlというファイルを作成します。
CreationCollisionOption.ReplaceExistingと指定し、既に同名のファイルがある場合は上書きします。

OpenAsyncメソッドで、作成した現在の年月日時分秒.xmlを「読み取り/書き込み」モードで開き、入出力データへの、ランダムアクセスをサポートするIRandomAccessStreamインターフェース型のmyStreamで参照します。

データを出力ストリームに書き込む、新しいDataWriterのインスタンスをmyStreamで初期化し、writerオブジェクトを作成します。
出力ストリームのUnicode文字エンコードを設定する、UnicodeEncodingプロパティにUtf8を指定します。
Writeメソッドで出力ストリームにresult変数の値を書き込みます。
StoreAsyncメソッドでバッキングストアにバッファーのデータをコミットします。Canvas内をクリアします。

文字列型の新しいリストであるmyFileListオブジェクトを作成します。
GetFilesAsyncメソッドでClaudiaData2フォルダ内のファイルを取得し、コレクション変数myImageFileに格納します。
myImageFileに格納されたファイルの個数が0より大きい場合は以下の処理を行います。

コレクション変数myImageFile内を変数fileResultで反復処理しながら、fileResult.Pathで取得した絶対パス付きファイル名から、パスと拡張子を除いたファイル名を取得し、再度”.xml”という拡張子を追加して、そのファイル名をmyFileListオブジェクトに追加します。
fileListBoxのItemsSourceプロパティにmyFileListオブジェクトを指定します。これで、リストボックスにClaudiaData2内のXMLファイルが一覧表示されます。
言葉の表示されているリストボックスの使用を不可とし、Saveアイコンの使用を不可とします。

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

  Private Async Sub saveButton_Click(sender As Object, e As RoutedEventArgs) Handles saveButton.Click
    Dim claudiaWidth = CInt(myClaudiaImage.Width * myTrans.ScaleX)
    Dim claudiaHeight = CInt(myClaudiaImage.Height * myTrans.ScaleY)
    Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim SubFolder = Await myFolder.CreateFolderAsync("ClaudiaData2", CreationCollisionOption.OpenIfExists)
    Dim saveXml As XElement = <Claudia><Info><Image Width=<%= claudiaWidth %> Height=<%= claudiaHeight %> Top=<%= myTrans.TranslateY.ToString %> Left=<%= myTrans.TranslateX.ToString %> Wav=<%= SoundFile %>><%= claudiaImageFileName %></Image><PersonalImage Width=<%= 640 %> Height=<%= 480 %> Top=<%= 167 %> Left=<%= 303 %>><%= myFileName %></PersonalImage></Info></Claudia>
    Dim saveXmldoc As XElement = XElement.Parse(saveXml.ToString)
    Dim result As String = saveXmldoc.ToString
 
    Dim myXmlFile As StorageFile = Await SubFolder.CreateFileAsync(DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒") & ".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(result)
      Await writer.StoreAsync
    End Using
    Canvas1.Children.Clear()
 
    Dim myFileList As New List(Of String)
    Dim myImageFile = Await SubFolder.GetFilesAsync
    If myImageFile.Count > 0 Then
      For Each fileResult As StorageFile In myImageFile
        myFileList.Add(Path.GetFileNameWithoutExtension(fileResult.Path) & ".xml")
      Next
    Else
      fileListBox.Items.Clear()
    End If
    fileListBox.ItemsSource = myFileList
    soundListBox.IsEnabled = False
    saveButton.IsEnabled = False
  End Sub

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

Frameを非表示にし、ピクチャライブラリのClaudiaData2フォルダ内にファイルが存在した場合は、リストボックスにファイルの一覧を表示し、クラウディアの画像と、音声用のファイルを表示する処理である、DataShowプロシージャを実行します。

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

ファイルの一覧から任意のファイルが選択された時の処理

Frameを表示状態にし、fileListBoxから選択されたファイル名を変数mySelectedFileに格納します。
この変数を引数にDataShowPageに遷移します。

  Private Sub fileListBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles fileListBox.SelectionChanged
    If fileListBox.SelectedIndex >= 0 Then
      myFrame.Visibility = Windows.UI.Xaml.Visibility.Visible
      mySelectedFile = fileListBox.SelectedItem.ToString
      myFrame.Navigate(GetType(DataShowPage), mySelectedFile)
    Else
      Exit Sub
    End If
  End Sub
End Class

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

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

リスト5 (DataShowPage.xaml.vb)

Option Strict On

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

Imports Windows.Storage

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

Imports Windows.Storage.Streams

コンテキストメニューおよびメッセージダイアログのサポートを提供するクラスの含まれる、Windows.UI.Popups名前空間をインポートします。

Imports Windows.UI.Popups

メンバ変数mySelectedFileを宣言しておきます。

  Dim mySelectedFile As String

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

音声を再生するPlayボタンの使用を可能にします。

MainPage.xamlから渡された値はe.Parameterで受け取れます。これはObject型であるためDirectCastで文字列にキャストして、メンバ変数mySelectedFileに格納します。XMLファイルが格納されています。

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

GetFileAsyncメソッドで、指定されたファイルの名前を使用して、現在のフォルダから1つのファイルを取得します。
取得したファイルを変数myFileで参照します。
OpenFileAsyncメソッドでmyFileを「読み込み専用」モードで開き、myStream変数で参照します。
myStreamと文字のエンコード(UTF-8)で初期化された、新しいStreamReaderクラスのインスタンスreaderオブジェクトを作成します。
ReadToEndメソッドでファイルの最後まで読み取り変数resultに格納しておきます。

XElement.Parseメソッドで変数resultの値を文字列として読み込みます。

変数myPersonalImageに要素の値、つまりClaudiaフォルダから取得した画像名を格納します。

変数myClaudiaImageには要素の値を格納します。クラウディアの画像名が格納されます。

あと、クラウディアのWidth、Heigt、Top、Left属性の値を取得して各変数に格納しておきます。
音声ファイルであるWav属性の値も取得して変数c_Wavに格納しておきます。

同様にClaudiaのフォルダから取得した画像のWidth、Height、Top、Left属性の値も取得して、各変数に格納しておきます。

選択されたXMLファイルは1個で、各要素も属性も1個しか存在しないため、Firstメソッドで、シーケンスの最初の属性を取得しています。

CreateFolderAsyncメソッドでピクチャライブラリ内にClaudiaというサブフォルダを作成します。
CreationCollisionOption.OpenIfExistsと指定すると、同名フォルダがある場合はそのフォルダ名を返し、ない場合は新規に作成します。
GetFileAsyncメソッドで、指定されたファイルの名前を使用して、現在のフォルダから1つのファイルを取得します。

BitmapImageクラスの新しいインスタンスmyBmpを作成します。
SetSourceメソッドにAwait peronalImageFile.OpenReadAsyncと指定して、ファイルの内容を読むために、現在のファイルのランダムアクセスストリームを開き、BitmapSourceのソースイメージに設定します。

新しいImageのインスタンスpersonalImgを作成します。
Widthにp_Width変数の値を、Heightにp_Heightの値を指定します。
SourceプロパティにはmyBmpオブジェクトを指定します。
Marginプロパティには、p_Leftとp_Topの値を指定します。減算している数値は位置合わせの数値です。

ShowAreaという名前のCanvasにperonalImgオブジェクトを追加します。Claudiaフォルダから読み込んだ画像が表示されます。

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

Widthプロパティにc_Width変数の値を、Heightプロパティにc_Height変数の値を、Marginプロパティにはc_Leftとc_Top変数の値を指定します。
Sourceプロパティには、Imagesフォルダ内の、変数myClaudiaImageが格納しているファイル名を指定します。

MediaElementのSourceプロパティにはWAVフォルダ内で変数c_Wavに格納されている音声ファイルを指定します。
ShowAreaというCanvasにclaudiaImgを追加します。これで、先に追加しておいたClaudiaフォルダの画像の上にクラウディアのイメージが重なって表示されます。

土台となる画像をClaudiaフォルダから読み込まなかった場合は例外が発生します。その際にはErrorShowプロシージャを実行します。

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

  Protected Overrides Async Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
    Try
      delTextBlock.Visibility = Windows.UI.Xaml.Visibility.Collapsed
      playButton.IsEnabled = True
      mySelectedFile = DirectCast(e.Parameter, String)
      Dim result As String
      Dim myStorageFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
      Dim mySubFolder = Await myStorageFolder.CreateFolderAsync("ClaudiaData2", CreationCollisionOption.OpenIfExists)
      Dim myFile = Await mySubFolder.GetFileAsync(mySelectedFile)
      Using myStream As IRandomAccessStream = Await myFile.OpenAsync(FileAccessMode.Read)
        Using reader As StreamReader = New StreamReader(myStream.AsStream, System.Text.Encoding.UTF8)
          result = reader.ReadToEnd
        End Using
      End Using
      Dim doc As XElement = XElement.Parse(result)
      Dim myPeronalImage = doc.Descendants("PersonalImage").Value
      Dim myClaudiaImage = doc.Descendants("Image").Value
      Dim c_Width = doc.Descendants("Image").Attributes("Width").First
      Dim c_Height = doc.Descendants("Image").Attributes("Height").First
      Dim c_Top = doc.Descendants("Image").Attributes("Top").First
      Dim c_Left = doc.Descendants("Image").Attributes("Left").First
      Dim c_Wav = doc.Descendants("Image").Attributes("Wav").First.Value
 
      Dim p_Width = doc.Descendants("PersonalImage").Attributes("Width").First
      Dim p_Height = doc.Descendants("PersonalImage").Attributes("Height").First
      Dim p_Top = doc.Descendants("PersonalImage").Attributes("Top").First
      Dim p_Left = doc.Descendants("PersonalImage").Attributes("Left").First
      Dim personalSubFolder As StorageFolder = Await myStorageFolder.CreateFolderAsync("Claudia", CreationCollisionOption.OpenIfExists)
      Dim peronalImageFile = Await personalSubFolder.GetFileAsync(myPeronalImage)
      Dim myBmp As New BitmapImage
      myBmp.SetSource(Await peronalImageFile.OpenReadAsync)
      Dim personalImg As New Image
      With personalImg
        .Width = CDbl(p_Width)
        .Height = CDbl(p_Height)
        .Source = myBmp
        .Margin = New Thickness(CDbl(p_Left) - 300, CDbl(p_Top) - 170, 0, 0)
      End With
      ShowArea.Children.Add(personalImg)
      Dim claudiaImg As New Image
      With claudiaImg
        .Width = CDbl(c_Width)
        .Height = CDbl(c_Height)
        .Margin = New Thickness(CDbl(c_Left), CDbl(c_Top), 0, 0)
        .Source = New BitmapImage(New Uri("ms-appx:///Images/" & myClaudiaImage))
      End With
 
      MediaElement1.Source = New Uri("ms-appx:///WAV/" & c_Wav)
      
      ShowArea.Children.Add(claudiaImg)
    Catch
      ErrorShow()
    End Try
  End Sub

例外が発生した場合の処理

警告メッセージを発生して、処理を抜けます。
非同期処理で行われるためメソッドの先頭にAsyncを追加します。

  Private Async Sub ErrorShow()
    Dim myMessage As New MessageDialog("ピクチャフォルダ内のClaudiaサブフォルダ内の画像ではありません。Deleteアイコンでこのファイルを削除してください。")
    Await myMessage.ShowAsync
    Exit Sub
  End Sub

Playアイコンがタップされた時の処理

音声を再生します。

  Private Sub playButton_Click(sender As Object, e As RoutedEventArgs) Handles playButton.Click
    MediaElement1.Play()
  End Sub

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

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

DeleteAsyncで取得したファイルを削除します。
ShowAreaという名前のCanvasをクリアします。
「削除しました!」を表示するために、delTextBlockを表示状態にします。
Playアイコンの使用を不可にします。またDeleteアイコンの使用も不可とします。

  Private Async Sub delButton_Click(sender As Object, e As RoutedEventArgs) Handles delButton.Click
    Dim myStorageFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim mySubFolder = Await myStorageFolder.CreateFolderAsync("ClaudiaData2", CreationCollisionOption.OpenIfExists)
    Dim myFile = Await mySubFolder.GetFileAsync(mySelectedFile)
    Await myFile.DeleteAsync()
    ShowArea.Children.Clear()
    delTextBlock.Visibility = Windows.UI.Xaml.Visibility.Visible
    playButton.IsEnabled = False
    delButton.IsEnabled = False
  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メルマガ会員のサービス内容を見る

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