注意:ここで紹介しているDataGridは、System.Windows.Forms名前空間のDataGrid(Windowsフォーム)です。System.Web.UI.WebControls名前空間のDataGrid(Webフォーム)ではありません。
Windowsアプリケーションにおいて、ListViewコントロールのMultiSelectプロパティをFalseにした時のように、DataGridコントロールで複数行選択することができなく、さらに、セルをクリックした時にセルがアクティブになることなく、そのセルの行全体が選択されるようにする方法について説明します。
まず、DataGridで一つの行だけを選択できるようにする方法を考えてみましょう。これに関しては、「How can I make my DataGrid support a single select mode, and not the default multiselect mode?」や「Make the DataGrid support single select vs multiselect mode.」でその方法が紹介されています。
これらで紹介されている方法は、DataGridクラスのOnMouseMoveをオーバーライドして、マウスドラッグによる選択が無効になるようにし、さらにOnMouseDownをオーバーライドして、前に選択した行の選択を取り消すという方法です。
しかしこのやり方では、キーボードによる複数行の選択を全く考慮しておらず、不完全です。(マウスの部分も不完全な点が多々ありますが。)
そこで以下に示すコードでは、OnMouseMoveとOnMouseDownをオーバーライドする以外に、ProcessCmdKeyをオーバーライドして、Shiftキーと上矢印または下矢印キーが押された時、ShiftキーとCtrlキーとHomeまたはEndキーが押された時、CtrlキーとAキーが押された時のそれぞれの場合を無効にしています。
Imports System Imports System.Drawing Imports System.Windows.Forms Namespace Dobon.Samples.Forms Public Class MyDataGrid Inherits DataGrid Private lastRowSelected As Integer = -1 Private rowSelecting As Boolean = False Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs) If rowSelecting = False OrElse _ e.Button <> MouseButtons.Left Then MyBase.OnMouseMove(e) End If End Sub Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs) rowSelecting = False Dim info As HitTestInfo = Me.HitTest(e.X, e.Y) If e.Button = MouseButtons.Left AndAlso _ (info.Type = HitTestType.Cell OrElse _ info.Type = HitTestType.RowHeader) Then '選択されている行をリセットする If lastRowSelected <> -1 Then Dim cm As CurrencyManager = _ CType(BindingContext(DataSource, DataMember), _ CurrencyManager) If lastRowSelected < cm.Count Then Me.UnSelect(lastRowSelected) Else Me.ResetSelection() End If End If If info.Type = HitTestType.Cell Then 'セル上の時 lastRowSelected = -1 MyBase.OnMouseDown(e) Else If info.Type = HitTestType.RowHeader Then '行ヘッダ上の時 If (Control.ModifierKeys And Keys.Shift) = 0 Then MyBase.OnMouseDown(e) Else CurrentCell = _ New DataGridCell(info.Row, info.Column) End If Me.Select(info.Row) lastRowSelected = info.Row rowSelecting = True End If Else MyBase.OnMouseDown(e) End If End Sub Protected Overrides Function ProcessCmdKey( _ ByRef msg As Message, ByVal keyData As Keys) As Boolean Const WM_KEYDOWN As Integer = &H100 Const WM_KEYUP As Integer = &H101 If msg.Msg = WM_KEYDOWN OrElse msg.Msg = WM_KEYUP Then Dim keyCode As Keys = _ CType(CInt(keyData), Keys) And Keys.KeyCode 'Shift+Up,Downキーでの複数行選択を防止 If (keyData And Keys.Shift) = Keys.Shift AndAlso _ (keyCode = Keys.Up OrElse keyCode = Keys.Down) Then Return True End If 'Shift+Ctrl+Home,Endキーでの複数行選択を防止 If (keyData And Keys.Shift) = Keys.Shift AndAlso _ (keyData And Keys.Control) = Keys.Control AndAlso _ (keyCode = Keys.Home OrElse keyCode = Keys.End) Then Return True End If 'Ctrl+Aキーでの複数行選択を防止 If (keyData And Keys.Control) = Keys.Control AndAlso _ keyCode = Keys.A Then Return True End If End If Return MyBase.ProcessCmdKey(msg, keyData) End Function End Class End Namespace
using System; using System.Drawing; using System.Windows.Forms; namespace Dobon.Samples.Forms { public class MyDataGrid : DataGrid { private int lastRowSelected = -1; private bool rowSelecting = false; protected override void OnMouseMove(MouseEventArgs e) { if (rowSelecting == false || e.Button != MouseButtons.Left) base.OnMouseMove(e); } protected override void OnMouseDown(MouseEventArgs e) { rowSelecting = false; HitTestInfo info = this.HitTest(e.X, e.Y); if (e.Button == MouseButtons.Left && (info.Type == HitTestType.Cell || info.Type == HitTestType.RowHeader)) { //選択されている行をリセットする if (lastRowSelected != -1) { CurrencyManager cm = (CurrencyManager) BindingContext[DataSource, DataMember]; if (lastRowSelected < cm.Count) this.UnSelect(lastRowSelected); else this.ResetSelection(); } if (info.Type == HitTestType.Cell) { //セル上の時 lastRowSelected = -1; base.OnMouseDown(e); } else if (info.Type == HitTestType.RowHeader) { //行ヘッダ上の時 if ((Control.ModifierKeys & Keys.Shift) != Keys.Shift) base.OnMouseDown(e); else CurrentCell = new DataGridCell(info.Row, info.Column); this.Select(info.Row); lastRowSelected = info.Row; rowSelecting = true; } } else { base.OnMouseDown(e); } } protected override bool ProcessCmdKey( ref Message msg, Keys keyData) { const int WM_KEYDOWN = 0x100; const int WM_KEYUP = 0x101; if (msg.Msg == WM_KEYDOWN || msg.Msg == WM_KEYUP) { Keys keyCode = (Keys)(int)keyData & Keys.KeyCode; //Shift+Up,Downキーでの複数行選択を防止 if ((keyData & Keys.Shift) == Keys.Shift && (keyCode == Keys.Up || keyCode == Keys.Down)) return true; //Shift+Ctrl+Home,Endキーでの複数行選択を防止 if ((keyData & Keys.Shift) == Keys.Shift && (keyData & Keys.Control) == Keys.Control && (keyCode == Keys.Home || keyCode == Keys.End)) return true; //Ctrl+Aキーでの複数行選択を防止 if ((keyData & Keys.Control) == Keys.Control && keyCode == Keys.A) return true; } return base.ProcessCmdKey(ref msg, keyData); } } }
次にセルがアクティブにならないようにする方法を考えます。この方法は、「How can I make my grid never have an active edit cell and always select whole rows (as in a browser-type grid)?」や「How can I prevent all the cells in my DataGrid from being edited without deriving GridColumnStyle?で紹介されています。前者は、GridColumnStyleを使い、GridColumnStyleのEditメソッドをオーバーライドし、編集できないようにする方法です。後者は、GridColumnStyleを使わずに、DataGrid.Controlsからスクロールバー以外のコントロールを削除するという方法です。
ここでは、後者の方法を採用し、DataGridのOnControlAddedメソッドをオーバーライドして、スクロールバー以外のコントロールが追加された時は、これを削除するようにします。(ただしこの方法ではDataGridBoolColumnによるチェックボックスはアクティブになってしまうようです。)
Protected Overrides Sub OnControlAdded( _ ByVal e As ControlEventArgs) MyBase.OnControlAdded(e) If Not TypeOf e.Control Is VScrollBar AndAlso _ Not TypeOf e.Control Is HScrollBar Then Me.Controls.Remove(e.Control) End If End Sub
protected override void OnControlAdded(ControlEventArgs e) { base.OnControlAdded(e); if (!(e.Control is VScrollBar) && !(e.Control is HScrollBar)) this.Controls.Remove(e.Control); }
セルをクリックした時に行全体が選択されるようにするには、「5.11 How can I select the entire row when the user clicks on a cell in the row」にあるように、DataGridのMouseUpイベントで行を選択するようにすればよいでしょう。
クリックでも行が選択されるようにするには、CurrentCellChangedイベントなど、適当なイベントを使ってください。ここでは、OnMouseDownメソッドで行を選択するようにします。
以上の考察により書かれたDataGridクラスの派生クラス(MyDataGridクラス)のコードを以下に示します。
これを使用するには、DataGridの代わりにこのクラスを使うようにします。
注意:新しく作成したクラスを、基のコントロールクラスの代わりに使用するという意味が分からないという方は、こちらをご覧ください。
Imports System Imports System.Drawing Imports System.Windows.Forms Namespace Dobon.Samples.Forms Public Class MyDataGrid Inherits DataGrid Private lastRowSelected As Integer = -1 Private rowSelecting As Boolean = False Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs) If rowSelecting = False OrElse _ e.Button <> MouseButtons.Left Then MyBase.OnMouseMove(e) End If End Sub Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs) rowSelecting = False Dim info As HitTestInfo = Me.HitTest(e.X, e.Y) If e.Button = MouseButtons.Left AndAlso _ (info.Type = HitTestType.Cell OrElse _ info.Type = HitTestType.RowHeader) Then '選択されている行をリセットする If lastRowSelected <> -1 Then Dim cm As CurrencyManager = _ CType(BindingContext(DataSource, DataMember), _ CurrencyManager) If lastRowSelected < cm.Count Then Me.UnSelect(lastRowSelected) Else Me.ResetSelection() End If End If If info.Type = HitTestType.Cell Then 'セル上の時 lastRowSelected = -1 MyBase.OnMouseDown(e) Me.Select(info.Row) Else If info.Type = HitTestType.RowHeader Then '行ヘッダ上の時 If (Control.ModifierKeys And Keys.Shift) <> Keys.Shift Then MyBase.OnMouseDown(e) Else CurrentCell = _ New DataGridCell(info.Row, info.Column) End If Me.Select(info.Row) lastRowSelected = info.Row rowSelecting = True End If Else MyBase.OnMouseDown(e) End If End Sub Protected Overrides Function ProcessCmdKey( _ ByRef msg As Message, ByVal keyData As Keys) As Boolean Const WM_KEYDOWN As Integer = &H100 Const WM_KEYUP As Integer = &H101 If msg.Msg = WM_KEYDOWN OrElse msg.Msg = WM_KEYUP Then Dim keyCode As Keys = _ CType(CInt(keyData), Keys) And Keys.KeyCode 'Shift+Up,Downキーでの複数行選択を防止 If (keyData And Keys.Shift) = Keys.Shift AndAlso _ (keyCode = Keys.Up OrElse keyCode = Keys.Down) Then Return True End If 'Shift+Ctrl+Home,Endキーでの複数行選択を防止 If (keyData And Keys.Shift) = Keys.Shift AndAlso _ (keyData And Keys.Control) = Keys.Control AndAlso _ (keyCode = Keys.Home OrElse keyCode = Keys.End) Then Return True End If 'Ctrl+Aキーでの複数行選択を防止 If (keyData And Keys.Control) = Keys.Control AndAlso _ keyCode = Keys.A Then Return True End If End If Return MyBase.ProcessCmdKey(msg, keyData) End Function Protected Overrides Sub OnControlAdded( _ ByVal e As ControlEventArgs) MyBase.OnControlAdded(e) If Not TypeOf e.Control Is VScrollBar AndAlso _ Not TypeOf e.Control Is HScrollBar Then Me.Controls.Remove(e.Control) End If End Sub End Class End Namespace
using System; using System.Drawing; using System.Windows.Forms; namespace Dobon.Samples.Forms { public class MyDataGrid : DataGrid { private int lastRowSelected = -1; private bool rowSelecting = false; protected override void OnMouseMove(MouseEventArgs e) { if (rowSelecting == false || e.Button != MouseButtons.Left) base.OnMouseMove(e); } protected override void OnMouseDown(MouseEventArgs e) { rowSelecting = false; HitTestInfo info = this.HitTest(e.X, e.Y); if (e.Button == MouseButtons.Left && (info.Type == HitTestType.Cell || info.Type == HitTestType.RowHeader)) { //選択されている行をリセットする if (lastRowSelected != -1) { CurrencyManager cm = (CurrencyManager) BindingContext[DataSource, DataMember]; if (lastRowSelected < cm.Count) this.UnSelect(lastRowSelected); else this.ResetSelection(); } if (info.Type == HitTestType.Cell) { //セル上の時 lastRowSelected = -1; base.OnMouseDown(e); this.Select(info.Row); } else if (info.Type == HitTestType.RowHeader) { //行ヘッダ上の時 if ((Control.ModifierKeys & Keys.Shift) != Keys.Shift) base.OnMouseDown(e); else CurrentCell = new DataGridCell(info.Row, info.Column); this.Select(info.Row); lastRowSelected = info.Row; rowSelecting = true; } } else { base.OnMouseDown(e); } } protected override bool ProcessCmdKey( ref Message msg, Keys keyData) { const int WM_KEYDOWN = 0x100; const int WM_KEYUP = 0x101; if (msg.Msg == WM_KEYDOWN || msg.Msg == WM_KEYUP) { Keys keyCode = (Keys)(int)keyData & Keys.KeyCode; //Shift+Up,Downキーでの複数行選択を防止 if ((keyData & Keys.Shift) == Keys.Shift && (keyCode == Keys.Up || keyCode == Keys.Down)) return true; //Shift+Ctrl+Home,Endキーでの複数行選択を防止 if ((keyData & Keys.Shift) == Keys.Shift && (keyData & Keys.Control) == Keys.Control && (keyCode == Keys.Home || keyCode == Keys.End)) return true; //Ctrl+Aキーでの複数行選択を防止 if ((keyData & Keys.Control) == Keys.Control && keyCode == Keys.A) return true; } return base.ProcessCmdKey(ref msg, keyData); } protected override void OnControlAdded(ControlEventArgs e) { base.OnControlAdded(e); if (!(e.Control is VScrollBar) && !(e.Control is HScrollBar)) this.Controls.Remove(e.Control); } } }
この様な方法が面倒であれば、DataGridのPaintイベントでCurrentCellの行をSelectで選択するという方法がお手軽です。
(この記事は「.NETプログラミング研究 第34号」で紹介したものです。)
注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。