タッチパネルでドラッグ&ドロップを使う汎用的なサンプル

2012年5月24日(木)
PROJECT KySS

アニメーションを作成する。

画像を削除する時のアニメーションを作成します。

「オブジェクトとタイムライン(B)」に表示されているオブジェクトの一覧からdelImageを選択し、[+]アイコンをクリックして新しいストリーボードを作成します。「名前(キー)」にはdelStoryboardと指定します。

アートボードが赤い枠線で囲まれ、タイムラインの記録がオンになった状態で、再生ヘッドを0位置に合わせ、[変換]パネル内の、RenderTransformのAngleの値に90を指定します。

次に、黄色の再生ヘッドを0.2の位置に合わせ、delImageを上方に少し移動し、[変換]パネルにあるRenderTransformのAngleの値に360を指定します。続けて再生ヘッドの位置を移動して0.4に合わせ、delImageを少し上方に移動し、Angleの値を0に指定します。

最後に再生ヘッドを0.6の位置に合わせて、delImageをListBoxの中央あたりまで移動し、Angleに270を指定し、1.0の位置で90を指定します。表示されている「愛」画像は横に寝ていますが、カメラで写した画像は縦向きで撮影するとデフォルトで横向きに表示されますので、最後は横向きとしておきます(図10)。

delImageにはプログラムから画像を指定するため、アニメーション作成時には何も表示されず、作成しづらい面がありますので、ダミーでSourceプロパティに画像を読み込んで作成しています。最終的には、Sourceプロパティには何も指定しないでください。プログラム上からSourceプロパティに表示される画像を指定します。

 図10:delImageのストリーボードを作成している(クリックで拡大)

次に、削除確認メッセージが表示されて[OK]が表示された場合に、画像の透明化を0にして消滅させるアニメーションを作成します。

「オブジェクトとタイムライン(B)」に表示されている、オブジェクトの一覧からdelImageを選択し、[+]アイコンをクリックして新しいストーリーボードを作成します。「名前(キー)」にはdelStoryboard2と指定します。アートボードが赤い枠線で囲まれ、タイムラインが記録オンの状態になりますので、ここで●をクリックして、一時記録をオフにします。そして、delImageを選択してListBoxの中央まで移動します。RenderTransformのAngleに90と指定しておきます。図11の状態にしておきます。

 図11:delStoryboard2の記録を開始する最初の画面(クリックで拡大)

●をクリックし記録オンの状態にします。黄色の再生ヘッドを1まで移動し、プロパティの[外観]パネルにあるOpacityに0と指定します。1秒かけて画像が消滅するアニメーションができました。

次に、削除確認メッセージが表示されて[キャンセル]が表示された場合、画像をカバンの中まで移動するアニメーションを作成します。

「オブジェクトとタイムライン(B)」に表示されている、オブジェクトの一覧からdelImageを選択し、[+]アイコンをクリックして新しいストーリーボードを作成します。

「名前(キー)」にはnoDeleteStoryboardと指定します。アートボードが赤い枠線で囲まれ、タイムラインの記録がオンになります。この前に作成したdelStoryboard2の状態で画像が表示されていますので、そのまま再生ヘッドを1まで移動し、delImageをカバンの画像の中まで移動します(図12)。ダミーで読み込んでいた「愛」の画像はカバンの背景に隠れて見えなくなります。

 図12:delImageをカバンの画像の中まで移動する(クリックで拡大)

最後にdelImageのSourceプロパティに指定していたダミーの画像を削除します。

Expression Blendを終了してVS2010に戻ります。

次に、DeletePage.xamlを展開して表示されるDeletePage.xaml.vbをダブルクリックして、リスト6のコードを記述します。

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

リスト6 (DeletePage.xaml.vb)

Option Strict On
Imports System.Xml.Linq
Imports System.IO
Imports System.IO.IsolatedStorage
Imports System.Windows.Media.Imaging
Imports Microsoft.Phone

_ImageInfoクラス内にWriteableBitmapクラス型のimageFileNameプロパティを定義しておきます。

Public Class _ImageInfo
  Property imageFileName As WriteableBitmap
End Class

Partial Public Class DeletePage
  Inherits PhoneApplicationPage
 
  Public Sub New()
    InitializeComponent()
  End Sub
  Dim myIndex As Integer = 0

ページがアクティブになった時呼び出されるメソッド

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.CombineでPictureDataというフォルダと、imageFileList.xmlというXMLファイル名と連結し、変数filePathに格納します。

分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数myStreamを用意し、IsolatedStorageFileクラスのOpenFileメソッドで、PictureDataフォルダ内のimageFileList.xmlファイルを、指定したファイルアクセスを使用して指定したモードで開きます。

開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り変数readXmldoc変数に格納しておきます。

読み込んだXMLテキストをParseメソッドでXElementとして読み込みます。_ImageInfoクラス型の新しいリストであるmyImageInfoオブジェクトを作成します。

Descendantsメソッドで、子孫要素である全ての 要素のコレクションを選択し、各要素を、変数resultに格納しながら、以下の処理を繰り返します。

Path.CombineでPictureDataフォルダと、要素の属性”imageFileName”の値を連結して、変数imageFilePathに格納しておきます。変数imageStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。IsolatedStorageFileStreamクラスのOpenFileメソッドで、PictureDataフォルダ内のimageFileList.xmlファイルを、指定したファイルアクセスを使用して指定したモードでファイルを開きます。

WriteableBitmap型の変数imageSourceを宣言し、PictureDecoder.DecodeJpegメソッドで、開いたストリームをJPEGファイルとしてWriteableBitmapオブジェクトにデコードします。PictureDecoder.DecodeJpegメソッドはMicrosof.Phone名前空間に属しています。WriteableBitmapクラスは書き込み更新することのできるBitmapSourceを提供するクラスです。

_ImageInfoクラスのimageFileNameプロパティに、読み込んだWriteableBitmapオブジェクトのimageSourceオブジェクトを指定し、AddメソッドでmyImageInfoオブジェクトに追加していきます。

ListBox1のItemsSourceプロパティにmyImageInfoオブジェクトを指定します。ListBoxのSetValueメソッドにZIndexPropertyを指定し、値に0を指定します。ListBoxコントロールが全てのオブジェクトの背面に表示されます。これで、撮影した画像の一覧が表示されます。

  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
    Try
      Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
      Dim filePath As String = Path.Combine("PictureData", "imageFileList.xml")
      Using myStream As IsolatedStorageFileStream = storage.OpenFile(filePath, FileMode.Open, FileAccess.Read)
        Using reader As StreamReader = New StreamReader(myStream)
          Dim readXmldoc As String = reader.ReadToEnd
          Dim doc As XElement = XElement.Parse(readXmldoc)
          Dim myImageInfo As New List(Of _ImageInfo)
 
          For Each result In From c In doc.Descendants("fileName") Select c
            Dim imageFilePath As String = Path.Combine("PictureData", result.Attribute("imageFileName").Value)
            Dim imageStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
            Using stream As IsolatedStorageFileStream = imageStorage.OpenFile(imageFilePath, FileMode.Open, FileAccess.Read)
              Dim imageSource As WriteableBitmap = PictureDecoder.DecodeJpeg(stream)
              myImageInfo.Add(New _ImageInfo With {.imageFileName = imageSource})
            End Using
          Next
          ListBox1.ItemsSource = myImageInfo
          ListBox1.SetValue(Canvas.ZIndexProperty, 0)
        End Using
      End Using
 
      MyBase.OnNavigatedTo(e)
    Catch
      Exit Sub
    End Try
  End Sub

ListBoxから任意の画像が選択された時の処理

ListBoxの選択された項目のインデックスが、0または0より大きい(きちんと画像が選択された)場合は、ListBoxの選択されたインデックスをメンバ変数myIndexに格納しておきます。カバンの中に隠れているdelImage1のSetValueメソッドにZIndexPropertyを指定し、値に10を指定して一番前面に表示されるようにします。delImage1に選択された画像を表示するDeleteImageプロシージャを実行し、カバンから画像が回転しながら飛び出してくるdelStoryboardを開始します。

  Private Sub ListBox1_SelectionChanged(sender As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles ListBox1.SelectionChanged
    If ListBox1.SelectedIndex => 0 Then
      myIndex = ListBox1.SelectedIndex
      delImage1.SetValue(Canvas.ZIndexProperty, 10)
      DeleteImage()
      delStoryboard.Stop()
      delStoryboard.Begin()
    Else
      Exit Sub
    End If
  End Sub

delImageコントロールにListBoxから選択された画像を表示させる処理

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.Combineで、PictureDataというフォルダとimageFileList.xmlというXMLファイル名と連結し、変数filePathに格納します。

分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数myStreamを用意し、IsolatedStorageFileクラスのOpenFileメソッドで、PictureDataフォルダ内のimageFileList.xmlファイルを、指定したファイルアクセスを使用して、指定したモードで開きます。開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドで、ファイルの最後まで読み取り変数readXmldoc変数に格納しておきます。

ListBoxより選択されたインデックスに該当する要素の属性、”imageFileName”の値を取得して、変数delImageに格納します。Path.CombineでPictureDataというフォルダと、delImage変数に格納されている画像名を連結して、変数imageFilePathに格納します。

変数imageStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数streamを用意し、IsolatedStorageFileクラスのOpenFileメソッドでPictureDataフォルダ内のdelImage変数に格納されている画像ファイルを、指定したファイルアクセスを使用して、指定したモードで開きます。

WriteableBitmap型の変数imageSourceを宣言し、PictureDecoder.DecodeJpegメソッドで、開いたストリームをJPEGファイルとしてWriteableBitmapオブジェクトにデコードします。PictureDecoder.DecodeJpegメソッドはMicrosof.Phone名前空間に属しています。WriteableBitmapクラスは書き込み更新することのできるBitmapSourceを提供するクラスです。

delImage1のSourceプロパティに、読み込んだWriteableBitmapオブジェクトのimageSourceオブジェクトを指定します。ZIndexPropertyに10を指定し最前面に表示されるようにします。

  Private Sub DeleteImage()
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim filePath As String = Path.Combine("PictureData", "imageFileList.xml")
    Using myStream As IsolatedStorageFileStream = storage.OpenFile(filePath, FileMode.Open, FileAccess.Read)
      Using reader As StreamReader = New StreamReader(myStream)
        Dim readXmldoc As String = reader.ReadToEnd
        Dim doc As XElement = XElement.Parse(readXmldoc)
        Dim delImage As String = doc.Descendants("fileName")(ListBox1.SelectedIndex).Attribute("imageFileName").Value
        Dim imageFilePath As String = Path.Combine("PictureData", delImage)
        Dim imageStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
        Using stream As IsolatedStorageFileStream = imageStorage.OpenFile(imageFilePath, FileMode.Open, FileAccess.Read)
          Dim imageSource As WriteableBitmap = PictureDecoder.DecodeJpeg(stream, 480, 640)
          delImage1.Source = imageSource
          delImage1.SetValue(Canvas.ZIndexProperty, 10)
        End Using
      End Using
    End Using
  End Sub

削除用の画像がカバンから回転しながら飛び出す、delStoryboardが完了した時の処理

選択した画像を削除するかどうかの確認メッセージを表示します。[ok]の場合は、画像が透明化されて消えていくdelStoryboard2を開始します。[キャンセル]の場合は、画像がカバンの中に戻っていくnoDeleteStoryboardを開始します。

  Private Sub delStoryboard_Completed(sender As Object, e As System.EventArgs) Handles delStoryboard.Completed
  
    Dim kakunin = MessageBox.Show("この画像を削除しますか?", "削除確認", MessageBoxButton.OKCancel)
    Select Case kakunin
      Case MessageBoxResult.OK
          delStoryboard2.Stop()
          delStoryboard2.Begin()
          Exit Select
      Case Else
          ListBox1.SelectedIndex = -1
          noDeleteStoryboard.Stop()
          noDeleteStoryboard.Begin()
      Exit Select
    End Select
  End Sub

画像の透明化アニメーションが完了した時の処理

削除処理のDeleteImageDataプロシージャを実行します。その後、透明化された画像がカバンの中に戻るよう、noDeleteStoryboardを開始します。この動作は目には見えません。

  Private Sub delStoryboard2_Completed(sender As Object, e As System.EventArgs) Handles delStoryboard2.Completed
    DeleteImageData()
    noDeleteStoryboard.Begin()
 
  End Sub

画像がカバンの中に戻るアニメーションが完了した時の処理

delImage1に表示されている画像をカバンの背面に位置します。画像の透明化を解除します。

  Private Sub noDeleteStoryboard_Completed(sender As Object, e As System.EventArgs) Handles noDeleteStoryboard.Completed
    delImage1.SetValue(Canvas.ZIndexProperty, 0)
    delImage1.Opacity = 1
  End Sub

画像が削除される処理

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.CombineでPictureDataというフォルダとimageFileList.xmlというXMLファイル名と連結し、変数filePathに格納します。

imageFileList.xmlが存在する場合は、分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数myStreamを用意します。そして、IsolatedStorageFileクラスのOpenFileメソッドで、PictureDataフォルダ内のimageFileList.xmlファイルを、指定したファイルアクセスを使用して、指定したモードでファイルを開きます。

開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り変数readXmldoc変数に格納しておきます。読み取ったreadXmldocをXElement.Parseメソッドで文字列として読み込みます。

読み込んだXMLから、ListBoxの選択されたインデックスに該当する要素の属性”imageFileName”の値をdelImage変数に格納します。DeleteFileメソッドで、このdelImageに格納されている画像ファイルを削除します。

ListBoxの選択されたインデックスに該当する要素を選択し、Removeメソッドで削除します。画像ファイルと共に、画像ファイルを記録していたXMLの要素も削除されます。Saveメソッドで削除されたXMLを保存します。削除した旨のメッセージを表示し、DeletePage.xamlを再読み込みすると、データの削除が反映されます。

要素の個数が0の場合、つまり、XML文書内からが全部削除された場合、画像も全部削除された場合になります、その場合は、imageFileName.xmlファイル自体を削除し、MainPage.xamlに遷移します。

  Private Sub DeleteImageData()
    Dim delXml As XElement
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim filePath As String = Path.Combine("PictureData", "imageFileList.xml")
    If storage.FileExists(filePath) = True Then
      Dim myStream As IsolatedStorageFileStream = storage.OpenFile(filePath, FileMode.Open, FileAccess.Read)
      Using reader As StreamReader = New StreamReader(myStream, System.Text.Encoding.UTF8)
        Dim readXmldoc As String = reader.ReadToEnd
        delXml = XElement.Parse(readXmldoc)
        myStream.Close()
      End Using
 
      Dim delImage As String = delXml.Descendants("fileName")(myIndex).Attribute("imageFileName").Value
      storage.DeleteFile(delImage)
 
      Dim delElement = delXml.Descendants("fileName")(myIndex)
      delElement.Remove()
      Using stream As IsolatedStorageFileStream = New IsolatedStorageFileStream(filePath, FileMode.Create, FileAccess.Write, storage)
        delXml.Save(stream)
      End Using
      MessageBox.Show("削除しました。")
 
      Dim allDeleteQuery = From c In delXml.Descendants("fileName") Select c
 
      If allDeleteQuery.Count <= 0 Then
        storage.DeleteFile(filePath)
        NavigationService.Navigate(New Uri(String.Format("/MainPage.xaml?date={0}", DateTime.Now.ToShortDateString & DateTime.Now.ToLongTimeString), UriKind.Relative))
        Exit Sub
      End If
      NavigationService.Navigate(New Uri(String.Format("/DeletePage.xaml?date={0}", DateTime.Now.ToShortDateString & DateTime.Now.ToLongTimeString), UriKind.Relative))
    Else
      Exit Sub
    End If
  End Sub
End Class
  • タッチパネルでドラッグ&ドロップを使うサンプル

四国のSOHO。薬師寺国安(VBプログラマ)と、薬師寺聖(デザイナ、エンジニア)によるコラボレーション・ユニット。1997年6月、Dynamic HTMLとDirectAnimationの普及を目的として結成。共同開発やユニット名義での執筆活動を行う。XMLおよび.NETに関する著書や連載多数。最新刊は「Silverlight実践プログラミング」両名とも、Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。http://www.PROJECTKySS.NET/

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

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

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