更新日: 2010 年 11 月 12 日

C# の内容はこちらに掲載しています。10 行でズバリ!! [C#] Silverlight - マルチタッチ

このコンテンツのポイント

  • マルチタッチ イベントの処理
  • タッチとマウス クリックの区別

今回紹介するコード

Visual Basic
Partial Public Class MainPage
    Inherits UserControl

    Dim shapes As New List(Of Shape)

    Public Sub New()
        InitializeComponent()

        shapes.Add(circle1)
        shapes.Add(circle2)

        AddHandler Touch.FrameReported, AddressOf Touch_FrameReported
    End Sub

    Sub Touch_FrameReported(ByVal sender As Object, ByVal e As TouchFrameEventArgs)
        Dim primary As TouchPoint = e.GetPrimaryTouchPoint(Nothing)
        If primary IsNot Nothing Then
            If primary.Action = TouchAction.Down Then e.SuspendMousePromotionUntilTouchUp()
        End If

        Dim touchPoints As TouchPointCollection = e.GetTouchPoints(LayoutRoot)

        For Each touch In touchPoints
            Dim id As Integer = touch.TouchDevice.Id
            Dim shape As Shape = Nothing
            For Each c In shapes
                If (c.Tag IsNot Nothing) And (CType(c.Tag, Integer) = id) Then
                    shape = c
                    Exit For
                End If
            Next
            If shape Is Nothing Then
                For Each c In shapes
                    If c.Tag Is Nothing Then
                        c.Tag = id
                        shape = c
                        Exit For
                    End If
                Next
            End If
            If shape Is Nothing Then
                Continue For
            End If

            Select Case touch.Action
                Case TouchAction.Down
                    shape.SetValue(Canvas.LeftProperty, touch.Position.X)
                    shape.SetValue(Canvas.TopProperty, touch.Position.Y)
                    shape.Visibility = Windows.Visibility.Visible
                Case TouchAction.Move
                    shape.SetValue(Canvas.LeftProperty, touch.Position.X)
                    shape.SetValue(Canvas.TopProperty, touch.Position.Y)
                Case TouchAction.Up
                    shape.Visibility = Windows.Visibility.Collapsed
            End Select
        Next
    End Sub

End Class
 

目次

  1. はじめに
  2. サンプル アプリケーションの作成準備
  3. マルチタッチの基本
  4. マルチタッチ イベントの処理
  5. マウス イベントとの区別
  6. おわりに

1. はじめに

マルチタッチは、一般的なユーザー インターフェイスになりつつあります。とくにキーボードやマウスを使えない環境では、1 本の指で操作するよりもはるかに多様な操作ができます。Silverlight はマルチタッチ イベントを処理することができ、Web 向けのユーザー インターフェイスにマルチタッチの体験を追加できます。

ここでは、簡単なアプリケーションを作成して、マルチタッチの基本処理とタッチとマウスの区別について解説します。

ページのトップへ


2. サンプル アプリケーションの作成準備

まず、Visual Studio を起動して、[ファイル] メニューの [新規作成] から [プロジェクト] をクリックし、[新しいプロジェクト] を開きます。次に、[インストールされたテンプレート] で [Silverlight] カテゴリーにある [Silverlight アプリケーション] を選びます。[名前] には任意のプロジェクト名を指定できますが、ここでは「SampleTouch」とします。

図 1. [新しいプロジェクト] ダイアログで Silverlight アプリケーションのプロジェクトを新規作成

[OK] をクリックすると、[新しい Silverlight アプリケーション] ダイアログが表示され、これから作成する Silverlight アプリケーションが埋め込まれた Web ページを持つ Web サイトを作成するかどうかが確認されます。ここでは、[Silverlight アプリケーションを新しい Web サイトでホストする] のチェックを外します。これで、この Silverlight アプリケーションを実行しようとするたびに、仮の Web サイトが自動作成されます。

図 2. [新しい Silverlight アプリケーション] ダイアログ

[OK] ボタンをクリックすると、SampleTouch ソリューションと、Silverlight アプリケーションのための SampleTouch プロジェクトが作成されます。

図 3. プロジェクト作成直後の Visual Studio 2008

ページのトップへ


3. マルチタッチの基本

Silverlight のマルチタッチ サポートは、基本的なものです。マルチタッチを処理するアプリケーションでは、Touch というクラスを使います。このクラスは、FrameReported イベントのみを持つ静的クラスです。次のようにイベント ハンドラーを記述すれば、タッチ イベントを処理することができます。この記述は、マルチタッチに対応していない環境では何の影響もありません。

    ......
    AddHandler Touch.FrameReported, AddressOf Touch_FrameReported
    ......

Sub Touch_FrameReported(ByVal sender As Object, ByVal e As TouchFrameEventArgs)
......
End Sub

タッチと (マウスの) クリックは異なるイベントです。タッチを使う場合は、マウスを使わない (または使えない) ことも考えられるので、通常は、最初のタッチ (プライマリー タッチ) をマウス操作とみなします。プライマリー タッチをマウス イベントに切り替えることを "昇格" (promotion) と言い、後述する通り抑止することもできます。

逆に、マウスのクリックはタッチ イベントを発生しません。アプリケーションがタッチ イベントだけを処理する場合、そのアプリケーションはタッチ専用のアプリケーションになってしまい、マウスで操作できないことになります。ユーザー環境を特定できる場合を除いて、「マウスしかない環境」「タッチしかない環境」「マウスとタッチの両方を持つ環境」のそれぞれを考慮する必要があります。

ページのトップへ


4. マルチタッチ イベントの処理

実際にユーザー インターフェイスを作成して、マルチタッチ イベントを処理します。まず、MainPage.xaml を次のように記述します。


<UserControl x:Class="SilverlightApplication2.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
    <Canvas x:Name="LayoutRoot" Background="LightYellow">
        <Ellipse x:Name="circle1" Width="50" Height="50" Fill="Green" Visibility="Collapsed">
            <Ellipse.RenderTransform>
                <TranslateTransform X="-25" Y="-25" />
            </Ellipse.RenderTransform>
        </Ellipse>
        <Ellipse x:Name="circle2" Width="50" Height="50" Fill="Blue" Visibility="Collapsed">
            <Ellipse.RenderTransform>
                <TranslateTransform X="-25" Y="-25" />
            </Ellipse.RenderTransform>
        </Ellipse>
    </Canvas>

</UserControl>

ここでは Grid レイアウトの代わりに Canvas レイアウトを使っています。これはタッチされた座標に目印となる Ellipse コントロールを配置しやすくするためです。Ellipse はタッチ場所を示すための図形 (円) で、ここでは 2 つの円を配置しています。これは 2 点対応のマルチタッチ デバイスに対応するためですが、定義を増やすことでより多くのタッチに対応することもできます (後述するソース コードでの対応も必要)。RenderTransform で TranslateTransform を指定しているのは、Left と Top で座標を指定するときに、その座標値を中央にした円を描画するためです。また、初期状態では円を表示する必要がないので、Visibility プロパティを Collapsed (表示しない) に設定しています。

イベント ハンドラーは次のように記述します。

Partial Public Class MainPage
    Inherits UserControl

    Dim shapes As New List(Of Shape)

    Public Sub New()
        InitializeComponent()

        shapes.Add(circle1)
        shapes.Add(circle2)

        AddHandler Touch.FrameReported, AddressOf Touch_FrameReported
    End Sub

    Sub Touch_FrameReported(ByVal sender As Object, ByVal e As TouchFrameEventArgs)
        Dim primary As TouchPoint = e.GetPrimaryTouchPoint(Nothing)
        If primary IsNot Nothing Then
            If primary.Action = TouchAction.Down Then e.SuspendMousePromotionUntilTouchUp()
        End If

        Dim touchPoints As TouchPointCollection = e.GetTouchPoints(LayoutRoot)

        For Each touch In touchPoints
            Dim id As Integer = touch.TouchDevice.Id
            Dim shape As Shape = Nothing
            For Each c In shapes
                If (c.Tag IsNot Nothing) And (CType(c.Tag, Integer) = id) Then
                    shape = c
                    Exit For
                End If
            Next
            If shape Is Nothing Then
                For Each c In shapes
                    If c.Tag Is Nothing Then
                        c.Tag = id
                        shape = c
                        Exit For
                    End If
                Next
            End If
            If shape Is Nothing Then
                Continue For
            End If

            Select Case touch.Action
                Case TouchAction.Down
                    shape.SetValue(Canvas.LeftProperty, touch.Position.X)
                    shape.SetValue(Canvas.TopProperty, touch.Position.Y)
                    shape.Visibility = Windows.Visibility.Visible
                Case TouchAction.Move
                    shape.SetValue(Canvas.LeftProperty, touch.Position.X)
                    shape.SetValue(Canvas.TopProperty, touch.Position.Y)
                Case TouchAction.Up
                    shape.Visibility = Windows.Visibility.Collapsed
            End Select
        Next
    End Sub

End Class

コンストラクターで Shape のリスト (shapes) に 2 つの Ellipse オブジェクト (circle1、circle2) を設定しているのは、実際のタッチ イベントの処理で使いやすくするためです。XAML で図形オブジェクトの定義を増やし、リストに追加することで、同時に処理するタッチの数を増やすことができます。
※追加するオブジェクトは、Shape クラスから派生したクラスであれば Ellipse である必要はありません (Polygon、Rectangle などを追加できます)。

また、マルチタッチを処理するためのイベント ハンドラー (Touch_FrameReported) では、まずイベントを発生させたタッチがプライマリー タッチ ポイントかどうかを判断しています。プライマリー タッチ ポイントとは 1 本の指でタッチしたもので、通常はマウス イベントに昇格されます。ここでは、タッチ アクションが Down (押したとき) であることを判断し、SuspendMousePromotionUntilTouchUp メンバーを呼び出して、離されるまでマウス イベントへの昇格を停止します。つまり、タッチ操作ではマウス イベントが発生しなくなります。

すべてのタッチ ポイントは、GetTouchPoints によって取得できます。このとき引数として Canvas コントロール (LayoutRoot) を渡し、LayoutRoot を基準にした座標値を取得できるようにします。それぞれのタッチ ポイントには ID (TouchDevice.Id) が割り振られるので、これをコントロールの Tag に設定して、タッチ操作を追跡できるようにします。shapes リストを調べて Tag と ID の一致するオブジェクトがあれば、そのオブジェクトを、なければ Tag の空いているもの (使われていないもの) を探して ID を代入します。

タッチ操作は、TouchAction.Down (押した)、TouchAction.Move (移動)、TouchAction.Up (離した) のいずれかです。押したときには対象となる図形を表示し (Visibility プロパティを Visible にする)、移動したときには座標値を変更し、離したときには図形を消します (Visibility プロパティを Collapsed にする)。これで、タッチ場所に図形が描画されることになります。

実際にプログラムを動かすと、次のようになります。

図 4. タッチしている場所に円が表示される

Note: マルチタッチ デバイスには、同時に認識できるタッチが 2 点までのものが少なくありません。Silverlight は、より多くのタッチ ポイントを認識しますが、マルチタッチ アプリケーションを作成する場合は、できるだけ 2 点までのタッチで操作できるよう配慮してください。

ページのトップへ


5. マウス イベントとの区別

通常、プライマリー タッチポイントをマウス イベントに置き換えることで、タッチ操作でマウス クリックを代替しています。しかし、前述のとおり、タッチとマウスは異なる操作です。ここでは、タッチ イベントとマウス イベントの両方を処理して、この違いを見てみます。

まず、MainPage.xaml に新たな図形として小さめの赤い円 (Ellipse) を追加します。

    </Ellipse>
    <Ellipse x:Name="circlex" Width="30" Height="30" Fill="Red" Visibility="Collapsed">
        <Ellipse.RenderTransform>
            <TranslateTransform X="-15" Y="-15" />
        </Ellipse.RenderTransform>

    </Ellipse>
</Canvas>

次に、マウスの左ボタンを押したとき (MouseLeftButtonDown)、移動したとき (MouseMove)、左ボタンを離したとき (MouseLeftButtonUp) のそれぞれに対するイベント ハンドラーを追加します。このコードにより、MainPage で追加した赤い円がマウスのドラッグに追従して表示されるようになります。

Public Sub New()
    InitializeComponent()

    shapes.Add(circle1)
    shapes.Add(circle2)

    AddHandler Touch.FrameReported, AddressOf Touch_FrameReported

    AddHandler LayoutRoot.MouseLeftButtonDown, AddressOf LayoutRoot_MouseLeftButtonDown
    AddHandler LayoutRoot.MouseMove, AddressOf LayoutRoot_MouseMove
    AddHandler LayoutRoot.MouseLeftButtonUp, AddressOf LayoutRoot_MouseLeftButtonUp

End Sub

Sub LayoutRoot_MouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
    Dim point As Point = e.GetPosition(LayoutRoot)
    circlex.SetValue(Canvas.LeftProperty, point.X)
    circlex.SetValue(Canvas.TopProperty, point.Y)
    circlex.Visibility = Windows.Visibility.Visible
End Sub

Sub LayoutRoot_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
    Dim point As Point = e.GetPosition(LayoutRoot)
    circlex.SetValue(Canvas.LeftProperty, point.X)
    circlex.SetValue(Canvas.TopProperty, point.Y)
End Sub

Sub LayoutRoot_MouseLeftButtonUp(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
    circlex.Visibility = Windows.Visibility.Collapsed
End Sub

プログラムを実行してみると、タッチ操作とマウスのドラッグが別々に処理されていることがわかります。ユーザー インターフェイスに Button コントロールを配置しても、タッチ操作で押すことはできません。これは、Touch_FrameReported イベント ハンドラーで、タッチからマウス イベントへの昇格を抑止しているためです。

ここで、次のように昇格を抑止するコードをコメント化して、プログラムを実行します。

Sub Touch_FrameReported(ByVal sender As Object, ByVal e As TouchFrameEventArgs)
    'Dim primary As TouchPoint = e.GetPrimaryTouchPoint(Nothing)
    'If primary IsNot Nothing Then
    '    If primary.Action = TouchAction.Down Then e.SuspendMousePromotionUntilTouchUp()
    'End If

    Dim touchPoints As TouchPointCollection = e.GetTouchPoints(LayoutRoot)

タッチ操作をすると、図 5 のようにタッチ用の円とマウス用の円が重なって表示されるようになります。

図 5. タッチ操作だけで、マウス用の円が重なって表示される

アプリケーションが、タッチ非対応のデバイスにも対応する場合、タッチ イベントだけでなく、マウス イベントも処理する必要があります。プライマリー タッチをマウス イベントに昇格させると、タッチ操作が、タッチ イベントでも、マウス イベントでも処理されてしまうことになります。最初の例でプライマリー タッチからマウス イベントへ昇格するのを抑止していたのは、このためです (イベント ハンドラーの MouseEventArgs 型引数で StylusDevice.DeviceType を調べれば、発生元のデバイスを判別できます)。

一方、マウス イベントへの昇格を抑止すると、ボタンを押したり、リスト ボックスから項目を選ぶといった、マウス操作が必要な場所でタッチ操作が使えなくなります。すべてをタッチ イベントで処理することもできますが、プライマリー タッチポイントの位置 (Position プロパティ) が一定の範囲にあるかどうかで限定的にマウス イベントへの昇格させることもできます。こうすれば、その範囲の中では、タッチ操作でコントロールを使えます。

Note: Silverlight を全画面モードで動作させているときは、タッチ イベントは発生せず、マウス イベントに昇格することに注意してください。

ページのトップへ


6. おわりに

現在、Silverlight のマルチタッチ機能は、Windows 7 に依存しているため、Mac OS では動作しません。また、WPF (Windows Presentation Foundation) や Windows API (Win32) がサポートするジェスチャーと呼ばれる機能 (基本的なマルチタッチ操作を解釈する機能) はサポートされません。Silverlight が、さまざまな環境から利用される Web 向けの技術であることを考えると、マルチタッチが使えない場合のことも考慮する必要があります。タッチ イベントとマウス イベントの両方を考慮してプログラミングする必要があるのは、そのためです。

なお、マルチタッチは Windows Phone の標準的なユーザー インターフェイスにもなっており、Silverlight for Windows Phone でも利用できます。Silverlight for Windows Phone では、タッチ イベントを直接処理するだけでなく、UIElement から派生したコントロールで Manipulation イベントを使うことができます。


Code Recipe Silverlight デベロッパー センター

ページのトップへ