[WPF]ButtonがあるGrid内のどこをクリックしてもEventHandlerが実行されるようにする方法

# 背景
Gridに配置したButtonなどのコントロールを含む全てのGrid内の領域で同一のイベントハンドラーが実行されるようにしたい時どうすればいいのか分からず、結構調べたのでまとめておきます。
間違いなどあればご指摘お願いします。

# 修正前のコード
Grid内どこでもクリックすると、「Grid_MouseDown」と書かれたメッセージボックスを表示させたいといったサンプル。

```xml:MainWindow
    <Grid x:Name="Grid" MouseDown="Grid_MouseDown" IsHitTestVisible="True" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Button x:Name="Button1" Click="Button_Click"
                Content="Click me!" Grid.Row="0" Grid.Column="0"/>
        <Button x:Name="Button2" Click="Button_Click"
                Content="Click me!" Grid.Row="1" Grid.Column="0"/>
    </Grid>

```
```csharp:MainWindow
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (sender is Grid item)
                MessageBox.Show(item.Name + "_MouseDown");
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (sender is Control item)
                MessageBox.Show(item.Name + "_Click");
        }
    }
```

上記のサンプルでは、Buttonがないところでクリックした場合は、「Grid_MouseDown」のメッセージボックスが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245399/5913daf4-8216-78ea-b5f2-b53606ccebee.png)

しかし、Buttonをクリックした時は、「Button1_Click」または「Button2_Click」のメッセージボックスしか表示されず、「Grid_MouseDown」のメッセージボックスが表示されません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245399/d44658e8-23c4-30ae-a7f2-d4d9d19a82a5.png)

# 修正後のコード
__GridにButtonBase.ClickイベントにGrid_MouseDownイベントハンドラーの登録を追加する__と、Buttonをクリック時でもGridに登録したイベントハンドラーが実行されるようになります。

```xml:MainWindow
    <!--修正前-->
    <!--<Grid x:Name="Grid" MouseDown="Grid_MouseDown" IsHitTestVisible="True" Background="Transparent">-->
    <!--修正後-->
    <Grid x:Name="Grid" MouseDown="Grid_MouseDown" ButtonBase.Click="Grid_MouseDown" 
          IsHitTestVisible="True" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Button x:Name="Button1" Click="Button_Click"
                Content="Click me!" Grid.Row="0" Grid.Column="0"/>
        <Button x:Name="Button2" Click="Button_Click"
                Content="Click me!" Grid.Row="1" Grid.Column="0"/>
    </Grid>
```
GridのMouseDownとButtonBase.Clickのイベントハンドラーは同じGrid_MouseDownを使いますのでxaml.csのコードは変更不要です。

Buttonをクリックした時は、「Button1_Click」または「Button2_Click」のメッセージボックスしか表示された後、「Grid_MouseDown」のメッセージボックスが表示されるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245399/6167b6db-a69b-01c0-79cb-bc1e7df88083.png)

# イベントについて
イベントはTunnelとBubbleがあります。
PreViewMouseDownなどPreViewがTunnel、ClickやMouseDownなどはBubbleになっています。

PreViewMouseDownとMouseDownイベントが実行される順番は
1. Grid.PreViewMouseDown
2. Button.PreViewMouseDown
3. Button.MouseDown
4. Grid.MouseDown
となります。
つまり、修正前のコードでは先に3. Button.MouseDownが実行されます。これは意図通りの挙動だといえます。
何故、4. Grid.MouseDownが実行されないかというと、処理済のRoutedEventは実行されなくなるためです。
そこで、修正後のコードのようにGridにButtonBase.Clickにイベントハンドラーを登録することで、添付イベントとしてButtonのクリック時にもGridで定義したイベントハンドラーが実行されるようになります。

# GridのButtonBase.ClickのEventHandlerをコードビハインドに置きたくない場合
ButtonBase.Clickは本来Gridには定義されていないRotedEventなので、添付イベントとして処理する方法で実現します。
[UIElement.AddHandler](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.uielement.addhandler?f1url=https%3A%2F%2Fmsdn.microsoft.com%2Fquery%2Fdev15.query%3FappId%3DDev15IDEF1%26l%3DJA-JP%26k%3Dk(System.Windows.UIElement.AddHandler);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.6.1);k(DevLang-csharp)%26rd%3Dtrue&view=netframework-4.8
)を使ってイベントハンドラーを登録することができます。
今回はコードビハインドにイベントハンドラーを置きたくないので、イベントハンドラーをBehaiviorに移動してButtonBase.ClickのRoutedEventHandlerとして登録しましょう。

```xml:MainWindow.xaml
    <Grid x:Name="Grid" IsHitTestVisible="True" Background="Transparent">
    <!--<Grid x:Name="Grid" MouseDown="Grid_MouseDown" ButtonBase.Click="Grid_MouseDown" IsHitTestVisible="True" Background="Transparent">-->
        <i:Interaction.Behaviors>
            <b:GridBehaivior />
        </i:Interaction.Behaviors>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Button x:Name="Button1" Click="Button_Click"
                Content="Click me!" Grid.Row="0" Grid.Column="0"/>
        <Button x:Name="Button2" Click="Button_Click"
                Content="Click me!" Grid.Row="1" Grid.Column="0"/>
    </Grid>
```
```csharp:MainWindow.xaml.cs
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        /* Behaiviorに移動
        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (sender is Grid item)
                MessageBox.Show(item.Name + "_MouseDown");
        }
        */
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (sender is Control item)
                MessageBox.Show(item.Name + "_Click");
        }
    }
```
```csharp:GridBehaivior.cs
    public class GridBehaivior : Behavior<Grid>
    {
        private RoutedEventHandler routedEventHandler;
        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.MouseDown += AssociatedObject_MouseDown;

            // ButtonBase.ClickイベントのRoutedEventHandlerを登録する
            routedEventHandler = new RoutedEventHandler(AssociatedObject_MouseDown);
            AssociatedObject.AddHandler(ButtonBase.ClickEvent, routedEventHandler);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.MouseDown -= AssociatedObject_MouseDown;
            AssociatedObject.RemoveHandler(UIElement.MouseDownEvent, routedEventHandler);
        }

        private void AssociatedObject_MouseDown(object sender, RoutedEventArgs e)
        {
            if (sender is Grid item)
                MessageBox.Show(item.Name + "_MouseDown");
        }
    }
```

## Button以外のUIElementがある場合でもEventHandlerが実行したい場合

UIElement.MouseUpのイベントハンドラーとして登録します。
MouseDownのイベントハンドラーとして登録すると、Button.Clickより先にButton.MouseDownが実行されるため処理済のRoutedEvent (Button.Click) は実行されなくなるためです。

マウスイベントは以下の順で実行されます。
>MouseDown→MouseHover→Click→MouseClick→MouseUp→MouseCaptureChanged

([車輪の再発明C# マウスイベント](http://mousou-degarasi.cocolog-nifty.com/blog/2009/05/c-6b6f.html)より)

そのためButton.Clickより後に来るMouseUpのイベントハンドラーとして登録し、[UIElement.AddHandler メソッド](https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.uielement.addhandler?view=netframework-4.8#System_Windows_UIElement_AddHandler_System_Windows_RoutedEvent_System_Delegate_System_Boolean_)の  handledEventsToo をtrueにすることで処理済のRoutedEventが実行されるようにイベントハンドラーを登録してあげます。

>AddHandler(RoutedEvent, Delegate, Boolean)
指定したルーティング イベントのルーティング イベント ハンドラーを追加します。このハンドラーは、現在の要素のハンドラー コレクションに追加されます。 イベント ルート上の別の要素により既にハンドル済みとしてマークされているルーティング イベントに対し、指定したハンドラーが呼び出されるようにするには、handledEventsToo を true に指定します。

```csharp:GridBehaivior.cs
    public class GridBehaivior:Behavior<Grid>
    {
        private RoutedEventHandler routedEventHandler;

        protected override void OnAttached()
        {
            base.OnAttached();

            //AssociatedObject.MouseDown += AssociatedObject_MouseDown;
            //AssociatedObject.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(AssociatedObject_MouseDown));

            // UIElement.MouseUpEventイベントのRoutedEventHandlerを登録する
            routedEventHandler = new RoutedEventHandler(AssociatedObject_MouseDown);
            AssociatedObject.AddHandler(UIElement.MouseUpEvent, routedEventHandler, true);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();

            //AssociatedObject.MouseDown -= AssociatedObject_MouseDown;
            AssociatedObject.RemoveHandler(UIElement.MouseDownEvent, routedEventHandler);
        }

        private void AssociatedObject_MouseDown(object sender, RoutedEventArgs e)
        {
            if (sender is Grid item)
                MessageBox.Show(item.Name + "_MouseDown" + Environment.NewLine + 
                    "OriginalSource: " + e.OriginalSource);
        }
    }
```


# 参考
[http://csharphelper.com/blog/2015/03/understand-event-bubbling-and-tunneling-in-wpf-and-c/](http://csharphelper.com/blog/2015/03/understand-event-bubbling-and-tunneling-in-wpf-and-c/)  
[https://blog.okazuki.jp/entry/2014/08/22/211021](https://blog.okazuki.jp/entry/2014/08/22/211021)  
[http://codingseason.blogspot.com/2012/09/events-in-wpf.html](http://codingseason.blogspot.com/2012/09/events-in-wpf.html)