Data Grid Customization

I need to customize the datagrid so that it works much like Excel. When a user moves left and right they need to jump from cell to cell. If they type text it should simply overwrite the data in that cell or they could press F2 to edit the cell. I also need to be able to handle mouse clicks to change the current cell.

I have derived a class from datagrid to handle the mouse clicks. That works just fine. I have also derived a class from DataGridTextBoxColumn and I am overriding the Edit method to only display the textbox if the user is "editing" a cell. Unfortunately the way I am doing this does not seem to work... The whole thing works just fine with the mouse, however, if you try to cursor to the left most column and type no text appears, however, if you type on any other cells they seem to work (as long as you type more than one character!) Why can't I edit the cells on the left without clicking on them? And why when I enter just one letter on all the other cells does it lose my changes?

I've provided some code below. Please let me know what I am doing wrong!

Public Class MyDataGrid
Inherits DataGrid
Public blnEdit As Boolean = False

Private Sub MyDataGrid_CurrentCellChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.CurrentCellChanged
blnEdit = False
End Sub

Private Sub MyDataGrid_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
' Use the HitTest method to get a HitTestInfo object.
Dim hi As DataGrid.HitTestInfo
Dim grid As DataGrid = CType(sender, DataGrid)
hi = grid.HitTest(e.X, e.Y)
' Test if the clicked area was a cell.
If hi.Type = DataGrid.HitTestType.Cell Then
blnEdit = True
End If
End Sub

Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean
If msg.WParam.ToInt32() = CInt(Keys.Enter) Then
SendKeys.Send("{Tab}")
Return True
End If

End Function 'ProcessCmdKey

Private Sub MyDataGrid_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
blnEdit = True
End Sub

End Class

Public Class MyDataGridTextBoxColumn
Inherits DataGridTextBoxColumn

Protected Overloads Overrides Sub Edit(ByVal source As System.Windows.Forms.CurrencyManager, ByVal rowNum As Integer, ByVal bounds As System.Drawing.Rectangle, ByVal [readOnly] As Boolean, ByVal instantText As String, ByVal cellIsVisible As Boolean)
If Not CType(Me.DataGridTableStyle.DataGrid, MyDataGrid).blnEdit Then
Me.HideEditBox()
Else
MyBase.Edit(source, rowNum, bounds, [readOnly], instantText, cellIsVisible)
End If
End Sub

End Class

[2821 byte] By [codefund.com] at [2007-12-16]
# 1
Hmm... I took your code, created a form, ran it, and see no difference in behavior from a standard data grid except that pressing Enter moves me to the next cell, horizontally. The other artifacts you mention simply aren't happening here. That's not what you wanted to hear, I'm sure, but to my eyes, you've written all this code and the only difference I see from standard datagrid is that movement. I put your control, and a standard datagrid, on the same form to compare their behaviors. I tried it in VS.NET 2002 (the released version), and it worked fine for me, albeit it didn't add much to the DataGrid's native behavior. Were there any properties you might have set at design time that I didn't set? I just placed two datagrids on my form, bound them to data from Northwind, then replaced the control type with MyDataGrid for one of the controls.
codefund.com at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms Data Controls and Databinding...
# 2
Thanks for the quick response Ken. I just did exactly the same thing... created a new project and pasted the code into it... I then realized that I had failed to mention that I had added CustomDataTableStyles with my MyDataGridTextBoxColumn class. So here's ALL of my code thus far. You can simply create a new project and paste all this code into Form1.vb. Oddly enough, everything seems to work now except that you can't type text into the leftmost column without double clicking it or pressing "F2".

Public Class Form1
Inherits System.Windows.Forms.Form
Private MyDataSet As DataSet

#Region " Windows Form Designer generated code "

Public Sub New()
MyBase.New()

'This call is required by the Windows Form Designer.
InitializeComponent()

'Add any initialization after the InitializeComponent() call

End Sub

'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub

'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer

'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents DataGrid1 As MyDataGrid
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.DataGrid1 = New MyDataGrid
CType(Me.DataGrid1, System.ComponentModel.ISupportInitialize).BeginInit()
Me.SuspendLayout()
'
'DataGrid1
'
Me.DataGrid1.DataMember = ""
Me.DataGrid1.HeaderForeColor = System.Drawing.SystemColors.ControlText
Me.DataGrid1.Location = New System.Drawing.Point(8, 8)
Me.DataGrid1.Name = "DataGrid1"
Me.DataGrid1.Size = New System.Drawing.Size(520, 440)
Me.DataGrid1.TabIndex = 0
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(536, 454)
Me.Controls.Add(Me.DataGrid1)
Me.Name = "Form1"
Me.Text = "Form1"
CType(Me.DataGrid1, System.ComponentModel.ISupportInitialize).EndInit()
Me.ResumeLayout(False)

End Sub

#End Region

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Create Data Set
MyDataSet = New DataSet("MyDataSet")

Dim myTable As New DataTable("Customers")

Dim myColumn As New DataColumn("CustomerID")
myTable.Columns.Add(myColumn)

myColumn = New DataColumn("CustomerName")
myTable.Columns.Add(myColumn)

MyDataSet.Tables.Add(myTable)

Dim myRow As DataRow = myTable.NewRow()

myRow("CustomerID") = "100"
myRow("CustomerName") = "John Doe"
myTable.Rows.Add(myRow)

myRow = myTable.NewRow()

myRow("CustomerID") = "200"
myRow("CustomerName") = "Mary Lou"
myTable.Rows.Add(myRow)

'Bind Data Grid
DataGrid1.SetDataBinding(MyDataSet, "Customers")

'Add Custom DataTableStyle
Dim myTableStyle As New DataGridTableStyle
myTableStyle.MappingName = "Customers"

Dim myTextColumn As New MyDataGridTextBoxColumn
myTextColumn.MappingName = "CustomerID"
myTextColumn.HeaderText = "Customer ID"
myTextColumn.Width = 100

myTableStyle.GridColumnStyles.Add(myTextColumn)

myTextColumn = New MyDataGridTextBoxColumn
myTextColumn.MappingName = "CustomerName"
myTextColumn.HeaderText = "Customer Name"
myTextColumn.Width = 200

myTableStyle.GridColumnStyles.Add(myTextColumn)

DataGrid1.TableStyles.Add(myTableStyle)

End Sub
End Class

Public Class MyDataGrid
Inherits DataGrid
Public blnEdit As Boolean = False

Private Sub MyDataGrid_CurrentCellChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.CurrentCellChanged
blnEdit = False
End Sub

Private Sub MyDataGrid_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseDown
' Use the HitTest method to get a HitTestInfo object.
Dim hi As DataGrid.HitTestInfo
Dim grid As DataGrid = CType(sender, DataGrid)
hi = grid.HitTest(e.X, e.Y)
' Test if the clicked area was a cell.
If hi.Type = DataGrid.HitTestType.Cell Then
blnEdit = True
End If
End Sub

Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean
If msg.WParam.ToInt32() = CInt(Keys.Enter) Then
SendKeys.Send("{Tab}")
Return True
End If

blnEdit = True

Return MyBase.ProcessCmdKey(msg, keyData)

End Function 'ProcessCmdKey

Private Sub MyDataGrid_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
blnEdit = True
End Sub
End Class

Public Class MyDataGridTextBoxColumn
Inherits DataGridTextBoxColumn

Protected Overloads Overrides Sub Edit(ByVal source As System.Windows.Forms.CurrencyManager, ByVal rowNum As Integer, ByVal bounds As System.Drawing.Rectangle, ByVal [readOnly] As Boolean, ByVal instantText As String, ByVal cellIsVisible As Boolean)
If Not CType(Me.DataGridTableStyle.DataGrid, MyDataGrid).blnEdit Then
Me.HideEditBox()
Else
MyBase.Edit(source, rowNum, bounds, [readOnly], instantText, cellIsVisible)
End If
End Sub

Protected Overloads Overrides Sub Paint(ByVal g As System.Drawing.Graphics, ByVal bounds As System.Drawing.Rectangle, ByVal source As System.Windows.Forms.CurrencyManager, ByVal rowNum As Integer, ByVal backBrush As System.Drawing.Brush, ByVal foreBrush As System.Drawing.Brush, ByVal alignToRight As Boolean)
If DataGridTableStyle.DataGrid.CurrentRowIndex = rowNum _
And DataGridTableStyle.DataGrid.CurrentCell.ColumnNumber = DataGridTableStyle.GridColumnStyles.IndexOf(Me) Then
backBrush = New SolidBrush(Color.Red)
End If

MyBase.Paint(g, bounds, source, rowNum, backBrush, foreBrush, alignToRight)
End Sub
End Class

codefund.com at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms Data Controls and Databinding...
# 3
Ya got me on this one. Looks like it ought to work for both columns the same way, yet it doesn't. Hopefully, someone with intimate experience with this can chime in.
codefund.com at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms Data Controls and Databinding...
# 4
The data grid processes the key messages and the mouse messages before setting the current cell. after setting the current cell the grid calls Edit on the data grid table.

in your code you set the blnEdit bit to true when processing the key messages and the mouse messages but you reset it to false in the handler for current cell changed event. then, when your column gets the edit mode the data grid::blnEdit is false so no editing happens.

I suggest that you don't use the blnEdit bit at all: in the DataGridColumn::Edit method simply put your text box on top of the grid.

codefund.com at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms Data Controls and Databinding...