Help us understand the problem. What is going on with this article?

[WPF] Buttonをマウスクリック/タッチしたときのイベントの流れ(親子関係なし)

全体もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

イベント関連のもくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E9%96%A2%E9%80%A3wpfxaml

やりたいこと

WPFの画面には、バブリングイベントとかトンネリングイベントなどがあって、上から下に、下から上に、イベントが伝達している、という浅い知識はあったが、その知識だけではよくわからない現象があった。

具体的には、ボタンのコントロール<Button>MouseLeftButtonDown="Button_MouseLeftButtonDown"てな感じでマウスの左ボタンを押したときの処理を書いたが、どうもそのButton_MouseLeftButtonDownメソッドは通ってくれないっぽい。

なぜなのか調べたい。

やったこと

各イベントのハンドラを追加して、そこを通るかどうかを確かめていった。

見たイベント

ハンドラは、下記のものをセットした。

トンネリング バブリング 備考
PreviewMouseDown MouseDown
PreviewMouseUp MouseUp
PreviewTouchDown TouchDown
PreviewTouchUp TouchUp
PreviewStylusDown StylusDown
PreviewStylusUp StylusUp
PreviewMouseLeftButtonDown MouseLeftButtonDown
PreviewMouseLeftButtonUp MouseLeftButtonUp
PreviewMouseRightButtonDown MouseRightButtonDown
PreviewMouseRightButtonUp MouseRightButtonUp
なし Click <Button>に対してのセット

実験プログラム

コード

セットしたハンドラを通ったら、Debug.WriteLineでどのメソッドを通ったか出力するようにしている。
※今回の実験には関係ないが、IsManipulationEnabledをONOFFするためのボタンも付けている。

MainWindow.xaml
<Window x:Class="WpfApp64.MainWindow"
        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"
        xmlns:local="clr-namespace:WpfApp64"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="2*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>

        <Grid x:Name="MainGrid"
              Grid.Row="0"
              Background="Red"
              Margin="50"
              IsManipulationEnabled="True" 
              ManipulationStarting="MainGrid_ManipulationStarting" 
              ManipulationDelta="MainGrid_ManipulationDelta"
              ManipulationCompleted="MainGrid_ManipulationCompleted"

              PreviewMouseDown=             "MainGrid_PreviewMouseDown"             MouseDown="MainGrid_MouseDown"
              PreviewMouseUp=               "MainGrid_PreviewMouseUp"               MouseUp="MainGrid_MouseUp"
              PreviewTouchDown=             "MainGrid_PreviewTouchDown"             TouchDown="MainGrid_TouchDown"
              PreviewTouchUp=               "MainGrid_PreviewTouchUp"               TouchUp="MainGrid_TouchUp"
              PreviewStylusDown=            "MainGrid_PreviewStylusDown"            StylusDown="MainGrid_StylusDown"
              PreviewStylusUp=              "MainGrid_PreviewStylusUp"              StylusUp="MainGrid_StylusUp"
              PreviewMouseLeftButtonDown=   "MainGrid_PreviewMouseLeftButtonDown"   MouseLeftButtonDown="MainGrid_MouseLeftButtonDown"
              PreviewMouseLeftButtonUp=     "MainGrid_PreviewMouseLeftButtonUp"     MouseLeftButtonUp="MainGrid_MouseLeftButtonUp"
              PreviewMouseRightButtonDown=  "MainGrid_PreviewMouseRightButtonDown"  MouseRightButtonDown="MainGrid_MouseRightButtonDown"
              PreviewMouseRightButtonUp=    "MainGrid_PreviewMouseRightButtonUp"    MouseRightButtonUp="MainGrid_MouseRightButtonUp">

        </Grid>

        <Button Margin="60" Content="ボタン"
                Grid.Row="1"
                Click="Button_Click"
                PreviewMouseDown="Button_PreviewMouseDown"                          MouseDown="Button_MouseDown"
                PreviewMouseUp="Button_PreviewMouseUp"                              MouseUp="Button_MouseUp"
                PreviewTouchDown="Button_PreviewTouchDown"                          TouchDown="Button_TouchDown"
                PreviewTouchUp="Button_PreviewTouchUp"                              TouchUp="Button_TouchUp"
                PreviewStylusDown="Button_PreviewStylusDown"                        StylusDown="Button_StylusDown"
                PreviewStylusUp="Button_PreviewStylusUp"                            StylusUp="Button_StylusUp"
                PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"      MouseLeftButtonDown="Button_MouseLeftButtonDown"
                PreviewMouseLeftButtonUp="Button_PreviewMouseLeftButtonUp"          MouseLeftButtonUp="Button_MouseLeftButtonUp"
                PreviewMouseRightButtonDown="Button_PreviewMouseRightButtonDown"    MouseRightButtonDown="Button_MouseRightButtonDown"
                PreviewMouseRightButtonUp="Button_PreviewMouseRightButtonUp"        MouseRightButtonUp="Button_MouseRightButtonUp"
        />

        <Button Name="KirikaeBtn" 
                Width="150" Height="40" 
                HorizontalAlignment="Left" VerticalAlignment="Top" 
                Content="IsManipulationEnabled = true" 
                Click="IsManipulationEnabledChange"/>
    </Grid>
</Window>

MainWindow.xaml.cs
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Input;

namespace WpfApp64
{
    public partial class MainWindow : Window
    {
        public MainWindow() => InitializeComponent();

        private void Button_Click(object sender, RoutedEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_MouseDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_PreviewTouchDown(object sender, TouchEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_TouchDown(object sender, TouchEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_PreviewStylusDown(object sender, StylusDownEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); /* e.Handled = true; */ }
        private void Button_StylusDown(object sender, StylusDownEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_ManipulationStarting(object sender, ManipulationStartingEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_MouseDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_PreviewTouchDown(object sender, TouchEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_TouchDown(object sender, TouchEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_PreviewStylusDown(object sender, StylusDownEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_StylusDown(object sender, StylusDownEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_MouseRightButtonUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_MouseRightButtonUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_PreviewMouseUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_MouseUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_PreviewTouchUp(object sender, TouchEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_TouchUp(object sender, TouchEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_PreviewStylusUp(object sender, StylusEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void MainGrid_StylusUp(object sender, StylusEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_PreviewMouseUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_MouseUp(object sender, MouseButtonEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_PreviewTouchUp(object sender, TouchEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_TouchUp(object sender, TouchEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_PreviewStylusUp(object sender, StylusEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }
        private void Button_StylusUp(object sender, StylusEventArgs e) { Debug.WriteLine(MethodBase.GetCurrentMethod().Name); }

        private void IsManipulationEnabledChange(object sender, RoutedEventArgs e)
        {
            if (MainGrid.IsManipulationEnabled)
            {
                MainGrid.IsManipulationEnabled = false;
                KirikaeBtn.Content = "false";
            }
            else
            {
                MainGrid.IsManipulationEnabled = true;
                KirikaeBtn.Content = "true";
            }
        }
    }
}

画面

実験プログラムは下記のような画面。

  • 左上のボタンは、赤い領域のGridのIsManipulationEnabledをONOFFするためのボタン(今回は関係ない)
  • 赤い部分は、Gridに対して上に挙げたイベントのハンドラをセットした部分。
  • 下の細長いボタンは、Buttonに対して上に挙げたイベントのハンドラをセットした部分。

image.png

結果

下のButton部分を指でタッチしたとき

出力

下記のような結果になった。

Button_PreviewStylusDown
Button_StylusDown
Button_PreviewTouchDown
Button_TouchDown
Button_PreviewMouseLeftButtonDown
Button_PreviewMouseDown
Button_PreviewStylusUp
Button_StylusUp
Button_PreviewTouchUp
Button_TouchUp
Button_PreviewMouseLeftButtonUp
Button_PreviewMouseUp
Button_Click

絵で描くと
image.png

結果、Preview〇〇〇イベントがきたら、対応する〇〇〇イベントが来ているイベントもあるが、そうでないものもある。下記のようなクセがあるなと感じた。

クセがあるなと思う点その①

イメージとして、Preview〇〇〇イベントがきたら、対応する〇〇〇イベントが来るイメージだが、PreviewMouseLeftButtonDownPreviewMouseDownについては、Previewが2つ連続して来ている。

公式のPreviewMouseDownの解説によると、

PreviewMouseDownとPreviewMouseLeftButtonDownまたはPreviewMouseRightButtonDownは一緒に来る

とのこと。ゆえに、
PreviewのあとにPreviewなしが来るのではなく、連続してその2つのPreviewがくる
っぽい。

クセがあるなと思う点その②

下記の4つのトンネリングイベント(Preview_〇〇〇)に対するバブリングイベント(Previewない奴)を通っていない

  • PreviewMouseLeftButtonDown
  • PreviewMouseDown
  • PreviewMouseLeftButtonUp
  • PreviewMouseUp

公式のClickイベントの解説によると、

ButtonBaseを継承しているもの(Buttonがそれ)では、MouseLeftButtonDownがhandledとして扱われるので、MouseLeftButtonDownイベントは発生しない。
代わりにPreviewMouseLeftButtonDownを使えますよ。

とのこと。

その説明だけでは上の結果すべての謎が解けるわけではなかったが、動きからすると、
<Button>では、PreviewMouseLeftButtonDownに対応するMouseLeftButtonDownなど、マウスを押したとき系イベントのトンネリングイベントは来るが、バブリングイベントは来ない
ということっぽい。

上のやりたいことに書いた、Button_MouseLeftButtonDownメソッドは通ってくれないというのは、これが原因っぽい。

その他、気づいたこと

  • 指でタッチすると、指のタッチやスタイラスのタッチのイベントが来た後に、マウスのクリックのイベントが来る。
    ⇒タッチとマウスで同じことをさせたければ、マウスのイベントやClickイベントに処理を書けばよくて、タッチ時にタッチ特有の処理をさせたければ、タッチのイベントに処理を書けばよいっぽい。
  • Clickイベントは、マウスがDown⇒Upしたあとにくる。
    ⇒マウスのイベントが一通り片付いた後に、Clickとしての処理をするということか。
    ⇒基本、ボタンを押した時の処理はClickに書いて、ボタンではない、画面上のどこかの領域を押した、というときは、MouseDownなどのイベントに処理を書けばよいっぽい。

下のボタン部分をマウスでクリックしたとき

上では指でタッチをした時の動作を見たが、マウスでクリックしたときの動作も見た。

出力

Button_PreviewMouseLeftButtonDown
Button_PreviewMouseDown
Button_PreviewMouseLeftButtonUp
Button_PreviewMouseUp
Button_Click

絵で描くと
image.png

指でタッチしたときの結果から、StylusなんとかTouchなんとかを抜いた形。
マウスでボタンをクリックしたときも、やはりバブリングイベントは来ない。
これはイメージ通り。

tera1707
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away