none
How can you achieve single click editing in a DataGridView

    Question

  •  

    I have a DataGridView with a Description, Quantity and Price columns where the descriptions usually fits on one line.

     

    With the default functionality of the DataGridView, if the user spots a typo in a description they have to click the mouse three times to position the cursor at the point of the mistake.

     

    The first click makes the DataGridView cell current, the second click puts the cell into edit mode and the third click positions the caret (editing cursor).

     

    This is two clicks too many for me; I would like the DataGridView to work like the Properties pane in Visual Studio, or more specifically the right hand values column, where a single click positions the caret at the point you want to edit.

     

    Monday, March 17, 2008 6:00 PM

Answers

  • See below for my solution to this issue, which is in the form of a derived class called OneClickEditDataGridView.

     

    This has received plenty of usage on an XP and Vista system, using Framework 2.0 and has no known problems.

     

    To use the class, you just change references to the System.Windows.Forms.DataGridView class in the Form Designer generated code to OneClickEditDataGridView.

     

    By default, the one click functionality is enabled for all editable (not read-only) text columns.

     

    If you only want this functionality for specific columns, as did I (standard functionality is fine for the Quantity and Price columns of my DataGridView), you can call the DefineSingleClickColumns method (in OnLoad event handler of form etc) passing the columns for which you wish to enable the feature.

     



     

    /// <summary>
    /// OneClickEditDataGridView : Provides single click editing of text columns
    ///
    /// Simple usage (assuming DataGridView is setup in Visual Studio Designer):
    ///
    /// 1. Add this class to the source file where your DataGridView is defined, before the
    /// closing '}' of the namespace (or the Form if you prefer).
    ///   
    /// 2. Manually edit the Form Designer Code changing the two System.Windows.Forms.DataGridView
    /// references to OneClickEditDataGridView.
    ///
    /// 3. If you only wish to enable the functionality for specific text columns, in the OnLoad
    /// event handler of your form, call the DefineSingleClickColumns method, passing the columns
    /// you require. e.g.
    ///
    ///     myDataGridView.DefineSingleClickColumns(descriptionDataGridViewTextBoxColumn,
    ///         urlDataGridViewTextBoxColumn);
    ///    
    /// The method is overloaded, so you can pass column indexes instead.
    ///
    /// There is one other protected method that you can use in a OnMouseDown event handler of
    /// a derived class:
    ///
    ///     void BaseOnMouseDown(MouseEventArgs e)
    ///    
    /// This is provided so you can programatically bypass the single click functionality.
    ///
    /// </summary>
    public class OneClickEditDataGridView : DataGridView
    {
        #region Operations

     

        public void DefineSingleClickColumns(params DataGridViewColumn[] columns)
        {
            singleClickColumns.Clear();

            foreach (DataGridViewColumn column in columns)
            {
                if (this.Columns.IndexOf(column) == -1)
                {
                    throw new ArgumentException("Instance of column (" + column.Name + ") is not in this DataGridView");
                }

                singleClickColumns.Add(column);
            }
        }

     

        public void DefineSingleClickColumns(params int[] columnIndexes)
        {
            singleClickColumns.Clear();

            foreach (int columnIndex in columnIndexes)
            {
                if (columnIndex < 0 || columnIndex >= this.Columns.Count)
                {
                    throw new ArgumentOutOfRangeException("Column index (" + columnIndex + ") is out of range");
                }

                singleClickColumns.Add(this.Columns[columnIndex]);
            }
        }

     

        protected void BaseOnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
        }

        #endregion

     

        #region Overrides

     

        protected override void OnMouseDown(MouseEventArgs e)
        {
            // If primary mouse button not down, do standard processing
            if (e.Button != MouseButtons.Left)
            {
                base.OnMouseDown(e);

                return;
            }

     

            // Get info on where user clicked
            DataGridView.HitTestInfo hitInfo = HitTest(e.X, e.Y);

     

            // If a cell wasn't clicked, column isn't text or it's read only, do standard processing
            if (hitInfo.Type != DataGridViewHitTestType.Cell ||
                !(this.Columns[hitInfo.ColumnIndex] is DataGridViewTextBoxColumn) ||
                this.Columns[hitInfo.ColumnIndex].ReadOnly)
            {
                base.OnMouseDown(e);

                return;
            }

     

            // If specific columns specified and column clicked is not
            // one of these, do standard processing
            if (singleClickColumns.Count >= 1 && singleClickColumns.IndexOf(this.Columns[hitInfo.ColumnIndex]) == -1)
            {
                base.OnMouseDown(e);

                return;
            }

     

            // Get clicked cell
            DataGridViewCell clickedCell = this.Rows[hitInfo.RowIndex].Cells[hitInfo.ColumnIndex];

     

            // If cell not current, try and make it so

            if (CurrentCell != clickedCell)
            {
                // Allow standard processing make clicked cell current
                base.OnMouseDown(e);

     

                // If this didn't happen (validation failed etc), abort
                if (this.CurrentCell != clickedCell)
                {
                    return;
                }
            }

     

            // If already in edit mode, do standard processing (will position caret)
            if (this.CurrentCell.IsInEditMode)
            {
                base.OnMouseDown(e);

                return;
            }

     

            // Enter edit mode
            this.BeginEdit(false);

     

            // Ensure text is scrolled to the left
            TextBoxBase textBox = (TextBoxBase) this.EditingControl;

            textBox.SelectionStart = 0;

            textBox.ScrollToCaret();

     

            // Position caret by simulating a mouse click within control
            int editOffset = e.X - hitInfo.ColumnX - this.EditingControl.Left;

            Int32 lParam = MakeLong(editOffset, 0);

            SendMessage(this.EditingControl.Handle, WM_LBUTTONDOWN, 0, lParam);

            SendMessage(this.EditingControl.Handle, WM_LBUTTONUP, 0, lParam);
        }

     

        #endregion

     

        #region Implementation

     

        const int WM_LBUTTONDOWN = 0x0201;

        const int WM_LBUTTONUP = 0x0202;

     

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern bool SendMessage(IntPtr hWnd, Int32 msg, Int32 wParam, Int32 lParam);

     

        List<DataGridViewColumn> singleClickColumns = new List<DataGridViewColumn>();

     

        int MakeLong(int loWord, int hiWord)
        {
            return (hiWord << 16) | (loWord & 0xffff);
        }

        #endregion
    }


     

     



     

    New">

    ''' <summary>
    ''' OneClickEditDataGridView : Provides single click editing of text columns
    '''
    ''' Simple usage (assuming DataGridView is setup in Visual Studio Designer):
    '''
    ''' 1. Add this class to the source file where your DataGridView is defined, before End Namespace

    ''' 
    ''' 2. Manually edit the Form Designer Code changing the two System.Windows.Forms.DataGridView
    ''' references to OneClickEditDataGridView.
    '''
    ''' 3. If you only wish to enable the functionality for specific text columns, in the OnLoad
    ''' event handler of your form, call the DefineSingleClickColumns method, passing the columns you require. e.g.
    '''
    '''     myDataGridView.DefineSingleClickColumns(descriptionDataGridViewTextBoxColumn,   urlDataGridViewTextBoxColumn);
    '''
    ''' The method is overloaded, so you can pass column indexes instead.
    '''
    ''' There is one other protected method that you can use in a OnMouseDown event handler of a derived class:
    '''
    '''     void BaseOnMouseDown(MouseEventArgs e)
    '''
    ''' This is provided so you can programatically bypass the single click functionality if required.
    '''
    ''' </summary>
    Public Class OneClickEditDataGridView
        Inherits DataGridView
        #Region "Operations"
       
        Public Sub DefineSingleClickColumns(ParamArray columns As DataGridViewColumn())
            singleClickColumns.Clear()
           
            For Each column As DataGridViewColumn In columns
                If Me.Columns.IndexOf(column) = -1 Then
                    Throw New ArgumentException("Instance of column (" + column.Name + ") is not in this DataGridView")
                End If
               
                singleClickColumns.Add(column)
            Next
        End Sub
       
        Public Sub DefineSingleClickColumns(ParamArray columnIndexes As Integer())
            singleClickColumns.Clear()
           
            For Each columnIndex As Integer In columnIndexes
                If columnIndex < 0 OrElse columnIndex >= Me.Columns.Count Then
                    Throw New ArgumentOutOfRangeException("Column index (" + columnIndex + ") is out of range")
                End If
               
                singleClickColumns.Add(Me.Columns(columnIndex))
            Next
        End Sub
       
        Protected Sub BaseOnMouseDown(ByVal e As MouseEventArgs)
            MyBase.OnMouseDown(e)
        End Sub
       
        #End Region
       
        #Region "Overrides"
       
        Protected Overloads Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
            ' If primary mouse button not down, do standard processing
            If e.Button <> MouseButtons.Left Then
                MyBase.OnMouseDown(e)
               
                Return
            End If
           
            ' Get info on where user clicked
            Dim hitInfo As DataGridView.HitTestInfo = HitTest(e.X, e.Y)
           
            ' If a cell wasn't clicked, column isn't text or it's read only, do standard processing
            If hitInfo.Type <> DataGridViewHitTestType.Cell OrElse Not (TypeOf Me.Columns(hitInfo.ColumnIndex) Is DataGridViewTextBoxColumn) OrElse Me.Columns(hitInfo.ColumnIndex).[ReadOnly] Then
                MyBase.OnMouseDown(e)
               
                Return
            End If
           
            ' If functionality enabled for specific columns and column clicked is not
            ' one of these, do standard processing
            If singleClickColumns.Count >= 1 AndAlso singleClickColumns.IndexOf(Me.Columns(hitInfo.ColumnIndex)) = -1 Then
                MyBase.OnMouseDown(e)
               
                Return
            End If
           
            ' Get clicked cell
            Dim clickedCell As DataGridViewCell = Me.Rows(hitInfo.RowIndex).Cells(hitInfo.ColumnIndex)
           
            ' If cell not current, try and make it so
            If CurrentCell <> clickedCell Then
                ' Allow standard processing make clicked cell current
                MyBase.OnMouseDown(e)
               
                ' If this didn't happen (validation failed etc), abort
                If Me.CurrentCell <> clickedCell Then
                    Return
                End If
            End If
           
            ' If already in edit mode, do standard processing (will position caret)
            If Me.CurrentCell.IsInEditMode Then
                MyBase.OnMouseDown(e)
               
                Return
            End If
           
            ' Enter edit mode
            Me.BeginEdit(False)
           
            ' Ensure text is scrolled to the left
            Dim textBox As TextBoxBase = DirectCast(Me.EditingControl, TextBoxBase)
           
            textBox.SelectionStart = 0
           
            textBox.ScrollToCaret()
           
            ' Position caret by simulating a mouse click within control
            Dim editOffset As Integer = e.X - hitInfo.ColumnX - Me.EditingControl.Left
           
            Dim lParam As Int32 = MakeLong(editOffset, 0)
           
            SendMessage(Me.EditingControl.Handle, WM_LBUTTONDOWN, 0, lParam)
           
            SendMessage(Me.EditingControl.Handle, WM_LBUTTONUP, 0, lParam)
        End Sub
       
        #End Region
       
        #Region "Implementation"
       
        Const WM_LBUTTONDOWN As Integer = 513
       
        Const WM_LBUTTONUP As Integer = 514
       
        <System.Runtime.InteropServices.DllImport("user32.dll")> _
        Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As Boolean
        End Function
       
        Private singleClickColumns As New List(Of DataGridViewColumn)()
       
        Private Function MakeLong(ByVal loWord As Integer, ByVal hiWord As Integer) As Integer
            Return (hiWord << 16) Or (loWord And 65535)
        End Function
       
        #End Region
    End Class

     

     

     

    Monday, March 17, 2008 7:55 PM

All replies

  • See below for my solution to this issue, which is in the form of a derived class called OneClickEditDataGridView.

     

    This has received plenty of usage on an XP and Vista system, using Framework 2.0 and has no known problems.

     

    To use the class, you just change references to the System.Windows.Forms.DataGridView class in the Form Designer generated code to OneClickEditDataGridView.

     

    By default, the one click functionality is enabled for all editable (not read-only) text columns.

     

    If you only want this functionality for specific columns, as did I (standard functionality is fine for the Quantity and Price columns of my DataGridView), you can call the DefineSingleClickColumns method (in OnLoad event handler of form etc) passing the columns for which you wish to enable the feature.

     



     

    /// <summary>
    /// OneClickEditDataGridView : Provides single click editing of text columns
    ///
    /// Simple usage (assuming DataGridView is setup in Visual Studio Designer):
    ///
    /// 1. Add this class to the source file where your DataGridView is defined, before the
    /// closing '}' of the namespace (or the Form if you prefer).
    ///   
    /// 2. Manually edit the Form Designer Code changing the two System.Windows.Forms.DataGridView
    /// references to OneClickEditDataGridView.
    ///
    /// 3. If you only wish to enable the functionality for specific text columns, in the OnLoad
    /// event handler of your form, call the DefineSingleClickColumns method, passing the columns
    /// you require. e.g.
    ///
    ///     myDataGridView.DefineSingleClickColumns(descriptionDataGridViewTextBoxColumn,
    ///         urlDataGridViewTextBoxColumn);
    ///    
    /// The method is overloaded, so you can pass column indexes instead.
    ///
    /// There is one other protected method that you can use in a OnMouseDown event handler of
    /// a derived class:
    ///
    ///     void BaseOnMouseDown(MouseEventArgs e)
    ///    
    /// This is provided so you can programatically bypass the single click functionality.
    ///
    /// </summary>
    public class OneClickEditDataGridView : DataGridView
    {
        #region Operations

     

        public void DefineSingleClickColumns(params DataGridViewColumn[] columns)
        {
            singleClickColumns.Clear();

            foreach (DataGridViewColumn column in columns)
            {
                if (this.Columns.IndexOf(column) == -1)
                {
                    throw new ArgumentException("Instance of column (" + column.Name + ") is not in this DataGridView");
                }

                singleClickColumns.Add(column);
            }
        }

     

        public void DefineSingleClickColumns(params int[] columnIndexes)
        {
            singleClickColumns.Clear();

            foreach (int columnIndex in columnIndexes)
            {
                if (columnIndex < 0 || columnIndex >= this.Columns.Count)
                {
                    throw new ArgumentOutOfRangeException("Column index (" + columnIndex + ") is out of range");
                }

                singleClickColumns.Add(this.Columns[columnIndex]);
            }
        }

     

        protected void BaseOnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
        }

        #endregion

     

        #region Overrides

     

        protected override void OnMouseDown(MouseEventArgs e)
        {
            // If primary mouse button not down, do standard processing
            if (e.Button != MouseButtons.Left)
            {
                base.OnMouseDown(e);

                return;
            }

     

            // Get info on where user clicked
            DataGridView.HitTestInfo hitInfo = HitTest(e.X, e.Y);

     

            // If a cell wasn't clicked, column isn't text or it's read only, do standard processing
            if (hitInfo.Type != DataGridViewHitTestType.Cell ||
                !(this.Columns[hitInfo.ColumnIndex] is DataGridViewTextBoxColumn) ||
                this.Columns[hitInfo.ColumnIndex].ReadOnly)
            {
                base.OnMouseDown(e);

                return;
            }

     

            // If specific columns specified and column clicked is not
            // one of these, do standard processing
            if (singleClickColumns.Count >= 1 && singleClickColumns.IndexOf(this.Columns[hitInfo.ColumnIndex]) == -1)
            {
                base.OnMouseDown(e);

                return;
            }

     

            // Get clicked cell
            DataGridViewCell clickedCell = this.Rows[hitInfo.RowIndex].Cells[hitInfo.ColumnIndex];

     

            // If cell not current, try and make it so

            if (CurrentCell != clickedCell)
            {
                // Allow standard processing make clicked cell current
                base.OnMouseDown(e);

     

                // If this didn't happen (validation failed etc), abort
                if (this.CurrentCell != clickedCell)
                {
                    return;
                }
            }

     

            // If already in edit mode, do standard processing (will position caret)
            if (this.CurrentCell.IsInEditMode)
            {
                base.OnMouseDown(e);

                return;
            }

     

            // Enter edit mode
            this.BeginEdit(false);

     

            // Ensure text is scrolled to the left
            TextBoxBase textBox = (TextBoxBase) this.EditingControl;

            textBox.SelectionStart = 0;

            textBox.ScrollToCaret();

     

            // Position caret by simulating a mouse click within control
            int editOffset = e.X - hitInfo.ColumnX - this.EditingControl.Left;

            Int32 lParam = MakeLong(editOffset, 0);

            SendMessage(this.EditingControl.Handle, WM_LBUTTONDOWN, 0, lParam);

            SendMessage(this.EditingControl.Handle, WM_LBUTTONUP, 0, lParam);
        }

     

        #endregion

     

        #region Implementation

     

        const int WM_LBUTTONDOWN = 0x0201;

        const int WM_LBUTTONUP = 0x0202;

     

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern bool SendMessage(IntPtr hWnd, Int32 msg, Int32 wParam, Int32 lParam);

     

        List<DataGridViewColumn> singleClickColumns = new List<DataGridViewColumn>();

     

        int MakeLong(int loWord, int hiWord)
        {
            return (hiWord << 16) | (loWord & 0xffff);
        }

        #endregion
    }


     

     



     

    New">

    ''' <summary>
    ''' OneClickEditDataGridView : Provides single click editing of text columns
    '''
    ''' Simple usage (assuming DataGridView is setup in Visual Studio Designer):
    '''
    ''' 1. Add this class to the source file where your DataGridView is defined, before End Namespace

    ''' 
    ''' 2. Manually edit the Form Designer Code changing the two System.Windows.Forms.DataGridView
    ''' references to OneClickEditDataGridView.
    '''
    ''' 3. If you only wish to enable the functionality for specific text columns, in the OnLoad
    ''' event handler of your form, call the DefineSingleClickColumns method, passing the columns you require. e.g.
    '''
    '''     myDataGridView.DefineSingleClickColumns(descriptionDataGridViewTextBoxColumn,   urlDataGridViewTextBoxColumn);
    '''
    ''' The method is overloaded, so you can pass column indexes instead.
    '''
    ''' There is one other protected method that you can use in a OnMouseDown event handler of a derived class:
    '''
    '''     void BaseOnMouseDown(MouseEventArgs e)
    '''
    ''' This is provided so you can programatically bypass the single click functionality if required.
    '''
    ''' </summary>
    Public Class OneClickEditDataGridView
        Inherits DataGridView
        #Region "Operations"
       
        Public Sub DefineSingleClickColumns(ParamArray columns As DataGridViewColumn())
            singleClickColumns.Clear()
           
            For Each column As DataGridViewColumn In columns
                If Me.Columns.IndexOf(column) = -1 Then
                    Throw New ArgumentException("Instance of column (" + column.Name + ") is not in this DataGridView")
                End If
               
                singleClickColumns.Add(column)
            Next
        End Sub
       
        Public Sub DefineSingleClickColumns(ParamArray columnIndexes As Integer())
            singleClickColumns.Clear()
           
            For Each columnIndex As Integer In columnIndexes
                If columnIndex < 0 OrElse columnIndex >= Me.Columns.Count Then
                    Throw New ArgumentOutOfRangeException("Column index (" + columnIndex + ") is out of range")
                End If
               
                singleClickColumns.Add(Me.Columns(columnIndex))
            Next
        End Sub
       
        Protected Sub BaseOnMouseDown(ByVal e As MouseEventArgs)
            MyBase.OnMouseDown(e)
        End Sub
       
        #End Region
       
        #Region "Overrides"
       
        Protected Overloads Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
            ' If primary mouse button not down, do standard processing
            If e.Button <> MouseButtons.Left Then
                MyBase.OnMouseDown(e)
               
                Return
            End If
           
            ' Get info on where user clicked
            Dim hitInfo As DataGridView.HitTestInfo = HitTest(e.X, e.Y)
           
            ' If a cell wasn't clicked, column isn't text or it's read only, do standard processing
            If hitInfo.Type <> DataGridViewHitTestType.Cell OrElse Not (TypeOf Me.Columns(hitInfo.ColumnIndex) Is DataGridViewTextBoxColumn) OrElse Me.Columns(hitInfo.ColumnIndex).[ReadOnly] Then
                MyBase.OnMouseDown(e)
               
                Return
            End If
           
            ' If functionality enabled for specific columns and column clicked is not
            ' one of these, do standard processing
            If singleClickColumns.Count >= 1 AndAlso singleClickColumns.IndexOf(Me.Columns(hitInfo.ColumnIndex)) = -1 Then
                MyBase.OnMouseDown(e)
               
                Return
            End If
           
            ' Get clicked cell
            Dim clickedCell As DataGridViewCell = Me.Rows(hitInfo.RowIndex).Cells(hitInfo.ColumnIndex)
           
            ' If cell not current, try and make it so
            If CurrentCell <> clickedCell Then
                ' Allow standard processing make clicked cell current
                MyBase.OnMouseDown(e)
               
                ' If this didn't happen (validation failed etc), abort
                If Me.CurrentCell <> clickedCell Then
                    Return
                End If
            End If
           
            ' If already in edit mode, do standard processing (will position caret)
            If Me.CurrentCell.IsInEditMode Then
                MyBase.OnMouseDown(e)
               
                Return
            End If
           
            ' Enter edit mode
            Me.BeginEdit(False)
           
            ' Ensure text is scrolled to the left
            Dim textBox As TextBoxBase = DirectCast(Me.EditingControl, TextBoxBase)
           
            textBox.SelectionStart = 0
           
            textBox.ScrollToCaret()
           
            ' Position caret by simulating a mouse click within control
            Dim editOffset As Integer = e.X - hitInfo.ColumnX - Me.EditingControl.Left
           
            Dim lParam As Int32 = MakeLong(editOffset, 0)
           
            SendMessage(Me.EditingControl.Handle, WM_LBUTTONDOWN, 0, lParam)
           
            SendMessage(Me.EditingControl.Handle, WM_LBUTTONUP, 0, lParam)
        End Sub
       
        #End Region
       
        #Region "Implementation"
       
        Const WM_LBUTTONDOWN As Integer = 513
       
        Const WM_LBUTTONUP As Integer = 514
       
        <System.Runtime.InteropServices.DllImport("user32.dll")> _
        Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As Boolean
        End Function
       
        Private singleClickColumns As New List(Of DataGridViewColumn)()
       
        Private Function MakeLong(ByVal loWord As Integer, ByVal hiWord As Integer) As Integer
            Return (hiWord << 16) Or (loWord And 65535)
        End Function
       
        #End Region
    End Class

     

     

     

    Monday, March 17, 2008 7:55 PM
  • I know this post is old but your solution works really good! I can't figure out who would want to click three times in order to start editing at a specific location in a cell. I did get it down to two clicks by changing the EditMode but that wasn't good enough. Your solution is awesome.
    Friday, October 19, 2012 4:02 PM