DataGridView (dgr)

DataGrid (dgr) - Allows the user to display (and edit) a table of information.

dgrDataGridView.SelectedRows(0).Cells(0).Value.ToString 

If you want to select a row programmatically and if the datagridview is on a tabcontrol then that particular tab must be visible.


dgrDataGridView.Rows(0).Selected = True
dgrDataGridView.Rows[0].Selected = True 'this in C#


Default Keyboard and Mouse Handling in the Windows Forms DataGridView Control



How to cast objects to a datagridviewrow ??


How can I remove all the rows ?

objDataGridView.Rows.Clear 

How can I remove the gridlines ?

Set the CellBorderStyle to None


How can I always display the vertical scrollbar ?

By default the datagridview only displays scrollbars when they are needed.
If you do want scrollbars permanently displayed then you need to derive your own class.


How can I get the value in the first column ?

The first column is at cell position 0.

objDataGridViewRow.Cells(0).Value.ToString 

How can I display the scrollbars after its been disabled ?

If you set the Enabled property to False to disable a datagridview and then later change it back to True, the scrollbars are not displayed.
try repopulating the datagridview or don't disable this control


How can I maintain the scroll position ?

Me.dgrDataGrid.CurrentCell = 
Me.dgrDataGrid.FirstDisplayedScrollingIndex


Properties

AllowDropWhether the control can accept data that is dragged and dropped onto this control.
AllowUserToAddRowsGets or sets the option for the user to add rows
AllowUserToDeleteRowsGets or sets the option for the user to delete rows
AllowUserToOrderColumnsGets or sets the option for the user to reorder the columns
AllowUserToResizeColumnsGets or sets the option for the user to adjust the column widths
AllowUserToResizeRowsGets or sets the option for the user to adjust the row heights
AlternatingRowsDefaultCellStyleOnly for rows with odd index numbers
AutoGenerateColumnsWhen you bind a DataGridView control you can set this property to true for the column types to be automatically generated which are appropriate for the datatype in the bound source.
AutoSizeColumnsModeIndicates how column widths are determined.
BackgroundColorGets or sets the color of the non-row area of the grid. This is always set to Window.
BorderStyleGets or sets the grid's border style.
CellBorderStyleGets or sets the cell border style
ClipboardCopyMode 
ColumnHeadersBorderStyle 
ColumnHeadersDefaultCellStyle 
ColumnHeadersHeight20
ColumnHeadersHeightSizeModeDisableResizing
ColumnHeadersVisibleGets or sets a value indicating whether the column headers a table are visible.
Columns 
ContextMenuStrip 
CurrentCellGets or sets which cell has the focus. Not available at design time.
CurrentRowIndexGets or sets index of the selected row.
DataMemberGets or sets the specific list in a DataSource for which the System.Windows.Forms.DataGrid control displays a grid.
DataSourceGets or sets the data source that the grid is displaying data for.
DefaultCellStyle 
EditMode 
EnableHeadersVisibleStyles 
FirstVisibleColumnGets the index of the first visible column in a grid.
FlatModeGets or sets a value indicating whether the grid displays in flat mode.
ForeColorOverridden. Gets or sets the foreground color (typically the color of the text) property of the System.Windows.Forms.DataGrid control.
GenerateMember 
GridColor 
GridLineColorGets or sets the color of the grid lines.
GridLineStyleGets or sets the line style of the grid.
HeaderBackColorGets or sets the background color of all row and column headers.
HeaderFontGets or sets the font used for column headers.
HeaderForeColorGets or sets the foreground color of headers.
LinkColorGets or sets the color of the text that you can click to navigate to a child table.
LinkHoverColorGets or sets the color a link changes to when the mouse pointer moves over it.
Margin 
MultiSelect 
ParentRowsBackColorGets or sets the background color of parent rows.
ParentRowsForeColorGets or sets the foreground color of parent rows.
ParentRowsLabelStyleGets or sets the way parent row labels are displayed.
ParentRowsVisibleGets or sets a value indicating whether the parent rows of a table are visible.
PreferredColumnWidthGets or sets the default width of the grid columns in pixels.
PreferredRowHeightGets or sets the preferred row height for the System.Windows.Forms.DataGrid control.
ReadOnlyGets or sets a value indicating whether the grid is in read-only mode.
RowHeadersBorderStyle 
RowHeadersDefaultCellStyle 
RowHeadersVisibleGets or sets a value that specifies whether row headers are visible.
RowHeadersWidthGets or sets the width of row headers.
RowHeadersWidthSizeMode 
RowsDefaultCellStyle 
ScrollBarsIndicates the type of scroll bars that are displayed: None, Horizontal, Vertical or Both. The scroll bars will only appear when they are needed and not all the time. If you want them to appear all the time then you must derive you own class.
SelectionModeFullRowSelect
ShowCellErrors 
ShowCellToolTipsFalse
ShowEditingIcon 
ShowRowErrors 
Size 
StandardTabSetting this to True prevents the tab key from being used to move the focus to the next cell.
TabIndex 
TabStop 
Tag 
UseWaitCursor 
VirtualMode 
Visible 

Methods

BeginEditAttempts to put the grid into a state where editing is allowed.
BeginInitBegins the initialization of a System.Windows.Forms.DataGrid that is used on a form or used by another component. The initialization occurs at run time.
ClearSelection 
CollapseCollapses child relations, if any exist for all rows, or for a specified row.
EndEditRequests an end to an edit operation taking place on the System.Windows.Forms.DataGrid control.
EndInitEnds the initialization of a System.Windows.Forms.DataGrid that is used on a form or used by another component. The initialization occurs at run time.
ExpandDisplays child relations, if any exist, for all rows or a specific row.
GetCellBoundsOverloaded. Gets the Rectangle that specifies the four corners of a cell.
GetCurrentCellBoundsGets a Rectangle that specifies the four corners of the selected cell.
HitTestOverloaded. Gets information about the System.Windows.Forms.DataGrid control at a specified point on the screen.
IsExpandedGets a value that indicates whether a specified row's node is expanded or collapsed.
IsSelectedGets a value indicating whether a specified row is selected.
ItemOverloaded. Gets or sets the value of a specified cell.
NavigateBackNavigates back to the table previously displayed in the grid.
NavigateToNavigates to the table specified by row and relation name.
ResetAlternatingBackColorResets the AlternatingBackColor property to its default color.
ResetBackColorOverridden. Resets the BackColor property to its default value.
ResetForeColorOverridden. Resets the ForeColor property to its default value.
ResetGridLineColorResets the GridLineColor property to its default value.
ResetHeaderBackColorResets the HeaderBackColor property to its default value.
ResetHeaderFontResets the HeaderFont property to its default value.
ResetHeaderForeColorResets the HeaderForeColor property to its default value.
ResetLinkColorResets the LinkColor property to its default value.
ResetSelectionBackColorResets the SelectionBackColor property to its default value.
ResetSelectionForeColorResets the SelectionForeColor property to its default value.
SelectOverloaded. Selects a specified row.
SetDataBindingSets the DataSource and DataMember properties at run time.
UnSelectUnselects a specified row.

Events

CellClickOccurs when any part of a cell is clicked by using the mouse. This is required if selection has been removed from the control and you need to know when focus comes back to this control.
CellBeginEdit 
CellEndEdit 
CellEnterOccurs when the current cell changes or when the control receives input focus.
CellFormattingOccurs when the contents of a cell need to be formatted for display.
CellValidatedOccurs after the cell has finished validating.
CellValidatingOccurs when a cell loses input focus, enabling content validation.
CurrentCellChangedOccurs when the CurrentCell property changes. This is fired when both arrow keys or mouse are used.
EditingControlShowingOccurs when a control for editing a cell is showing.
RowEnterOccurs when the current row is changed
SelectionChangedOccurs when the current selection changes.
TextChanged 

DataGridView-ListView-Equivalent

Properties to Change

AllowUserToAddRowsFalse
AllowUserToDeleteRowsFalse
AllowUserToResizeColumnsFalse
AllowUserToResizeRowsFalse
BackColourWindow
CellBorderStyleNone
ColumnHeadersVisibleFalse
MultiSelectFalse
RowHeadersVisibleFalse
RowTemplate - Height16
RowTemplate - ReadOnlyTrue
SelectionModeFullRowSelect


DataGridViewCell

This is the fundamental unit of interaction for the DataGridView
You can access cells by using the Cells collection of the DataGridViewRows class.

dgvDataGridViewRow.Cells(0) 

You can access the selected cells by using the SelectedCells collection

dgvDataGridViewRow.Cells(0) 

DataGridViewCell objects do not control their own appearance and painting features


Value

For a DataGridView that is not databound and not in virtual mode each cell stores its own value


Value (Databound)

For a DataGridView that is databound each cell does not know its actual value and must be looked up in the datasource.
The data type of each cell is automatically changed and this can be seen from the ValueType property.


FormattedValue

The FormattedValueType property determines the type that is used for display.
Most columns use String


Any time the FormattedValue is required the CellFormatting event is fired


If a cell cannot retrieve its formatted value correctly it raises the DataError event.


Question - How can I display a string date column in a specific date format ?

You must use MMMM and not mmmm (lowercase)

Private Sub dgvDataGridView_CellFormatting(ByVal sender As Object, _ 
                                           ByVal e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) _
                                           Handles dgvProjects.CellFormatting

   If Me.dgvDataGridView.Columns(e.ColumnIndex).Name = "DateStart" Then
      If e.Value.ToString.Length > 0 Then
         e.Value = CType(e.Value, System.DateTime).ToString("dd MMMM yy")
      End If
   End If
End Sub


DataGridViewCellStyle

Always try and set the default cell style for the whole DataGridView control

dgvDataGridView.DefaultCellStyle = 

Dim highlightCellStyle As New DataGridViewCellStyle 
highlightCellStyle.BackColor = Color.Red

Dim currencyCellStyle As New DataGridViewCellStyle
currencyCellStyle.Format = "C"
currencyCellStyle.ForeColor = Color.Green

With Me.dataGridView1
    .DefaultCellStyle.BackColor = Color.Beige
    .DefaultCellStyle.Font = New Font("Tahoma", 12)
    .Rows(3).DefaultCellStyle = highlightCellStyle
    .Rows(8).DefaultCellStyle = highlightCellStyle
    .Columns("UnitPrice").DefaultCellStyle = currencyCellStyle
    .Columns("TotalPrice").DefaultCellStyle = currencyCellStyle
End With

The following are the list of objects and properties that can get or set DataGridViewCellStyle objects

DataGridView.DefaultCellStyle 
DataGridViewColumn.DefaultCellStyle
DataGridViewRow.DefaultCellStyle

DataGridView.RowsDefaultCellStyle 
DataGridView.AlternatingRowsDefaultCellStyle
DataGridView.RowHeadersDefaultCellStyle
DataGridView.ColumnHeadersDefaultCellStyle

DataGridViewCell.Style 

DataGridViewColumn.InheritedStyle 
DataGridViewRow.InheritedStyle
DataGridViewCell.InheritedStyle

Getting the value of a style automatically instantiates a new DataGridViewCellStyle object if the property has not been previously set.
To avoid creating these accidentally you should check first using the following properties:

DataGridViewColumn.HasDefaultCellStyle 
DataGridViewRow.HasDefaultCellStyle

DataGridViewCell.HasStyle 


Properties

Alignment 
BackColor 
DataSourceNullValue 
Font 
ForeColor 
Format 
FormatProvider 
NullValue 
Padding 
SelectionBackColor 
SelectionForeColor 
WrapMode 



DataGridViewCellTemplate

Cell templates are instances of a DataGridViewCell derived class.


Making changes to the CellTemplate of a column will update the template and update all the cells in that column, refreshing the display if necessary.



DataGridViewRow

You can access the rows by using the Rows collection

dgvDataGridView.Rows 


AllowUserToAddRows

If this is enabled a special row is added.
This row is included in the Rows collection



Hiding Rows

Can be very slow

objDataGridView.SuspendLayout 
objDataGridViewRow.Visible = False
objDataGridView.ResumeLayout




Question - How can I change the Row Height ?

Expand the Row Template for the default and change the height property to 16


Question - How can I select the whole row ?

Change the SelectionMode property to FullRowSelect


Question - How can I get the rowindex of the currently selected cell ?

Me.dgrDataGridView.CurrentRow.Index 

although this is no good if you are inside the SelectionChanged event, in this situation use the following line

Me.dgrDataGridView.SelectedRows.Item(0).Cells(0).RowIndex 

Question - How can I select a different row ?

Me.dgrDataGridView.Rows(index).Selected = True 

Question - How can I be notified when a different row is selected ?

Save a form-level variable to keep track of the last selected row.

Private ilastselectedrowindex As System.Int32 

Private Sub dgrDataGridView_CurrentCellChanged(---)
   If (ilastselectedrowindex <> Me.dgrDataGridView.CurrentCell.RowIndex) Then

   End If
   ilastselectedrowindex = Me.dgrDataGridView.CurrentCell.RowIndex
End Sub

Question - How can I check if a selection has been made ?

If (Me.dgrDataGridView.SelectedRows.Count > 0) Then 
End If

Question - How can I remove the current selection from the datagridview, so nothing appears selected ?

Me.dgrDataGridView.ClearSelection 

Question - How can I enable typing a character and moving to that specific row (auto-complete for the first character) ?

It is possible to jump to the row starting with a particular letter.
This can be achieved using the KeyDown event.


Question - How can I remove the currently selected rows from the datagridview ?


Dim irowno As Integer 
For irowno = dgrDataGridView.SelectedRows.Count - 1 To 0 Step -1
   objDataRow = dgrDataGridView.SelectedRows(irowno)
   dgrDataGridView.Rows.Remove(objDataRow)

Next irowno

Question - How can I automatically scroll to the selected row ?

objDataGirdView.FirstDisplayedScrollingRowIndex 

Question - How can I format a whole row ?

Have to do it cell by cell

Rows().Cells(0).Style.BackColor = 

DataGridViewRowTemplate

Gets or sets the row that represents the template for all the rows.


Using a RowTemplate lets you create and initialise a DataGridViewRow for use as a template, including the row for new records if the AllowUserToAddRows property is true.


Useful if you are setting default values or a specific row height.




Adding Rows

Rows cannot be added to a DataGridView programmatically when the control is data bound.


Dim objDataGridViewRow As System.Windows.Forms.DataGridViewRow 

objDataGridView.Rows.Add()
objDataGridViewRow = objDataGridView.Rows(objDataGridView.Rows.Count -1)

objDataGridViewRow.Cells(0).Value = "first column"
objDataGridViewRow.Cells(1).Value = "second column"

objDataGridView.Rows.Add(New String() {"Parker", "Seattle"}) 


Bulk Addition

Do not use BeginEdit and EndEdit



The following line does not work ??

objDataGridView.Rows.Add(objDataGridViewRow) 

Defining and Populating a DataGridView

Dim ascolumnsarray As String(,) 

'create the application DataSet
clsDataSet.mobjDataSet = New System.Data.DataSet()

clsDataSet.mobjDataSet.Tables.Add(New System.Data.DataTable("Worksheets"))

ascolumnsarray = DataGridWorksheets_DefineColumns()
Call clsDataTable.DefineColumns(Me.dgrWorksheets, "Worksheets", ascolumnsarray)

Call DataGridWorksheets_TablePopulate()

Call clsDataGrid.ViewCreate(Me.dgrWorksheets, "Worksheets", True)

Call clsDataGrid.TableStyleDefine("Worksheets", _
                                  Me.dgrWorksheets, _
                                  ascolumnsarray, _
                                  New clsDataGrid.DataGridFormatCellEventHandler(AddressOf DataGrid_CellFormatEvent), _
                                  System.Drawing.Color.DarkBlue, _
                                  System.Drawing.Color.White)



How can I remove rows


dgvDataGridView.Rows.Clear(); 

dgvDataGridView.Rows.Remove(objDataGridViewRow) 



How can I shade the cells based on their value ?

This can be done using the CellFormatting event





DataGridViewColumn

The DataGridView control can have several different types of columns depending on what exactly you want to show.


You can access the columns in a DataGridView by using the Columns collection

dgvDataGridView.Columns 

You can access the selected columns by using the SelectedColumns collection

dgvDataGridView.SelectedColumns 


When data is not bound you should create an instance of a column type and then add it to the Columns collection.
When you define a column, you have to specify a cell template on which the column is based.



Question - How can I hide a column ?

dgvDataGridView.Columns(2).Visible = False 

Question - How can I make the last column wide enough to occupy the rest of the grid ?

Set the AutoSizeMode for the last column to Fill
You should also set a MinimumSize


Question - How can I prevent a column from being selected ?

This can be achieved by checking the column of the active cell in the CellBeginEdit event.

Private Sub dgrDataGridView_CellBeginEdit(---) 
   If (CType(sender, System.Windows.Forms.DataGridView).CurrentCell.ColumnIndex <> 1 Then
      e.Cancel
   End If
End Sub




DataGridViewTextBoxColumn

This column type has a cell template defined for a textbox.
By default this column type has its cell template initialised to a new DataGridViewTextBoxCell.


DataGridViewTextBoxCell

This is a specialised type of DataGridViewCell that is used to display a single string of editable text.
When you derive from this class you need to overrite the Clone method.



Setting a Default Value

Inherit a class from DataGridViewColumn and override the DefaultNewRowValue property



Properties

Name 
Width 
ReadOnly 


Question - How to sort a column that contains numbers

Make sure that the ValueType is set to a numeric datatype

Me.dgrDataGrid.Columns(2).ValueType = System.Type.GetType("Double") 




DataGridViewComboBoxColumn


Used to display a drop-down list inside a column of cells.
You can populate the drop-down list either manually or by binding it to a data source through the DataSource, DisplayMember and ValueMember properties.


When you add rows to the datagridview make sure this column is assigned a value from the drop-down list.



Single Click on Drop-Down

It is possible to have the drop-down displayed on a single click rather than 2 clicks.

objDataGridView.EditMode = Windows.Forms.DataGridViewEditMode.EditOnEnter 


Display Drop-Down when you click on cell

objDataGridView_CellEnter() 
   If e.ColumnIndex = 2 Then
      If Me.objDataGridView(e.ColumnIndex, e.RowIndex).EditType.ToString = _
         "System.Windows.Forms.DataGridViewComboBoxEditingControl" Then
          System.Windows.Forms.SendKeys.Send("{F4}")
      End If
   End If



Properties

Name 
Width 
DropDownWidth 
MaxDropDownItems 
FlatStyle 
StyleThis is set to DropDown to default. Change it to DropDownList to edit as well.


Events

SelectedIndexChangedOccurs when the selected index has changed, ie a different item is being selected.
CellBeginEdit 
SelectionChangeCommittedOccurs when the selected item has changed and that change is displayed in the ComboBox.



Question - How can I allow typing in a combo box cell ?

Two things have to be done.
The DropDownStyle property of the ComboBox editing control has to be changed to DropDown
Ensure that the entry made is added to the combobox collection.
The best place to add this entry is in the CellValidating event

Private Sub dgvDataGridView_CellValidating(ByVal sender As Object, _ 
                                           ByVal e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) _

   If Me.dgvDataGridView.Columns(e.ColumnIndex).Name = "ComboColumnName" Then
      If Me.dgvDataGridView.Columns(e.ColumnIndex).Items.Contains(e.FormattedValue) = False Then
         Me.dgvDataGridView.Columns(e.ColumnIndex).Items.Add(e.FormattedValue)
      End If
   End If
End Sub


Question - How can I have a combobox display a subset based on the selection in another combobox column ?

To enable this you need two versions of the filtered list (or subcategory).
Once list has no filters and the other is filtered based on the selection in the first combobox.


Private Sub dgvDataGridView_CellBeginEdit(ByVal sender As Object, _ 
                                          ByVal e As System.Windows.Forms.DataGridViewCellCancelEventArgs)

'set the combobox cell datasource to the filtered Binding Source
Dim objcomboboxcell As System.Windows.Forms.DataGridViewComboBoxCell
   objcomboboxcell = CType(Me.dgvDataGridView(e.ColumnIndex, e.RowIndex) , _
                           System.Windows.Forms.DataGridViewComboBoxCell)

   objcomboboxcell.DataSource =

End Sub

Private Sub dgvDataGridView_CellEndEdit(ByVal sender As Object, _ 
                                        ByVal e As System.Windows.Forms.DataGridViewCellEventArgs)

End Sub



DataGridViewComboBoxColumn - Multiple Selection

CellFormatting



SelectionChanged



EditingControlShowing


m_objEditingComboBox = CType(e.Control, Forms.ComboBox) 

AddHandler m_objEditingComboBox.SelectedIndexChanged
AddHandler m_objEditingComboBox.DropDown
AddHandler m_objEditingComboBox.DropDownClosed
AddHandler m_objEditingComboBox.KeyDown
AddHandler m_objEditingComboBox.KeyUp


Adding Columns


objdatagridviewcolumn = New System.Windows.Forms.DataGridViewColumn 
objdatagridviewcolumn.Name = "Title"
objdatagridviewcolumn.Width = 100
objdatagridviewcolumn.CellTemplate = New System.Windows.Forms.DataGridViewTextBoxCell
dgvSearchResults.Columns.Add(objdatagridviewcolumn)


Hiding Columns

The minimum width for a column is 5 and if you need to hide the column completely you can change the visible property in the Columns dialog box.



Sorting with a Particular Column

By default, users can sort the data in a DataGridView control by clicking the header of a text box column.
You can modify the SortMode property of specific columns to allow users to sort by other column types when it makes sense to do so.
You can also sort the data programmatically by any column, or by multiple columns.
The sort mode for each column is specified through the SortMode property of the column, which can be set to one of the following DataGridViewColumnSortMode enumeration values.


AutomaticDefault for text box columns. Unless column headers are used for selection, clicking the column header automatically sorts the DataGridView by this column and displays a glyph indicating the sort order.
NotSortableDefault for non-text box columns. You can sort this column programmatically; however, it is not intended for sorting, so no space is reserved for the sorting glyph.
ProgrammaticYou can sort this column programmatically, and space is reserved for the sorting glyph.

You might want to change the sort mode for a column that defaults to NotSortable if it contains values that can be meaningfully ordered.
For example, if you have a database column containing numbers that represent item states, you can display these numbers as corresponding icons by binding an image column to the database column. You can then change the numerical cell values into image display values in a handler for the System.Windows.Forms.DataGridView.CellFormatting event. In this case, setting the SortMode property to Automatic will enable your users to sort the column. Automatic sorting will enable your users to group items that have the same state even if the states corresponding to the numbers do not have a natural sequence. Check box columns are another example where automatic sorting is useful for grouping items in the same state.
You can sort a DataGridView programmatically by the values in any column or in multiple columns, regardless of the SortMode settings. Programmatic sorting is useful when you want to provide your own user interface (UI) for sorting or when you want to implement custom sorting. Providing your own sorting UI is useful, for example, when you set the DataGridView selection mode to enable column header selection. In this case, although the column headers cannot be used for sorting, you still want the headers to display the appropriate sorting glyph, so you would set the SortMode property to Programmatic.


Columns set to programmatic sort mode do not automatically display a sorting glyph. For these columns, you must display the glyph yourself by setting the System.Windows.Forms.DataGridViewColumnHeaderCell.SortGlyphDirection property. This is necessary if you want flexibility in custom sorting. For example, if you sort the DataGridView by multiple columns, you might want to display multiple sorting glyphs or no sorting glyph.


Although you can programmatically sort a DataGridView by any column, some columns, such as button columns, might not contain values that can be meaningfully ordered. For these columns, a SortMode property setting of NotSortable indicates that it will never be used for sorting, so there is no need to reserve space in the header for the sorting glyph.
When a DataGridView is sorted, you can determine both the sort column and the sort order by checking the values of the SortedColumn and SortOrder properties. These values are not meaningful after a custom sorting operation. For more information about custom sorting, see the Custom Sorting section later in this topic.
When a DataGridView control containing both bound and unbound columns is sorted, the values in the unbound columns cannot be maintained automatically. To maintain these values, you must implement virtual mode by setting the VirtualMode property to true and handling the CellValueNeeded and CellValuePushed events. For more information, see How to: Implement Virtual Mode in the Windows Forms DataGridView Control. Sorting by unbound columns in bound mode is not supported.


Programmatic Sorting

You can sort a DataGridView programmatically by calling its Sort method.
The Sort(DataGridViewColumn,ListSortDirection) overload of the Sort method takes a DataGridViewColumn and a ListSortDirection enumeration value as parameters. This overload is useful when sorting by columns with values that can be meaningfully ordered, but which you do not want to configure for automatic sorting. When you call this overload and pass in a column with a SortMode property value of System.Windows.Forms.DataGridViewColumnSortMode.Automatic, the SortedColumn and SortOrder properties are set automatically and the appropriate sorting glyph appears in the column header.


When the DataGridView control is bound to an external data source by setting the DataSource property, the Sort(DataGridViewColumn,ListSortDirection) method overload does not work for unbound columns. Additionally, when the VirtualMode property is true, you can call this overload only for bound columns. To determine whether a column is data-bound, check the IsDataBound property value. Sorting unbound columns in bound mode is not supported.



Custom Sorting

You can customize DataGridView by using the Sort(IComparer) overload of the Sort method or by handling the SortCompare event.


The Sort(IComparer) method overload takes an instance of a class that implements the IComparer interface as a parameter. This overload is useful when you want to provide custom sorting; for example, when the values in a column do not have a natural sort order or when the natural sort order is inappropriate. In this case, you cannot use automatic sorting, but you might still want your users to sort by clicking the column headers. You can call this overload in a handler for the ColumnHeaderMouseClick event if you do not use column headers for selection.
The Sort(IComparer) method overload works only when the DataGridView control is not bound to an external data source and the VirtualMode property value is false. To customize sorting for columns bound to an external data source, you must use the sorting operations provided by the data source. In virtual mode, you must provide your own sorting operations for unbound columns.


As an alternative to the Sort(IComparer) method overload, you can provide custom sorting by implementing a handler for the SortCompare event. This event occurs when users click the headers of columns configured for automatic sorting or when you call the Sort(DataGridViewColumn,ListSortDirection) overload of the Sort method. The event occurs for each pair of rows in the control, enabling you to calculate their correct order.


The SortCompare event does not occur when the DataSource property is set or when the VirtualMode property value is true.


Me.dataGridView1.Columns("Priority").SortMode = DataGridViewColumnSortMode.Automatic   



Sorting by a column - leave the current row highlighted but do not alter the scroll bar
maybe always leave the totoal frozen at the top ?



AllowSorting

This is a property of the DataGridTableStyle and indicates whether sorting is allowed on the grid table.
It has a default value of TRUE.

objDataGridTableStyle.AllowSorting = True 

When the AllowSorting property is True, a triangle appears in each column header indicating the direction of the sort.
The user can click on any column header to sort the grid by that column.
Clicking the column header a second time will automatically sort in the opposite direction.
This property overrides the AllowSorting property of the DataGrid object.


Custom Sorting

You can customise the sorting of your data by using the MouseDown and MouseUp events.

Private mbSortColumn As Boolean 

Private Sub dgrDataGrid_MouseDown(ByVal sender As Object, _ 
                                  ByVal e As System.Windows.Forms.MouseEventArgs) _
                                  Handles dgrDataGrid.MouseDown

   Dim objDataGridHitTestInfo As DataGrid.HitTestInfo

'Only use left mouse button clicks as right might have shortcut menu
   If e.Button = MouseButtons.Left Then
            
'Perform a hit test to determine where the mousedown event occured.
      objDataGridHitTestInfo = dgrDataGrid.HitTest(e.X, e.Y)
        
      If objDataGridHitTestInfo.Type = dgrDataGrid.HitTestType.ColumnHeader Then
          mbSortColumn = True
      End If
   End If
End Sub

Private Sub dgrDataGrid_MouseUp(ByVal sender As Object, _ 
                                ByVal e As System.Windows.Forms.MouseEventArgs) _
                                Handles dgrDataGrid.MouseUp

   Dim objDataGridHitTestInfo As DataGrid.HitTestInfo
   Dim objDataTable As DataTable
   Dim objDataView As DataView
   Dim scolumnname As String

' Use only left mouse button clicks.
   If e.Button = MouseButtons.Left And
       mbSortColumn = True Then

       mbSortColumn = False 'Reset mouse button state
        
' Perform a hit test to determine where the mousedown event occured.
       objDataGridHitTestInfo = dataGrid.HitTest(e.X, e.Y)
        
' If the mousedown event occured on a column header,
' then perform the sorting operation.
       If objDataGridHitTestInfo.Type = dgrDataGrid.HitTestType.ColumnHeader Then

          objDataTable = dgrDataGrid.DataSource
          objDataView = dgrDataTable.DefaultView
          scolumnname = dataTable.Columns(hitTest.Column).ColumnName
        
' If the sort property of the DataView is already the current column name, sort that in column descending order.
' Otherwise, sort on the column name.
          If dataView.Sort = scolumnname Then
              dataView.Sort = scolumnname + " DESC"
          Else
              dataView.Sort = scolumnname
          End If
      End If
   End If
End Sub

Linking a DataGridView directly to a DataTable

Never use this because it is a nightmare if you want to create a bespoke interface.


Rows cannot be added to a DataGridView programmatically when the control is data bound.
This can be done using the DataSource property of the DataGridView control.
The column headings will be automatically taken from the headings in the DataTable
After the DataGridView has been populated you need to then define the widths of the columns


When you bind a DataGridView control you can use the "AutoGenerateColumns" property to automatically generate default column types appropriate for the data types in the bound data sources.



Call clsDatabase.ConnectionOpen(gsConnectionString, gsSOLUTION_NAME) 

clsDatabase.mobjDataSet = New System.Data.DataSet()

clsDatabase.gDataAdapter = New System.Data.OleDb.OleDbDataAdapter()
clsDatabase.gDataAdapter.TableMappings.Add("Table", "TableName")

clsDatabase.gsSQLQuery = ""
clsDatabase.gsSQLQuery = clsDatabase.gsSQLQuery & " SELECT"
clsDatabase.gsSQLQuery = clsDatabase.gsSQLQuery & " Columns
clsDatabase.gsSQLQuery = clsDatabase.gsSQLQuery & " FROM"
clsDatabase.gsSQLQuery = clsDatabase.gsSQLQuery & " TableName

objdatacommand.Connection = clsDatabase.gDataConnection
objdatacommand.CommandText = clsDatabase.gsSQLQuery

clsDatabase.gDataAdapter.SelectCommand = objdatacommand
irowsreturned = clsDatabase.gDataAdapter.Fill(clsDatabase.mobjDataSet, "TableName")

dgvDataGridView.DataSource = clsDatabase.mobjDataSet.Tables("TableName").DefaultView
dgvDataGridView.Columns(0).Width = 50

Call clsDatabase.ConnectionClose(gsSOLUTION_NAME)


The dataset object in your program is only a representation of the data in your original database. If the user makes any changes to this data, it is not written back to the original database unless you specifically instruct the data adapter object to make these changes


When you make a change to the data grid, the data grid automatically updates the dataset to which the data grid is bound.



Alternating Row Shading

This enables you use style characteristics like foreground color and font, in addition to background color, to differentiate alternating rows.

DataGridView1.RowsDefaultCellStyle.BackColor = Color.Bisque 
DataGridView1.AlternatingRowsDefaultCellStyle.BackColor = Color.Beige

The styles specified using the RowsDefaultCellStyle and AlternatingRowsDefaultCellStyle properties override the styles specified on the column and DataGridView level, but are overridden by the styles set on the individual row and cell level.





With the DataGridView control, you can customize the appearance of the control's border and gridlines to improve the user experience.
You can modify the gridline color and the control border style in addition to the border styles for the cells within the control.
You can also apply different cell border styles for ordinary cells, row header cells, and column header cells.


The gridline color is used only with the Single, SingleHorizontal, and SingleVertical values of the DataGridViewCellBorderStyle enumeration and the Single value of the DataGridViewHeaderBorderStyle enumeration. The other values of these enumerations use colors specified by the operating system. Additionally, when visual styles are enabled on Windows XP and the Windows Server 2003 family through the System.Windows.Forms.Application.EnableVisualStyles method, the GridColor property value is not used.


Me.dataGridView1.GridColor = Color.BlueViolet 
Me.dataGridView1.BorderStyle = BorderStyle.Fixed3D

With Me.dataGridView1
    .CellBorderStyle = DataGridViewCellBorderStyle.None
    .RowHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single
    .ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single
End With

Cell Styles

Each cell within the DataGridView control can have its own style, such as text format, background color, foreground color, and font. Typically, however, multiple cells will share particular style characteristics.


Groups of cells that share styles may include all cells within particular rows or columns, all cells that contain particular values, or all cells in the control. Because these groups overlap, each cell may get its styling information from more than one place. For example, you may want every cell in a DataGridView control to use the same font, but only cells in currency columns to use currency format, and only currency cells with negative numbers to use a red foreground color.



Multiple Select Checked ListBox

modAppSpecific

Public mobjDataTable As New System.Data.DataTable() 
Public mobjDataView As System.Data.DataView

Public gdlgDialogName As frmDialog
Public giDataGridRowIndex As Integer

BET_DataGridName

Option Strict On 

Public Class BET_DataGridWorksheets
   Inherits System.Windows.Forms.DataGrid

   Private Const msMODULENAME As String = "BET_DataGrid"

   Private mobjCOMException As System.Runtime.InteropServices.COMException
   Private mobjException As Exception

'***************************************************************************************
   Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, _
                                              ByVal keyData As System.Windows.Forms.Keys) _
                                              As Boolean

      Try
         If clsError.ErrorFlag() = True Then Exit Function

         clsDataGrid.MouseOrArrowKey = "ArrowKey"

         Select Case msg.WParam.ToInt32

            Case CType(System.Windows.Forms.Keys.Up, Integer)
               Call CustomMouseUpEvent()


            Case CType(System.Windows.Forms.Keys.Down, Integer)
               Call CustomMouseDownEvent()

         End Select

         Call MyBase.ProcessCmdKey(msg, keyData)

      Catch objCOMException As System.Runtime.InteropServices.COMException
         mobjCOMException = objCOMException
      Catch objException As Exception
         mobjException = objException

      Finally
         If gbDEBUG = True Or _
            ((IsNothing(mobjCOMException) = False Or IsNothing(mobjException) = False)) Then

            Call clsError.Handle("ProcessCmdKey", msMODULENAME, _
                 "respond to this event successfully.", _
                 mobjCOMException, mobjException)
         End If
      End Try
   End Function
'***************************************************************************************
   Private Sub CustomMouseUpEvent()

      Try
         If clsError.ErrorFlag() = True Then Exit Sub

         Dim irowno As Integer

'the first row index in the datagrid
         If giDataGridRowIndex = 0 Then Exit Sub

         giDataGridRowIndex = giDataGridRowIndex - 1

      Catch objCOMException As System.Runtime.InteropServices.COMException
         mobjCOMException = objCOMException
      Catch objException As Exception
         mobjException = objException

      Finally
         If gbDEBUG = True Or _
            ((IsNothing(mobjCOMException) = False Or IsNothing(mobjException) = False)) Then

            Call clsError.Handle("CustomMouseUpEvent", msMODULENAME, _
                 "respond to this event successfully.", _
                 mobjCOMException, mobjException)
         End If
      End Try
   End Sub
'***************************************************************************************
   Private Sub CustomMouseDownEvent()

      Try
         If clsError.ErrorFlag() = True Then Exit Sub

         Dim irowno As Integer

'the last row indes in the datagrid
         Call MsgBox("Number of rows : " & mobjDataView.Table.Rows.Count)

         If giDataGridRowIndex = mobjDataView.Table.Rows.Count - 1 Then Exit Sub

         giDataGridRowIndex = giDataGridRowIndex + 1

      Catch objCOMException As System.Runtime.InteropServices.COMException
         mobjCOMException = objCOMException
      Catch objException As Exception
         mobjException = objException

      Finally
         If gbDEBUG = True Or _
            ((IsNothing(mobjCOMException) = False Or IsNothing(mobjException) = False)) Then

            Call clsError.Handle("CustomMouseDownEvent", msMODULENAME, _
                 "respond to this event successfully.", _
                 mobjCOMException, mobjException)
         End If
      End Try
   End Sub
'***************************************************************************************
End Class


frmDialog

Private Sub frmSheetManager_Load(ByVal sender As System.Object, _ 
                                 ByVal e As System.EventArgs) _
                                 Handles MyBase.Load

Dim sacolumnsarray(,) As String
Dim isheetnumber As Integer
Dim sadataarray(,) As String

   ReDim sacolumnsarray(1, 4)

   sacolumnsarray(0, 0) = "CheckBox" 'Type
   sacolumnsarray(0, 1) = "Include" 'Mapping Name
   sacolumnsarray(0, 2) = "" 'Header Name
   sacolumnsarray(0, 3) = "26" 'Width
   sacolumnsarray(0, 4) = "Include" 'Registry Prefix

   sacolumnsarray(1, 0) = "TextBox" 'Type
   sacolumnsarray(1, 1) = "Name" 'Mapping Name
   sacolumnsarray(1, 2) = "Name" 'Header Name
   sacolumnsarray(1, 3) = "90" 'Width
   sacolumnsarray(1, 4) = "Name" 'Registry Prefix

   mobjDataTable = clsDataGrid.DataTableDefine("Worksheets", sacolumnsarray)

   ReDim sadataarray(gApplication.Sheets.Count - 1, 1)

   For isheetnumber = 1 To gApplication.Sheets.Count

      objWorksjeet = CType(gApplication.Sheets(isheetnumber), Excel.Worksheet)

      sadataarray(isheetnumber - 1, 0) = "TRUE"

      sadataarray(isheetnumber - 1, 1) = objWorksjeet.Name

   Next isheetnumber


   Call clsDataTable.InsertRowsFromArray(mobjDataTable.TableName, "Worksheets", sadataarray)

   mobjDataView = New DataView(mobjDataTable)

   mobjDataView.AllowNew = False
   mobjDataView.AllowDelete = False
   mobjDataView.AllowEdit = False

   Me.dgrWorksheets.DataSource = mobjDataView

   Call clsDataGrid.DataTableStyleDefine(mobjDataTable.TableName.ToString, _
                                         Me.dgrWorksheets, _
                                          sacolumnsarray)
End Sub

   Private Sub dgrWorksheets_Click(ByVal sender As Object, _ 
                                   ByVal e As System.EventArgs) _
                                   Handles dgrWorksheets.Click

      Try
         If clsError.ErrorFlag() = True Then Exit Sub

         Dim objPoint As System.Drawing.Point = _
            Me.dgrWorksheets.PointToClient(System.Windows.Forms.Control.MousePosition)

         Dim objDataGridHitTest As System.Windows.Forms.DataGrid.HitTestInfo = _
            Me.dgrWorksheets.HitTest(objPoint)

         Dim objBindingManagerBase As System.Windows.Forms.BindingManagerBase = _
            Me.BindingContext(Me.dgrWorksheets.DataSource, Me.dgrWorksheets.DataMember)

         If (clsDataGrid.CellChangedEvent = True) And _
            (objDataGridHitTest.Row < objBindingManagerBase.Count) And _
            (objDataGridHitTest.Type = System.Windows.Forms.DataGrid.HitTestType.Cell) Then

'this toggles the value of the checkbox
            Me.dgrWorksheets(objDataGridHitTest.Row, 0) = _
               Not CBool(Me.dgrWorksheets(objDataGridHitTest.Row, 0))

         End If

      Catch objCOMException As System.Runtime.InteropServices.COMException
         mobjCOMException = objCOMException
      Catch objException As Exception
         mobjException = objException

      Finally
         If gbDEBUG = True Or _
            ((IsNothing(mobjCOMException) = False Or IsNothing(mobjException) = False)) Then

            Call clsError.Handle("dgrWorksheets_Click", msMODULENAME, _
                 "respond to this event successfully.", _
                 mobjCOMException, mobjException)
         End If
      End Try
   End Sub
'*************************************************************************************
   Private Sub dgrWorksheets_CurrentCellChanged(ByVal sender As Object, _
                                                ByVal e As System.EventArgs) _
                                                Handles dgrWorksheets.CurrentCellChanged

      Try
         If clsError.ErrorFlag() = True Then Exit Sub

         Dim itempdatagridrowindex As Integer
         Dim irowno As Integer

         If clsDataGrid.MouseOrArrowKey.Length = 0 Then
            clsDataGrid.MouseOrArrowKey = "Mouse"
         End If

         Dim objPoint As System.Drawing.Point = _
            Me.dgrWorksheets.PointToClient(System.Windows.Forms.Control.MousePosition)

         Dim objDataGridHitTest As System.Windows.Forms.DataGrid.HitTestInfo = _
            Me.dgrWorksheets.HitTest(objPoint)

         If clsDataGrid.MouseOrArrowKey = "Mouse" Then
            itempdatagridrowindex = objDataGridHitTest.Row()
         Else
            itempdatagridrowindex = -1
         End If

         If (itempdatagridrowindex > -1) Then
            giDataGridRowIndex = itempdatagridrowindex

            irowno = CInt(Me.dgrWorksheets(giDataGridRowIndex, 2))
         End If

         clsDataGrid.MouseOrArrowKey = ""

      Catch objCOMException As System.Runtime.InteropServices.COMException
         mobjCOMException = objCOMException
      Catch objException As Exception
         mobjException = objException

      Finally
         If gbDEBUG = True Or _
            ((IsNothing(mobjCOMException) = False Or IsNothing(mobjException) = False)) Then

            Call clsError.Handle("dgrWorksheets_CurrentCellChanged", msMODULENAME, _
                 "respond to this event successfully.", _
                 mobjCOMException, mobjException)
         End If
      End Try

   End Sub

#Region " Windows Form Designer generated code "

Change the control declaration to refer to the new custom control.

Friend WithEvents dgrWorksheets As BET_DataGridName 
   Me.dgrWorksheets = New BET_DataGridName()

Columns and Column Order

The correspondence between a particular column in a DataTable, and a particular DataGridColumnStyle object is made through the DataGridColumnStyle.MappingName property. This property is the one required property that is needed to be set when creating a DataGridColumnStyle. Other DataGridColumnStyle properties of interest include Header, ReadOnly and Width.
The following is a modified Form Load event handler that will create the DataGridTableStyle (step 1), create the DataGridColumnStyles and add them to the GridColumnStyles collection (step 2), and finally, add the DataGridTableStyle to the DataGrid.TableStyles property (step 3).


Private Sub Form1_Load(ByVal sender As System.Object, _ 
                       ByVal e As System.EventArgs) Handles MyBase.Load
                       Me.SqlDataAdapter1.Fill(Me.DataSet11)

Dim tableStyle As New DataGridTableStyle()
Dim discontinuedCol As New DataGridBoolColumn()
Dim column As New DataGridTextBoxColumn()

   tableStyle = New DataGridTableStyle()
   tableStyle.MappingName = "Products"

   discontinuedCol.MappingName = "Discontinued"
   discontinuedCol.HeaderText = ""
   discontinuedCol.Width = 30
   discontinuedCol.AllowNull = False
   tableStyle.GridColumnStyles.Add(discontinuedCol)

   column = New DataGridTextBoxColumn()
   column.MappingName = "ProductID"
   column.HeaderText = "ID"
   column.Width = 30
   tableStyle.GridColumnStyles.Add(column)

   column = New DataGridTextBoxColumn()
   column.MappingName = "ProductName"
   column.HeaderText = "Name"
   column.Width = 140
   tableStyle.GridColumnStyles.Add(column)

'Add the tablestyle to the datagrid
   Me.DataGrid1.TableStyles.Add(tableStyle)
End Sub


Change Click Behavior on a Discontinued Row

Move focus to a particular column when the user selects any cell the focus is moved to the first column
Add a handler for the CurrentCellChanged event. In that handler, if the value in the first column is true then set the current cell to the first column.

Private Sub dgrDataGrid_CurrentCellChanged(ByVal sender As Object, _ 
                                           ByVal e As System.EventArgs) _
                                           Handles dgrDataGridCurrencies.CurrentCellChanged

Dim objCheckBoxValue As Object = Me.dgrDataGrid(Me.DataGrid1.CurrentRowIndex, 0)
Dim bCheckBoxValue As Boolean = CBool(objCheckBoxValue)

  If (bCheckBoxValue = True) Then
     Me.dgrDataGrid.CurrentCell = _
         New System.Windows.Forms.DataGridCell(Me.dgrDataGrid.CurrentRowIndex, 0)
   End If
End Sub


Changing Cell BackColor, ForeColor and Font

To customize the way a DataGrid cell is drawn, it is necessary to override the Paint method in a derived DataGridTextBoxColumn class. When the DataGrid with a DataGridTableStyle associated with it needs to paint a cell, it calls the Paint method of the DataGridTextBoxColumn. The arguments in this call include a ForeBrush and BackBrush that controls the colors used to draw the cell. Therefore, to control the colors used to paint a cell on a per cell basis, it is possible to override the virtual Paint method of the DataGridTextBoxColumn class, and change the brushes according to the particular cell being drawn.


The following strategy is to add an event to the derived DataGridTextBoxColumn. In the derived Paint method, this event is fired to allow listeners to provide the BackColor and ForeColor to be used for the cell being painted. Once this event is in place, it is only necessary to write an event handler to control the color of a grid cell. Also derived are special EventArgs to provide the event listener with the row index and column index values, and to allow the event listener to set the color properties.


Creating the Event Arguments Class
In Solution Explorer, right-click the project, and add a new class named DataGridFormatCellEventArgs, which is inherited from EventArgs. The following is a list of the public properties of this class and a note on how they are used. The first three properties are passed into the event handler through the argument; the last seven properties are set by the event listener to indicate how the cell should be drawn.


Column (Integer): The column number of the cell being painted.
Row (Integer): The row number of the cell being painted.
CurrentCellValue (Object): The current cell value.
TextFont (Font): The font to be used to draw text in the cell.
BackBrush (Brush): The brush used to paint the cell's background.
ForeBrush (Brush): The brush used to paint the text in the cell.
TextFontDispose (Boolean): True to call the TextFont.Dispose.
BackBrushDispose (Boolean): True to call the BackBrush.Dispose.
ForeBrushDispose (Boolean): True to call the ForeBrush.Dispose.
UseBaseClassDrawing (Boolean): True to call the MyBase.Paint.
The dispose properties included as part of the DataGridFormatCellEventArgs are set by the listener if the Paint override should call the Dispose method on the objects that implement IDisposable. For example, if dynamically creating a BackBrush every time a grid cell is drawn, use the Paint method to call Dispose on the brush after it is finished with its drawing. On the other hand, if a single brush is created and cached that will be used to provide the BackBrush for some cells, it is not desirable to have the Paint method to call Dispose on the cached brush. These dispose properties let the listener tell the Paint override how to handle the Dispose calls.


The following is how the actual class code should appear. Before the class definition, add the delegate that determines the signature of the event handler required for the SetCellFormat event that will be defined as part of the derived DataGridTextBoxColumn.


Public Delegate Sub FormatCellEventHandler( _ 
     ByVal sender As Object, _
     ByVal e As DataGridFormatCellEventArgs)

Public Class DataGridFormatCellEventArgs
    Inherits EventArgs

    Public Sub New(ByVal row As Integer, _
       ByVal col As Integer, _
       ByVal cellValue As Object)
    End Sub 'New
End Class


Creating the Derived DataGridTextBoxColumn

The next step is to add the class code for the derived DataGridTextBoxColumn. This is done in the same way, right-clicking the project in Solution Explorer and adding a new class named FormattableTextBoxColumn. To add the stub for the Paint override, select (Overrides) in the left drop-down list on top of the editing window, and then select the second Paint method (the one with seven arguments) listed in the right drop-down list at the top of the editing window. The following is what the code will look like.


Note Notice the SetCellFormat event declaration is also added, using the event delegate FormatCellEventHandler that was defined in the previous class file.

Public Class FormattableTextBoxColumn 
    Inherits DataGridTextBoxColumn

    Public Event SetCellFormat As FormatCellEventHandler

 Protected Overloads Overrides Sub Paint(………)
        
    End Sub 'Paint
End Class

Implementing the Paint Override

The first thing to do in the Paint override is to fire the SetCellFormat event. It is through this event that an event listener will provide the format preferences that are to be used with this cell. The steps are to create the DataGridFormatCellEventArgs, initializing the row, column and current value, and then raise the event with this argument. Finally, after the event has been raised, query the event argument to see what formatting actions need to be done.


Raising the Event

The following is the code snippet that creates the argument and fires the event. The variables source and rowNum are passed into the Paint method.


Dim e As DataGridFormatCellEventArgs = Nothing 

'Get the column number
    Dim col As Integer = _
     Me.DataGridTableStyle.GridColumnStyles.IndexOf(Me)
    
'Create the eventargs object
 e = New DataGridFormatCellEventArgs( _
       rowNum, col, _
       Me.GetColumnValueAtRow([source], rowNum))

'fire the formatting event
   RaiseEvent SetCellFormat(Me, e)

Color Formatting

Once the event has returned, inspect the properties of the DataGridFormatCellEventArgs object to find out about the colors in which the cell should appear. The two brushes, foreBrush and backBrush, used to draw the cell are passed as arguments to the Paint method. To change the cell colors, swap the old brushes for the new brushes before calling the Paint method's base class implementation. The following is a code snippet that illustrates this idea:


' assume we will call the baseclass

Dim callBaseClass As Boolean = True 

' check the brushes returned from the event
   If Not (e.BackBrush Is Nothing) Then
        backBrush = e.BackBrush
   End If
   If Not (e.ForeBrush Is Nothing) Then
        foreBrush = e.ForeBrush
   End If

' check the UseBaseClassDrawing property
   If Not e.UseBaseClassDrawing Then
            callBaseClass = False
   End If
   
   If callBaseClass Then
        MyBase.Paint(g, bounds, _
          [source], rowNum, backBrush, _
          foreBrush, alignToRight)
   End If

The previous snippet sets the brushes depending upon the properties of the DataGridFormatCellEventArgs object. Depending upon the UseBaseClassDrawing value, it calls the base class. Calling the base class with the modified brushes is how to affect the cell color. Recall that this method will be called for each cell in a column as it is drawn.


Handling the TextFont Property

Trying to change the font used to draw in the cell adds a real complication to the Paint override. The reason is that the font used to draw the text is not a simple parameter passed as an argument in the Paint method. Instead, the font used to draw the cell text is originally gotten from the DataGrid.Font property. But this font is cached, and is not reset from the DataGrid.Font property for each cell. Thus, the first impulse, dynamically changing DataGrid.Font for each cell does not work. Therefore, to change the font of the cell, it is necessary to actually draw the string so that the font can be specified. Doing so adds significant work to try to mimic exactly the output of the standard DataGrid. The example will not go to extremes in this respect, but will be satisfied to get similar output, but not exactly the same output as with the unaltered DataGrid. The following is the code snippet that is used to implement drawing the string with a newly specified font.


'if TextFont set, then must call drawstring
If Not (e.TextFont Is Nothing) Then
    Try
        Dim charWidth As Integer = _
            Fix(Math.Ceiling(g.MeasureString("c", _
                e.TextFont, 20, _
                StringFormat.GenericTypographic).Width))
        Dim s As String = _
           Me.GetColumnValueAtRow([source], _
                 rowNum).ToString()
        Dim maxChars As Integer = _
           Math.Min(s.Length, bounds.Width / charWidth)

        Try
            g.FillRectangle(backBrush, bounds)
            g.DrawString(s.Substring(0, maxChars), _
                e.TextFont, foreBrush, _
                bounds.X, bounds.Y + 2)
        Catch ex As Exception
              Console.WriteLine(ex.Message.ToString())
        End Try
    Catch 'empty catch
    End Try
    callBaseClass = False
End If

The first part of the code approximates the number of characters that can be displayed in the current cell width. It does so by approximating an average character size, and dividing this value into the cell width. It would be possible to do this more rigorously—repeatedly calling MeasureString to get the exact number of characters that would fit—but doing so makes the output significantly different than what the default DataGrid produces. Using this average character size approach is much quicker, and gives results much closer to the default DataGrid.


After getting the number of characters, draw the background and call Graphics.DrawString passing it the font and brush desired for use. Finally, set the callBaseClass flag so the baseclass Paint method is not later called, undoing all of the previous work.


Using the New FormattableTextBoxColumn Class

To use the newly derived column, go back to the form code, and swap out the occurrences of DataGridTextBoxColumn with FormattableTextBoxColumn. Then for each column, wire up the listener for the SetCellFormat event exposed by the derived class. There exists the option of using a different event handler for each column. But the requirements (coloring rows) allow the use of the same handler for all the columns. The following is the code for the typical column after these changes:


'ProductName
column = New FormattableTextBoxColumn()
column.MappingName = "ProductName"
column.HeaderText = "Name"
column.Width = 140
AddHandler column.SetCellFormat, AddressOf FormatGridRow
tableStyle.GridColumnStyles.Add(column)

Notice that an instance of a derived class is now being created, FormattableTextBoxColumn. Also, a handler is added, FormatGridRow, for the SetCellFormat event.


The SetCellFormat Event Handler

In the SetCellFormat handler, the brushes used to set the forecolor and backcolor of the cell whose row and column indexes are passed in through the event arguments will be set. If the cell is on the current row of the grid, use one set of colors and font. If the cell is on a row that has the Discontinued Boolean value set to true, use another set of colors. The following is the code that declares and defines these cached GDI+ objects.


'form variables to cache GDI+ objects
Private disabledBackBrush As Brush
Private disabledTextBrush As Brush
Private currentRowFont As Font
Private currentRowBackBrush As Brush

' In Form1_Load create and cache some GDI+ objects.
Me.disabledBackBrush = _
  New SolidBrush(SystemColors.InactiveCaptionText)
Me.disabledTextBrush = _
  New SolidBrush(SystemColors.GrayText)
Me.currentRowFont = _
  New Font(Me.DataGrid1.Font.Name, _
           Me.DataGrid1.Font.Size, _
           FontStyle.Bold)
Me.currentRowBackBrush = Brushes.DodgerBlue

In the code for the event handler, members of the DataGridFormatCellEventArgs value that are passed to display particular rows with special effects will be set. Discontinued rows will be grayed out, and the current row will be displayed with a DodgerBlue background using a bold font. The following is the handler that will implement these effects:


Private Sub FormatGridRow(ByVal sender As Object, _ 
        ByVal e As DataGridFormatCellEventArgs)
    Dim discontinuedColumn As Integer = 0
' Conditionally set properties in e depending upon e.Row and e.Col.
    Dim discontinued As Boolean = CBool( _
       IIf(e.Column <> discontinuedColumn, _
       Me.DataGrid1(e.Row, discontinuedColumn), _
       e.CurrentCellValue))
' Check if discontinued?
    If e.Column > discontinuedColumn AndAlso _
CBool(Me.DataGrid1(e.Row, discontinuedColumn)) Then
         e.BackBrush = Me.disabledBackBrush
         e.ForeBrush = Me.disabledTextBrush
' current row?
    ElseIf e.Column > discontinuedColumn AndAlso _
           e.Row = Me.DataGrid1.CurrentRowIndex Then
         e.BackBrush = Me.currentRowBackBrush
         e.TextFont = Me.currentRowFont
    End If
End Sub

Recall that there are actually several FormattableTextBoxColumn objects in the DataGrid.TableStyle(0).GridColumnStyles collection. In fact, every column, except the Boolean column, is using one of these objects. In addition, each of these objects has a SetCellFormat event, to which is being listened. However, each of these events is using the same handler, FormatGridRow, which was previously listed. Thus, the same formatting logic is being applied to every cell (except the Boolean cell) in a row. Therefore, the formatting is effectively setting row styles in that all cells in the row will show the same formatting behavior, even though the formatting is being done on a cell-by-cell basis.



The following is a picture of the grid at this point. Notice that the checked rows are grayed out, showing the item is discontinued, and the current row is DodgerBlue with a bold font.



The initial display looks pretty good. But there is a small problem that needs some effort to handle. The problem is that a click on a checked first column unchecks the cell properly, but does not redraw the row. This row needs to be refreshed anytime the check value changes. To handle this problem, it is necessary to derive from DataGridBooleanColumn and add an event handler that fires an event anytime the bool value changes. This will allow reaction to the changing click, and forces the row to be redrawn reflecting the new click value.



Adding a Boolean Column with a Value Changed

The Event Arguments
Start by adding a new class derived from EventArgs that holds the information the BoolValueChanged event will require. In particular, properties will be added that give the row and column of the cell being changed, as well as add properties to the new Boolean value. The following is the BoolValueChangedEventArgs class. The missing implementation uses private fields only to shadow the public properties with the fields being initialized in the constructor. The delegate will also be defined, BoolValueChangedEventHandler, for this event at the beginning of this file.


Public Delegate Sub BoolValueChangedEventHandler( _ 
       ByVal sender As Object, _
       ByVal e As BoolValueChangedEventArgs)

Public Class BoolValueChangedEventArgs
    Inherits EventArgs
    
    Public Sub New(ByVal row As Integer, _
                   ByVal col As Integer, _
                   ByVal val As Boolean)
           . . .
    End Sub 'New

    Public Property Column() As Integer
           . . .
 End Property

    Public Property Row() As Integer
         . . .
    End Property

    Public ReadOnly Property BoolValue() As Boolean
           . . .
    End Property
End Class 'BoolValueChangedEventArgs


The Derived DataGridBoolColumn Class

Now for the derived class, ClickableBooleanColumn, three methods will be overridden to handle different aspects of monitoring the Boolean value in the cell. The first override is Edit. This method is called when a cell needs to go into edit mode. In the override, some private field members will be set to reflect the current Boolean state as well as the fact that the current mode is in edit mode. The second override is the same Paint method that was previously used in FormattableTextBoxColumn. Here, instead of trying to modify the cell appearance, this method is used to track the change in the Boolean value, and to raise the BoolValueChanged event if it does change. The third override is the Commit method. This method is called when the editing is complete, and the value should be committed to the datasource. This method is used to turn off the fields that were set in the Edit override.


There are some technical points that need to be handled. The Boolean value can change because of a click in the cell, or a pressed spacebar, while the cell has focus. The helper method, ManageBoolValueChanging, is used to monitor these possibilities. Furthermore, it raises the BoolValueChanged event if the value is changed. To monitor the mouse click, the ManageBoolValueChanging checks the Control.MousePosition and Control.MouseButtons shared (static) properties. However, checking the KeyState of the spacebar is problematic. Resort to an Interop call into Win32 API GetKeyState to handle this problem.


The following is the code for the class: 

Public Class ClickableBooleanColumn
    Inherits DataGridBoolColumn

'changed event
    Public Event BoolValueChanged _
         As BoolValueChangedEventHandler

'set variables to start tracking bool changes
    Protected Overloads Overrides Sub Edit(. . .)
        Me.lockValue = True
        Me.beingEdited = True
        Me.saveRow = rowNum
        Me.saveValue = CBool( _
           MyBase.GetColumnValueAtRow( _
                    [source], rowNum))
        MyBase.Edit(. . .)
    End Sub 'Edit

'overridden to handle BoolChange event
    Protected Overloads Overrides Sub Paint(. . .)
        Dim colNum As Integer = _
           Me.DataGridTableStyle.GridColumnStyles.IndexOf(Me)

'used to handle the boolchanging
        ManageBoolValueChanging(rowNum, colNum)

        MyBase.Paint(. . .)

    End Sub 'Paint

'turn off tracking bool changes
    Protected Overrides Function Commit(. . .) As Boolean
        Me.lockValue = True
        Me.beingEdited = False
        Return MyBase.Commit(dataSource, rowNum)
    End Function 'Commit

'fields to track bool value
    Private saveValue As Boolean = False
    Private saveRow As Integer = -1
    Private lockValue As Boolean = False
    Private beingEdited As Boolean = False
    Public Const VK_SPACE As Integer = 32

'needed to get the space bar changing of the bool value
    <System.Runtime.InteropServices.DllImport("user32.dll")> _
    Shared Function GetKeyState( _
           ByVal nVirtKey As Integer) As Short
    End Function

'fire the bool change event if the value changes
    Private Sub ManageBoolValueChanging( _
                ByVal rowNum As Integer, _
                ByVal colNum As Integer)
        Dim mousePos _
            As Point = Me.DataGridTableStyle.DataGrid.PointToClient( _
                       Control.MousePosition)
        Dim dg As DataGrid = Me.DataGridTableStyle.DataGrid
        Dim isClickInCell As Boolean = _
            Control.MouseButtons = MouseButtons.Left AndAlso _
            dg.GetCellBounds(dg.CurrentCell).Contains(mousePos)

        Dim changing As Boolean = _
            dg.Focused AndAlso isClickInCell _
            OrElse GetKeyState(VK_SPACE) < 0
' or spacebar
        If Not lockValue AndAlso _
                  beingEdited AndAlso _
                  changing AndAlso _
                  saveRow = rowNum Then
            saveValue = Not saveValue
            lockValue = False

'fire the event
            Dim e As New BoolValueChangedEventArgs( _
                          rowNum, colNum, saveValue)
            RaiseEvent BoolValueChanged(Me, e)
        End If
        If saveRow = rowNum Then
            lockValue = False
        End If
    End Sub 'ManageBoolValueChanging
End Class


Using the ClickableBooleanColumn

To use the special column style, create an instance of it when setting up the columnstyle for the discontinuedCol. A handler for the new BoolValueChanged event will also be added.


'    Discontinued.
Dim discontinuedCol As New ClickableBooleanColumn()
discontinuedCol.MappingName = "Discontinued"
discontinuedCol.HeaderText = ""
discontinuedCol.Width = 30
' turn off tristate
discontinuedCol.AllowNull = False
AddHandler discontinuedCol.BoolValueChanged, _
AddressOf BoolCellClicked
tableStyle.GridColumnStyles.Add(discontinuedCol)


The BoolValueChanged Event Handler

In the event handler, the row must be forced to be redrawn using the new value of the Boolean cell. Anytime the Boolean value changes, the event handler invalidates the rectangle associated with a row to reflect the proper format. To force a redraw of a row, use a RefreshRow helper method. In the event handler, the Boolean value is forced to be stored by ending the edit mode. Doing so allows the FormatGridRow method to obtain the proper value of the Boolean cell. Without this call, only the initial value of the cell is available through the indexed DataGrid.


Private Sub BoolCellClicked(ByVal sender As Object, _ 
            ByVal e As BoolValueChangedEventArgs)
    Dim g As DataGrid = Me.DataGrid1
    Dim discontinuedCol As New _
         ClickableBooleanColumn = _
         g.TableStyles(0).GridColumnStyles("Discontinued")
    g.EndEdit(discontinuedCol, e.Row, False)
    RefreshRow(e.Row)
    g.BeginEdit(discontinuedCol, e.Row)
End Sub

' Forces a repaint of given row.
Private Sub RefreshRow(ByVal row As Integer)
    Dim r As Rectangle = _
        Me.dataGrid1.GetCellBounds(row, 0)
    r = New Rectangle(r.Right, r.Top, _
        Me.dataGrid1.Width, r.Height)
   Me.dataGrid1.Invalidate(r)
End Sub 'RefreshRow

There is one last problem to handle before clicking the check box to be certain that the cell behaves as expected with the row changing colors with each click. If a checkbox is clicked that is not checked and is not on the current row, then the click sets the row in the current row colors, instead of the discontinued colors. Furthermore, the problem appears strictly to be a refresh problem because dragging the grid on and off the display will cause the row to be redrawn using the expected discontinued format. This refresh problem can be handled by forcing the row to be refreshed during the existing dataGrid1_Click event handler. In that event, the code is executed to change the Boolean value, and also to refresh the row.


If afterCurrentCellChanged AndAlso _ 
   hti.Row < bmb.Count AndAlso _
   hti.Type = DataGrid.HitTestType.Cell AndAlso _
   hti.Column = 0 Then
        Me.DataGrid1(hti.Row, 0) = _
            Not CBool(Me.DataGrid1(hti.Row, 0))
        RefreshRow(hti.Row)
End If


Adding Row-Dependent ToolTips

The Windows Forms ToolTip class can be used to add a ToolTip for an entire DataGrid. To do so, drag the ToolTip component onto the Form and set the ToolTip property that is added to every control on the form. However, implementing a ToolTip in this manner does not allow the tip to be varied when moving from row to row (or cell to cell) in the DataGrid. The goal is to display a ToolTip that can be varied from row to row. Using the same strategy, ToolTips could be implemented that vary from cell to cell or column to column.


Start by adding two fields to the Form class, hitRow and ToolTip1. These fields are initialized in Form1_Load, and are dynamically changed in the dataGrid1_MouseMove event handler that was added. In the MouseMove, check that the cursor is over a new row, and if so, reset the ToolTip with different text. See the following code example:


    Private hitRow As Integer 
    Private toolTip1 As System.Windows.Forms.ToolTip

' In Form1_Load
    Me.hitRow = -1
    Me.toolTip1 = New ToolTip()
    Me.toolTip1.InitialDelay = 300
    AddHandler Me.DataGrid1.MouseMove, _
          AddressOf dataGrid1_MouseMove

Private Sub dataGrid1_MouseMove( _
        ByVal sender As Object, _
        ByVal e As MouseEventArgs)

   Dim g As DataGrid = Me.dataGrid1
   Dim hti As DataGrid.HitTestInfo = _
              g.HitTest(New Point(e.X, e.Y))
   Dim bmb As BindingManagerBase = _
            Me.BindingContext(g.DataSource, g.DataMember)
   If hti.Row < bmb.Count AndAlso _
      hti.Type = DataGrid.HitTestType.Cell AndAlso _
      hti.Row <> hitRow Then
       If Not (Me.toolTip1 Is Nothing) AndAlso _
            Me.toolTip1.Active Then
            Me.toolTip1.Active = False 'turn it off
       End If
       Dim tipText As String = ""
       If CBool(Me.DataGrid1(hti.Row, 0)) Then
           tipText = Me.DataGrid1(hti.Row, 2).ToString() & _
                " discontinued"
           If tipText <> "" Then
'new hit row
                hitRow = hti.Row
                Me.toolTip1.SetToolTip(Me.DataGrid1, tipText)
' make it active so it can show
                Me.toolTip1.Active = True
            Else
                hitRow = -1
            End If
        Else
            hitRow = -1
        End If
    End If
End Sub 'dataGrid1_MouseMove


Conclusion

The final DataGrid uses two derived DataGridColumnStyles to handle cell formatting and monitor Boolean value changes. The formatting is accomplished by overriding the derived classes' Paint method, and raising an event to allow a listener to furnish format information based on a row and column index. The derived class that manages Boolean values also overrides the Paint method, firing an event when the value changes at this point; in addition, it requires Edit and Commit overrides to fully manage this task.


Also used are form level events to control DataGrid behavior. The DataGrid.CurrentCellChanged event was used to force the current cell to a particular cell in the row, no matter which cell in the row was clicked. The DataGrid.Clicked event was used to make the CheckBox column responds to the first click, instead of requiring two clicks to change its value. Finally, the DataGrid.MouseMove event was used to make ToolTips dynamic by changing from row to row.


There are two samples accompanying this article in the attached download (see link at the top). The first one, CustomGrid, contains the VB .NET project that served as the source for all the code snippets in this discussion. The second, CustomGridA, contains two projects, one VB .NET and the other C#. The C# project is more involved than the one used in this discussion, but generally follows the same flow.


The KeyUp, KeyPress and KeyDown events of the DataGrid do not fire when if you are in a cell.


Skip Columns When Tabbing

You can solve this problem by handling the DataGrid's CurrentCellChanged event, and simulate a Tab keypress when the user selects an "inaccessible" column.


Enable the column-skipping mechanism for the input grid and for the columns with the specified index
save the array of column indexes in the grid's Tag property
attach the grid's CurrentCellChanged event to the GenDataGrid_CurrentCellChanged event handler

Public Sub EnableDataGridColumnSkip(ByVal grid As DataGrid, _ 
                                    ByVal ParamArray columnsToSkip() As Integer)
    grid.Tag = columnsToSkip

    AddHandler grid.CurrentCellChanged, AddressOf GenDataGrid_CurrentCellChanged
End Sub

Disable the column-skipping mechanism for the input grid
' detach the grid's CurrentCellChanged event from the GenDataGrid_CurrentCellChanged event handler

Public Sub DisableDataGridColumnSkip(ByVal grid As DataGrid) 
    RemoveHandler grid.CurrentCellChanged, AddressOf GenDataGrid_CurrentCellChanged
End Sub

Handle the grid's CurrentCellChanged, to skip the columns whose index is found in the array stored in grid's Tag property
cast the generic sender Object to a DataGrid
cast the grid's Tag to an array of Integers, that contains the indexes of the columns to skip
get the index of the current column
if the current column's index is found in the columnsToSkip array, simulate a TAB key press,

Public Sub GenDataGrid_CurrentCellChanged(ByVal sender As Object, _ 
                                          ByVal e As System.EventArgs)
    Dim grid As DataGrid = DirectCast(sender, DataGrid)
    Dim columnsToSkip() As Integer = DirectCast(grid.Tag, Integer())
    Dim currColIndex As Integer = grid.CurrentCell.ColumnNumber

' so that the focus is moved to the next column in the same row, or to the next row if this is the last column
    If Array.IndexOf(columnsToSkip, currColIndex) > -1 Then
        SendKeys.Send("{TAB}")
    End If
End Sub



© 2020 Better Solutions Limited. All Rights Reserved. © 2020 Better Solutions Limited TopPrevNext