The DataGridView is a very powerful windows form control for providing a user interface to tabular data. It has the initial "look and feel" of an Excel spreadsheet. You can bind a DataTable to the control so the user can easily add/delete/modify data in a database.
Note: This control is not related to the web-based DataGrid 2.0 control
Automatic Mode
Let's say that you have a Microsoft Access database that you plan to use in your program. From Visual Studio, all you'd have to do is add the MDB file to the project and it will automatically launch the Data Source Conversion Wizard. At the conclusion of this wizard, you'll have a strongly-typed dataset, a TableAdapter, and DataTable added to your project.
Next, you'll need to add a DataGridView to a form. From inside the Form Designer, you should see a very small triangle at the upper right-hand corner of the DataGridView control. Click on this triangle to select the data source that was created for you when you ran the Data Source Conversion Wizard.
Afterwards, you should have a fully-functional program that will allow you to view the data in the DataTable. However, you'll need to add a button and one line of code to be able to save changes back to the database.
Below is an example of the automatically generated form Load Event and the manually-created Update button event handler for saving the data:
Private Sub fmMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
'TODO: This line of code loads data into the 'NorthWindDataSet.Products' table.
' You can move, or remove it, as needed.
Me.ProductsTableAdapter.Fill(Me.NorthWindDataSet.Products)
End Sub
'
' Save the changes you've made via the DataGridView back to the
' underlying database
'
Private Sub bnUpdate_Click(ByVal sender As System.Object, ByVal e As _
System.EventArgs) Handles bnUpdate.Click
ProductsTableAdapter.Update(NorthWindDataSet.Products)
End Sub
Column Tweaks
By default, the automatically generated columns may need some tweaking. For example, all columns are aligned to the left and have have no formatting. You can perform most of these tweaks by either using the designer or via code.
Let's look at how you could perform some simple tweaks in a column that contains a Money field.
' create new style object
Dim style As New DataGridViewCellStyle
' set the alignment to the right (for numbers)
style.Alignment = DataGridViewContentAlignment.MiddleRight
' add a wee bit of padding to the right side
Dim p As New Windows.Forms.Padding
p.Right = 5
style.Padding = p
' set the monetary string format
style.Format = "$#.00"
' apply the style to the column
DataGridView1.Columns("UnitPrice").DefaultCellStyle = style
Error Control
The major technique for detected errors in a DataGridView is by the RowValidating event. This allows you to use a "business rule" to validate the user input. For example, if the UnitsOnHand column can never be a negative number,you could write a validating rule that looks to see if the value is negative prior to committing the changes to that row.
You can use the ErrorProvider class to display the problems detected during validation. When an input error occurs, there will be a little red exclamation point at that cell location. If the user hovers over the red "eye ball" (it kinda looks like a lower-case "i") they will see the reason why the input was rejected.
Note: You should the RowValidating "e.Cancel" method with some caution, as it will "lock down" the DataGridView control, and not let the user move to any other row (or switch to any other control) until after the error is fixed.
The RowValidating event and ErrorProvider may not be able to detect all errors. For example, if a user types in alpha characters (like "abc") into a numeric field, a data conversion error occurs before Row Validation. In this case we need a "global" error checker to detect data conversion errors.
Note: See the technique below for using custom column types to prevent (rather than detect) data conversion errors.
Let's see how you could implement the RowValidating event and ErrorProvider control:
'
' Check to see if the user input meets all of the business rules before
' we move on to another row.
'
Private Sub DataGridView1_RowValidating(ByVal sender As Object, ByVal e As _
System.Windows.Forms.DataGridViewCellCancelEventArgs) Handles _
DataGridView1.RowValidating
' Assume that text input is complete when you attempt to navigate
' away from the row
DataGridView1.EndEdit()
' Apply some business rule. In this case we're just checking to
' see if the UnitPrice is a positive number
If DataGridView1.CurrentRow.Cells("UnitPrice").Value < 0.0 Then
' The text in the "eye ball"
DataGridView1.CurrentRow.Cells("UnitPrice").ErrorText = _
"Yikes, you can't have a negative Unit Price"
' this will prevent them from moving to another row until
' they fix the problem
e.Cancel = True
Else
' this is required since after you fix the error, you want the
' error message to go away
DataGridView1.CurrentRow.Cells("UnitPrice").ErrorText = ""
End If
End Sub
'
' Catch any "global" Data Errors here
'
Private Sub dgv_DataError(ByVal sender As Object, ByVal e As _
System.Windows.Forms.DataGridViewDataErrorEventArgs) Handles dgvAssign.DataError
MsgBox(e.Exception.Message, MsgBoxStyle.Information, "Data Input Error")
End Sub
Column Types
Out of the box, the DataGridView supports the following column types:
- DataGridViewTextBoxColumn
- DataGridViewCheckBoxColumn
- DataGridViewImageColumn
- DataGridViewComboBoxColumn
- DataGridViewLinkColumn
Note: Only the first 3 column types are automatically generated.
The error checking discussed in the previous section may not detect "data integrity errors" (those errors where the input violates a database foreign key, unique, or primary key constraint). Luckily, the DataGridViewComboBoxColumn control was designed to help solve this problem.
You can use a DataGridViewComboBoxColumn to act a as "look up table" to show a user friendly version of the field, rather than just a numeric equivalent. Not only does this look better, it also prevents the user from selecting a value that would violate the foreign key constraint (meaning, you can't select a numeric value that doesn't exist in the related table)
Take a look at the following example that replaces a numeric "CategoryID" value with the associated user-friendly "Category" string from another table (called "Categories").
Dim index As Integer
' find the location of the column
index = DataGridView1.Columns.IndexOf(DataGridView1.Columns("CategoryID"))
' remove the existing column
DataGridView1.Columns.RemoveAt(index)
' create a new combobox column that we use as a "lookup"
Dim dgvc As New DataGridViewComboBoxColumn
dgvc.Name = "Category" ' the column name (and heading)
dgvc.DataPropertyName = "CategoryID" ' the matching column from the "base" table
dgvc.ValueMember = "CategoryID" ' the numeric value to look up in the "child" table
dgvc.DisplayMember = "CategoryName" ' the column value you want displayed
dgvc.DataSource = CategoriesTableAdapter.GetData() ' the child table
' some more tweaking
dgvc.SortMode = DataGridViewColumnSortMode.Automatic
' insert the new column at the same location
DataGridView1.Columns.Insert(index, dgvc))
Now the user can select the user-friendly Category strings and also is prevented from selecting a value that's not a valid CategoryID
Custom DataGridView columns
If the 5 built-in column types is not sufficient, it is fairly easy to add a custom column type yourself. For example, you could create a new column type that supports the Masked Edit feature found in the MaskedTextBox control. This new custom column type would be very useful when trying to prevent input errors.
Let's see what's required to implement a custom column control. The source code for the custom DataGridViewMaskedEditColumn is found below. The example below shows how you could replace an existing automatically-generated "UnitsInStock" column with a new custom column that supports the Masked Edit feature.
Dim index As Integer
' find the location of the column
index = DataGridView1.Columns.IndexOf(DataGridView1.Columns("UnitsInStock"))
' remove the existing column
DataGridView1.Columns.RemoveAt(index)
' create a new custom column
Dim dgvMaskedEdit As New DataGridViewMaskedEditColumn
dgvMaskedEdit.Mask = "####" ' this mask will allow only numbers (max of 4)
dgvMaskedEdit.ValidatingType = GetType(Integer)
dgvMaskedEdit.Name = "UnitsInStock"
dgvMaskedEdit.DataPropertyName = "UnitsInStock"
' some more tweaking
dgvMaskedEdit.SortMode = DataGridViewColumnSortMode.Automatic
' insert the new column at the same location
DataGridView1.Columns.Insert(index, dgvMaskedEdit)
Now the user will only be able to type numbers into this column. This technique provides an enhancement to the DataGridView control that further affirms the notion that you should always attempt to prevent user input errors rather than just detect them.
Documentation Links
DataGridViewMaskedEditColumn control
This custom DataGridView column control is contained in 3 classes... A Column control that inherits from DataGridViewColumn, a Cell control that inherits from DataGridViewTextBoxCell, and an EditingControl that implements the IDataGridViewEditingControl interface
The Masked Edit Column class is fairly straight forward; it only overrides one property (to ensure that the correct cell type it used). It also implements the properties that will be required later on by the MaskedTextBox control, namely the Mask, PromptChar, and ValidatingType properties.
Public Class DataGridViewMaskedEditColumn
Inherits DataGridViewColumn
Private pPromptChar As Char = "_"c
Private pValidatingType As Type = GetType(String)
Private pMask As String = ""
Public Sub New()
MyBase.New(New DataGridViewMaskedEditCell())
End Sub
Public Overrides Property CellTemplate() As DataGridViewCell
Get
Return MyBase.CellTemplate
End Get
Set(ByVal value As DataGridViewCell)
' Ensure that the cell used for the template is a MaskedEditCell
If Not (value Is Nothing) And Not value.GetType().IsAssignableFrom( _
GetType(DataGridViewMaskedEditCell)) Then
Throw New InvalidCastException("Must be a DataGridViewMaskedEditCell")
End If
MyBase.CellTemplate = value
End Set
End Property
'
' New properties required by the MaskedTextBox control
'
Public Property Mask() As String
Get
Return pMask
End Get
Set(ByVal value As String)
pMask = value
End Set
End Property
Public Property PromptChar() As Char
Get
Return pPromptChar
End Get
Set(ByVal value As Char)
pPromptChar = value
End Set
End Property
Public Property ValidatingType() As Type
Get
Return pValidatingType
End Get
Set(ByVal value As Type)
pValidatingType = value
End Set
End Property
End Class
The next step is the Masked Edit Cell class. It overrides the InitializeEditingControl method to allow the Mask, PromptChar, and ValidatingType properties from the column class to be passed onto the Cell class. The remaining overrides are to adjust to the fact that the MaskedTextBox control supports multiple "validating types".
Public Class DataGridViewMaskedEditCell
Inherits DataGridViewTextBoxCell
Dim pColumn As DataGridViewMaskedEditColumn
Public Sub New()
End Sub
Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer, ByVal _
initialFormattedValue As Object, ByVal dataGridViewCellStyle As DataGridViewCellStyle)
MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)
pColumn = CType(Me.OwningColumn, DataGridViewMaskedEditColumn)
Dim ctl As MaskedEditEditingControl = CType(DataGridView.EditingControl, _
MaskedEditEditingControl)
' copy over the properties of the column
If Not IsNothing(Me.Value) Then
ctl.Text = Me.Value.ToString
Else
ctl.Text = ""
End If
ctl.ValidatingType = pColumn.ValidatingType
ctl.Mask = pColumn.Mask
ctl.PromptChar = pColumn.PromptChar
End Sub
Public Overrides ReadOnly Property EditType() As Type
Get
' Return the type of the editing control that MaskedEditEditingControl uses.
Return GetType(MaskedEditEditingControl)
End Get
End Property
Public Overrides ReadOnly Property ValueType() As Type
Get
' Return the type of the value that MaskedEditEditingControl contains.
Return pColumn.ValidatingType
End Get
End Property
Public Overrides ReadOnly Property DefaultNewRowValue() As Object
Get
Return ""
End Get
End Property
End Class
Lastly, is the Editing Control. It inherits from the MaskedTextBox control (to get the user interface we want) and implements the Editing Control interface so that the control works inside the DataGridView. Most of the code required to implement the methods and properties is fairly straight forward.
Class MaskedEditEditingControl
Inherits MaskedTextBox
Implements IDataGridViewEditingControl
Private dataGridViewControl As DataGridView
Private valueIsChanged As Boolean = False
Private rowIndexNum As Integer
Public Sub New()
End Sub
Public Property EditingControlFormattedValue() As Object Implements _
IDataGridViewEditingControl.EditingControlFormattedValue
Get
Return Me.valueIsChanged.ToString
End Get
Set(ByVal value As Object)
If TypeOf value Is [String] Then
Me.Text = value.ToString
End If
End Set
End Property
Public Function GetEditingControlFormattedValue(ByVal context As _
DataGridViewDataErrorContexts) As Object Implements _
IDataGridViewEditingControl.GetEditingControlFormattedValue
Return Me.Text
End Function
Public Sub ApplyCellStyleToEditingControl(ByVal dataGridViewCellStyle As _
DataGridViewCellStyle) Implements _
IDataGridViewEditingControl.ApplyCellStyleToEditingControl
Me.Font = dataGridViewCellStyle.Font
Me.ForeColor = dataGridViewCellStyle.ForeColor
Me.BackColor = dataGridViewCellStyle.BackColor
End Sub
Public Property EditingControlRowIndex() As Integer Implements _
IDataGridViewEditingControl.EditingControlRowIndex
Get
Return rowIndexNum
End Get
Set(ByVal value As Integer)
rowIndexNum = value
End Set
End Property
Public Function EditingControlWantsInputKey(ByVal key As Keys, ByVal _
dataGridViewWantsInputKey As Boolean) As Boolean Implements _
IDataGridViewEditingControl.EditingControlWantsInputKey
Return True
End Function
Public Sub PrepareEditingControlForEdit(ByVal selectAll As Boolean) Implements _
IDataGridViewEditingControl.PrepareEditingControlForEdit
' No preparation needs to be done.
End Sub
Public ReadOnly Property RepositionEditingControlOnValueChange() As Boolean _
Implements IDataGridViewEditingControl.RepositionEditingControlOnValueChange
Get
Return False
End Get
End Property
Public Property EditingControlDataGridView() As DataGridView Implements _
IDataGridViewEditingControl.EditingControlDataGridView
Get
Return dataGridViewControl
End Get
Set(ByVal value As DataGridView)
dataGridViewControl = value
End Set
End Property
Public Property EditingControlValueChanged() As Boolean Implements _
IDataGridViewEditingControl.EditingControlValueChanged
Get
Return valueIsChanged
End Get
Set(ByVal value As Boolean)
valueIsChanged = value
End Set
End Property
Public ReadOnly Property EditingPanelCursor() As Cursor Implements _
IDataGridViewEditingControl.EditingPanelCursor
Get
Return MyBase.Cursor
End Get
End Property
Protected Overrides Sub OnTextChanged(ByVal eventargs As EventArgs)
' Notify the DataGridView that the contents of the cell have changed.
valueIsChanged = True
Me.EditingControlDataGridView.NotifyCurrentCellDirty(True)
MyBase.OnTextChanged(eventargs)
End Sub
End Class
Documentation Links
-
DataGridViewColumn Class

-
DataGridViewTextBoxCell Class

-
MaskedTextBox Class

-
IDataGridViewEditingControl Interface

Downloads/Links
Download the VB.Net Source code example used in this article: DataGridView.zip