SilverlightのDataGridにダブルクリックイベントを実装する。

なぜかSilverlightのDataGridにはダブルクリックイベントがありません。そこで自前で実装してみました。実装は、ロードされた行インスタンスのマウスアップイベントを利用することにします。
最初にDataGridを継承したクラスを作成します。

public class ExDataGrid : DataGrid
{
}

ExDataGridでデータグリッドロウがロードされたときに呼ばれるOnLoadingRowメソッドをオーバライドします。OnLoadingRowメソッドでは行クリックを検知するためにイベントハンドラを登録しています。

/// <summary>
/// System.Windows.Controls.DataGrid.LoadingRow イベントを発生させる。
/// </summary>
/// <param name="e">イベントのデータ。</param>
protected override void OnLoadingRow(DataGridRowEventArgs e)
{
    base.OnLoadingRow(e);

    DataGridRow row = e.Row;

    // DataGridRowにダブルクリック検知用イベントハンドラを追加する。
    row.MouseLeftButtonUp -= new MouseButtonEventHandler(this.Row_MouseLeftButtonUp);
    row.MouseLeftButtonUp += new MouseButtonEventHandler(this.Row_MouseLeftButtonUp);
}

データグリッドロウにイベントハンドラを挿したままでは、メモリリークを起こします。OnUnloadingRowメソッドでイベントハンドラを削除します。

/// <summary>
/// System.Windows.Controls.DataGrid.UnloadingRow イベントを発生させる。
/// </summary>
/// <param name="e">イベントのデータ。</param>
protected override void OnUnloadingRow(DataGridRowEventArgs e)
{
    base.OnUnloadingRow(e);

    e.Row.MouseLeftButtonUp -= new MouseButtonEventHandler(this.Row_MouseLeftButtonUp);
}

後は、Row_MouseLeftButtonUpイベントハンドラを定義するだけです。

private void Row_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    DateTime now = DateTime.Now;
    DateTime lastClickTime = this.lastClickInfo.Time;
    DataGridRow lastClickRow = this.lastClickInfo.Row;

    // 直前にクリックされた行が今回クリックされた行と一致し、
    // かつ、クリックされた間隔が一定時間以内に収まっているか?
    if (lastClickRow == sender
        && (now - lastClickTime).TotalMilliseconds < DoubleClickDureationMilliSeconds)
    {
        // 直前にクリックされた情報を初期化。
        this.lastClickInfo = LastClickInfo.Empty;

        // ダブルクリックイベントを発行
        this.OnDoubleClick(sender as DataGridRow);
    }
    else
    {
        // クリックした行情報を保持しておく
        this.lastClickInfo = new LastClickInfo(now, sender as DataGridRow);
    }
}

lastClickInfoフィールドはクリックされた行情報を管理するクラス(LastClickInfoクラス)の変数です。

/// <summary> クリックした行情報を保持する。 </summary>
private class LastClickInfo
{
    public static readonly LastClickInfo Empty = new LastClickInfo(DateTime.MinValue, null);

    public LastClickInfo(DateTime time, DataGridRow row)
    {
        this.Time = time;
        this.Row = row;
    }

    /// <summary> クリックされた時間を取得する。 </summary>
    public DateTime Time { get; private set; }

    /// <summary> クリックされた行を取得する。 </summary>
    public DataGridRow Row { get; private set; }
}

以上で、実装完了となります。
DataGridのダブルクリックイベント実装は、

  • クリックした行を判定する。
  • クリック間隔を判定する。

の2点さえ押さえられれば簡単に実装できると思います。
最後にExDataGridの全ソースを乗せておきます。

/// <summary>
/// DataGridを拡張したコントロール
/// </summary>
public class ExDataGrid : DataGrid
{
    private const int DoubleClickDureationMilliSeconds = 250;
    private LastClickInfo lastClickInfo = LastClickInfo.Empty;

    /// <summary>
    /// ダブルクリックされたときに発行する。
    /// </summary>
    public event EventHandler DoubleClick;

    /// <summary>
    /// System.Windows.Controls.DataGrid.LoadingRow イベントを発生させる。
    /// </summary>
    /// <param name="e">イベントのデータ。</param>
    protected override void OnLoadingRow(DataGridRowEventArgs e)
    {
        base.OnLoadingRow(e);

        DataGridRow row = e.Row;

        // DataGridRowにダブルクリック検知用イベントハンドラを追加する。
        row.MouseLeftButtonUp -= new MouseButtonEventHandler(this.Row_MouseLeftButtonUp);
        row.MouseLeftButtonUp += new MouseButtonEventHandler(this.Row_MouseLeftButtonUp);
    }

    /// <summary>
    /// System.Windows.Controls.DataGrid.UnloadingRow イベントを発生させる。
    /// </summary>
    /// <param name="e">イベントのデータ。</param>
    protected override void OnUnloadingRow(DataGridRowEventArgs e)
    {
        base.OnUnloadingRow(e);

        e.Row.MouseLeftButtonUp -= new MouseButtonEventHandler(this.Row_MouseLeftButtonUp);
    }

    private void Row_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        DateTime now = DateTime.Now;
        DateTime lastClickTime = this.lastClickInfo.Time;
        DataGridRow lastClickRow = this.lastClickInfo.Row;

        // 直前にクリックされた行が今回クリックされた行と一致し、
        // かつ、クリックされた間隔が一定時間以内に収まっているか?
        if (lastClickRow == sender
            && (now - lastClickTime).TotalMilliseconds < DoubleClickDureationMilliSeconds)
        {
            // 直前にクリックされた情報を初期化。
            this.lastClickInfo = LastClickInfo.Empty;

            // ダブルクリックイベントを発行
            this.OnDoubleClick(sender as DataGridRow);
        }
        else
        {
            // クリックした行情報を保持しておく
            this.lastClickInfo = new LastClickInfo(now, sender as DataGridRow);
        }
    }

    private void OnDoubleClick(DataGridRow row)
    {
        if (this.DoubleClick != null)
        {
            this.DoubleClick(row, EventArgs.Empty);
        }
    }

    /// <summary> クリックした行情報を保持する。 </summary>
    private class LastClickInfo
    {
        public static readonly LastClickInfo Empty = new LastClickInfo(DateTime.MinValue, null);

        public LastClickInfo(DateTime time, DataGridRow row)
        {
            this.Time = time;
            this.Row = row;
        }

        /// <summary> クリックされた時間を取得する。 </summary>
        public DateTime Time { get; private set; }

        /// <summary> クリックされた行を取得する。 </summary>
        public DataGridRow Row { get; private set; }
    }
}