連載
» 2016年01月06日 05時00分 UPDATE

.NET TIPS:WPF:GridやCanvasなどでマウスやタッチのイベントを拾うには?[C#/VB]

Grid/Canvas/Ellipse/Borderなどのコントロールでマウス/タッチ/スタイラスのイベントを発生させ、それを処理する方法を解説する。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Development]
.NET TIPS
Insider.NET

 

「.NET TIPS」のインデックス

連載目次

対象:.NET 3.5以降
ただし、タッチ対応は.NET 4.0以降


 マウスやタッチのイベントを拾えなくて困ったことはないだろうか? Grid/Canvas/Borderコントロール(いずれもSystem.Windows.Controls名前空間)やEllipse/Rectangleコントロール(System.Windows.Shapes名前空間)などの幾つかのコントロールは、マウスやタッチのイベントハンドラーを記述しただけではイベントを拾えないのである。しかしその対応は簡単だ。本稿ではその方法を紹介する。なお、本稿のサンプルは「Windows desktop code samples:.NET Tips #1123」からダウンロードできる。

GridやCanvasなどでマウスやタッチのイベントを拾うには?

 背景色または塗りつぶし色を設定すればよい。透明のままにするには、背景色または塗りつぶし色として「Transparent」を指定する。

 Grid/Canvas/BorderコントロールなどではBackgroundプロパティに、Ellipse/RectangleコントロールなどではFillプロパティに色を設定する。

 分かってしまえば何ということもないのだが、マウスでクリックしたり指でタッチしたりするときに、そこに何かがなければイベントは発生しないということなのだ。透明であろうとも、背景色または塗りつぶし色が設定されていることが必要なのである。

 なお、マウスやタッチのイベントはバブルアップするので、ビジュアルツリーの親要素で一括してハンドリングできる。

サンプルコード

 以下、簡単なサンプルコードを書いて、確かめてみよう。ここでは無償のVisual Studio Express 2012 for Windows Desktopを使い、.NET 4.0をターゲットにしてコーディングする(タッチ対応の部分を削れば.NET 3.5でも動作する)。

 WPFのプロジェクトを作成したら、「MainWindow.xaml」ファイルを次のコードのように編集する。

<Window ……省略……
        Title="MainWindow" Height="350" Width="525">
  <Grid x:Name="RootGrid" StylusDown="RootGrid_StylusDown"
        TouchDown="RootGrid_TouchDown" MouseDown="RootGrid_MouseDown">
    <Ellipse Fill="Blue" Opacity="0.1" Width="300" Height="60"
             HorizontalAlignment="Left" VerticalAlignment="Top" Margin="60,100"/>
    <StackPanel Margin="8">
      <TextBlock x:Name="TextBlock1" Margin="0,0,0,8" HorizontalAlignment="Left" />
      <StackPanel Orientation="Horizontal">
        <Border x:Name="Border1" Background="Transparent"
                BorderBrush="Green" BorderThickness="4" Width="200" Height="200" />
        <Border x:Name="Border2" Background="{x:Null}" BorderBrush="DarkRed"
                BorderThickness="4" Width="200" Height="200" Margin="8,0" />
      </StackPanel>
    </StackPanel>
  </Grid>
</Window>

「MainWindow.xaml」ファイル(.NET 4.0)
.NET 3.5で試すには、RootGridの「TouchDown="RootGrid_TouchDown"」を削除する。

 上のXAMLコードでは、Ellipseコントロールに塗りつぶし色が設定してある(「Fill="Blue"」)。従って、このEllipseコントロールの内側をマウスでクリックしたり指でタッチしたりすればイベントが発生する。発生したイベントは、親要素であるGridコントロール「RootGrid」でハンドリングしている。

 「RootGrid」には、スタイラスダウン(スタイラスペンが画面に触れたとき)/タッチダウン(指が画面に触れたとき)/マウスダウン(マウスのボタンが押されたとき)の三つのイベントハンドラーをセットしている(この三つのイベントは、ここに書いた順序で発生する)。なお、「RootGrid」自体には背景色を設定していないので、マウスやタッチのイベントは発生しない。これらのハンドラーは、「RootGrid」の子要素であるコントロールからバブルアップしてくるイベントを受け取る。

 そして次が、背景を透明に保ったままイベントを受け取れるようにする例だ。Borderコントロール「Border1」には、背景色に「Transparent」を設定してある。これは、マウスやタッチのイベントを受け取れる。

 比較のために、Borderコントロール「Border2」では、背景色をnullに設定している(これは既定値なので、背景色を指定しないのと同じ)。これはマウスやタッチのイベントを受け取らない。なお、Borderコントロールの枠線には色と幅を指定してあるので、枠線をクリック/タッチすれば「Border2」でもイベントが発生する。

 コードビハインドには次のコードのようにイベントハンドラーを記述する。

// スタイラスペンが画面に触れたとき
private void RootGrid_StylusDown(object sender, StylusDownEventArgs e)
{
  Point pos = e.GetPosition(RootGrid);
  TextBlock1.Text = string.Format("StylusDown: {0}, {1} ({2})"
                                  pos.X, pos.Y, e.Source.GetType().Name);
  e.Handled = true; // TouchDownイベントを抑制
}

// 指が画面に触れたとき
private void RootGrid_TouchDown(object sender, TouchEventArgs e)
{
  TouchPoint tp = e.GetTouchPoint(RootGrid);
  TextBlock1.Text 
    = string.Format("TouchDown: {0}, {1} ({2})",
                    tp.Position.X, tp.Position.Y, e.Source.GetType().Name);
}

// マウスのボタンが押されたとき
private void RootGrid_MouseDown(object sender, MouseButtonEventArgs e)
{
  if (e.StylusDevice != null)
    return; // スタイラス操作/タッチ操作のときはスルー

  Point pos = e.GetPosition(RootGrid);
  TextBlock1.Text 
    = string.Format("MouseDown: {0}, {1} ({2})",
                    pos.X, pos.Y, e.Source.GetType().Name);
}

' スタイラスペンが画面に触れたとき
Private Sub RootGrid_StylusDown(sender As Object, e As StylusDownEventArgs)
  Dim pos As Point = e.GetPosition(RootGrid)
  TextBlock1.Text = String.Format("StylusDown: {0}, {1} ({2})",
                                  pos.X, pos.Y, e.Source.GetType().Name)
  e.Handled = True ' TouchDownイベントを抑制
End Sub

' 指が画面に触れたとき
Private Sub RootGrid_TouchDown(sender As Object, e As TouchEventArgs)
  Dim tp As TouchPoint = e.GetTouchPoint(RootGrid)
  TextBlock1.Text _
    = String.Format("TouchDown: {0}, {1} ({2})",
                    tp.Position.X, tp.Position.Y, e.Source.GetType().Name)
End Sub

' マウスのボタンが押されたとき
Private Sub RootGrid_MouseDown(sender As Object, e As MouseButtonEventArgs)
  If (e.StylusDevice IsNot Nothing) Then
    Return ' スタイラス操作/タッチ操作のときはスルー
  End If

  Dim pos As Point = e.GetPosition(RootGrid)
  TextBlock1.Text _
    = String.Format("MouseDown: {0}, {1} ({2})",
                    pos.X, pos.Y, e.Source.GetType().Name)
End Sub

イベントハンドラーのコード例(.NET 4.0、上:C#、下:VB)
この三つのイベントは、スタイラスダウン→タッチダウン→マウスダウンの順に発生する(マウス操作のときは、マウスダウンのみ)。
それぞれのイベントハンドラーでは、マウスやタッチの座標とイベントが発生したコントロールのクラス名を、画面左上のTextBlockコントロールに表示している。
スタイラスダウンのイベントハンドラーで「e.Handled = true」とすれば、タッチダウンのイベントは抑制される(=イベントが発生しない)。
ただし、スタイラスダウンとタッチダウンのイベントハンドラーで「e.Handled = true」としても、マウスダウンイベントは抑制できない。そのため、マウスダウンのイベントハンドラーでは、スタイラス/タッチデバイスかを調べて、マウス操作によらないイベントを判別する。
なお、タッチパネルを指で操作しても、スタイラスダウンイベントが発生する。従って、実はこのコードではタッチダウンのイベントハンドラーは実行されない(それを確認することも含めてのサンプルである。ブレークポイントを置いてデバッグ実行してみてほしい)。

 実行中の画面は、次の画像のようになる。

 前述したような結果になることを確かめてほしい。マウス/タッチに反応して画面左上の文字列が変わるのは、次の場所である。

  • Ellipseコントロールの内部(塗りつぶし色を指定)
  • Borderコントロール「Border1」(左側の緑枠)の内部(背景色にTransparentを指定)
  • Borderコントロールの枠線(二つとも)
  • TextBlockコントロール(これは文字の線がない部分でも反応する)
サンプルコードを実行している様子(Windows 10での実行) サンプルコードを実行している様子(Windows 10での実行)
Ellipseコントロールの内側をクリックしたところ。左上の表示を見ると、イベントはBorderコントロールで発生している。
Borderコントロール「Border1」(左側の緑枠)は、背景色が透明といえどもEllipseコントロールの手前に配置されているので、「Border1」の全域でイベントが発生するのだ。対して、Borderコントロール「Border2」(右側の赤枠)では、背景色にnullをセットしているため、右側のBorderコントロール内にあるEllipseコントロールをクリックすればEllipseコントロールでイベントが発生する。二つのBorderコントロールの見た目は同じように透明だが、「Transparent」とnullは違うのだ。

まとめ

 マウスやタッチのイベントを拾うには、そこに何かしらの存在が必要である。コントロールによっては、背景色または塗りつぶし色の既定値がnullになっているものがある。そのようなコントロールでは、マウスやタッチのイベントを拾うために背景色または塗りつぶし色を設定する必要がある。

 なお、サンプルコードでは、スタイラス/タッチ/マウスによるイベントの関係も分かるようにしてあるので、参考にしていただければ幸いである。

利用可能バージョン:.NET Framework 3.5以降
カテゴリ:WPF/XAML 処理対象:イベント
使用ライブラリ:Gridクラス(System.Windows.Controls名前空間)
使用ライブラリ:Borderクラス(System.Windows.Controls名前空間)
使用ライブラリ:Ellipseクラス(System.Windows.Shapes名前空間)
関連TIPS:WPF:ウィンドウを透明にするには?[C#/VB]
関連TIPS:WPF:子ウィンドウを透明にするには?[C#/VB]


「.NET TIPS」のインデックス

.NET TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。