Q086. DataGrid/XamDataGrid でスクロールバーをダブルクリックすると、MouseDoubleClickイベントが発生しちゃう!><

A.Forms の DataGridView はスクロールバーをダブルクリックしても MouseDoubleClick イベントは発生しません。しかし WPFDataGridXamDataGrid は、スクロールバーをダブルクリックすると MouseDoubleClick イベントが発生します。どうやらこれは仕様みたいです。これを回避するには、イベントを発行したオブジェクトで判定するしかなさそうです。そこで少し手が込みますが仕掛けを用意します。


まず Forms の DataGrid で試してみました。セルやヘッダをダブルクリックすると MouseDoubleClick イベントを発行しますが、スクロールバーをダブルクリックしてもイベントは発生しません。


次に WPF の DataGrid です。スクロールバーをダブルクリックすると、見事に MouseDoubleClick イベントが発生します。


e.OriginalSource に格納された要素をビジュアルツリーで見ると、上位要素が ScrollBar であることが確認できます。


これを回避するには、上位要素が ScrollBar か否かを判定するしかありません。そこで下記のクラスを用意します。
UIHelpers.FindParent メソッドは上位要素に指定した型があるか検索し、あればその要素を返却します。元ネタは stackoverflow.com の記事を参考にさせて頂きました。

using System.Windows;
using System.Windows.Media;

public static class UIHelpers {
    /// <summary>
    /// 指定要素の親要素を検索します。 
    /// </summary>
    public static DependencyObject FindParent(DependencyObject child, string typeName) {
        var parent = VisualTreeHelper.GetParent(child) as DependencyObject;
        if (parent != null) {
            if (parent.GetType().Name == typeName) {
                return parent;
            }
            else {
                parent = FindParent(parent, typeName);
            }
        }
        return parent;
    }
}


PreviewMouseDoubleClick イベント内で以下のように実走すれば、スクロールバーをダブルクリックしても MouseDoubleClick イベントの発生を抑止します。

private void dataGrid1_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e) {
    var source = e.OriginalSource as DependencyObject;
    var parent = UIHelpers.FindParent(source, "ScrollBar");
    if (parent != null) {
        e.Handled = true;
    }
}


しかしこれだと DataGrid を配置する全ての Window にイベントを実装しなければいけません。それでは保守が面倒なのでトリガーをひとつ用意します。プロジェクトの参照設定に System.Windows.Interactivity アセンブリを追加し、以下のクラスをプロジェクトに追加します。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;

// サンプルなので namespace はわざと外してます。ご注意ください。
public sealed class DataGridDoubleClickTrigger : TriggerBase<DataGrid> {

    protected override void OnAttached() {
        base.OnAttached();
        AssociatedObject.PreviewMouseDoubleClick += AssociatedObject_MouseDoubleClick;
    }

    protected override void OnDetaching() {
        base.OnDetaching();
        AssociatedObject.PreviewMouseDoubleClick -= AssociatedObject_MouseDoubleClick;
    }

    void AssociatedObject_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
        var source = e.OriginalSource as DependencyObject;
        if (source != null) {
            var presenter = UIHelpers.FindParent(source, "ScrollBar");
            if (presenter != null) {
                e.Handled = true;
            }
        }
    }
}


XAML です。XAML 名前空間に「interactivity」と「現在のプロジェクト」を追加し、DataGrid にインタラクショントリガーを設定、先ほど用意した DataGridDoubleClickTrigger を設定します。これでスクロールバーをダブルクリックしても MouseDoubleClick イベントは発生しません。

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="183" Width="323" ContentRendered="Window_ContentRendered" >
    
    <Grid>
        <DataGrid Name="dataGrid1" MouseDoubleClick="dataGrid1_MouseDoubleClick" >
            <i:Interaction.Triggers>
                <local:DataGridDoubleClickTrigger />
            </i:Interaction.Triggers>
        </DataGrid>
    </Grid>
</Window>


これを応用すれば、XamDataGrid でもスクロールバーのイベント回避が可能になります。


以下のクラスは XamDataGrid 用のトリガーです。DataRecord しかダブルクリックイベントを発生させないよう抑止してます。

using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using Infragistics.Windows.DataPresenter;

public sealed class XamDataGridDoubleClickTrigger : TriggerBase<XamDataGrid> {
    protected override void OnAttached() {
        base.OnAttached();
        AssociatedObject.PreviewMouseDoubleClick += AssociatedObject_MouseDoubleClick;
    }

    protected override void OnDetaching() {
        base.OnDetaching();
        AssociatedObject.PreviewMouseDoubleClick -= AssociatedObject_MouseDoubleClick;
    }

    void AssociatedObject_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
        var source = e.OriginalSource as DependencyObject;
        if (source != null) {
            var presenter = UIHelpers.FindParent(source, "DataRecordPresenter");
            if (presenter == null) {
                e.Handled = true;
            }
        }
    }
}


WPF FAQ の目次に戻る