写真をハート型に切り抜いて撮影するサンプル

2012年6月4日(月)
PROJECT KySS

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

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

リスト4 (DataIchiranPage.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と、文字列型のrecordDateプロパティを定義しておきます。

Public Class ImageInfo
  Property imageFileName As WriteableBitmap
  Property recordDate As String
End Class

Partial Public Class DataIchiranPage
  Inherits PhoneApplicationPage

  Public Sub New()
    InitializeComponent()
  End Sub

新しいMenuItemクラスのインスタンスmyMenuItem1~myMenuItem2をメンバ変数として宣言します。MenuItemクラスは、ContextMenu 内に表示される個別の項目を表すクラスです。

  Dim myMenuItem1 As New MenuItem
  Dim myMenuItem2 As New MenuItem

ContextMenuクラスの新しいインスタンスmyContextMenuオブジェクトをメンバ変数として宣言します。ContextMenuコントロールは、コントロールのコンテキストに固有の機能を公開するポップアップメニューを表示するコントロールです。このコントロールは、Silverlight for Windows Phone Toolkit - Nov 2011.msiに含まれていますので、下記URLよりダウンロードしてインストールしてください。
→参照:Windows Phone Toolkit - Nov 2011 (7.1 SDK)

  Dim myContextMenu As New ContextMenu
 
  Dim imageName As String
  
  Dim doc As XElement
  Dim myIndex As Integer

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

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

IsolatedStorageFileクラスのOpenFileメソッドで、ImageInFrameフォルダ内のImageList.xmlファイルを、指定したファイルアクセスを使用して指定したモードで開き、StreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り、変数readXmldocに格納しておきます。読み込んだXMLテキストをParseメソッドでXElementとして読み込みます。

ImageInfoクラスの新しいリストであるmyImageInfoを作成します。

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

Path.Combineメソッドで、ImageInFrameフォルダと要素の内容を連結して、変数imageFilePathに格納しておきます。

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

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

ImageInfoクラスのimageFileNameプロパティに、読み込んだWriteableBitmapオブジェクトのimageSourceオブジェクトを指定し、recordDateプロパティに要素の属性”記録日”の値を指定して、AddメソッドでmyImageInfoオブジェクトに追加していきます。ListBox1のItemsSourceプロパティにmyImageInfoオブジェクトを指定します。

これで、撮った画像と記録日の一覧が表示されます。

  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
      Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
      Dim filePath As String = Path.Combine("ImageInFrame", "ImageList.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
          doc = XElement.Parse(readXmldoc)
          Dim myImageInfo As New List(Of ImageInfo)
 
          For Each result In From c In doc.Descendants("風景") Select c
            Dim imageFilePath As String = Path.Combine("ImageInFrame", result.Element("画像名").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)
              With myImageInfo
                .Add(New ImageInfo With {.imageFileName = imageSource,
                .recordDate = result.Attribute("記録日").Value})
              End With
              'stream.Close()
            End Using
          Next
          ListBox1.ItemsSource = myImageInfo
        End Using
      End Using
    MyBase.OnNavigatedTo(e)
  End Sub

リストボックスから任意の画像が選択された時の処理

ListBoxより、選択されたインデックスをメンバ変数myIndexに格納しておきます。

myIndexに該当する要素の子要素の値を取得して、メンバ変数imageNameに格納しておきます。MenuItem型の新しいリストであるmenuItemListを作成します。

Header、FontSize、FontWeightプロパティを設定し、memuItemListオブジェクトに各プロパティの設定されたmenuItem1とmenuItem2を追加します。ContextMenuのItemsSourceにmenuItemListオブジェクトを指定し、透明度を表すOpacityに0.8を指定して、少し透明化させます。ContextMenuService.SetContextMenu メソッドでListBoxオブジェクトにmyContextMenuオブジェクトの値を設定します。IsOpenメソッドでコンテキストメニューを開きます。

AddHandlerステートメントでmyMenuItem1とmyMenuItem2のClickイベントにmyMenuItem_Clickのイベントハンドラを指定します。その前に、一度RemoveHandlerステートメントで削除のイベントである、myMenuItem1のClickイベントのイベントハンドラを削除しておきます。この処理を追加していないと、削除確認のメッセージが重複して表示される場合があります。

  Private Sub ListBox1_SelectionChanged(sender As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles ListBox1.SelectionChanged
    Try
      myIndex = ListBox1.SelectedIndex
      imageName = doc.Descendants("風景")(myIndex).Element("画像名").Value
 
      Dim menuItemList As New List(Of MenuItem)
 
      myMenuItem1.Header = "データの削除"
      myMenuItem1.FontSize = 25
      myMenuItem1.FontWeight = FontWeights.Bold
 
      myMenuItem2.Header = "キャンセル"
      myMenuItem2.Foreground = New SolidColorBrush(Colors.Red)
      myMenuItem2.FontSize = 25
      myMenuItem2.FontWeight = FontWeights.Bold
 
      menuItemList.Add(myMenuItem1)
      menuItemList.Add(myMenuItem2)
 
      myContextMenu.ItemsSource = menuItemList
      myContextMenu.Opacity = 0.8
 
      ContextMenuService.SetContextMenu(ListBox1, myContextMenu)
      myContextMenu.UpdateLayout()
      myContextMenu.IsOpen = True
 
      RemoveHandler myMenuItem1.Click, AddressOf myMenuItem_Click
      AddHandler myMenuItem1.Click, AddressOf myMenuItem_Click
      AddHandler myMenuItem2.Click, AddressOf myMenuItem_Click
    Catch
      Exit Sub
    End Try
  End Sub

ContextMenuに表示されたメニューがタップされた時の処理

senderオブジェクトからMenuItemのHeaderプロパティの情報を取得して、変数selectHeaderに格納しておきます。selectHeaderの値で条件分岐を行います。

値が「データの削除」の場合は、削除確認メッセージを表示し、[ok]の場合は、DeleteDataプロシージャを実行します。[キャンセル]の場合は、ContexMenuを閉じ、ListBoxの選択を解除します。

  Private Sub myMenuItem_Click(sender As Object, e As EventArgs)
    Try
      Dim selectHeader = DirectCast(sender, MenuItem).Header
      Select Case selectHeader.ToString
        Case "データの削除"
          Dim kakunin = MessageBox.Show("このデータを削除しますか?", "削除確認", MessageBoxButton.OKCancel)
 
          Select Case kakunin
            Case MessageBoxResult.OK
              DeleteData()
              Exit Select
            Case Else
              ListBox1.SelectedIndex = -1
              Exit Sub
          End Select
        Case "キャンセル"
          myContextMenu.IsOpen = False
          ListBox1.SelectedIndex = -1
          Exit Select
        Case Else
          ListBox1.SelectedIndex = -1
          Exit Select
      End Select
    Catch
      Exit Sub
    End Try
 
  End Sub

データを削除する処理

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。分離ストレージ内のImageInFrameフォルダとImageList.xmlというファイルをPath.Combineメソッドで連結し、変数filePathに格納しておきます。ImageInFrameフォルダ内にImageList.xmlファイルが存在していた場合は、分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数myStreamを用意し、IsolatedStorageFile.OpenFileメソッドでfilePathに格納しているファイルを、指定したモード、指定したファイルアクセスモードで開きます。開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り変数readXmldocに格納しておきます。

読み込んだXMLテキストを、ParseメソッドでXElementとして読み込みます。myStreamオブジェクトを閉じます。

読み込んだXMLから、メンバ変数myIndexに該当する要素の子要素の値を取得し、変数delImageに格納しておきます。分離ストレージ内のImageInFrameフォルダにある、delImageに格納されている画像ファイル名をPath.Combineメソッドで連結して、変数delJPGfileに格納します。DeleteFileメソッドで、このファイルを削除します。

メンバ変数myIndexに該当する要素を選択します。Removeメソッドで、この要素とその子要素を全て削除します。

分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数streamを用意し、filePath変数と、ファイルモード(作成モード)、ファイルアクセス(書き込み)で初期化された、新しいIsolatedStorageFileStreamを作成します。Saveメソッドで要素とその子要素の削除されたXMLを保存します。削除した旨を表示します。

要素を選択するクエリを定義します。要素が存在しない (全てのデータが削除された)場合は、ImageList.xml自体を削除し、MainPage.xamlに遷移します。まだImageList.xmlが存在している場合は、DataIchiranPage.xamlに遷移し、削除されたListBoxを再描画します。

  Sub DeleteData()
      Dim delXml As XElement
      Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
      Dim filePath As String = Path.Combine("ImageInFrame", "ImageList.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("風景")(myIndex).Element("画像名").Value
      Dim delJPGfile As String = Path.Combine("ImageInFrame", delImage)
      If delImage <> String.Empty Then
        storage.DeleteFile(delJPGfile)
      End If
 
      Dim delElement = delXml.Descendants("風景")(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("風景") Select c
    
      If allDeleteQuery.Count <= 0 Then
        storage.DeleteFile(filePath)
        storage.Dispose()
        NavigationService.Navigate(New Uri(String.Format("/MainPage.xaml?date={0}", DateTime.Now.ToShortDateString & DateTime.Now.ToLongTimeString), UriKind.Relative))
        Exit Sub
      End If
      storage.Dispose()
      NavigationService.Navigate(New Uri(String.Format("/DataIchiranPage.xaml?date={0}", DateTime.Now.ToShortDateString & DateTime.Now.ToLongTimeString), UriKind.Relative))
 
    Else
      storage.Dispose()
      Exit Sub
    End If
  End Sub

Backボタン(←)がタップされた時の処理

NavigationService.BackStack.Countで画面遷移の履歴スタック数を取得し、変数myStacCountに格納しておきます。

myStacCount -2と記述し、NavigationService.RemoveBackEntry()で、MainPageのみを残して遷移スタックを削除します。myStacCount -1とすると、アプリケーションが終了してしまうので注意してください。

  Protected Overrides Sub OnBackKeyPress(e As System.ComponentModel.CancelEventArgs)
    Dim myStacCount = NavigationService.BackStack.Count
    For i As Integer = 0 To myStacCount - 2
      NavigationService.RemoveBackEntry()
    Next
    MyBase.OnBackKeyPress(e)
  End Sub
End Class

今回のサンプルは以上で終了です。

【参照リンク】

PROJECT KySSでは現在、16個のWindows PhoneアプリをMarketplaceに公開しています。試用版もありますので、興味のある方はお試しください。
→参照:Windows Phone App Information(PROJECT KySS)

  • フレームで切り抜いて撮影するサンプル

四国の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メルマガ会員のサービス内容を見る

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