カメラで写した写真を分離ストレージに保存し、写真の一覧を表示する

2011年11月22日(火)
PROJECT KySS

ImageコントロールをButtonコントロールに変換する

まずカメラの画像の表示されているImage1を選択し、マウスの右クリックで表示されるメニューから、「コントロールの作成」を選択します(図4)。

図4:Image1を選択し、マウスの右クリックで表示されるメニューから「コントロールの作成」を選択(クリックで拡大)

「コントロールの作成」画面が表示されます。「コントロールの種類」からButtonを選択し、「定義先」の「アプリケーション」にチェックを付け[OK]ボタンをクリックします(図5)。「名前(キー)」はデフォルトのままにしておきます。

図5:「コントロールの種類」にButtonを選択し、「定義先」の「アプリケーション」をチェックする(クリックで拡大)

中央にButtonと表示されたカメラの画像が表示されます。Buttonの文字を選択し、[Delete]キーで削除します。アートボードの上部に表示されている[Button]タブをクリックします。コントロールを配置した画面が表示されます。カメラの画像を選択して、プロパティを見ると「種類」がButtonになっています。「名前」にcameraButtonと指定します(図6)。

図6:ImageがButtonに変換されている。Buttonの名前にcameraButtonと指定する(クリックで拡大)

同様にImage2のフロッピーの画像もButtonに変換します。Buttonの名前はsaveButtonとします。

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

ImageからButtonに変換した箇所がリスト2のように書き出されています。

リスト2 ImageからButtonに変換されたXAMLコード(MainPage.xamlの一部)

(1)cameraButtonとsaveButtonにStyle属性が持たされ、ButtonStyle1とButtonStyle2を参照しています。ButtonStyle1とButtonStyle2はApp.xaml内で定義されています。saveButtonは最初の状態では、使用不可とするため、IsEnabledのチェックを外しておきます。写真が撮影された時点でsaveButton(フロッピーのアイコン)の使用を可能とします。
(2)ichiranButton(データ一覧)も最初の状態では使用不可としておきます。
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <Image Height="356" HorizontalAlignment="Left" Margin="40,71,0,0" x:Name="Image" Stretch="Fill" VerticalAlignment="Top" Width="398" />
    <TextBox Height="91" HorizontalAlignment="Left" Margin="-6,485,0,0" x:Name="commentTextBox" VerticalAlignment="Top" Width="456" TextWrapping="Wrap" AcceptsReturn="True" />
    <Button Content="データ一覧" Height="72" HorizontalAlignment="Left" Margin="6,624,0,0" Name="ichiranButton" VerticalAlignment="Top" Width="444" IsEnabled="False" /> ■(2)
    <TextBlock Height="31" HorizontalAlignment="Left" Margin="12,463,0,0" Name="TextBlock1" Text="コメント" VerticalAlignment="Top" Width="131" />
    <TextBlock Height="33" HorizontalAlignment="Left" Margin="140,18,0,0" Name="TextBlock2" Text="カメラは縦向きのみの対応です" VerticalAlignment="Top" Width="298" FontWeight="Bold" Foreground="Crimson" />
    <Button x:Name="cameraButton" Content="Button" HorizontalAlignment="Left" Height="45" Margin="40,6,0,0" Style="{StaticResource ButtonStyle1}" VerticalAlignment="Top" Width="61"/> ■(1)
    <Button x:Name="saveButton" Content="Button" HorizontalAlignment="Left" Height="46" Margin="190,572,0,0" Style="{StaticResource ButtonStyle2}" VerticalAlignment="Top" Width="50" IsEnabled="False" /> ■(1)
  </Grid>

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

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

リスト3 (MainPage.xaml.vb)

Option Strict On

ランチャーやチューザーに関するクラスの含まれる、Microsoft.Phone.Tasks名前空間をインポートします。
Imports Microsoft.Phone.Tasks

Imports System.Windows.Media.Imaging
Imports Microsoft.Phone

仮想ファイルシステムを作成および使用するための型が含まれている、System.IO.IsolatedStorage名前空間をインポートします。分離ストレージによって、安全なクライアント側のストレージが提供されます。
Imports System.IO.IsolatedStorage

Imports System.IO
Imports System.Xml.Linq
Partial Public Class MainPage
  Inherits PhoneApplicationPage

  ' コンストラクター
  Public Sub New()
    InitializeComponent()
  End Sub

カメラアプリケーションを起動するCameraCaptureTaskクラス用メンバ変数、myCameraTaskを宣言します。
  Dim myCameraTask As CameraCaptureTask

バイト型の配列、imageByteをメンバ変数として宣言します。
  Dim imageByte As Byte()

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

新しいCameraCaptureTaskクラスのインスタンス、myCameraTaskオブジェクトを生成します。
AddHandlerステートメントで、チューザータスクが完了した時に発生するCompletedイベントに、イベントハンドラを追加します。イベントハンドラ内では以下の処理が実行されます。
カメラアプリケーションが起動して、写真がきちんと撮れた場合は、CompositeTransformクラスの新しいインスタンスmyCompositeを生成します。CompositeTransformクラスは、1 つのオブジェクトに複数の異なる変換を適用することができるクラスです。CenterXとCenterYプロパティに、撮った画像が表示されるImageコントロールのWidthとHeightの約半分のサイズを指定します。CenterXプロパティでは、CompositeTransform で指定された、全ての変換の中心点の x 座標を設定します。CenterYプロパティでは、CompositeTransform で指定された、全ての変換の中心点の y 座標を設定します。回転を表すRotationに90を指定します。カメラを縦向きで撮った画像もデフォルトでは横向きに表示されるため、90度回転して縦向きに表示されるようにしています。
写真のデータを含む、ストリームのバイトの長さを取得し、バイト配列を作成します。Readメソッドで、現在のストリームからバイトシーケンスを読み取り、読み取ったバイト数でストリーム内の位置を進めます。
ChosenPhoto.Readの書式は以下の通りです。

ChosenPhoto.Read(バイト配列,0ベースのバイトオフセット(現在のストリームから読み取ったデータの格納を開始します),現在のストリームから読み取るバイトの最大数)

Seekメソッドで、現在のストリーム内の位置を設定します。書式は以下の通りです。

ChosePhoto.Seek(基点のパラメーターのバイト オフセット, 参照ポイントを示すSeekOrigin型の値) 

SeekOriginにはBeginを指定し、ストリームの先頭を指定しています。
WriteableBitmap型の変数imageSourceを宣言し、PictureDecoder.DecodeJpeg(resultArgs.ChosenPhoto)で、撮った写真をJPEGファイルとしてWriteableBitmapオブジェクトにデコードします。PictureDecoder.DecodeJpegメソッドはMicrosof.Phone名前空間に属しています。WriteableBitmapクラスは書き込み更新することのできるBitmapSourceを提供するクラスです。
ImageのRenderTransformプロパティにmyCompsiteオブジェクトを指定して、Image領域を90度回転し、Sourceプロパティに、デコードされたJPEGファイルを格納しているimageSourceオブジェクトを指定します。これでImage内に撮った写真が表示されます。
カメラを起動している状態で撮影を中止した場合は、saveButton(フロッピーのアイコン)は使用不可のままで処理を抜けます。
変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.Combineメソッドで、2つの文字列を1つのパスに結合します。ここでは、myPicturesフォルダとimageFileList.xmlを結合しています。myPictures\imageFileList.xmlとなります。この値をfilePath変数に格納します。FileExistsメソッドでfilePathに格納したファイルが存在する場合は、ichiranButton(データ一覧)の使用を可能にします。そうでない場合は、不可のままです。

  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
    myCameraTask = New CameraCaptureTask
    AddHandler myCameraTask.Completed, Sub(resultSender As Object, resultArgs As PhotoResult)
                       If resultArgs.TaskResult = TaskResult.OK Then
                            Dim myComposite As New CompositeTransform
                            myComposite.CenterX = 189
                            myComposite.CenterY = 173
                            myComposite.Rotation = 90
                            imageByte = New Byte(CInt(resultArgs.ChosenPhoto.Length)) {}
                            resultArgs.ChosenPhoto.Read(imageByte, 0, imageByte.Length)
                            resultArgs.ChosenPhoto.Seek(0, IO.SeekOrigin.Begin)
                             Dim imageSource As WriteableBitmap = PictureDecoder.DecodeJpeg(resultArgs.ChosenPhoto)
                             Image.RenderTransform = myComposite
                             Image.Source = imageSource
                          Else
                             saveButton.IsEnabled = False
                             Exit Sub
                          End If 
      End Sub
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim xmlFilePath As String = Path.Combine("myPictures", "imageFileList.xml")
    If storage.FileExists(xmlFilePath) = True Then
      ichiranButton.IsEnabled = True
    Else
      ichiranButton.IsEnabled = False
    End If
    MyBase.OnNavigatedTo(e)
  End Sub

カメラアイコンがタップされた時の処理

Showメソッドでカメラアプリケーションを起動します。saveButton(フロッピーのアイコン)の使用を可能にします。
  Private Sub cameraButton_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles cameraButton.Click
    myCameraTask.Show()
    saveButton.IsEnabled = True
  End Sub

フロッピーのアイコン(保存)がタップされた時の処理

保存する画像のファイル名を作成します。現在の「年月日時間分秒.jpg」のファイル名とし、imageFileNameに格納しておきます。
変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。DirectryExistsメソッドでmyPicturesというフォルダが存在しているかどうかをチェックし、存在していない場合は、CreateDirectoryメソッドでmyPicturesというフォルダを作成します。
Path.CombineでmyPicturesというフォルダとimageFileNameに格納されている画像名とを連結します。
分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数myStream変数を用意し、IsolatedStorageFile.CreateFileメソッドで、分離ストレージ内にfilePath変数の持っているフォルダ名付き画像ファイル名を作成します。次に、IsolatedStorageFileStream.Writeメソッドで、バイト配列から読み取ったデータを使用して、IsolatedStorageFileStreamオブジェクトにバイトのブロックを書き込みます。
IsolatedStorageFileStream.Writeメソッドの書式は下記の通りです。

IsolatedStorageFileStream.Write(書き込むバッファ, 開始位置を示すバッファ内のバイト オフセット,書き込む最大バイト数)

変数xmlStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.Combineで、myPicturesというフォルダ名と、imageFileList.xmlという画像ファイル名を記録したXMLファイルを連結し、xmlFilePath変数に格納しておきます。
myPicturesというフォルダ内にimageFileList.xmlが存在していない場合は、Visual Basic の埋め込み式を用いて、XML宣言とルート要素image、その子要素としてfileName、属性にimageFileNameを指定し、埋め込み式の構文である <%= expression %>を用いてimageFileName変数の値を指定します。また、fileNameの内容テキストには、commentTextBox.Textの値を指定します。これは ASP.NET で使用される構文と同じです。 分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数xmlStreamを用意し、IsolatedStorageFile.CreateFileメソッドで、分離ストレージ内にxmlFilePath変数の持っているフォルダ名付きXMLファイルを作成します。
imageFileList.xmlファイルが存在する場合は、新しいStreamWriter 生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。Writeメソッドで埋め込み式のXMLをストリームに書き込みます。保存した旨のメッセージを表示します。

次は、既にmyPicturesフォルダ内にimageFileList.xmlが存在する場合の処理です。
IsolatedStorageFileクラスのOpenFileメソッドでmyPicturesフォルダ内のimageFileList.xmlファイルを、指定したファイルアクセスを使用して指定したモードでファイルを開きます。開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り変数readXmldoc変数に格納しておきます。読み込んだXMLテキストをParseメソッドでXElementとして読み込みます。
追加するfileName要素を、埋め込み式を用いて生成し、imageFileName属性の値や、fileNameの内容テキストを指定します。新しく生成したXML要素を、読み込んだXMLにAddメソッドで追加します。IsolatedStorageFileStreamを閉じます。
imageFileList.xmlファイルが存在する場合は、新しいStreamWriter 生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。Writeメソッドで新しいXML要素が追加されたXMLを、ストリームに書き込みます。保存した旨のメッセージを表示します。
「データ一覧」ボタンの使用を可能にし、コメント欄と画像の表示されていた領域をクリアします。フロッピーアイコンの保存ボタンの使用を不可とします。
  Private Sub saveButton_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles saveButton.Click
  '画像の保存
    Dim imageFileName As String=DateTime.Now.ToString("yyyyMMddHHmmss") & ".jpg"
 
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    If storage.DirectoryExists("myPictures") = False Then
      storage.CreateDirectory("myPictures")
    End If
 
    Dim filePath As String = Path.Combine("myPictures", imageFileName)
    Using myStream As IsolatedStorageFileStream = storage.CreateFile(filePath)
      myStream.Write(imageByte, 0, imageByte.Length)
    End Using
 
    'XMLファイルの保存
    Dim xmlStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim xmlFilePath As String = Path.Combine("myPictures", "imageFileList.xml")
 
    If xmlStorage.FileExists(xmlFilePath) = False Then
      Dim xmldoc As XDocument = <?xml version="1.0" encoding="utf-8"?>
                              <image>
                                     <fileName imageFileName=<%= imageFileName %>><%= commentTextBox.Text %></fileName>
                              </image>
      Using xmlStream As IsolatedStorageFileStream = xmlStorage.CreateFile(xmlFilePath)
      End Using
 
      If xmlStorage.FileExists(xmlFilePath) = True Then
        Using xmlwriter As StreamWriter = New StreamWriter(xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write))
          xmlwriter.Flush()
          xmlwriter.Write(xmldoc.ToString)
        End Using
      MessageBox.Show("画像とXMLファイルを保存しました。")
    End If
  Else
    Dim xmlStream As IsolatedStorageFileStream = xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Read)
    Using xmlreader As StreamReader = New StreamReader(xmlStream)
 
      Dim readXmldoc As String = xmlreader.ReadToEnd
      Dim doc As XElement = XElement.Parse(readXmldoc)
      Dim addXml As XElement = <fileName imageFileName=<%= imageFileName %>><%= commentTextBox.Text %></fileName>
      doc.Add(addXml)
      xmlStream.Close()
 
      If xmlStorage.FileExists(xmlFilePath) = True Then
        Using xmlwriter As StreamWriter = New StreamWriter(storage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write))
          xmlwriter.Flush()
          xmlwriter.Write(doc.ToString)
          MessageBox.Show("画像とXMLファイルを保存しました。")
        End Using
      End If
    End Using
  End If
  ichiranButton.IsEnabled = True
  commentTextBox.Text = String.Empty
  Image.Source = Nothing
  saveButton.IsEnabled = False    End Sub

[データ一覧]ボタンがクリックされた時の処理

これから作成する、DataShowPage.xamlに遷移します。
  Private Sub ichiranButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles ichiranButton.Click
    NavigationService.Navigate(New Uri("/DataShowPage.xaml", UriKind.Relative))
  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メルマガ会員のサービス内容を見る

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