DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

DataGridで複数行選択できないようにし、セルがアクティブにならならず、行全体が選択されるようにする

注意:ここで紹介している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キーが押された時のそれぞれの場合を無効にしています。

VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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によるチェックボックスはアクティブになってしまうようです。)

VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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の代わりにこのクラスを使うようにします。

注意:新しく作成したクラスを、基のコントロールクラスの代わりに使用するという意味が分からないという方は、こちらをご覧ください。
VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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で選択するという方法がお手軽です。

  • 履歴:
  • 2006/11/11 右クリックで複数行選択できてしまう不具合を修正。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • .NET Tipsをご利用いただく際は、注意事項をお守りください。