Showing posts with label DataGridView. Show all posts
Showing posts with label DataGridView. Show all posts

Thursday, January 28, 2010

DataGridView control change Selection on Mouse Up

Level: Intermediate

Knowledge Required:
  • DataGridView Control
  • Windows Forms
Description:
Recently, one of our friend "Luc" asked me,

Is there an "easy" way to change the selection behavior in a datagridview so that a click on a selected row does not toggle the selection as long as the mouse button is not released, without resorting to a user-defined control with inheritence and method overrides ?


So I was about to paste the whole code in comments but that was looking a mess. So I decided to publish a new post, after a very long time :).

The solution "is" simple. We just need to look what is the event sequence. So whenever we press mouse button on a cell or row of DataGridView control, events trigger in this way,
  1. MouseDown
  2. SelectionChanged
  3. MouseUp
When MouseDown event occurs Selection does NOT change. So in this event handler we can save the current row in some variable and then in SelectionChanged event handler we can select previously saved row, in this way user will experience that selection never changed.

And here is the code,

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Me.DataGridView1.Columns.Add("Column1", "Column1")
        Me.DataGridView1.Columns.Add("Column2", "Column2")
        Me.DataGridView1.Columns.Add("Column3", "Column3")

        Me.DataGridView1.Rows.Add("laksjdf", "alksjfsdf", "aksldjf")
        Me.DataGridView1.Rows.Add("laksjdf", "alksjfsdf", "aksldjf")
        Me.DataGridView1.Rows.Add("laksjdf", "alksjfsdf", "aksldjf")
        Me.DataGridView1.Rows.Add("laksjdf", "alksjfsdf", "aksldjf")
        Me.DataGridView1.Rows.Add("laksjdf", "alksjfsdf", "aksldjf")
        Me.DataGridView1.Rows.Add("laksjdf", "alksjfsdf", "aksldjf")
        Me.DataGridView1.Rows.Add("laksjdf", "alksjfsdf", "aksldjf")

        ' ***************************************
        ' IMPORTANT: MultiSelect property of DataGridView control should be False
        '            The result maybe same but if we set it to true then you
        '            may experience the flickering
        ' ***************************************
        Me.DataGridView1.MultiSelect = False
    End Sub

    Private _MouseDown As Boolean
    Private _PreviousSelectedCell As DataGridViewCell
    Private _NewSelectedCell As DataGridViewCell

    Private Sub DataGridView1_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles DataGridView1.MouseDown
        ' if left button is clicked
        If e.Button = Windows.Forms.MouseButtons.Left Then
            Me._MouseDown = True    ' we will mark a flag that mouse is down
            ' and will note the row before the selection changed
            Me._PreviousSelectedCell = Me.DataGridView1.CurrentCell
        End If
    End Sub

    Private Sub DataGridView1_SelectionChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles DataGridView1.SelectionChanged
        If Me._MouseDown Then       ' if this event is triggerd after the mouse down
            ' we will first note the new row on which mouse was clicked
            Me._NewSelectedCell = Me.DataGridView1.CurrentCell
            ' then change the selection back to the one before mouse was down
            Me.DataGridView1.CurrentCell = Me._PreviousSelectedCell
        End If
    End Sub

    Private Sub DataGridView1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles DataGridView1.MouseUp
        If Me._MouseDown Then       ' if this event is triggered after the mouse down
            ' we will set the selection to the new row which was clicked
            Me.DataGridView1.CurrentCell = Me._NewSelectedCell
            Me._MouseDown = False   ' reset the flag
        End If
    End Sub

End Class

Monday, January 26, 2009

DataGridView Custom Cell ToolTip

Level: Intermediate Knowledge Required:
  • DataGridView Control
  • Windows Forms
Description: We have a built-in ToolTip for each Cell in DataGridView control. Which is displayed when the text is long and cannot be displayed completely in Cell. But my requirement was to create a custom ToolTip (as displayed in above image). There were 2 main requirements, 1) ToolTip should be displayed permanently that is, it should NOT be disappeared automatically after sometime. 2) I want to have a fancy look of my ToolTip (again see the image). For this purpose we can also use the .net's built-in ToolTip control. Which can have a little fancy look by setting some of its properties like IsBubble, ToolTipIcon and ToolTipTitle. Also the ToolTip control can be more customized by using OwnerDraw mode. But still I would like to use my own ToolTip window. Because to me, this is more easier to do. Simply I implemented it by creating a new window for ToolTip and I show this window when mouse enters in a cell. But to make it more user friendly, I have used a Timer control. So that whenever mouse enters in a Cell I start timer and in Timer's Tick Event Handler, I display the ToolTip window. Few things to be considered here, 1) DataGridView's ShowCellToolTips property should be set to False 2) Decide where to display the ToolTip window So in my case I decided to display the ToolTip window just on the Cell. OK this is NOT just straight. We first need to get the Cell's actual Coordinate in DataGridView by executing DataGridView's GetCellDisplayRectangle() function. Then we need to convert these coordinates into Screen's Coordinates by calling the DataGridView's PointToScreen() function. Please note that, to display our own custom ToolTip window on our given position we need to set its property StartPosition = Manual, which I have discussed in my earlier post Setting Window / Form Position Programmatically Another thing I have added in the ToolTip window is the Close Button. Which is actually a NON Focusable button as I have discussed my previous post NOT Focusable / NOT Selectable Button Download Source: DataGridViewCustomCellToolTip.zip

Sunday, September 14, 2008

Binding DataGridView ComboBox Column

Level: Beginner Knowledge Required:
  • Data Binding
  • DataGridView
Description: We use DataGridView control lots of times while developing a Data Driven Application. We usually bind the DataGridView control with some BindingSource. Also we can bind the DataGridView's ComboBox Column to some other BindingSource. That is DataGridView has different DataSource and it's ComboBox column has a different. Consider the following scenario, We have following Typed DataSet,
OrderDataSet
First we will create a simple User Interface, As you can see in the above figure, the DataGridView control is bind with OrderDetail Table. Note that the Product_ID column is displaying ID which is NOT a friendly approach. Instead Product Name should be displayed here. We can use the DataGridView's ComboBox Column here, in which we will populate all the products. The main point to be focused here is that, we will be binding the DataGridView control with the same OrderDetail DataTable, in which there is no Product_Name column. But on front-end the Product_ID column will be set to ComboBox Column in which we will populate the Products' Name. Since we need to populate the ComboBox with Products' Name. Therefore we will add another Typed DataSet and BindingSource to our Form, We have bind our ProductBindingSource with ProductDataSet which contains Product DataTable. We will fill this table in Form_Load event Handler.
IMPORTANT: This table needs to be filled before we fill the OrderDataSet OR at-least before the OrderDetail DataGridView is displayed
Now we need to setup the Product_ID column. First set the ColumnType of Product_ID column to DataGridViewComboBoxColumn and then set the properties as,
  • DataPropertyName = Product_ID
  • DataSource = ProductBindingSource
  • DisplayMember = Product_Name
  • ValueMember = Product_ID
DataPropertyName: tells that Product_ID column of OrderDetail DataTable should be updated DataSource: is the source from where the list of Products will be taken, in this case the ProductBindingSource which is actually bind with Product DataTable DisplayMember: Column of Product DataTable which should be used to display in DataGridView control ValueMember: Column of Product DataTable which should be used to set the Value in DataGridView which ultemately will send the value in OrderDetail DataTable Download: BindingDataGridViewComboBox.zip

Thursday, September 4, 2008

Using DataGridView CheckBox Column as RadioButton (OptionButton)

Level: Beginner Knowledge Required:
  • DataGridView Control
  • Data Binding
Description: In this post we shall see that how we can transform DataGridView control’s CheckBox column into Radio Button (option button). I have done 2 main things,
  1. Created a back-end logic when user clicks on CheckBox Column so only one CheckBox should be checked at a time
  2. Change the look of CheckBox column so it looks a Radio Button Column
So to understand the first one, consider a DataGridView control as shown in the above figure. This DataGridView control is actually bind with a DataSet for example, As you can see there are 2 columns. The IsSelected column is actually a Boolean column which is normally rendered as CheckBox in DataGridView control. What we will be doing is that we first set our DataGridView to Read Only i.e., AllowUserToAddRows = False AllowUserToDeleteRows = False ReadOnly = True We are making our DataGridView control Read Only because it is easier to set the CheckBox checked or unchecked programmatically otherwise DataGridView control itself will be interfering and will create problems and complexities for us. OK now whenever user clicks on CheckBox we will be performing our custom operation. To do this we will use the DataGridView’s CellContentClick event. Here is the code,
Private Sub ShutDownOptionsDataGridView_CellContentClick(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles ShutDownOptionsDataGridView.CellContentClick
    If e.ColumnIndex = Me.columnIsSelected.Index Then
        Dim drv As DataRowView
        Dim rowShutDownOption As ShutdownOptionDataSet.ShutDownOptionsRow
        ' in this event handler we know that which DataGridView's row is clicked
        ' so we are going to extract out the actual DataTable's row which is
        ' bind with this DataGridView's Row
        drv = CType(Me.ShutDownOptionsDataGridView.Rows(e.RowIndex).DataBoundItem, DataRowView)
        ' get the DataTable's row
        rowShutDownOption = CType(drv.Row, ShutdownOptionDataSet.ShutDownOptionsRow)

        ' get the row which is currently selected
        Dim rowCurrentlySelected() As ShutdownOptionDataSet.ShutDownOptionsRow
        rowCurrentlySelected = Me.ShutdownOptionDataSet.ShutDownOptions.Select("IsSelected=True")
        ' if some row found then make it de-selected
        If rowCurrentlySelected.Length > 0 Then
            rowCurrentlySelected(0).IsSelected = False
        End If
            ' ok now select the row which is clicked
        rowShutDownOption.IsSelected = True
    End If
End Sub
What we do is first get the row in which IsSelected=True and we make that row IsSelected=False. Then we set the row which is clicked as IsSelected=True. Next thing is to change the look of CheckBox to OptionButton / RadioButton. For this purpose we will be using DataGridView’s CellPainting event. Here is the code,
Private Sub ShutDownOptionsDataGridView_CellPainting(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellPaintingEventArgs) Handles ShutDownOptionsDataGridView.CellPainting
    If e.ColumnIndex = Me.columnIsSelected.Index AndAlso _
        e.RowIndex >= 0 Then
        e.PaintBackground(e.ClipBounds, True)

        Dim rectRadioButton As Rectangle

        rectRadioButton.Width = 14
        rectRadioButton.Height = 14
        rectRadioButton.X = e.CellBounds.X + (e.CellBounds.Width - rectRadioButton.Width) / 2
        rectRadioButton.Y = e.CellBounds.Y + (e.CellBounds.Height - rectRadioButton.Height) / 2

        If IsDBNull(e.Value) OrElse e.Value = False Then
            ControlPaint.DrawRadioButton(e.Graphics, rectRadioButton, ButtonState.Normal)
        Else
            ControlPaint.DrawRadioButton(e.Graphics, rectRadioButton, ButtonState.Checked)
        End If

        e.Paint(e.ClipBounds, DataGridViewPaintParts.Focus)

        e.Handled = True
    End If
End Sub
As you can see we have used the ControlPaint class to draw the RadioButton / OptionButton. Download: DGVCheckBoxAsRadioButton.zip

Thursday, July 3, 2008

Making Enter Key, Move to Next Cell/Column in DataGridView After Cell Edit

This article explains how we can customize the DataGridView control so that when Enter Key is pressed then cursor (current selection) move to next cell / column after Cell Edit. Level: Intermediate Knowledge Required: DataGridView Description: By default in DataGridView control, when we press Enter Key to stop the editing in Current Cell, cursor moves to next Row and the current row is saved. This style is adopted from Excel, users working on Excel feel no problem with this. But users who worked in older applications do NOT like this type of editing. What they want is when Enter Key is pressed then Cursor should move to next cell / column. In DataGridView control we need to press TAB key to achieve this. Now we are going to customize the DataGridView control, so that when User Stop the Editing in Current Cell by pressing Enter Key then cursor should move in next cell / column. To understand the solution first note the followings, 1) When user presses enter key to stop the Editing, DataGridView CellEndEdit event occurs 2) Then cursor (current selection) moves to next row, on this point SelectionChanged event occurs To achieve this I have used a logic, 1) When CellEndEdit event occurs, I note the Cell which was Edited 2) Then in SelectionChanged event, I first check if this event is occured after the Editing, then I set the cursor (current selection) in the same row but next column of last edited cell Here I have created a customized control which is actually inherited from DataGridView control, and have added this functionality.
Public Class DataGridViewEnterMoveNext
    Inherits DataGridView

    Dim celWasEndEdit As DataGridViewCell
    Private _EnterMoveNext As Boolean = True

    <System.ComponentModel.DefaultValue(True)> _
    Public Property OnEnterKeyMoveNext() As Boolean
        Get
            Return Me._EnterMoveNext
        End Get
        Set(ByVal value As Boolean)
            Me._EnterMoveNext = value
        End Set
    End Property

    Private Sub DataGridView_CellEndEdit(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles Me.CellEndEdit
        Me.celWasEndEdit = Me(e.ColumnIndex, e.RowIndex)
    End Sub

    Private Sub DataGridView_SelectionChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.SelectionChanged
        ' if Enter Move Next should work andalso
        '    mouse button was NOT down
        ' we are checking mouse buttons because if select was changed
        ' by Mouse then we will NOT do our Enter Move Next
        If Me._EnterMoveNext AndAlso MouseButtons = 0 Then
            ' if selection is changed after Cell Editing
            If Me.celWasEndEdit IsNot Nothing AndAlso _
               Me.CurrentCell IsNot Nothing Then
                ' if we are currently in the next line of last edit cell
                If Me.CurrentCell.RowIndex = Me.celWasEndEdit.RowIndex + 1 AndAlso _
                   Me.CurrentCell.ColumnIndex = Me.celWasEndEdit.ColumnIndex Then
                    Dim iColNew As Integer
                    Dim iRowNew As Integer
                    ' if we at the last column
                    If Me.celWasEndEdit.ColumnIndex >= Me.ColumnCount - 1 Then
                        iColNew = 0                         ' move to first column
                        iRowNew = Me.CurrentCell.RowIndex   ' and move to next row
                    Else ' else it means we are NOT at the last column
                        ' move to next column
                        iColNew = Me.celWasEndEdit.ColumnIndex + 1
                        ' but row should remain same
                        iRowNew = Me.celWasEndEdit.RowIndex
                    End If
                    Me.CurrentCell = Me(iColNew, iRowNew)   ' ok set the current column
                End If
            End If
            Me.celWasEndEdit = Nothing                      ' reset the cell end edit
        End If
    End Sub
End Class
Note that I have added a property OnEnterKeyMoveNext if this property is True then our customization will work otherwise NOT. Limitations: As you can see when user presses enter key, the row will be first saved in DataGridView control (ultimately the Source of DataGridView is updated), at this point if one or more columns are NOT allowed to have NULL values then our customization will NOT work properly. Since exception will be thrown and the row will be deleted. To overcome this issue we can set the AllowDBNull = False in our DataTable (which is bind with DataGridView) and use the custom validation as discussed in my previous post. How to add Column/Row Validation Using Typed DataTable Download Source Code: DataGridViewEnterAsTab.Zip See Also: DataGridView control FAQs and Quick Tips Paste Text in DataGridView Control (Bound with BindingSource)

Monday, June 23, 2008

Paste Text in DataGridView Control (Bound with BindingSource)

DataGridView control does NOT have the built-in Paste functionality. Therefore we are going to create a Generic Function which will Paste Text in DataGridView control.

Level: Intermediate

Knowledge Required:
  • DataGridView
  • Clipboard
  • String
  • BindingSource
  • DataTable
Description:
In the my earlier post:

DataGridView Control FAQs and Quick Tips

I have discussed how to Copy Data from DataGridView control to Clipboard. Now we will see how can we Paste Data from Clipboard to DataGridView control. The following function is Generic for DataGridView control which is bound with some BindingSource. Optionally user can provide the DataTable (which is DataGridView source). This parameter is used in the function to check a Column whether it is Read-Only or NOT.

Features:
1) Paste Data at the End: Set the current position at the last row (which creates the new row) and execute the function, new rows will be added
2) Paste Data in the middle rows: Set the current position anywhere in middle (not at end) and execute the function, middle rows will be overwritten by new data
3) Paste Data in any column: Set the current position in any column and execute function, function will start pasting data from that particular column

Public Sub PasteInBoundDataGridView(ByVal dgvToUse As DataGridView, ByVal bsDataGridView As BindingSource, ByVal tblSource As DataTable)
Dim iCurrentRow As Integer = -1
Dim bFullRowSelected As Boolean = False

' if some row is selected
If dgvToUse.CurrentRow IsNot Nothing Then
' if it is NOT a new row
If Not dgvToUse.CurrentRow.IsNewRow Then
' get the index of that row
iCurrentRow = dgvToUse.CurrentRow.Index
End If

' if current row is selected
If dgvToUse.CurrentRow.Selected Then
' it means full row is selected
bFullRowSelected = True
End If
End If


' cancel the current edit
bsDataGridView.CancelEdit()

Dim sText As String
Dim sLines() As String
Dim iCurCol As Integer

' if full row is selected
If bFullRowSelected Then
' then set the initial column = 0
iCurCol = 0
Else ' else if full row is NOT selected
' set the initial column = current column
iCurCol = dgvToUse.CurrentCell.ColumnIndex
End If

' get the text from clipboard
sText = My.Computer.Clipboard.GetText()
' split the text into lines
sLines = sText.Split(New String() {vbCrLf}, StringSplitOptions.RemoveEmptyEntries)
' for each line in extracted lines
For Each sLine As String In sLines
Dim sColValues() As String
' split the line into columns
sColValues = sLine.Split(vbTab)

Dim c As Integer = iCurCol
Dim rowEdit As DataRowView

' if currently some middle rows are selected and also
' selected row is NOT the last row

If iCurrentRow >= 0 AndAlso iCurrentRow < dgvToUse.Rows.Count - 1 Then
' row is selected row
rowEdit = CType(dgvToUse.Rows(iCurrentRow).DataBoundItem, DataRowView)
' now move to next row
iCurrentRow += 1
Else ' else it means we are at end then
' we will add the row

rowEdit = bsDataGridView.AddNew
End If

' for each column in extracted columns
For Each sColValue As String In sColValues
' if this column is bound
If dgvToUse.Columns(c).DataPropertyName <> "" Then
' if some table is mentioned and also
' the column in which we are going to paste is NOT read-only

If tblSource Is Nothing OrElse _
Not tblSource.Columns(dgvToUse.Columns(c).DataPropertyName).ReadOnly Then
' if extracted value is empty string
If sColValue = "" Then
' then paste the DBNULL.value
rowEdit(dgvToUse.Columns(c).DataPropertyName) = DBNull.Value
Else ' else it means some value is mentioned
' then paste that value
rowEdit(dgvToUse.Columns(c).DataPropertyName) = sColValue
End If
End If
End If

' increase the column count
c += 1
' if reached at last column then stop setting values in columns
If c >= dgvToUse.Columns.Count Then Exit For
Next ' next column

' ok row edit is complete so end it

bsDataGridView.EndEdit()
Next 'next line
End Sub

Usage:
Private Sub Paste()
Try
If Not Me.DataGridView1.IsCurrentCellInEditMode Then
Call PasteInBoundDataGridView(Me.DataGridView1, Me.BindingSource1, Me.DataSet1.DataTable1)
End If
Catch
ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Exclamation, "Paste")
End Try
End Sub


See Also:
DataGridView Control FAQs and Quick Tips

Thursday, June 19, 2008

DataGridView control FAQs and Quick Tips

In this post we will discuss some FAQs regarding DataGridView control. I will be (inshaALLAH) updating this post, when I find new FAQ, if you have any question(s) you can contact me on my email address displayed on right side.

Level: Beginner

Introduction:

Q: What is DataGridView control?
Ans: DataGridView control is used to display the data from Data Source (e.g. DataTable) in a Tabular Form i.e. in Columns and Rows like Spreadsheet.

Q: How can I use DataGridView control?
Ans: DataGridView control can be used either by attaching it to some Data Source or without attaching it with any Data Source. You can simply drag and drop the control on form, add columns using Columns Properties or by using Smart Tags, run the program and start using the control just like you use the Excel.

Data Binding:

Q: How can I attach the DataGridView to some Data Source?
Ans: Attaching of DataGridView to some Data Source is called Data Binding. You can bind the DataGridView with DataTable directly or can use a BindingSource.

Q: How to Bind DataGridView to a DataTable Programmatically?
Ans: Use the following code

DataGridView1.DataSource = MyDataTable

Q: How to Bind DataGridView to a DataTable of a DataSet Programmatically?
Ans: Use the following code

DataGridView1.DataSource = MyDataSet
DataGridView1.DataMember = "MyTable"


Q: How to Bind DataGridView to a BindingSource Programmatically?
Ans: Use the following code

DataGridView1.DataSource = MyBindingSource

Q: When I Bind my DataGridView programmatically it automatically generates the Columns, how can I prevent it?
Ans: Set the AutoGenerateColumns Property = False before setting the DataSource

DataGridView1.AutoGenerateColumns = False
DataGridView1.DataSource = Me.BindingSource1


Q: I have added some columns in my DataGridView control and haven't bind it to anything, how can I add rows in it?
Ans: Use the following code

DataGridView1.Rows.Add(5) ' This will ad blank 5 Rows
OR
DataGridView1.Rows.Add("Item in 1st Column", "Item in 2nd Column")

Q: How to make DataGridView Read-only
Ans: Set the following properties,

AllowUserToAddRows = False
AllowUserToDeleteRows = False
ReadOnly = True


Selection:

Q: How can I select all the Columns and Rows (cells) of DataGridView control Programmatically?
Ans: Use the following code

DataGridView1.SelectAll()

Q: I want to change the selection style of DataGridView from single Cell to Full Row
Ans: Use the following code

DataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect

Q: How can I copy the Selected Cells onto Clipboard Programmatically?
Ans: Use the following code

My.Computer.Clipboard.SetDataObject(DataGridView1.GetClipboardContent())

Q: How can I Paste the data from Clipboard to DataGridView control?
Ans: See Paste Text in DataGridView Control (Bound with BindingSource)

Q: How can I prevent user to select multiple Cells/Rows? How can I restrict user to select only one cell/row at a time?
Ans: Set the property

DataGridView1.MultiSelect = False

Q: How can I get the current row of DataGridView which is selected?
Ans: Use DataGridView.CurrentRow property. This will be Nothing if no row exists in DataGridView.

Q: How can I get all currently Selected Rows of DataGridView control?
Ans: Use DataGridView.SelectedRows property.

Q: How can I get all currently selected cells of DataGridView control?
Ans: User DataGridView.SelectedCells property.

Q: How can I select DataGridView row programmatically?
Ans: Use the following code:

DataGridView1.Rows(0).Selected = True

Q: How can I change the current row of DataGridView control?
Q: DataGridView control's CurrentRow property is read-only, how can I change the Current Row?
Q: I want to change the focus (dotted border) to some other cell in DataGridView control, programmatically.

Ans: Use CurrentCell property,

DataGridView1.CurrentCell = DataGridView1.Rows(0).Cells(0)

Layout:

Q: I there any way to make all the columns fit in the DataGridView control?
Ans: 2 Ways

i) Set the DataGridView's AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill

OR

ii) Set at least one column's AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill

Formatting:

Q: How can I change the Format of Numeric and Date Values in DataGridView?
Ans: Set the DefaultCellStyle.Format property

E.g.
Column1.DefaultCellStyle.Format = "dd-MMM-yyyy" ' display as 01-Jan-2008
Column2.DefaultCellStyle.Format = "N0" ' only numeric NO decimals


Definitions:

Q: What is Unbound Column in DataGridView control?
Ans: Unbound column is a column added in DataGridView control but NOT bound to any thing i.e. the Column's DataProperptyName Property has NOT been assigned any value.

Column1.DataPropertyName = ""

Q: What is Virtual Mode of DataGridView control?
Ans: When VirtualMode property of DataGridView Control is set to True, then the DataGridView Control is said to be in Virtual Mode. In this mode CellValueNeeded event triggers for Unbound Columns. In the event Handler of this event we provide some value (using e.Value) which then renders that value in cell.

Events:

Q: CellValueNeeded event is NOT being fired/triggered/executed!
Q: CellValueNeeded event is NOT working!

Ans: VirtualMode property of DataGridView control must be True, also CellValueNeeded is only triggered for Unbound Columns. See previous question.

Q: What event occurs when user Double clicks on a Cell in DataGridView control?
Ans: CellDoubleClick event

Q: What is the difference between CellContentClick and CellClick events in DataGridView control?
Ans:

CellContentClick is triggered for
*) DataGridViewLinkColumn
*) DataGridViewButtonColumn
*) DataGridViewCheckBoxColumn
when user click on link, button or checkbox in cell

CellClick event occurs when user clicks on Cell (NOT its contents i.e. link, button or checkbox)

Misc.:

Q: I have a Editable DataGridView control. I want to detect whether current row is the New Row (new row = last row in DataGridView which has a "*" sign in its row header)?
Ans: Use the IsNewRow property of DataGridViewRow

DataGridView1.Rows(0).IsNewRow

Q: When user presses TAB key in DataGridView Control, current cell changes to next cell, I want to suppress this behavior and want to change the Focus to next control, how can I do this?
Ans: Set the property of DataGridView

StandardTab = True

Q: How can I change the default Editing behavior of DataGridView Control?
Ans: Use the EditMode property, values can be:

EditOnEnter = Edit begins immediately when cell gets focus, no need to press F2 or any key
EditOnKeyStroke = Edit begins on Key stroke, NOT on F2
EditOnKeystrokeOrF2 = Edit begins on both F2 or Key stroke
EditOnF2 = Edit begins only on F2
EditProgrammatically = Edit programmatically i.e. you have to call BeginEdit() method

Q: I have bind my DataGridView to the DataTable, one column of this Table does NOT allow NULL, when user tries to enter a NULL value in this column through DataGridView control, an exception occurs but DataGridView automatically handles it and displays a big Dialog Box. How can I suppress it?
Ans: Use the DataError event of DataGridView control. In the handler of this event you can use e.Exception to see which exception is occurred, and can show your own Dialog Box.

Q: I started adding first row in DataGridView control, and then I pressed ESC (to cancel) an exception occurred which unfortunately cannot be handled, why?
Ans: This is a bug confirmed by Microsoft

See the following KB Article

FIX: Error message when you try to press ESC to cancel adding a new row to a DataGridView control in the .NET Framework 2.0: "An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll"

Friday, June 13, 2008

How to add Serial Number Column in DataGridView

This article explains how to add a Serial Number (Row Index) Column in DataGridView control without adding this column in Physical Database or in DataSet's DataTable.



Level: Beginner

Knowledge Required:
  • DataGridView
  • Data Binding

Description:
We use DataGridView control alot of times in Data Manipulation application. Sometimes we require to have a Serial Number (S.No.) Column in DataGridView Control in such a way that no matter how Sorting/Filtering is done, Serial Number should remain constant i.e. in a Sequence.

One way to accomplish this is to create a Column in DataSet's DataTable in which we can store the Serial Numbers, but this will make our job too complex if sorting/filtering is also done. Because we have to re-check the Serial Number column again and again each time the Sorting/Filtering is performed.

So the better way is to use the DataGridView control's Virtual Mode. Here are the steps that we will do,

1) Bind the DataGridView control to some BindingSource and setup its Columns
2) Add another column (Unbound Column) and make it the first column
3) Set its name = ColumnSNo
4) Set its ReadOnly = True
4) Set the DataGridView control's VirtualMode property to True
5) In CellValueNeeded event use the following code:

Private Sub DataGridView1_CellValueNeeded(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValueEventArgs) Handles DataGridView1.CellValueNeeded
If e.RowIndex >= 0 AndAlso e.ColumnIndex = Me.ColumnSNo.Index Then
e.Value = e.RowIndex + 1
End If
End Sub
Note that if we don't set the VirtualMode Property to True then CellValueNeeded event wouldn't fire


Summary:
To display the Serial Numbers we have added an unbound column and set the Virtual Mode Property of DataGridView control to True. Then in CellValueNeeded Event we just return the Row Index whose value is required

Tuesday, May 13, 2008

How To Increase Performance of Loading Large Data from Database (Part-3)

Level: Advanced

Knowledge Required:
Part-3:
  • Typed DataSet
  • TableAdapter
  • DataGridView


Description:
So far we have discussed the 2 better ways of loading large data,

Implement Paging in SQL Server 2005
Implement Paging in Application using SQLDataAdapter

Now the last one is to use to Virtual Mode of DataGridView control. Here we will NOT use the Data Binding since I have already discussed in Part-1 that Data Binding also slows down the process.

When DataGridView control is in Virtual Mode (VirtualMode=True) then CellValueNeeded Event is triggered for Unbound Columns. In the same event we receive the Row and Column Index, in which DataGridView is needing the Value. Therefore we just go in the same Row and Column of our loaded DataTable, get the Value and give to the DataGridView control.


So what we will do is,

  1. Create a DataGridView Control NOT bound to anything
  2. Add the Columns (manually) equal to number of Columns in our DataTable
  3. Set the property of DataGridView Control i.e. VirtualMode = True
  4. Load the Data in our DataTable which is also NOT bound to anything
  5. Add the same number Rows in the DataGridView
  6. And finally in the CellValueNeeded event you can use the following code



Private Sub grdLargeData_CellValueNeeded(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValueEventArgs) Handles grdLargeData.CellValueNeeded
Dim rowLargeData As LargeDataDataSet.LargeDataRow
rowLargeData = Me.dsLargeData.LargeData(e.RowIndex)
e.Value = rowLargeData(e.ColumnIndex)
End Sub


This approach is a bit faster as compare to the Data Bound version. But has limitations i.e. Ordering and Filtering cannot be performed.

Source Code:
LargeDataWithVirtualDGV.rar
Database:
TemporaryDB.rar
Note: Execute the ScriptToInsertBulkData.sql file (ziped inside the TemporaryDB.rar) to add the Fake Data

Tuesday, May 6, 2008

Creating a Friendly User Interface for Many to Many Relationship Scenario

Level: Advanced
Knowledge Required: To understand the following solution you must have the knowledge of:

  • Typed DataSets
  • DataTables
  • Data Binding
  • DataGridView Control

Description:

In our daily life development, we usually face a scenario where we need to create a user interface for many to many relationship scenario. For example I will discuss here a scenario where we require to store the Shops along with the Products they deal in.

We have created 2 Typed DataSets here:

  • Product DataSet
  • Shop DataSet

Product DataSet contains 1 Table i.e. Product. We will load products in this Table. Note that I haven’t created any Physical Database here, so I will be manually filling some products in this table at runtime.

In the Shop DataSet we have 2 Tables:

  • Shop Table
  • ShopProducts Table

ShopProducts Table is a Junction Table, which means a shop can have multiple Products. One way to create this interface is to place a DataGridView control for entering the Shop’s Products as I have shown in the following figure:

As you can see user can add multiple Products in this list by opening the ComboBox and selecting a Product. This way of creating user interface is fine but NOT friendly. As you can see whenever user opens the ComboBox all the Products display no matter how many products user has already selected. Therefore whenever user tries to duplicate a product the Primary Key Violation exception will occur which we have handled in the DataError event of the DataGridView control. You can note that this Exception which user sees is also NOT Friendly.

So to make it simple we can add a Checked List Box here, in which all the products are displayed and user just has to check the products which he/she wants to be added with Shop. Since Checked List Box control is a bit old fashioned and NOT a good looking control so we will use the same DataGridView control but in a different way.

We will now add another DataSet here i.e. ProductSelectionDataSet. This DataSet is same as ProductDataSet except that it’s Product Table contains a field Boolean field IsSelected, which will be used to check whether a product is selected or NOT.

Next we will replace current DataGridView control with another DataGridView control having 2 Columns

  • IsSelected Column
  • Product Name Column

We will setup this DataGridView in the following manner:

  • AllowUserToAddRows = False
  • AllowUserToDeleteRows = False
  • RowHeadersVisible = False
  • ReadOnly = True

Also we will set the 1st Column i.e. IsSelected column as:

  • HeaderText = “” (empty string, we don’t want its header to be displayed)
  • Resizable = False
  • Width = 32

And for the 2nd column (Product Name):

  • HeaderText = “Product”
  • AutoSizeMode = Fill

This will make our DataGridView a bit like Checked ListBox. Now we just have to handle some of the events. First we will create a method SelectCurrentShopProducts; this method will select the Products according the given Products in the ShopProduct Table (of ShopDataSet). This method will be executed each time when Binding Source’s Position is changed.

Private Sub SelectCurrentShopProducts()
    ' first we will clear the currently selected shops
    Call Me.ClearAllSelection()

    Dim drvShop As DataRowView
    ' get current shop
    drvShop = Me.ShopBindingSource.Current
    ' if there is some shop selected
    If drvShop IsNot Nothing Then
        Dim rowShop As ShopDataSet.ShopRow
        Dim rowsProduct() As ShopDataSet.ShopProductsRow
        ' get the row from shop table
        rowShop = CType(drvShop.Row, ShopDataSet.ShopRow)
        ' get the current selected products of that shop
        rowsProduct = rowShop.GetShopProductsRows()
        ' for each selected product
        For Each r As ShopDataSet.ShopProductsRow In rowsProduct
           Dim rowProdSel As ProductSelectionDataSet.ProductWithSelectionRow
           ' get the product row from ProductWithSelection table
            rowProdSel =Me.ProductSelectionDataSet.ProductWithSelection.FindByProd_ID(r.Prod_ID)
           ' and mark it as selected
            rowProdSel.IsSelected = True
        Next
    End If
End Sub

Private Sub ClearAllSelection()
    For Each r As ProductSelectionDataSet.ProductWithSelectionRow In Me.ProductSelectionDataSet.ProductWithSelection
        r.IsSelected = False
    Next
End Sub

Next we will handle the CellContentClick event of DataGridView control. This event will be triggered whenever the CheckBox is checked or unchecked. In this event we will first check: If CheckBox is NOT selected then we will add this Product in our ShopProduct table otherwise if CheckBox is selected then it means we have already added this Product in ShopProduct table so now we will remove it.

Private Sub ProductWithSelectionDataGridView_CellContentClick(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles ProductWithSelectionDataGridView.CellContentClick
    If e.RowIndex >= 0 Then
        ' if checkbox is clicked
        If e.ColumnIndex = Me.ColumnIsSelected.Index Then
           Try
               Me.ShopBindingSource.EndEdit()

               Dim rowProdSel As ProductSelectionDataSet.ProductWithSelectionRow
                rowProdSel = CType(Me.ProductWithSelectionDataGridView.Rows(e.RowIndex).DataBoundItem, DataRowView).Row
                   ' if currently selected then we will de-select it
                   If rowProdSel.IsSelected Then
                   Try
                       Call Me.RemoveProductFromCurrentShop(rowProdSel.Prod_ID)
                        rowProdSel.IsSelected = False
                   Catch ex As Exception
                        MsgBox(ex.Message, MsgBoxStyle.Exclamation, "De-Select Shop")
                   End Try
               Else ' if NOT selected then we will select it
                   Try
                       Call Me.AddProductInCurrentShop(rowProdSel.Prod_ID)
                        rowProdSel.IsSelected = True
                   Catch ex As Exception
                        MsgBox(ex.Message, MsgBoxStyle.Exclamation, "Select Shop")
                   End Try
                End If
            Catch
ex As Exception
                MsgBox(ex.Message, MsgBoxStyle.Exclamation, "Edit Shop")
           End Try
        End If
    End If
End Sub


Private Sub AddProductInCurrentShop(ByVal Prod_ID As Integer)
    Dim drvShop As DataRowView
    Dim rowNew As ShopDataSet.ShopProductsRow
    ' get the current shop
    drvShop = Me.ShopBindingSource.Current
    ' create new ShopProduct Row
    rowNew = Me.ShopDataSet.ShopProducts.NewShopProductsRow()
    ' set values
    With rowNew
        .Shop_ID = drvShop("Shop_ID")
        .Prod_ID = Prod_ID
    End With
    ' add it in DataTable
    Me.ShopDataSet.ShopProducts.AddShopProductsRow(rowNew)
End Sub

Private Sub RemoveProductFromCurrentShop(ByVal Prod_ID As Integer)
    Dim drvShop As DataRowView
    Dim rowShopProduct As ShopDataSet.ShopProductsRow
    ' get the current shop
    drvShop = Me.ShopBindingSource.Current
    ' get the ShopProductRow
    rowShopProduct = Me.ShopDataSet.ShopProducts.FindByShop_IDProd_ID(drvShop("Shop_ID"), Prod_ID)
    rowShopProduct.Delete()
End Sub

NOTE: We have used the READONLY version of DataGridView that means when user clicks on the CheckBox in first column, then it does NOT get checked or un-checked Automatically. We handle this in the CellContentClick event of the DataGridView control. In this event we set the IsSelected = True or False which automatically updates the CheckBox in DataGridView control since it is binded to this table.

Download the full code from here:

JunctionTable.rar

Tuesday, April 29, 2008

How to Display Row Icon in DataGridView

Level: Intermediate
Knowledge Required:
  • DataGridView Control
  • Data Bindings
  • DataGridView Columns

Description:

We usually use DataGridView control to display contents of a DataTable. For a good impression and User Friendly Interface, we display an Icon with each Row in the DataGridView. For Example we can have a DataTable to store Tasks. Each task can have Status = Done / NOT Done. So we want that when we display these tasks in the DataGridView control there should be an Icon representing the Status of Task. As shown in the following figure.


DataGridView Control with Row Status Icon

To get the above result we will add an Unbound Column in the DataGridView control of Type DataGridViewImageColumn and set the VirtualMode property of DataGridView control to True. This will cause the DataGridView to fire the CellValueNeeded event. This Event triggers whenever the Cell is going to be rendered. Note that only the Unbound Cell will cause this event to be triggered. The bound columns will render themselve automatically.

For this purpose we first have created a Typed DataSet i.e. TaskDataSet containing a DataTable Task as,

Task DataTable

In the above table Task_Status is an Int32 field. We have decided that when this field is 0 (zero) then it means task is NOT Done yet and if this field contains 1 then it means task is Done.

Next we will create a Form and will put the following things:

  • TaskDataSet
  • BindingSource
  • DataGridView Control

Then we will bind the DataGridView to the BindingSource.

After this we will setup our DataGridView control by removing the Columns: Task_ID and Task_Status then we will add an Unbound Column TaskStatusIconColumn of type DataGridViewImageColumn. This column should be the first column of DataGridView set its properties as,

Properties of TaskStatusIconColumn:

  • DefaultCellStyle:
    • BackColor = White
    • ForeColor = Black
    • SelectionBackColor = White
    • SelectedForeColor = Black
  • Resizable = False
  • Width = 32

Now set the properties of Task_Description column which should have the Name TaskDescriptionDataGridViewTextBoxColumn.

Properties of TaskDescriptionDataGridViewTextBoxColumn:

  • AutoSizeMode = Fill

Properties of DataGridView Control:

  • RowHeadersVisible = False
  • SelectionMode = FullRowSelect
  • VirtualMode = True

VirtualMode is an important property here which must be True otherwise the CellValueNeeded event will NOT be triggered. And finally in the CellValueNeeded Event Handler we will use the following code:

Private Sub TaskDataGridView_CellValueNeeded(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValueEventArgs) Handles TaskDataGridView.CellValueNeeded
    If e.RowIndex >= 0 AndAlso e.ColumnIndex = Me.TaskStatusIconColumn.Index Then
        Dim drvTask As DataRowView
        Dim rowTask As TaskDataSet.TaskRow
        drvTask = Me.TaskDataGridView.Rows(e.RowIndex).DataBoundItem
        rowTask = CType(drvTask.Row, TaskDataSet.TaskRow)
        Select Case rowTask.Task_Status
            Case 0 ' NOT Done
                e.Value = My.Resources.Resources.Blank16
            Case 1 ' Done
                e.Value = My.Resources.Resources.OK
        End Select
    End If
End Sub

In above code I have used a technique to get the DataRow of DataTable from DataGridView's Row. I have discussed this technique in my previous post How to Get the Table Row from DataGridView Row

Note that I have created 2 PNG images (Blank16 and OK) and have added them in my Resources. Blank16 is a Blank PNG if we dont use this, then DataGridView will render its default image i.e. a Red Cross

You can download the source from here:

DataGridViewRowStatusIcon.rar

Wednesday, April 23, 2008

DataGridView Custom Percentage/Progress Bar Column


Description:
This is a smart as well as simple Custom Progress Bar Column for DataGridView Control, which is used to display the Percentage Graphically.

Here is the Source

DataGridViewPercentageColumn.zip

Saturday, April 19, 2008

How To Get the Table Row from DataGridView Row

Level: Beginner
Knowledge Required: To understand the following solution you must have knowledge of:
  • Typed DataSets
  • Data Tables
  • DataGridView Control
  • Data Binding
  • Windows Forms
Description:
We use DataGridView Control to display the contents of a DataTable. For this purpose we first bind the DataTable with DataGridView Control using a BindingSource. Sometimes it is required that we need to get the DataTable's Row from DataGridView Row.

For example, we have an event in DataGridView i.e. CellDoubleClick event. This event is triggered whenever the DataGridView Cell is double clicked. In this event we are supplied with an Event Argument of type DataGridViewCellEventArgs which contains 2 Members RowIndex and ColumnIndex.

Suppose we want that whenever the Cell is double clicked in the DataGridView, we extract out that particular row of our DataTable and then perform some action.

To fully understand this consider a Student Table having Student ID, Student Name and lots of other different fields. We have bound this Table with our DataGridView control and in DataGridView control we are only displaying the Student Name. Whenever the Student Name is double clicked we want that another Window should open i.e. Student Detail Window that contains all the Fields of Table. For this purpose we will use the following code:

Private Sub StudentDataGridView_CellDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles StudentDataGridView.CellDoubleClick

    Dim drvStudent As DataRowView
    Dim rowStudent As StudentDataSet.StudentRow
    Dim frmNew As StudentDetailForm

    drvStudent = StudentDataGridView.Rows(e.RowIndex).DataBoundItem
    rowStudent = drvStudent.Row

    frmNew = New StudentDetailForm(rowStudent.Student_ID)
    frmNew.ShowDialog()

End Sub

As you can see following code gets the DataRowView which is actually bound with DataGridView Row

drvStudent = StudentDataGridView.Rows(e.RowIndex).DataBoundItem

Then we get the actual DataTable Row as,

drvStudent.Row

Friday, April 11, 2008

How To Programmatically Select the DataGrid Row and/or Cell

Title: How To Programmatically Select the DataGrid Row and/or Cell
Issue: Cannot highlight/select the DataGrid Row and/or Cell through Code
Level: Beginner
Knowledge Required:
To understand the following solution you must have the knowledge of:


  • DataGrid View Control

Description:
To select the Particular row and/or cell of DataGrid View Control there are several techniques. Firstly you need to decide whether user can select more than one rows or cells. For this purpose you can use MultiSelect Property of DataGrid Control:

MultiSelect is a Boolean Property that can have values:
True = Enable user to select multiple rows and/or cells
False = Disable user to select only one row and/or cell at a time

To select particular Cell:
Tech #1:

myDataGrid.CurrentCell = myDataGrid(col, row)

where
col = Column Index
row = Row Index

Tech #2:
myDataGrid.Rows(row).Cells(col).Selected = True

To select particular Row:
myDataGrid.Rows(row).Selected = True

To clear previously selected Cells:
myDataGrid.ClearSelection