Leap Motion実用サンプル(Visual Basic編)
Leap Motionによる、WPFアプリ上に動的に作成したオブジェクトのイベント処理
Leap Motionの動くサンプルを実際に作ってみる。今回はWPFの画面上に動的に作成したオブジェクトを、Leap Motionによる操作でアニメーションさせるアプリを作成。
今回は、画面上に動的に作成したオブジェクトを、Leap Motionによる操作でアニメーションさせるWPFアプリを作成する。さっそく、その開発内容を説明していこう。
まずWPFプロジェクトを作成しよう
今回のLeap MotionアプリもWPFで作成する。
これには、Visual Studio 2012(以下、VS 2012)のIDEを起動して、メニューバーから[ファイル]-[新規作成]-[プロジェクト]と選択して、それにより表示される[新しいプロジェクト]ダイアログで「Visual Basic」のテンプレートから「WPF アプリケーション」を選択する(Leap Motionは、.NET Framework 3.5と4.0に対応している。しかし、.NET Framework 4.5でも動作する。今回のアプリは全て.NET Framework 4.5で作成している。しかし、あくまでも対応しているのは、.NET Framework 3.5と4.0だ。心配な方は、.NET Framework 4.0で作成すると安心だろう)。[名前]欄には、ここでは「SetMouseCursor_LeapMotion」と指定する。
WPFの基本的な作成手順は、第1回と同じ手順となるので、説明を割愛する。具体的な手順は、第1回の「参照の追加」「プロジェクトのルートに「LeapCSharp.dll」と「Leapd.dll」を追加する」「プロパティを設定する」を参考にしてほしい。
[ソリューション エクスプローラー]内に画像を追加する
[ソリューション エクスプローラー]内に「Images」というフォルダーを作成して、10枚の(任意の)画像を追加しておく。その際、画像サイズは640×480サイズの画像にしてほしい。
XMLファイルの追加
[ソリューション エクスプローラー]でプロジェクト項目を選択した状態で、VS 2012のメニューバーから[プロジェクト]-[新しい項目の追加]と選択する。そこで表示されるダイアログで、[データ]から「XML ファイル」を選択し、[名前]欄に「forest.xml」と指定する。
追加したファイルがXMLエディターで開かれるので、下記のリスト1のような内容のXMLコードを記述する。
<?xml version="1.0" encoding="utf-8" ?>
<画像>
<画像名>林_01.png</画像名>
<画像名>林_02.png</画像名>
<画像名>林_03.png</画像名>
<画像名>林_04.png</画像名>
<画像名>林_05.png</画像名>
<画像名>林_06.png</画像名>
<画像名>林_07.png</画像名>
<画像名>林_08.png</画像名>
<画像名>林_09.png</画像名>
<画像名>林_10.png</画像名>
</画像>
|
<画像名>要素に指定している画像ファイル名には、[ソリューション エクスプローラー]内の「Images」フォルダー内に実在する画像ファイルの名前を記述している。従って、それぞれの状況に合わせて実際のファイル名に書き直してほしい。
forest.xmlファイルは.EXEファイルと同じフォルダーに常に配置したいので、[ソリューション エクスプローラー]で「forest.xml」項目の右クリック・メニューで[プロパティ]を実行し、[プロパティ]ウィンドウの[ビルド アクション]の値を「コンテンツ」に、また[出力ディレクトリにコピー]の値を「常にコピーする」に変更しておこう。
今回のLeap Motionアプリについて
今回のアプリは、ListBoxコントロールに配置された10枚の画像のうち、任意の画像をLeap Motionを使って空中でタッチすると、タッチされた画像がアニメーションを伴って、回転しながら大きく表示されるアプリだ(次の画面を参照)。表示された画像を消すには、5本の指で画像をタッチすればよい。
今回のアプリではWin32 APIを使用している。
右端の画像をアニメーション表示させた場合、画像が半分ほど隠れてしまう。その場合は、1本の指で移動させることが可能だ。5本の指で空中タッチすると画像は消える。
画面のレイアウト(MainWindow.xaml)
前回と同じように、デフォルトでGridコントロールが配置されているのをCanvasコントロールに変更する。その方が、座標値が取得しやすいからだ。
XAMLデザイナー画面上で、先ほどのCanvasコントロール上に、「ListBox1」という名前のListBoxコントロールを1個配置する。
次に、再びCanvasコントロール上に、「Image1」という名前のImageコントロールを1個配置する。ImageコントロールのWidthプロパティには「20」、Heightプロパティには「15」と指定し、Visibilityプロパティに「Collapsed」を指定して非表示としておく。
またLeap Motionのタッチ・ポイントを表示させるために、「paintCanvas」という名前のInkPresenterコントロールをCanvasコントロール上に配置する。このInkPresenterコントロールは、どのコントロールよりも一番手前に配置する必要がある。
MainWindow.xamlファイルの編集
上記のレイアウトを行って書き出されたXAMLコードを、さらに次のリストの太字部分ように編集する。
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="1080" Width="1920" WindowState="Maximized">
<Window.Resources>
<DataTemplate x:Key="ListBoxTemplate">
<Image Width="320" Height="240" Margin="10" Stretch="Fill" Source="{Binding 画像名}"/>
</DataTemplate>
<ItemsPanelTemplate x:Key="WrapPanelTemplate">
<WrapPanel Width="1800"/>
</ItemsPanelTemplate>
</Window.Resources>
<Canvas>
<ListBox x:Name="ListBox1"
HorizontalAlignment="Left" VerticalAlignment="Top" Width="1801" Height="574" Canvas.Left="41" Canvas.Top="145"
ItemTemplate="{StaticResource ListBoxTemplate}" ItemsPanel="{StaticResource WrapPanelTemplate}"/>
<Image x:Name="Image1" Canvas.Left="647" Canvas.Top="106" Width="20" Height="15" Stretch="Fill" Visibility="Collapsed"/>
<InkPresenter Name="paintCanvas"/>
</Canvas>
</Window>
|
<Window.Resource>プロパティ要素内に、Key名が「ListBoxTemplate」の<DataTemplate>要素を定義する。その子要素として<Image>要素を配置し、Widthプロパティに「320」、Heightプロパティに「240」と指定し、Sourceプロパティに「<画像名>」をバインドする。この<画像名>は、VBコード(詳細後述)の中で、クラスに定義したプロパティ名だ。
次に、Key名が「WrapPanelTemplate」の<ItemsPanelTemplate>要素を定義する。その子要素として、<WrapPanel>要素を配置し、Widthプロパティを指定する。そのWidthプロパティ値は、ListBox1のWidthプロパティ値に近い値を指定する。必ずWidthプロパティの値は指定する必要がある。
ここで定義した各テンプレート(ListBoxTemplateやWrapPanelTemplate)を、StaticResourceマークアップ拡張機能を使用して、ListBox1のItemTemplateプロパティやItemsPanelプロパティにリソースとして参照させる。具体的には、ItemTemplateプロパティに「ListBoxTemplate」を、ItemsPanelプロパティに「WrapPanelTemplate」を参照させる。
こうすることで、普通なら、ListBoxコントロールに画像を表示させる場合、縦一列に表示されるのが、横方向に表示されるようになり、ListBoxコントロールのWidth値以上になれば、折り返して自動的に2列で表示されるようになる。
次の画面は、ここまでのレイアウト後の表示例である。
Image1(=小さな□)は最初の状態では非表示になって入る。
ListBox1を配置する。
全てのコントロールの一番前面に「paintCanvas」という名前のInkPresenterコントロールを配置する。この領域にタッチ・ポイントが表示される。
Blendを起動してストーリーボードを作成
次に、アニメーションのためのストーリーボードを作成する。
まずはBlendを起動する。実際にBlend起動する前に、必ずVS 2012でプロジェクトをビルドしておこう。
VS 2012からBlendを直接起動する方法もあるが、今回は.exeファイルを実行して起動する。64bitのWindows 8環境であれば、「C:\Program Files (x86)\Microsoft Visual Studio 11.0\Blend\Blend.exe」を実行すればよい。なお、VS 2012をインストールすると、Blendも自動的にインストールされている。Windows 8のスタート画面にも表示されているはずだから、それをタップして起動してもよい。
Blendが起動したら、[プロジェクトを開く]から、現在作成している「SetMouseCursor_LeapMotion.sln」ファイルを指定して開く。
Blendにおけるストーリーボードの作成は、Leap Motionとは直接関係がないので、解説は手短にさせていただく。
「Storybord1」というストーリーボードを作成する(作成手順は「MSDN: ストーリーボードの作成、変更、または削除」を参照されたい)。アートボード全体が赤い枠線で囲まれてストーリーボードの記録が可能になるので、[オブジェクトとタイムライン]で「Image1」を選択して、黄色の再生ヘッドが「0」の位置で、Image1のVisibilityプロパティの値を「Visible」に指定する。次に、再生ヘッドを「1」の位置に移動し、Widthプロパティに「640」、Heightプロパティに「480」と指定する。Image1の[プロパティ]ウィンドウの[変換]タブの[RenderTransform]内にある[回転]タブの[Angle]に「720」と指定する(次の画面を参照)。
この状態でストーリーボードの記録をオフにして(=赤い枠線の左上隅にあるアイコンをクリック)、再生ヘッドを「0」の位置に戻して再生すると、画像が回転しながら大きく表示される。Image1は、最初は何も読み込んでいないため、動作確認する意味で、仮にImagesフォルダーの画像を1枚指定しておくといいだろう。ただし、動作を確認した後は、画像の指定を外しておくことを忘れないようにする。
Blendを閉じて、VS 2012に戻る。保存と適用のメッセージが出るが、保存して適用させる。
またストーリーボードのコードの下に、次のリストのコードが追加されているので、このコードは削除する。このコードを残したままにしておくと、アプリを実行した際に、即、ストーリーボードが実行されてしまう。
<Window.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource Storyboard1}"/>
</EventTrigger>
</Window.Triggers>
|
プログラム・コード(MainWindow.xaml.vb)
では、次にプログラム・コード(MainWindows.xaml.vbファイル)を見ていこう。プログラム・コードも、タッチ処理以外は第1回と基本的に同じ内容となるので、説明を割愛する。まずは第1回の「名前空間の読み込み」「メンバー変数の宣言」「MainWindow_Loadedメソッドの処理」「Updateメソッドの処理」の開発手順を参考にタッチ処理の前までを実装してほしい。相違点として、下記の7点を修正してほしい。
(1)名前空間の読み込みに「System.Windows.Media.Animation」を追加する。これはストーリーボードを扱うために必要な名前空間だ
(2)メンバー変数の宣言にWin32 APIの以下のリストを追加する
' Win32 APIの宣言
Private Declare Function SetCursorPos Lib "user32" (x As Integer, y As Integer) As Boolean
Private Declare Function apimouse_event Lib "user32" Alias "mouse_event" (ByVal dwFlags As Int32, ByVal dx As Int32, ByVal dy As Int32, ByVal cButtons As Int32, ByVal dwExtraInfo As Int32) As Boolean
Private Const MOUSEEVENTF_LEFTDOWN = &H2
|
(3)XMLの要素を表すメンバー変数「Private xmldoc As XElement」を宣言する
(4)ImageInfo型(後述)のコレクション変数「Private ImageList As New List(Of ImageInfo)」を宣言する
(5)今回はストーリーボードを扱うため、ストーリーボードのメンバー変数「Private strb As New Storyboard」を宣言する
(6)ListBox1より選択された画像名を格納しておく、文字列型のメンバー変数「Private SelectedImage As String」を宣言する
(7)第1回のリスト3にある「Private Message As String」という行は削除する
クラスの定義
ImageInfoクラスを定義し、その中に「画像名」という文字列型のプロパティを定義する(リスト5)。
Public Class ImageInfo
Public Property 画像名 As String
End Class
|
MainWindow_Loadedメソッドの処理
MainWindowが読み込まれたときの処理を実装する。
XElememt.Loadメソッドでforest.xmlファイルを読み込む。さらにDescendantsメソッドで全ての子孫要素「<画像名>」の内容を、変数「result」に格納しながら、以下の処理を行う。
- <画像名>要素の値と、文字列“Images/”を連結した文字列値を、ImageInfoクラスの「画像名」プロパティに指定してImageInfoクラスの新規インスタンスを作成し、それをImageListオブジェクトにAddメソッドで追加していく
ListBox1のItemsSourceプロパティにImageListオブジェクト指定する。これで、読み込まれた画像がListBox1に表示される。なお、ListBox1にはWrapPanelのテンプレートを適用しているので、画像は右端で折り返して表示されることになる。
~以下は第1回目と同じ処理の解説になるので割愛する。~
具体的には次のようなコードになる。
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
' XMLファイルを読み込む
xmldoc = XElement.Load("forest.xml")
' コレクション変数に<画像名>要素の内容を指定し、追加していく
For Each result In From c In xmldoc.Descendants("画像名") Select c
ImageList.Add(New ImageInfo With {.画像名 = "Images/" & result.Value})
Next
' ListBox1のItemsSourceプロパティにImageListオブジェクトを指定する。
' これで、ListBox1コントロール内に画像が表示される
ListBox1.ItemsSource = ImageList
……以下は第1回目と同じ処理になるので割愛する……
End Sub
|
Updateメソッドの処理(タッチ処理部分)
第1回でも説明したが、Leap Motionのタッチ処理について簡単に説明しておこう。
次にLeap Motionのタッチ処理になる。タッチのイメージは次の図のようなイメージだ。手前側が「ホバー状態(hovering)」、奥側が「タッチ状態(touching)」を表す。空間の範囲は前後「1」~「-1」となっている。
今回のサンプルでもホバーの場合は、表示されている円の色をNavyにする。その際、タッチ・ポイントの位置をメンバー変数「x」と「y」に格納する。画面に表示されている指の数をleap.Frame.Fingers.Countプロパティで取得して、メンバー変数「FingersCount」に格納しておく。
さらに今回は、指が1本表示されている場合は、Image1のSetValueメソッドを使って、Image1のTopPropertyとLeftPropertyに、タッチした座標を指定する。具体的には下記のコードのようになる。
For Each Pointable As Pointable In leap.Frame.Pointables
……コード略(前述)……
' ホバー状態のときの処理
If Pointable.TouchDistance > 0 AndAlso Pointable.TouchZone <> Global.Leap.Pointable.Zone.ZONENONE Then
touchIndicator.Color = Colors.Navy
' タッチ・ポイントの位置をメンバー変数「x」と「y」に格納する
x = touchPoint.X
y = touchPoint.Y
' 表示されている指の本数を取得して、メンバー変数「FingersCount」に格納しておく
FingersCount = leap.Frame.Fingers.Count
If FingersCount = 1 Then
' Image1の表示位置を決める
Image1.SetValue(Canvas.TopProperty, CDbl(y))
Image1.SetValue(Canvas.LeftProperty, CDbl(x))
End If
……コード略(続きは後述)……
End If
Next
|
次にタッチした処理になる。以下の処理を行う。
タッチした場合は、表示されている円の色をRedに変える。
指が1本だけ表示されている場合は、Win32 APIの「SetCursorPos(<タッチしたx座標>, <タッチしたy座標>)」と、マウスでクリックした動作の「apimouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)」を実行する。これにより、マウス・カーソルの位置と、Leap Motionで描かれた円のタッチ・ポイントの位置が同じになる。
また、Image1を表示状態にする。
さらに、「Storyboard1」という名前のStoryboardを見つけて、Beginメソッドでストーリーボードを実行する。これにより、画像をタッチした位置でアニメーションが実行される。
指を5本表示させてタッチした場合は、Image1を非表示にする。
具体的なコードは以下のとおり。
' ホバー状態のときの処理
If Pointable.TouchDistance > 0 AndAlso Pointable.TouchZone <> Global.Leap.Pointable.Zone.ZONENONE Then
……コード略(前述)……
' タッチ状態のときの処理
ElseIf Pointable.TouchDistance <= 0 Then
touchIndicator.Color = Colors.Red
If FingersCount = 1 Then
' Win32 APIを実行する
SetCursorPos(x, y)
apimouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
Image1.Visibility = Windows.Visibility.Visible
' ストーリーボードを実行する
strb = DirectCast(Me.FindResource("Storyboard1"), Storyboard)
strb.Begin()
End If
' 指5本をかざすと、Image1が非表示になり、ストーリーボードを停止する
If FingersCount = 5 Then
Image1.Visibility = Windows.Visibility.Collapsed
strb.Stop()
End If
' タッチ対象外
Else
touchIndicator.Color = Colors.Gold
End If
|
次に、ListBox1に表示されている画像が選択されたときの処理だ。ListBox1のSelectionChangedイベント・ハンドラーを追加して、その中に以下の処理を実装する。
ListBox1から選択された項目をImageInfo型にキャストして、「画像名」プロパティの値を取得して、メンバー変数「SelectedImage」に格納する。その値をUriクラスのコンストラクターに指定してUriクラスの新規インタンスを生成して、それをImage1のSourceプロパティに指定する。この際、Uriクラスのコンストラクターの第2パラメーターに「UriKind.Relative」と指定して、相対URIで指定することを忘れないようにしてほしい。
前述のように、Win32 APIの「apimouse_event(MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)」を使用しているため、Leap Motionのタッチとマウス・イベントが関連付けられている。
Private Sub ListBox1_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ListBox1.SelectionChanged
SelectedImage = DirectCast(ListBox1.SelectedItem, ImageInfo).画像名
' Image1にListBoxから選択された画像を表示する
Image1.Source = New BitmapImage(New Uri(SelectedImage, UriKind.Relative))
End Sub
|
このサンプルのコードは下記よりダウンロードできる*1。
- *1サンプルをダウンロードして動かす場合は、「LeapCSharp.NET4.0.dll」や「LeapCSharp.dll」、「Leap.dll」を読者自身のフォルダー内にあるDLLファイルに指定し直さなければ動かない可能性があるので、動かない場合は再指定していただきたい。
■
今回はこれで終わりだ。今回はWin32 APIを使ったが、見ていただくと分かるように、特に扱いが難しいというものでもない。Leap Motionで動的に作成したオブジェクトを選択する場合は、どうしてもWin32 APIを使う必要がある。これ以降のサンプルでもWin32 APIを多用しているので、いろいろ参考になるだろうと思う。
次回は、パーティクルを使ったサンプルを2つ同時に紹介する。Leap Motionの醍醐味(だいごみ)はパーティクルにあると筆者は思っている。しかし、パーティクル処理は難しい。筆者自身もよく理解できていないが、筆者なりにアレンジを加えたサンプルを紹介する。パーティクルの処理自体は全てDLL化しているので、コード自体は簡潔なコードになっている。
では、また次回の記事でお会いしよう。
- (※C++の連載記事「連載:C++で始めるLeap Motion開発 ―― タッチUIの先のカタチ ――」はこちら。「連載:Leap Motion開発入門(C#編)」はこちら)
- (※C#の記事「特集:コードで理解するLeapアプリ開発の概要 C#開発者から見たLeap Motion開発のファースト・インプレッション」はこちら)
※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
1. オブジェクト(Button)をLeap Motionでタッチして背景色を変化させる
Leap Motionの動くサンプルを実際に作ってみる連載スタート。今回はLeap Motionのタッチ操作で背景色を変化させるWPFアプリをVBで開発する。
2. Leap MotionによるWPFアプリ上のオブジェクトの移動
Leap Motionの動くサンプルを実際に作ってみる。今回はWPFの画面上に配置したオブジェクトを、Leap Motionによる操作で移動させるアプリを作成。
3. 【現在、表示中】≫ Leap Motionによる、WPFアプリ上に動的に作成したオブジェクトのイベント処理
Leap Motionの動くサンプルを実際に作ってみる。今回はWPFの画面上に動的に作成したオブジェクトを、Leap Motionによる操作でアニメーションさせるアプリを作成。
4. Leap Motionでパーティクルを使用して軌跡に無数の円や画像を表示する
さまざまな色の粒子(パーティクル)が、Leap Motionによる手の指の動きに合わせて飛び散りながら追従するサンプル・アプリを作ってみよう。
5. Leap MotionでBing Mapsを扱う
リスト内に表示された住所項目をLeap Motionによりタッチすることで、Web上のサービス「Bing Maps」での地図検索を行うサンプル・アプリを作ってみよう。