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

2012年5月24日(木)
PROJECT KySS

本連載では、「Marketplaceのアプリから解説するWindows Phoneの実践サンプルコード集」と称して、筆者が実際にMarketplaceで公開しているアプリの中から有効な機能を厳選して、コードを含め解説していきます。

実際のアプリ制作にあたっては、連載記事「Windows Phoneアプリ制作からマーケットプレイス公開までを完全ガイド!」も参照してください。アプリの申請からマーケットプレイスでの公開まで、ひと通りの流れを解説しています。

ちなみに筆者は、上記連載を執筆したパートナーの分も含めると、今のところ全部で16個のWindows Phoneアプリを公開しています。最近公開した「音声通知翻訳」もおかげさまで1000ダウンロードを突破しましたが、こういった経験から、皆さんがアプリを開発する際の手助けになる記事をお届け出来れば幸いです。試用版もありますので、興味のある方はお試しください。
→参照:Windows Phone App Information(PROJECT KySS)

さて、第1回となる今回は、Windows Phoneの画面をドラッグ&ドロップすることで、画像を保存したり、削除することができるサンプルを紹介します。この機能は、Marketplaceに登録しているアプリには使用されていないのですが、Windows Phoneアプリでアニメーションを使用する際の基本的な操作が含まれているため、習得することをおすすめします。

コードを含むため長くなりますが、ぜひ最後までお付き合いください。

Windows Phone SDK 7.1.1

Windows Phone SDKが3月にマイナーバージョンアップしました。下記のURLよりダウンロードしてインストールしてください。
→参照:Windows Phone SDK 7.1.1 更新プログラム

今回の更新プログラムで、256MB版のWindows Phoneデバイスに対応したアプリケーションの開発とテストができるようになりました。エミュレーターについては、256MBデバイスと512MBデバイスが用意され、選択することができるようになっています。

まずは、今回のサンプルで実装する機能の動作について、下記に解説しておきましょう。

プログラムを実機にデプロイすると、画面にカバンが表示されています。その下に並んでいる、「カメラ」「フォルダ」「ごみ箱」のアイコンから「カメラ」アイコンをタップして、適当なものを撮影します。

撮影した画像がカバンの左隅上に表示されますので、この画像をカバンの上にドラッグすると、画像が保存された旨のメッセージが表示されます(図1)。

次に「フォルダ」アイコンをクリックすると、保存した画像の一覧を見ることができます。

最後に「ごみ箱」をタップすると、カバンの上方に保存されている画像の一覧が表示されますので、削除したい画像をタップします。するとカバンの中から画像が飛び出し、本当に削除するか確認のメッセージが表示されます。ここで[OK]をタップすると画像が削除されます(図2)。

(*)今回のサンプルでは、エミュレーターでも操作の確認はできますが、写真は撮れません。基本的に実機での動作を基本として解説していきます。

 図1:写真を撮って保存した(クリックで拡大)
 図2:写真の一覧を表示し「ごみ箱」アイコンをタップして任意の画像を削除した(クリックで拡大)

サンプル一式は、会員限定特典としてダウンロードできます。記事末尾をご確認ください。

今回使用したカバンの画像は「Windows Phone Apps Arts Gallery」の画像を使用させていただきました。URLは下記です。
→参照:Windows Phone Apps Art Gallery(msdn/WindowsPhone)
※「ご利用上の注意事項」をよく読んでお使いください。

実機(IS12T)で動かした動画はこちらです。

プロジェクトの作成

筆者の環境は、Windows 7 Professional 32ビット+ SP1、Visual Studio 2010 Ultimate + SP1(以下VS2010) 、Windsows Phone SDK 7.1.1(以下Windsows Phone SDK 7.1)です。実機でのテストにはWindows Phone IS12Tを使用しています。

はじめに、VS 2010のメニューから[ファイル(F)/新規作成(N)/プロジェクト(P)]と選択します。次に、「Windows Phone アプリケーション」を選択して、「名前(N)」に任意のプロジェクト名を指定します。ここでは「WP71_DragDropSave」という名前を付けています。Windows Phoneのバージョンは7.1を選択します。

ソリューションエクスプローラー内にImageというフォルダを作成し、「Windows Phone Apps Arts Gallery」からダウンロードしたカバンの画像を追加しています。また、C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Icons\darkフォルダ内にある、「カメラ」「フォルダ」「ごみ箱」の画像をも追加しておきます。

この3つの画像はそれぞれ、プロパティから「ビルドアクション」に「コンテンツ」と指定する必要があります。デフォルトではResourceになっていますが、このままだと実行した際に画像が表示されないため、注意してください。カバンの画像はResourceのままで構いません。サンプルファイルには画像は追加済です。

また、XML to LINQを使用するため、VS2010メニューの「プロジェクト(P)/参照の追加(R)」から、System.Xml.Linqを追加しておいてください。Visual StudioからExpression BlendのBehaviorを使うため、Microsoft.Expression.InteractionsとSystem.Windows.Interactivityも追加しておきます。

もし、「参照の追加」で.NETタブ内にMicrosoft.Expression.InteractionsやSystem.Windows.Interactivityが見当たらない場合は、「参照」タブから、C:\Program Files\Microsoft SDKs\Expression\Blend\Windows Phone\v7.1\Librariesフォルダ内にある、Microsoft.Expression.Interactions.dllとSystem.Windows.Interactivit.dllを参照に追加してから使用します(図3)。

 図3:Microsoft.Expression.Interactions.dllとSystem.Windows.Interactivit.dllを参照に追加する(クリックで拡大)

MainPage.xamlの編集とコントロールの追加

x:NameがPageTitleというTextBlockのTextプロパティに「ドラッグドロップによる保存」と指定します。

名前がPageTitleの、TextBlockのStyleプロパティに指定されているPhoneTextTitle1Styleでは、文字サイズが大きすぎるので、PhoneTextTitle2Styleを指定すると、文字サイズが小さくなります。これらのスタイルは下記URLの「Theme Resources for Windows Phone」で定義されています。

{StaticResource}を使用して、これらのテーマリソースを参照します。
→参照:Theme Resources for Windows Phone

Targetというx:Nameの 要素を記述し、その中にツールボックスからImageコントロールを1個配置します。SourceプロパティにはカバンのPNG画像を指定します。

コメントアウトされている、のコメントを外し、3つのを配置します。

IconUriとText、Clickイベントをリスト1のように指定します。レイアウト図は図4のようになります。のIconUriに指定した画像はデザイン画面では表示されません。実行時に表示されます。

 図4:コントロールを配置した(クリックで拡大)

書き出されるXAMLコード。

リスト1 書き出されたXAMLコード(MainPage.xaml)

Windows Phone SDK 7.1 日本語版では、フォントの設定なしにアプリケーションを実行し、タイトルやテキストに日本語を使った場合、日本語フォントが使われず、中華フォントの表示になってしまうことがあります。これを解消するには、書き出されるXAMLコードのアプリケーションのトップである要素内に、 Language="ja-JP" と指定する方法もありますが、それでは全てのページに記述する必要があるため、マルチランゲージに対応したやり方として、App.xaml.vbのSub New()の中に

RootFrame.Language = System.Windows.Markup.XmlLanguage.GetLanguage(System.Globalization.CultureInfo.CurrentUICulture.Name)

と記述する方法を用います。これだと、各国のUIフォントに対応できます。今回のサンプルでは、全てこの方法を使用します。

(1)名前がPageTitleのTextBlockのStyleプロパティに指定されているPhoneTextTitle1Styleでは、文字サイズが大きすぎるので、PhoneTextTitle2Styleを指定します。

(2) 要素内に要素を配置し、Sourceプロパティにカバンの画像を指定しています。

(3)3つのを配置し、IconUriにImageフォルダの画像を、Textプロパティに、アイコンの名称を、Clickイベントに、このアイコンがタップされた時に実行されるイベントハンドラを指定します。「画像一覧」と「削除」のボタンは初期状態ではIsEnabledプロパティのチェックを外し(XAML上ではFalseと指定される)使用不可としておきます。

<phone:PhoneApplicationPage 
  x:Class="WP71_DragDropSave.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
  xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="696"
  FontFamily="{StaticResource PhoneFontFamilyNormal}"
  FontSize="{StaticResource PhoneFontSizeNormal}"
  Foreground="{StaticResource PhoneForegroundBrush}"
  SupportedOrientations="Portrait" Orientation="Portrait"
  shell:SystemTray.IsVisible="True">
  
  <!--LayoutRoot は、全てのページ コンテンツが配置されるルート グリッドです-->
  <Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
 
    <!--TitlePanel は、アプリケーション名とページ タイトルを格納します-->
    <StackPanel x:Name="TitlePanel" Margin="12,17,0,314" Height="69">
      <TextBlock x:Name="ApplicationTitle" Text="マイ アプリケーション" Style="{StaticResource PhoneTextNormalStyle}"/>
      <TextBlock x:Name="PageTitle" Text="ドラッグドロップによる保存" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle2Style}"/> ■(1)
    </StackPanel>

    <Canvas x:Name="ContentPanel" Margin="0,123,0,0" HorizontalAlignment="Left" VerticalAlignment="Top"/> 
    <Canvas x:Name="Target" Canvas.Left="47" Canvas.Top="297" Margin="106,0,54,0" Grid.Row="1">
      <Image Height="300" Name="Image1" Stretch="Fill" Width="323" Source="/WP71_DragDropSave;component/Image/IW007.png"/> ■(2)
    </Canvas>
  </Grid>
  
  <!--ApplicationBar の使用法を示すサンプル コード-->
  <phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
      <shell:ApplicationBarIconButton IconUri="/Image/camera.png" Text="カメラ起動" Click="GoCamera"/> ■(3)
      <shell:ApplicationBarIconButton IconUri="/Image/folder.png" Text="画像一覧" Click="GoIchiran" IsEnabled="False"/>  ■(3)
      <shell:ApplicationBarIconButton IconUri="/Image/delete.png" Text="削除" Click="GoDelete" IsEnabled="False"/>  ■(3)
    </shell:ApplicationBar>
  </phone:PhoneApplicationPage.ApplicationBar>
</phone:PhoneApplicationPage>

次に、ソリューションエクスプローラー内のMainPage.xamlを選択し、マウスの右クリックで表示される「Expression Blendを開く(X)」からExpression Blendを起動して、カバンの上に画像がドロップされた時のアニメーションを作成します。

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

Expression Blend内のアートボード上に配置されたImage1、または左に表示されている「オブジェクトとタイムライン(B)」内のImage1を選択し、「オブジェクトとタイムライン(B)」にある[+]の「新規作成」アイコンをクリックして、新しいストーリーボードを作成します。「名前(キー)」はStoryboard1としておきます(図5)。

 図5:新しいストーリーボードを作成する(クリックで拡大)

アートボード上が赤い枠線で囲まれ、タイムラインの記録が開始されました。この状態で、黄色の再生ヘッドを0.3まで移動します。

カバンを囲んでいる矩形の端にあるポインタをつかみ、幅(Width)を少し狭くします。次に、再生ヘッドを0.6の位置に移動して、幅を元に戻します。続けて、再生ヘッドを0.9の位置に移動して、今度は高さ(Height)を少し大きくします。

最後に、再生ヘッドを1.2の位置に移動し、高さを元に戻します。ここまでで、Image1のタイムライン上に楕円のマークがいくつか記録されたのがわかると思います(図6)。

 図6:再生ヘッドを移動しながら、幅(Width)と高さ(Height)の値を変化させる。タイムライン上には楕円のマークが記録される(クリックで拡大)

アートボード上の左上隅に表示されている、「●Storyboard1タイムライン記録オン」の●をクリックして、記録をオフにします。これで画像をドラッグした時のアニメーションが完成しました。

タイムラインのコントロールパネルの「先頭のフレームに移動」をクリックして「再生」し、動作を確認してください。カバンのアニメーションが確認できると思います。

アニメーションができたので、一度Expression Blendを終了し、VS2010に戻ります。XAMLコードにはタイムラインのコードが追加されます(コードの掲載は省略します)。

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

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

リスト2 (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

このサンプルでは、アタッチされた要素を、マウスのドラッグのジェスチャに応答して、要素の上に再配置するMouseDragElementBehaviorクラスを使用するため、このクラスの含まれる、Microsoft.Expression.Interactivity.Layout名前空間をインポートします。

Imports Microsoft.Expression.Interactivity.Layout

Partial Public Class MainPage
  Inherits PhoneApplicationPage
 
  ' コンストラクター
  Public Sub New()
    InitializeComponent()
  End Sub

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

  Dim myCameraTask As CameraCaptureTask

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

  Dim imageByte As Byte()

behaviorを、MouseDragElementBehaviorクラス用メンバ変数として宣言します。MouseDragElementBehaviorクラスは、アタッチされた要素を、マウスのドラッグのジェスチャに応答して要素の上に再配置するクラスです。

  Dim behavior As MouseDragElementBehavior

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

変数xmlStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。

Path.Combineメソッドで、2つの文字列を1つのパスに結合します。ここでは、PictureDataフォルダとimageFileList.xmlを結合しているため、PictureData\imageFileList.xmlとなります。この値を、xmlFilePath変数に格納します。FileExistsメソッドで、xmlFilePathに格納したファイルが存在する場合は、「画像一覧」用と「削除」用のApplicationBarIconButtonを使用可能とします。そうでない場合は、使用不可とします。ApplicationBar.Buttonsのインデックスは0から始まります。

新しいCameraCaptureTaskクラスのインスタンス、myCameraTaskオブジェクトを生成します。

AddHandlerステートメントで、チューザータスクが完了した時に発生するCompletedイベントに、イベントハンドラを追加します。イベントハンドラ内では以下の処理が実行されます。

カメラアプリケーションが起動して、写真がきちんと取れた場合は、CompositeTransformクラスの新しいインスタンスmyCompositeを作成します。CompositeTransformクラスは、1 つのオブジェクトに複数の異なる変換を適用することができるクラスです。

写真のデータを含むストリームのバイトの長さを取得し、バイト配列を作成します。Readメソッドで、現在のストリームからバイトシーケンスを読み取り、読み取ったバイト数でストリーム内の位置を進めます。

ChosenPhoto.Readの書式は以下の通りです。

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

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

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

SeekOriginにはBeginを指定し、ストリームの先頭を指定しています。

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

Compsiteオブジェクトの、回転を表すRotationに90を指定します。

CenterXとCenterYプロパティに、撮った画像の表示されるImageコントロールの、WidthとHeightの約半分のサイズを指定します。CenterXプロパティでは、CompositeTransform で指定された全ての変換の中心点となる x 座標を、CenterYプロパティでは、CompositeTransform で指定された全ての変換の中心点となるy 座標を設定します。カメラを縦向きで撮った画像も、デフォルトでは横向きに表示されるため、90度回転して縦向きに表示されるようにしています。

myImageのRenderTransformプロパティにmyCompsiteオブジェクトを指定して、Image領域を90度回転します。

ContentPanelという名前を持つCanvasに、AddメソッドでmyImageオブジェクトを追加します。

Imageの新しいインスタンスmyImageオブジェクトを作成します。Width、Heightプロパティを指定し、Sourceプロパティには先に取得しておいたimageSourceの値を指定します。

MouseDragElementBehavior のAttachメソッドで指定されたオブジェクトにアタッチします。この場合は作成されたmyImageオブジェクトにアタッチすることになるため、配置された画像はどの場所にでもドラッグが可能になります。

AddHandlerステートメントで、myImageオブジェクトから指が離れた時のイベントハンドラを指定します。

  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
    Dim xmlStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim xmlFilePath As String = Path.Combine("PictureData", "imageFileList.xml")
 
    If xmlStorage.FileExists(xmlFilePath) = True Then
      TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = True
      TryCast(ApplicationBar.Buttons(2), ApplicationBarIconButton).IsEnabled = True
    Else
      TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = False
      TryCast(ApplicationBar.Buttons(2), ApplicationBarIconButton).IsEnabled = False
    End If
 
    myCameraTask = New CameraCaptureTask
 
    AddHandler myCameraTask.Completed, Sub(resultSender As Object, resultArgs As PhotoResult)
  
          ContentPanel.Children.Clear()
          If resultArgs.TaskResult = TaskResult.OK Then
            Dim myCompsite As New CompositeTransform
            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, 480, 640)
            With myCompsite
              .Rotation = 90
              .CenterX = 100
              .CenterY = 90
            End With
  
            Dim myImage As New Image
            With myImage
              .Width = 200
              .Height = 180
              .Source = imageSource
              .RenderTransform = myCompsite
            End With
              ContentPanel.Children.Add(myImage)
              behavior = New MouseDragElementBehavior
              behavior.Attach(myImage)

            AddHandler myImage.MouseLeftButtonUp, AddressOf myImageDropped
            End If
          End Sub
    MyBase.OnNavigatedTo(e)
  End Sub
  • タッチパネルでドラッグ&ドロップを使うサンプル

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

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