お花とクラウディアさんを合成して表示するプログラムを作る

2013年5月8日(水)
薬師寺 国安

※前ページからの続きです。

画面にクラウディアの画像を表示しピンチで拡大縮小、ドラッグをする処理

ファイルを表すメンバ変数claudiaImageにGetFileFromApplicationUriAsyncメソッドを使って、Imageを格納しているフォルダとクラウディアのファイル名を格納しているメンバ変数claudiaImageFileNameを連結した上で、指定した URI のアプリケーションリソースを表す StorageFile オブジェクトを取得します。

新しいBitmapImageクラスのインスタンスmyBmpを作成します。
SetSourceメソッドにAwait claudiaImage.OpenReadAsyncと指定して画像ファイルを開きます。新しいImageクラスのインスタンスmyClaudiaImageを作成します。
WidthとHeightプロパティを指定し、SourceプロパティにmyBmpオブジェクトを指定します。
UIElementの描画位置に影響する変換情報設定するRenderTransformプロパティにオブジェクトに複数の変換操作を適用するCompositeTransformを指定します。
ジェスチャとともにUIElementの動作および操作に使用されるManipulationModeプロパティに、全ての対話モードを有効にする、ManipulationModes.Allを指定します。
CanvasにmyClaudiaImageオブジェクトを追加します。画面一杯にクラウディアの画像が表示されます。

AddHandlerステートメントで、myClaudiaImageオブジェクトの操作中に、入力デバイスが位置を変更した時に発生するManipulationDeltaイベントにイベントハンドラを追加します。
イベントハンドラ内では以下の処理を行います。

TranslateXとTranslateYでx軸とy軸にそって平行移動する距離を設定します。

ScaleXとScaleYでオブジェクトを拡大縮小する値を設定します。

クラウディアの画像一覧が表示されているListBoxの使用を不可とします。

  Private Async Sub claudiaShow()
    claudiaImage = Await StorageFile.GetFileFromApplicationUriAsync(New Uri("ms-appx:///Images/" & claudiaImageFileName))
    Dim myBmp As New BitmapImage
    myBmp.SetSource(Await claudiaImage.OpenReadAsync)
    myClaudiaImage = New Image
    With myClaudiaImage
      .Width = myBmp.PixelWidth
      .Height = myBmp.PixelHeight
      .Source = myBmp
      .RenderTransform = New CompositeTransform
      .ManipulationMode = ManipulationModes.All
    End With
    Canvas1.Children.Add(myClaudiaImage)
 
    AddHandler myClaudiaImage.ManipulationDelta, Sub(mySender As Object, myArgs As ManipulationDeltaRoutedEventArgs)
    myTrans = DirectCast(myClaudiaImage.RenderTransform, CompositeTransform)
    myTrans.TranslateX = myTrans.TranslateX + myArgs.Delta.Translation.X
    myTrans.TranslateY = myTrans.TranslateY + myArgs.Delta.Translation.Y
    myTrans.ScaleX = myTrans.ScaleX * myArgs.Delta.Scale
    myTrans.ScaleY = myTrans.ScaleY * myArgs.Delta.Scale
      End Sub
    claudiaListBox.IsEnabled = False
  End Sub

画面に花の画像を表示しピンチで拡大縮小、ドラッグをする処理

ファイルを表すメンバ変数flowerImageにGetFileFromApplicationUriAsyncメソッドを使って、Imageを格納しているフォルダと花のファイル名を格納しているメンバ変数FlowerFileを連結し、指定した URI のアプリケーションリソースを表す StorageFile オブジェクトを取得します。

新しいBitmapImageクラスのインスタンスmyBmpを作成します。
SetSourceメソッドにAwait flowerImage.OpenReadAsyncと指定して画像ファイルを開きます。新しいImageクラスのインスタンスmyFlowerImageを作成します。
WidthとHeightプロパティを指定し、SourceプロパティにmyBmpオブジェクトを指定します。
UIElementの描画位置に影響する変換情報設定するRenderTransformプロパティにオブジェクトに複数の変換操作を適用するCompositeTransformを指定します。
ジェスチャとともにUIElementの動作および操作に使用されるManipulationModeプロパティに、全ての対話モードを有効にする、ManipulationModes.Allを指定します。
CanvasにmyFlowerImageオブジェクトを追加します。画面一杯に花の画像が表示されます。

AddHandlerステートメントで、myFlowerImageオブジェクトの操作中に、入力デバイスが位置を変更した時に発生するManipulationDeltaイベントにイベントハンドラを追加します。
イベントハンドラ内では以下の処理を行います。

TranslateXとTranslateYでx軸とy軸にそって平行移動する距離を設定します。

ScaleXとScaleYでオブジェクトを拡大縮小する値を設定します。

花の画像一覧が表示されているListBoxの使用を不可とします。

  Private Async Sub FlowerShow()
    flowerImage = Await StorageFile.GetFileFromApplicationUriAsync(New Uri("ms-appx:///Images/" & FlowerFile))

    Dim myBmp As New BitmapImage
    myBmp.SetSource(Await flowerImage.OpenReadAsync)
    myFlowerImage = New Image
    With myFlowerImage
      .Width = myBmp.PixelWidth
      .Height = myBmp.PixelHeight
      .Source = myBmp
      .RenderTransform = New CompositeTransform
      .ManipulationMode = ManipulationModes.All
    End With
    Canvas1.Children.Add(myFlowerImage)
 
    AddHandler myFlowerImage.ManipulationDelta, Sub(mySender As Object, myflowerArgs As ManipulationDeltaRoutedEventArgs)
      myFlowerTrans = DirectCast(myFlowerImage.RenderTransform, CompositeTransform)
      myFlowerTrans.TranslateX = myFlowerTrans.TranslateX + myflowerArgs.Delta.Translation.X
      myFlowerTrans.TranslateY = myFlowerTrans.TranslateY + myflowerArgs.Delta.Translation.Y
      myFlowerTrans.ScaleX = myFlowerTrans.ScaleX * myflowerArgs.Delta.Scale
      myFlowerTrans.ScaleY = myFlowerTrans.ScaleY * myflowerArgs.Delta.Scale
          End Sub
    flowerListBox.IsEnabled = False
  End Sub

「クラウディア前面」にチェックが付いた時の処理

メンバ変数Indexを10で初期化、flowerIndexを0で初期化します。
SetValueプロパティでクラウディア画像のZIndexの値を指定します。同様に花のZIndexの値を指定します。
これで、クラウディアが花より前面に表示されます。ZIndex値が大きいほど前面に表示されます。

  Private Sub frontClaudiaRadioButton_Checked(sender As Object, e As RoutedEventArgs) Handles frontClaudiaRadioButton.Checked
    Index = 10
    flowerIndex = 0
    myClaudiaImage.SetValue(Canvas.ZIndexProperty, Index)
    myFlowerImage.SetValue(Canvas.ZIndexProperty, flowerIndex)
  End Sub

「クラウディア背面」にチェックが付いた時の処理

メンバ変数Indexを0で初期化、flowerIndexを10で初期化します。
SetValueプロパティでクラウディア画像のZIndexの値を指定します。同様に花のZIndexの値を指定します。
これで、クラウディアが花より背面に表示されます。

  Private Sub backClaudiaRadioButton_Checked(sender As Object, e As RoutedEventArgs) Handles backClaudiaRadioButton.Checked
    Index = 0
    flowerIndex = 10
    myClaudiaImage.SetValue(Canvas.ZIndexProperty, Index)
    myFlowerImage.SetValue(Canvas.ZIndexProperty, flowerIndex)
  End Sub

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

「クラウディア前面」「クラウディア背面」のどちらも選択されていない場合は警告メッセージを表示して処理を抜けます。

変数flowerWidthに花の幅にX軸の拡大縮小の値myFlowerTrans.ScaleXを乗算して指定します。

変数flowerHeightに花の高さにY軸の拡大縮小の値myFlowerTrans.ScaleYを乗算して指定します。

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

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

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

Visual Basic の埋め込み式を用いて、ルート要素、その子要素としてを作成し、の子要素として要素を作成します。
属性”Width”にflowerWidth変数の値を指定します。
属性”Height”にflowerHeightの値を指定します。
属性”Top”にはmyFlowerTrans.TranslateY.ToStringの値を、属性”Left”にはmyFlowerTrans.TranslateX.ToStringの値を指定します。
属性”FlowerIndex”にはZIndexの値となる変数flowerIndexの値を指定します。
要素自体にはFloweFileの値を指定します。

同じく要素の子要素としてを作成します。
属性”Width”にclaudiaWidth変数の値を指定します。
属性”Height”にclaudiaHeightの値を指定します。
属性”Top”にはmyTrans.TranslateY.ToStringの値を、属性”Left”にはmyTrans.TranslateX.ToStringの値を指定します。
属性Index”にはZIndexの値となる変数Indexの値を指定します。
要素自体にはclaudiaImageFileNameの値を指定します。

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

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

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

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

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

文字列型のリストであるmyFileListを作成します。
GetFilesAsyncメソッドでFlowerAndClaudiaサブフォルダ内のファイルを取得します。
Countメソッドでファイルの個数を取得し、ファイルが存在した場合は、以下の処理を行います。

  • FlowerAndClaudiaサブフォルダ内のファイルのコレクション内を、変数fileResultで反復処理しながら、AddメソッドでmyFileListにファイル名を追加していきます。
  • fileResult.Pathで取得されるファイル名は絶対パス付きファイル名になりますので、Path.GetFileNameWithoutExtension(fileResult.Path)として、パスと拡張子を除いたファイル名を取得し、再度拡張子となる”.xml”と連結します。
  • fileListBoxのItemsSourceプロパティにmyFileListオブジェクトを追加します。
  • これで、ファイルが存在する場合はListBoxに表示されます。

[Save]ボタンの使用を不可とします。
クラウディアの表示されているListBoxの選択状態を解除します。RadioButtonのチェックも外します。

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

  Private Async Sub saveButton_Click(sender As Object, e As RoutedEventArgs) Handles saveButton.Click
    If frontClaudiaRadioButton.IsChecked = False AndAlso backClaudiaRadioButton.IsChecked = False Then
      Dim myMessage As New MessageDialog("クラウディアの前面、背面にチェックを付けてください。")
      Await myMessage.ShowAsync
      Exit Sub
    End If
    Dim flowerWidth = CInt(myFlowerImage.Width * myFlowerTrans.ScaleX)
    Dim flowerHeight = CInt(myFlowerImage.Height * myFlowerTrans.ScaleY)
 
    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("FlowerAndClaudia", CreationCollisionOption.OpenIfExists)
    Dim saveXml As XElement = <Claudia><Info><FlowerImage Width=<%= flowerWidth %> Height=<%= flowerHeight %> Top=<%= myFlowerTrans.TranslateY.ToString %> Left=<%= myFlowerTrans.TranslateX.ToString %> FlowerZindex=<%= flowerIndex %>><%= FlowerFile %></FlowerImage><ClaudiaImage Width=<%= claudiaWidth %> Height=<%= claudiaHeight %> Top=<%= myTrans.TranslateY.ToString %> Left=<%= myTrans.TranslateX.ToString %> Zindex=<%= Index.ToString %>><%= claudiaImageFileName %></ClaudiaImage></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 myReadXmlFile = Await SubFolder.GetFilesAsync
      If myReadXmlFile.Count > 0 Then
        For Each fileResult As StorageFile In myReadXmlFile
          myFileList.Add(Path.GetFileNameWithoutExtension(fileResult.Path) & ".xml")
        Next
      Else
        fileListBox.Items.Clear()
      End If
 
      fileListBox.ItemsSource = myFileList
      saveButton.IsEnabled = False
      claudiaListBox.SelectedIndex = -1
      claudiaListBox.IsEnabled = True
      frontClaudiaRadioButton.IsChecked = False
      backClaudiaRadioButton.IsChecked = False    End Sub

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

Frameを非表示にし、XMLファイルが存在する場合はListBoxにXMLファイルの一覧を表示し、クラウディアや花の画像をListBoxに表示する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を表示状態にします。ListBoxから選択されたファイル名を変数mySelectedFileに格納します。
Navigateメソッドで変数mySelectedFileを引数にDataShowPageに遷移します。

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

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

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

リスト5 (DataShowPage.xaml.vb)

Option Strict On

Imports Windows.Storage
Imports Windows.Storage.Streams
Imports Windows.UI.Popups

Public NotInheritable Class DataShowPage
  Inherits Page
  Dim mySelectedFile As String

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

MainPage.xamlから送られてきた値はe.Parameterで取得できます。これはObject型であるため、DirectCastでString型に変換して変数mySelectedFileに格納しておきます。選択されたXMLファイルが格納されます。

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

GetFileAsyncメソッドでFlowerAndClaudiaサブフォルダ内のmySelectedFile変数に格納されているファイルを取得します。
OpenAsyncメソッドでXMLファイルを「読み取り」モードで開き、入出力データへの、ランダムアクセスをサポートするIRandomAccessStreamインターフェース型のmyStreamで参照します。
読み込んだXMLファイルと文字コードで初期化された新しいStreamReaderクラスのインスタンスreaderオブジェクトを作成します。
ReadToEndメソッドでXML文書を末尾まで読み込み変数resultに格納します。

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

読み込んだXMLファイルからクラウディアの画像名、Width、Height、Top、Left、Zindexの値を取得します。

同様に花の画像名、Width、Height、Top、Left、FlowerZindexの値を取得します。

新しいImageクラスのインスタンスflowerImgを作成します。
Width、Height、SourceにXMLから読み込んだ値を指定します。
MarginプロパティにはLeftとTopの値を指定します。
SetValueに花のZIndexの値を指定します。ShowAreaというCanvas内に花の画像を表示します。

新しいImageクラスのインスタンスclaudiaImgを作成します。

Width、Height、SourceにXMLから読み込んだ値を指定します。
MarginプロパティにはLeftとTopの値を指定します。
SetValueにクラウディアのZIndexの値を指定します。
ShowAreaというCanvas内にクラウディアの画像を表示します。

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

  Protected Overrides Async Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
    delTextBlock.Visibility = Windows.UI.Xaml.Visibility.Collapsed
    mySelectedFile = DirectCast(e.Parameter, String)
    Dim result As String
    Dim myStorageFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim mySubFolder = Await myStorageFolder.CreateFolderAsync("FlowerAndClaudia", 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 myFlowerImage = doc.Descendants("FlowerImage").Value
    Dim myClaudiaImage = doc.Descendants("ClaudiaImage").Value
    Dim c_Width = doc.Descendants("ClaudiaImage").Attributes("Width").First
    Dim c_Height = doc.Descendants("ClaudiaImage").Attributes("Height").First
    Dim c_Top = doc.Descendants("ClaudiaImage").Attributes("Top").First
    Dim c_Left = doc.Descendants("ClaudiaImage").Attributes("Left").First
    Dim c_Zindex = doc.Descendants("ClaudiaImage").Attributes("Zindex").First
 
    Dim f_Width = doc.Descendants("FlowerImage").Attributes("Width").First
    Dim f_Height = doc.Descendants("FlowerImage").Attributes("Height").First
    Dim f_Top = doc.Descendants("FlowerImage").Attributes("Top").First
    Dim f_Left = doc.Descendants("FlowerImage").Attributes("Left").First
    Dim f_Zindex = doc.Descendants("FlowerImage").Attributes("FlowerZindex").First
 
    Dim flowerImg As New Image
    With flowerImg
      .Width = CDbl(f_Width)
      .Height = CDbl(f_Height)
      .Source = New BitmapImage(New Uri("ms-appx:///Images/" & myFlowerImage))
      .Margin = New Thickness(CDbl(f_Left), CDbl(f_Top), 0, 0)
      .SetValue(Canvas.ZIndexProperty, CInt(f_Zindex))
    End With
    ShowArea.Children.Add(flowerImg)
 
    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))
      .SetValue(Canvas.ZIndexProperty, CInt(c_Zindex))
    End With
    ShowArea.Children.Add(claudiaImg)
  End Sub

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

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

GetFileAsyncメソッドで変数mySelectedFileに格納されているXMLファイルを取得し、変数myFileで参照します。DeleteAsyncメソッドで、現在のファイルを削除します。
ShowAreaというCanvas内をクリアします。
「削除しました!」と記述しているdelTextBlockを表示します。
[Delete]アイコンの使用を不可とします。

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

  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("FlowerAndClaudia", CreationCollisionOption.OpenIfExists)
    Dim myFile = Await mySubFolder.GetFileAsync(mySelectedFile)
    Await myFile.DeleteAsync()
    ShowArea.Children.Clear()
    delTextBlock.Visibility = Windows.UI.Xaml.Visibility.Visible
    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メルマガ会員のサービス内容を見る

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