DaraGridのイベントからセルの情報をVMで活用する。(壱)

DaraGridが持つセル選択、移動系イベントは以下の3つがある。

・CurrentCellChanged: 現在セル(CurrentCell)が変更された
・SelectionChanged: 選択項目が変更された
・SelectedCellsChanged: SelectedCellsコレクションが変更された


仕事で欲しかったのは、

・CellCliecked:セルがクリックされた
・CellDoubleClicked:セルがダブルクリックされた
・CurrentCellChanged:セルが移動した
・SelectionChanged:選択項目が変更された


で、微妙に求めるものがあったりなかったりする。特にダブルクリックは欲しかった。シングルクリックと区別したかったから。
シングルクリックには反応しないけど、ダブルクリックには反応するなんて機能をよく実装するし。
これらのイベントをMVVMで処理したいなーと思ったら、前に書いてた。
MVVMパターンで、イベントをどうにかしよう。
ただし、上の実装でも、どの"セル"がクリックされたか?なんて具体的な情報は、自動でセットされるわけではない。仕事では、各イベントに対応する添付プロパティを実装し、コマンドパラメータに具体的な情報を渡すようにした。
添付プロパティ:CellCliecked用コマンド

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;

namespace TawamureDays {

/// <summary>
/// DataGridを拡張するためのクラス
/// </summary>
public static class DataGridExtender {

/// <summary>
/// セルクリックイベント用コマンドを取得します。
/// </summary>
/// <param name="obj">対象オブジェクト</param>
/// <returns>セルクリックイベント用コマンド</returns>
public static ICommand GetCellClickedCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(CellClickedCommandProperty);
}

/// <summary>
/// セルクリックイベント用コマンドを設定します。
/// </summary>
/// <param name="obj">対象オブジェクト</param>
/// <param name="value">セルクリックイベント用コマンド</param>
public static void SetCellClickedCommand(DependencyObject obj, ICommand value) {
obj.SetValue(CellClickedCommandProperty, value);
}

/// <summary>セルクリックイベント用コマンド</summary>
public static readonly DependencyProperty CellClickedCommandProperty =
DependencyProperty.RegisterAttached("CellClickedCommand",
typeof(ICommand),
typeof(DataGridExtender),
new UIPropertyMetadata(null,
OnCellClickedCommandPropertyChanged));
}
}

プロパティ値変更イベント用メソッド(OnCellClickedCommandPropertyChanged)
namespace TawamureDays {

/// <summary>
/// DataGridを拡張するためのクラス
/// </summary>
public static class DataGridExtender {

/// <summary>
/// CellClickedCommandプロパティ値変更イベントハンドラ
/// </summary>
/// <param name="dpObj">変更元オブジェクト</param>
/// <param name="e">イベント引数</param>
private static void OnCellClickedCommandPropertyChanged(
DependencyObject dpObj, DependencyPropertyChangedEventArgs e) {

var dataGrid = dpObj as DataGrid;

if (dataGrid == null) {
return;
}

dataGrid.MouseLeftButtonDown -=
new MouseButtonEventHandler(DataGrid_MouseLeftButtonDown);

if (e.NewValue != null) {
dataGrid.MouseLeftButtonDown +=
new MouseButtonEventHandler(DataGrid_MouseLeftButtonDown);
}

return;
}

/// <summary>
/// マウス左ボタン押下イベントハンドラ<br/>
/// memo:<br/>
/// カレント(アクティブ)セル変更目的のマウスクリックには反応しません。<br/>
/// 同一セル上のクリックに反応します。
/// 同一セル上のダブルクリックもこっちが先に発生します。<br/>
/// </summary>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private static void DataGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
if (e.LeftButton != MouseButtonState.Pressed) {
//左ボタンが押されていなければ無視
return;
}

if (e.ClickCount != 1) {
//ボタン連打は無視する
e.Handled = false;
return;
}

DataGrid srcGrid = sender as DataGrid;

if (srcGrid != null) {
var command = DataGridExtender.GetCellClickedCommand(srcGrid) as ICommand;

if (command != null) {

//コマンドパラメータを生成します。
var parameter = DataGridExtender.CreateCommandParameter(srcGrid, e);

if (command.CanExecute(parameter)) {
command.Execute(parameter);
}
}
}

return;
}
}
}

DataGrid(というか、UIElement)が持つマウス系イベントにハンドラメソッドを登録する。
CellClickedは、シングルクリックのみ受け付けるので、e.ClickCount(クリック回数)が1以外の時は、処理をしないようにしている。ただし、セルから別セルへの移動には、CellClickedは反応しない。このあたりが曲者で、CellChangedと上手く連携しないと思ったような動きにならない。
添付プロパティに設定されているであろうコマンド(ICommand実装)を確認できたら、コマンドを実行する。
コマンドに与えるパラメータが肝心で、クリックされたセルの情報を渡す。このセルの情報を取得(作成)するメソッドが↓になる。

namespace TawamureDays {

/// <summary>
/// DataGridを拡張するためのクラス
/// </summary>
public static class DataGridExtender {

/// <summary>
/// ダブルクリック、シングルクリック用コマンドパラメータを生成します。
/// </summary>
/// <param name="srcGrid">イベント元のデータグリッド</param>
/// <param name="e">マウスイベント引数</param>
/// <returns>コマンドパラメータ用Tupleオブジェクト<br/>
/// 内訳:<br/>
/// Item1:(クリックされた)行インデックス<br/>
/// Item2:(クリックされた)列の表示インデックス(DisplayIndex)<br/>
/// Item3:データバインドされていれば、バインドされているItem<br/>
/// Item4:(クリックされた)列にバインドされているItemのプロパティ名<br/>
/// Item5:行全体が選択単位かどうか<br/>
/// </returns>
private static Tuple<int, int, object, string, bool> CreateCommandParameter(DataGrid srcGrid, MouseButtonEventArgs e) {

//DataGridCellだとは限らない(TextBlockだったりする)
DependencyObject dpObj = e.OriginalSource as DependencyObject;
string boundProperty = string.Empty;
int displayIndex = -1;
object boundData = null;

if (dpObj != null) {
//VisualTreeをさかのぼり、DataGridCellを探します。
dpObj = dpObj.FindAncestor<DataGridCell>();

if (dpObj != null) {
var dataGridCell = dpObj as DataGridCell;
//表示位置
displayIndex = dataGridCell.Column.DisplayIndex;
//バインドされているプロパティ情報を取得します。
boundProperty = dataGridCell.Column.GetBindingPath();

//VisualTreeをさかのぼり、DataGridRowを探します。
dpObj = dpObj.FindAncestor<DataGridRow>();
}
}

int rowIndex = -1;

if (srcGrid != null) {
if (dpObj != null) {
//dpObj != null → DataGridRowオブジェクト
//クリックされた行
rowIndex = srcGrid.ItemContainerGenerator.IndexFromContainer(dpObj);
//クリックされた行にバインドされているオブジェクト
boundData = ((DataGridRow)dpObj).Item;
}
}

return Tuple.Create(rowIndex, displayIndex, boundData, boundProperty,
srcGrid.SelectionUnit == DataGridSelectionUnit.FullRow);
}
}
}

戻り値はTupleオブジェクト。
・Item1:(クリックされた)行インデックス(0~)
・Item2:(クリックされた)列の表示インデックス(DisplayIndex)
・Item3:データバインドされていれば、バインドされているItem
・Item4:(クリックされた)列にバインドされているItemのプロパティ名
・Item5:行全体が選択単位かどうか
この戻り値の要素は、VM側でセルクリックや選択処理を実装する過程において増えていき、現在はこの5つになっている。
↑のメソッドはダブルクリックようにも使われる。

同じ様なノリで、ダブルクリック用の添付プロパティも実装する。(ちょい長い)
添付プロパティ:CellDoubleCliecked用コマンド

namespace TawamureDays {

/// <summary>
/// DataGridを拡張するためのクラス
/// </summary>
public static class DataGridExtender {

/// <summary>
/// セルダブルクリックイベント用コマンドを取得します。
/// </summary>
/// <param name="obj">対象オブジェクト</param>
/// <returns>セルダブルクリックイベント用コマンド</returns>
public static ICommand GetCellDoubleClickedCommand(DependencyObject obj) {
return (ICommand)obj.GetValue(CellDoubleClickedCommandProperty);
}

/// <summary>
/// セルダブルクリックイベント用コマンドを設定します。
/// </summary>
/// <param name="obj">対象オブジェクト</param>
/// <param name="value">セルダブルクリックイベント用コマンド</param>
public static void SetCellDoubleClickedCommand(DependencyObject obj, ICommand value) {
obj.SetValue(CellDoubleClickedCommandProperty, value);
}

/// <summary>セルダブルクリックイベント用コマンド</summary>
public static readonly DependencyProperty CellDoubleClickedCommandProperty =
DependencyProperty.RegisterAttached("CellDoubleClickedCommand",
typeof(ICommand),
typeof(DataGridExtender),
new UIPropertyMetadata(null,
OnCellDoubleClickedCommandPropertyChanged));

/// <summary>
/// CellDoubleClickedCommandプロパティ値変更イベントハンドラ
/// </summary>
/// <param name="dpObj">変更元オブジェクト</param>
/// <param name="e">イベント引数</param>
private static void OnCellDoubleClickedCommandPropertyChanged(
DependencyObject dpObj, DependencyPropertyChangedEventArgs e) {

DataGrid dataGrid = dpObj as DataGrid;

if (dataGrid != null) {
//ダブルクリックイベント用ハンドラを追加あるいは削除します。
dataGrid.MouseLeftButtonDown -=
new MouseButtonEventHandler(DgGrid_MouseDoubleClick);

if (e.NewValue != null) {
dataGrid.MouseLeftButtonDown +=
new MouseButtonEventHandler(DgGrid_MouseDoubleClick);
}
}

return;
}

/// <summary>
/// 別のセルに移動する目的でダブルクリックした際に発生します。<br/>
/// 同一セル上でダブルクリックした場合、<br/>
/// MouseLeftButtonDownイベントが先行して発生します。<br/>
/// </summary>
/// <remarks>DataGridDoubleClickCommandプロパティに関連します。</remarks>
/// <param name="sender">イベント発生元</param>
/// <param name="e">イベント引数</param>
private static void DgGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
if (e.LeftButton != MouseButtonState.Pressed) {
//左ボタンが押されていなければ無視
return;
}

if (e.ClickCount != 2) {
//2回(ダブルクリック以外は無視
e.Handled = false;
return;
}

DataGrid srcGrid = sender as DataGrid;

if (srcGrid != null) {
ICommand command = DataGridExtender.
GetCellDoubleClickedCommand(srcGrid) as ICommand;

//シングルクリック用のコマンドが存在する場合、そちらが先に処理されてしまう。
//ダブルクリックは処理されない。
if (command != null) {
//コマンドパラメータを生成します。
var parameter = DataGridExtender.CreateCommandParameter(srcGrid, e);

if (command.CanExecute(parameter)) {
command.Execute(parameter);
}
}
}

return;
}
}
}
ほとんどCellClickedと同じ。e.ClickCountに関するロジックが違うだけ。MouseLeftButtonDownイベントで処理したんだけど、今よく見ると、MouseDoubleClickというイベントもある感じなんだよな。なんでこっちで実装しなかったんだろ?ま、いっか。
イベントハンドラ(メソッド)は似たような処理だけど、いっしょにするとややこしいので分けてある。
長くなったので、続きは次のエントリで。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m