LoginSignup
2
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-09-16

背景

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

修正前のコード

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

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>

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

しかし、Buttonをクリックした時は、「Button1_Click」または「Button2_Click」のメッセージボックスしか表示されず、「Grid_MouseDown」のメッセージボックスが表示されません。
image.png

修正後のコード

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

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

イベントについて

イベントは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を使ってイベントハンドラーを登録することができます。
今回はコードビハインドにイベントハンドラーを置きたくないので、イベントハンドラーをBehaiviorに移動してButtonBase.ClickのRoutedEventHandlerとして登録しましょう。

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>
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");
        }
    }
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# マウスイベントより)

そのためButton.Clickより後に来るMouseUpのイベントハンドラーとして登録し、UIElement.AddHandler メソッドの handledEventsToo をtrueにすることで処理済のRoutedEventが実行されるようにイベントハンドラーを登録してあげます。

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

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/

https://blog.okazuki.jp/entry/2014/08/22/211021

http://codingseason.blogspot.com/2012/09/events-in-wpf.html

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2