WPF DataGridで列選択できるようにする
WPFのDataGridはSelectionUnitプロパティをCellOrRowHeaderにすると行ヘッダのクリックで行選択ができる。一方で列選択は用意されていない。必要になったので自前で再現してみたが、案外面倒くさかったので残しておく。
XAML
<DataGrid x:Name="MyGrid" SelectionMode="Extended" SelectionUnit="CellOrRowHeader"
CanUserSortColumns="False" CanUserReorderColumns="False"
CanUserResizeColumns="False" CanUserResizeRows="False">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<EventSetter Event="PreviewMouseDown" Handler="Column_MouseDown" />
<EventSetter Event="PreviewMouseMove" Handler="Column_MouseMove" />
<EventSetter Event="PreviewMouseUp" Handler="Column_MouseUp" />
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
セルの複数選択が必要なので、SelectionModeをExtended、SelectionUnitをCellまたはCellOrRowHeaderにする。また列ヘッダでのマウス操作で競合する、ソート、列の入れ替え、幅リサイズの機能は無効にする。
さらに列ヘッダにEventSetterでマウス操作のイベントハンドラを設定する。ColumnHeaderStyleを定義して、PreviewMouseDown、PreviewMouseMove、PreviewMouseUpのイベントハンドラを設定する。なおPreviewではないMouseDownイベントなどは発火しなかった。
コードビハインド
//列ヘッダドラッグ中
private bool IsColumnDrag = false;
//列ヘッダドラッグ開始列番号
private int ColumnDragStartOn = 0;
/// <summary>
/// 列ヘッダドラッグ開始
/// </summary>
private void Column_MouseDown(object sender, MouseButtonEventArgs e) {
var grid = MyGrid;
var clickOn = GetColumnNumberOfPoint(grid, e.GetPosition(grid).X);
grid.Focus(); //フォーカス
grid.SelectedCells.Clear();
//列選択
foreach(var x in grid.Items) {
var info = new DataGridCellInfo(x, grid.Columns[clickOn]);
grid.SelectedCells.Add(info);
}
IsColumnDrag = true;
ColumnDragStartOn = clickOn;
}
/// <summary>
/// 列ヘッダドラッグ中
/// </summary>
private void Column_MouseMove(object sender, MouseEventArgs e) {
if(!IsColumnDrag) return;
var grid = MyGrid;
var dragOn = GetColumnNumberOfPoint(grid, e.GetPosition(grid).X);
grid.SelectedCells.Clear();
//ドラッグ開始~現在地の列まで選択
var from = Math.Min(ColumnDragStartOn, dragOn);
var to = Math.Max(ColumnDragStartOn, dragOn);
foreach (var x in grid.Items) {
for (int i = from; i <= to; i++) {
var info = new DataGridCellInfo(x, grid.Columns[i]);
grid.SelectedCells.Add(info);
}
}
}
/// <summary>
/// 列ヘッダドラッグ解除
/// </summary>
private void Column_MouseUp(object sender, MouseButtonEventArgs e) {
IsColumnDrag = false;
}
/// <summary>
/// グリッドの相対位置にある列を取得
/// </summary>
/// <param name="grid">対象グリッド</param>
/// <param name="pointX">相対位置</param>
/// <returns>0から始まる列番号</returns>
private int GetColumnNumberOfPoint(DataGrid grid, double pointX) {
//行ヘッダサイズ
double p = grid.RowHeaderActualWidth;
//RowHeaderStyleの設定から取得
//double p = (double)grid.RowHeaderStyle.Setters
// .OfType<Setter>()
// .First(x=>x.Property.Name=="Width")
// .Value;
//左端以前
if(p >= pointX) return 0;
//ポインタ位置の列
for(int i=0; i<grid.Columns.Count; i++) {
p += grid.Columns[i].ActualWidth;
if(p >= pointX) return i;
}
//右端以降
return grid.Columns.Count -1;
}
マウスイベントが発生したときに、ポインタのX位置と重なる列のセルを選択状態にする。一度全てのセル選択をクリアした後に、Itemsをなめて各行と該当列のDataGridCellInfoを作りSelectedCellsに追加していく。
ポインタ位置の列の取得
マウスイベントの引数であるMouseEventArgsのGetPositionメソッドで、グリッドコントロールの原点からの相対位置を取得する。
グリッドの行ヘッダおよび各列の幅から、ポインタ位置と重なる列を判断する。なお実際の環境ではRowHeaderActualWidthがいい値にならなかったため、コメントのようにRowHeaderStyleの設定値から幅を取得した。
範囲選択の対応
行ヘッダと同じくドラッグで範囲選択するためには、MouseDownイベント発生時の列を保存しておき、MouseMoveイベントのたびに間のセルを選択しなおす。
なおWPFのコントロールではマウスを押し込んだまま動かすと、マウスポインタがコントロールの外(あるいはウィンドウの外)に出ても、イベントは元のコントロールで発火する。そのためMouseMoveのsender引数から対象の列ヘッダを取得できないが、マウスポインタが外に出た場合の処理は用意しないで済む。
できないこと
このサンプルではShift+クリックやCtrl+クリックでの複数選択に対応していない。恐らく作る機会は無い。
コメント
コメントを投稿