I am trying to get the results of a SQL Server stored procedure in VB.NET, parse them into a DataSet, then use the DataSet to fill a Windows Forms ListBox.
I have found many, many tutorials. The only ones that have gotten me close to a solution are ones that depend on the ListBox's DataBind() method. However:
myListBox.DataBind()
' ERROR: 'DataBind' is not a member of 'ListBox'
This is contrary to every tutorial I've found on the topic (approx a dozen so far).
Here is more code context:
Dim connection As New SqlConnection(myConnectionSecret)
Dim command As New SqlCommand("myStoredProcedureName")
command.Parameters().Add("#myParam", SqlDbType.Int).Value = myParamValue
command.Connection = connection
command.CommandType = CommandType.StoredProcedure
Dim adapter As New SqlDataAdapter(command)
adapter.SelectCommand.CommandTimeout = 300
'Fill the dataset
Dim dataSet As New DataSet
adapter.Fill(dataSet)
connection.Close()
myListBox.DataSource = dataSet
myListBox.DataBind() ' This method not found
A dataset is a collection of datatables. Typically you would bind your listbox to only one datatable. You can set the DataSource property to a dataset but you'll then also need to set the DataMember property to the name of the table in the dataset so the binding knows which table to rummage in
myListBox.DataSource = dataSet
myListBox.DataMember = "Person" 'whatever your table name is
Or set it to refer to the table directly:
myListBox.DataSource = dataSet.Tables("Person")
As other answers have commented you should then set the DisplayMember (chooses what text appears in the control) and ValueMember (chooses what value is emitted by the listbox.SelectedValue property) properties to strings representing the column names. If I want to show my persons name in the list but have their email be the selected value:
myListBox.DisplayMember = "FullName" 'fullname is a column in the datatable
myListBox.ValueMember = "Email"
If you don't set a ValueMember the whole DataRow of the selected person is returned from SelectedValue. (Clarification: Actually, it's a DataRowView because binding to a datatable actually happens to a DataView exported by the .DefaultView property - more on this later)
So, we've bound our list.DataSource to a datatable, and set the diplay/valuemember properties. How to we get the details of what Person is selected currently? Put a button on the form that has this handler code:
MessageBox.Show(DirectCast(listbox.SelectedValue, string)) 'shows e.g. john.smith#hotmail.com
Run the program, click "John Smith" in the list then click the button. The messagebox will show the selected person's email address
I mentioned earlier that databinding happens to a DataView object exported by the table's .DefaultView property. A DataView is a collection of DataRowView objects, and a DataRowView is a thin wrapper around a DataRow. DataRows exist in various versions such as original or updated values. A DataRowView is a way of selecting one of these versions for presentation, by default the Current version. You can address it like you would a DataRow:
'accessing the email address of a datatable row
Dim myDataRow = myPersonTable.Rows(0)
Dim email as String = DirectCast(myDataRow("EmailAddress"), string)
'accessing the email address of the dataview exported by the table defaultview
Dim myDataRowView = myPersonTable.DefaultView(0)
Dim email as String = DirectCast(myDataRowView("EmailAddress"), string)
As you can see, there isn't a lot of difference - as noted, the view just shows one of the various versions a datarow can exist in. If you want to get access to the underlying row you can do it via the Row property:
Dim myDataRowView = myPersonTable.DefaultView(0) 'or however you ended up holding a DataRowView object
Dim dr as DataRow = myDataRowView.Row
If you're using strongly typed datatables (discussed below), and want the strongly typed row:
Dim dr as PersonDataRow = DirectCast(myDataRowView.Row, PersonDataRow)
A useful aspect of the list binding to the .DefaultView DataView is that it can have its own filtering and sorting setup:
Dim dv = dataSet.Tables("Person").DefaultView
dv.Sort = "[FullName] ASC"
dv.RowFilter = "[FullName] LIKE 'J*'"
For more info on these see the documentation for dataview
Now, sea change: you don't have to do any of this by hand. All this can be linked up and done by visual studio and there are compelling reasons for doing so. For the same reason you don't write your form codes by hand, manually laying out all your controls etc, you can visually design and maintain your data access layer
Add a new dataSet file to your project, open it, right click the design surface and choose to add a tableadapter, go through the wizard setting your connection string and choosing that it's a stored procedure that gives you the data. At the end of it you'll see a datatable representation and all the columns returned by your stored procedure
If you now show the data sources window when you're in your forms designer you'll see a node representing your table from your dataSet, and you can either drag that node onto the form to create a DataGridview that is hooked up leafy, or you can expand the node in the data sources window to see individual columns, you can change the type of control to create for that property and you can drop them on the form. (I can't remember if listbox is one of them, but I know that ComboBox is). Dropping them on the form simply creates a control, already named and wired up with the right DataSource, Member and DisplayMember properties set, and you can change these and other properties like the value member in the properties grid.
Most critically of a difference, the controls the designer creates are all bound through a device called a bindingsource - this tracks the row in the datatable you're currently looking at, and keeps data bound controls in sync. If one control such as a grid or list is capable of showing multiple rows, clicking on different rows changes the Current property of the binding source, causing other controls (that only render one data row's data) such as textboxes to update to the new Current row values
Thus list controls may operate in one or two modes: they either serve as a device that can navigate a datatable's rows allowing you to pick one of a few rows so that you can edit the values using other textboxes etc, or lists serve as a way of showing a set of values for the user to cope from and cause another datarow's property to update to that chosen value. The differentiation in these two modes comes from whether the selectedvalue property is bound to something else or not. As a simple example in the context I've been discussing already, we could have a dataSet with two tables; person and gender; the person table has a gender column single char M or F, and the gender table has two columns one is a single char M or F and the other a text column of Male or Female (I won't get into the cases for other genders right now but the system is easy to extend by adding more rows). You could then have a form where the person table plus its binding source is causing a list of people to show in the first list box and click in different items in the list causes all the other textboxes (FullName) on the form to change so you can edit those details. You can also have a second listbox bound to the gender table via its own bindingsource (or direct, doesn't matter) that has its DisplayMember set to the "Male/female" column, its value member set to the "m/f" column and it's SelectedValue property bound to the main Person bindingsource's Person.gender column. When you choose a person from the first list, the current char value of their Gender will be used to set the current item selected in the gender list but if you change the value in the gender combo/list then it will write the new selectedvalue back into the person.gender column
That's the 101 of Windows forms binding; I recommend adding a dataset to your project because it then gives you datatables that are specifically typed with named properties. Your code looks like this:
ForEach PersonDataRow r in myDataset.Person
r.Age += 1
Instead of this:
ForEach DataRow r in myDataset.Tables("Person").Rows
r("Age") = DirectCast(r("Age"), Integer) +1
I was mid-answer when jmcilhinney commented: DataBind() is specific to ASP.Net Web Forms server controls.
Therefore, the DataBind() call isn't necessary, simply setting the DataSource property on a Windows Forms ListBox is enough.
As for parsing your results from your SQL Server, don't forget to set the ListBox's DisplayMember and ValueMember properties to correctly display your data to the user.
Here's the documentation on the DataSource property. It has a very decent example.
For filling a list box, you usually don't need DataSets or DataAdapters. Just a DataTable table will do.
The Using...End Using blocks ensure that your database objects are closed and disposed.
You can pass the connection directly to the constructor of the command.
Set the list box data source to the DataTable. Then you can use the names of the fields in the Select statement to set the .DisplayMember and .ValueMember. The display and value can be the same.
Private Sub FillListBox(myParamValue As Integer)
Dim dt As New DataTable
Using connection As New SqlConnection("myConnectionSecret")
'Pretend your stored procedure has a statement like "Select DeptarmentID, DepartmentName From Departments Where SupervisorID = #myParam;"
Using command As New SqlCommand("myStoredProcedureName", connection)
command.CommandType = CommandType.StoredProcedure
command.Parameters().Add("#myParam", SqlDbType.Int).Value = myParamValue
connection.Open()
dt.Load(command.ExecuteReader)
End Using
End Using 'Closes and disposes the connection
ListBox1.DataSource = dt
ListBox1.DisplayMember = "DepartmentName"
ListBox1.ValueMember = "DepartmentID"
End Sub
Related
I am trying (novice) to create a simple app that users can update a data table in a sql database via by adding, deleting or updating current records. So I have created the form and have combo box in there that is connected to a data table in my database. I want to load the value of the record selected into that combo box, but also have the list of options available to update that record in the database. the problem with my code is, it has the correct corresponding value for the record, but it is not showing. Also, when I select the combo box, the other values in the table are not there. the value is duplicated 2 times.
Here is how the combo box is bound:
How it Loads:
When I select the combo box:
Public Sub FillDriverDataSet()
Dim sql As String
Dim dt As New DataTable
Dim ds As New DataSet
Dim dv = New DataView(ds.Tables("Drivers"))
Dim bs As New BindingSource
Dim cm As CurrencyManager
sql = "SELECT * FROM tCommercialDrivers where id = 23"
OpenConnection()
Dim adapter As New SqlDataAdapter(sql, connection)
Dim drivers As New SqlDataAdapter(sql, connection)
adapter.Fill(dt)
drivers.Fill(ds, "Drivers")
dv = New DataView(ds.Tables("Drivers"))
cm = CType(Me.BindingContext(dv), CurrencyManager)
bs.DataSource = dt
adapter.Fill(dt)
drpRouteType.DataSource = dt
drpRouteType.DisplayMember = "RouteType"
drpRouteType.ValueMember = "RouteType"
txtRoute.DataBindings.Clear()
txtRoute.DataBindings.Add("text", dv, "RouteID")
End Sub
As an overview of how this is supposed to work ( and bearing in mind that I can't see your database structure so I'm guessing at it a bit)
open connection manager
add a connection to sql server
create a new DataSet type item in the project
open the DataSet
drag eg the CommercialDrivers table out of connection manager and into the DataSet then right click the table adapter and choose configure , or
right click the DataSet and choose add tableadapter, configure the connection, choose "select that retrieves rows.."
add or modify (depending what you did above) the sql so it's SELECT * FROM commercialDrivers WHERE iD = #DriverId
call your method FillById and GetDataById
finish
repeat true above process for the table that holds the RouteTypes but because we don't be selecting them singly, do not add a where clause and leave the default names of Fill and GetData
you should now have a DataSet with two tables and possibly a datarelation line between them. The relation isn't strictly necessary for our needs and can be removed
make a new form and open it
open the datasources window on the view menu (other windows)
from datasources drag the routetypes node to the form, then delete the grid view and bindingnavigator that appeared - keep the other stuff that appeared as it will be useful
rename the DataSet in the bottom tray so that it's called something else (anything; we just want the instance name to be different from the type name as it can cause confusion to have a variable named the same as a type. I prefix the name with an underscore, tableadapters too)
in the data sources window click the drop down next to CommercialDrivers and change it to Details mode, expand the node, change RouteType to a combobox
drag the CommercialDrivers parent node to the form, a bunch of controls will appear, with data bindings already set to the DataSet on the form
for the route type combo get its properties:
change dropdownstyle to dropdownlist
change DataSource to be routetypesbindingsource
change displaymember to be whatever is the route type text column
change valuemember to be whatever column holds the route type id
in the (data bindings) node at the top of the properties grid check that selectedvalue is bound to CommercialDrivers.RouteTypeId (CommercialDrivers binding source, datamember route type id), and remove the binding from the text property. Combos with a backing list should have selectedvalue bound, not text
That's it; the one line of code you have to write or sort out is filling the CommercialDriver table with the relevant ID. Technically it's probably zero lines; Visual studio probably already did that for you by putting a bindingnavigator complete with textbox and fill Button when you dragged the details into the form but you might want to change how it's done. The route types table in the DataSet is filled in form load, with all the different route types, the combo will read eg commercial drivers table's routetypeid value of 4, look up that value in the column set in its value member (in the route types table set in its DataSource) and show the value it finds in the column set in its displaymember. If you change the combo to something else it gets the value from the new row (From the column set in the valuemember) and writes that value into the commercialdrivers table routetypeid hence changing the routetype for that driver
It looks a lot when written down (and if you get stuck at any point let me know; I wrote this from memory, without a visual studio in front of me, on a cellphone so there may be some detail I forgot) but in reality when you're used to the process it probably takes about a minute to do this. VS writes all the code you did - you can see it in the various .Designer files if you're interested
So I cleaned it up a bit, and it works now. Let me know if you still see something I could have done better. Thank you for your reply!
Public Sub FillDriverDataSet()
Dim sql As String
Dim ds As New DataSet
Dim dv = New DataView(ds.Tables("Drivers"))
Dim bs As New BindingSource
Dim cm As CurrencyManager
sql = "SELECT * FROM tCommercialDrivers where id = 26"
OpenConnection()
Dim drivers As New SqlDataAdapter(sql, connection)
drivers.Fill(ds, "Drivers")
dv = New DataView(ds.Tables("Drivers"))
cm = CType(Me.BindingContext(dv), CurrencyManager)
drpRouteType.DataBindings.Clear()
drpRouteType.DataBindings.Add("text", dv, "RouteType")
txtRoute.DataBindings.Clear()
txtRoute.DataBindings.Add("text", dv, "RouteID")
txtDriverID.DataBindings.Clear()
txtDriverID.DataBindings.Add("text", dv, "DriverID")
txtEmailAddress.DataBindings.Clear()
txtEmailAddress.DataBindings.Add("text", dv, "EmailAddress")
txtDriverName.DataBindings.Clear()
txtDriverName.DataBindings.Add("text", dv, "DriverName")
txtDollYd.DataBindings.Clear()
txtDollYd.DataBindings.Add("text", dv, "YardageAmt")
txtDayRate.DataBindings.Clear()
txtDayRate.DataBindings.Add("text", dv, "DayRate")
cbxActive.DataBindings.Clear()
cbxActive.DataBindings.Add("Checked", dv, "IsActive")
cbxIncentive.DataBindings.Clear()
cbxIncentive.DataBindings.Add("Checked", dv, "IncentivePay")
' dteExpDate.DataBindings.Clear()
'dteExpDate.DataBindings.Add("text", dv, "ExperienceDate")
End Sub
I have a stored procedure in SQL server that return two tables to my VB.Net form. These two tables fills two grids. My data tables are parent and child. I want to filter second grid by first grid. In other words when user select one record in first grid, second grid must be filtered by regarding primary key that declared programmatically.
But it doesn't work!
My Code likes this:
Dim DAContractorDateTreeView As New SqlDataAdapter("spTimeSheetReportDateTree", My.Settings.SyncConnectionString)
Dim DSContractorDate As New DataSet
Dim fkeyConstraint As ForeignKeyConstraint
Dim Binding1, Binding2 As New BindingSource
Sub LoadContractorsDate(MyParameter As Integer)
Try
DAContractorDateTreeView.SelectCommand.CommandType = CommandType.StoredProcedure
'Some Codes for adding parameters
DAContractorDateTreeView.Fill(DSContractorDate)
'Declare parent column and child column variables.
Dim ParentColumn, ChildColumn As DataColumn
Dim primaryKey(1) As DataColumn
primaryKey(0) = DSContractorDate.Tables(2).Columns("TiSheID")
DSContractorDate.Tables(2).PrimaryKey = primaryKey
'Set parent and child column variables.
ParentColumn = DSContractorDate.Tables(0).Columns("TiSheID")
ChildColumn = DSContractorDate.Tables(1).Columns("TiSheID")
fkeyConstraint = New ForeignKeyConstraint("FKConstraintNaID", ParentColumn, ChildColumn)
'Set null values when a value is deleted.
fkeyConstraint.DeleteRule = Rule.Cascade
fkeyConstraint.UpdateRule = Rule.Cascade
fkeyConstraint.AcceptRejectRule = AcceptRejectRule.Cascade
'Add the constraint, and set EnforceConstraints to true.
DSContractorDate.Tables(1).Constraints.Add(fkeyConstraint)
DSContractorDate.EnforceConstraints = True
Binding1.DataSource = DSContractorDate.Tables.Item(0)
GridControlExit.DataSource = Binding1
Binding1.DataMember = "TiSheID"
Binding2.DataMember = "FKConstraintNaID" 'fkeyConstraint.ConstraintName
MsgBox(fkeyConstraint.ConstraintName)
Binding2.DataSource = Binding1
GridControlDetail.DataSource = Binding2
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
I haven't any problem with loading data from stored procedure.
Please help me. Thanks in advance.
You might well have found a solution for this now but the Microsoft intended it to be is:
add a new DataSet to your project
Open it and right click the surface
Add a tableadapter for select * from parent
do the same for the child. If the database contains a foreign key then a datarelation will be created automatically. If it is not, Highlight the column in the parent that links to the child then drag a line from the grey square next to the column name and drop it on the related child column name, ensure the datarelation window settings are correct
save the DataSet and go to your form
open the DataSources window (view menu, other windows) and expand every node and look at it. You'll see two children, one under the parent and one not
*drop all three of these grid on your form, run the app
You'll notice that for the grid under the parent, when you select a parent the child grid filters automatically. If you stop the app and examine the datasources of the child grids you'll see why. Both grids have datasources that are bindingsources but for the grid that filters automatically, the relevant bindingsource has its DataSource set to be the parent bindingsource and it's DataMember is the name of the datarelation
You can probably replicate the same into your manual code there but give a thought to starting working with datasets and tableadapters; they make a lot of the code you're writing now easier and more functional.
Final point; when configuring your adapters you made the queries "select * from table". In production you wouldn't do this; don't download all the table data and filter on the child. Add multiple relevant queries to your tableadapters and use them; select * from users where lastname like #ln And call it fillbylastname, if you want wildcards put them in the code: usersTableAdapter.FillByLastName(appDataSet.Users, "Smi%")
What I am doing
I am developing a VisualBasic application where the GUI is separated from the data manipulation (frontend and backend). This particular piece of code keeps track of the Serial Numbers already measured and displays them in the Form as a DGV with the serial number and an image or not.
My code
In a class that the GUI Form instantiates, my data is stored in a DataTable with a BindingSource, with the second column displaying an Image when I tell it to in the program (Nothing in the beginning):
Public SerialNumbersBindingSource As New BindingSource
Public SerialNumbersDataTable As New DataTable
(...)
SerialNumbersBindingSource.DataSource = SerialNumbersDataTable
SerialNumbersDataTable.Columns.Add("Serial", Type.GetType("System.String"))
SerialNumbersDataTable.Columns.Add("Pass", MyImage.GetType)
In my GUI Form code, a DataGridView has its DataSource set to the former DataTable:
DataGridViewSerialNumber.DataSource = MyObject.SerialNumbersDataTable
By just updating the DataTable in my class, the DataGridView updates automatically to reflect the current state of it. I do it like:
SerialNumbersDataTable.Add(CurrentSerial, MyImage)
The results I get
The code works, so I can modify and access the DataTable and the DataGridView autoupdates. But the images are not stretching, so I can only see a small part of them.
What I need
I need the second column named "Pass" in DataGridView to stretch the images.
What have I tried
If I access the column, it is treated like a DGVColumn and not a DGVImageColumn, so the Layout operation fails.
DataGridViewSerialNumber.Columns("Pass").DataGridViewImageCellLayout.Stretch
Microsoft's Docs page tells me to do this, which treats the columns like DGVImageColumn as I need "Pass" to. It fails because the first column is a Text one, not image.
For Each column As DataGridViewImageColumn In DataGridViewSerialNumber.Columns("Pass")
column.ImageLayout = DataGridViewImageCellLayout.Stretch
Next
Also I have tried creating a local DGVImageColumn, modify it and write it onto the original column, but it is read-only.
Dim imageColumn As DataGridViewImageColumn
imageColumn = DataGridViewSerialNumber.Columns("Pass")
imageColumn.ImageLayout = DataGridViewImageCellLayout.Stretch
DataGridViewSerialNumber.Columns("Pass") = imageColumn
I have also tried to do it from the designer. If I click the DGV, arrow to the right and 'Edit Column', I can create the two columns and setup Pass as ImageColumn with Stretched Layout. But when I set up DGVSerialNumbers.Datasource to my DataTable, it adds the DataTable's columns to the DGV's.
Failed DGV with columns added in designer
It's time for you to learn how to cast. If you want to access one column then don't use a loop. Simply access the column you want and then cast it as the type you want to use it as. You also need to actually assign the appropriate value to the appropriate property.
DirectCast(DataGridViewSerialNumber.Columns("Pass"), DataGridViewImageColumn).ImageLayout = DataGridViewImageCellLayout.Stretch
If you want to break that up for clarity:
Dim imageColumn = DirectCast(DataGridViewSerialNumber.Columns("Pass"), DataGridViewImageColumn)
imageColumn.ImageLayout = DataGridViewImageCellLayout.Stretch
Also, be aware that Stretch will not retain the original aspect ratio, so you might want to use Zoom instead.
I have around 15 comboboxes on my form, all being loaded with the same information pulled from a table(~150 entries). Currently I am taking the information from the table, then looping through the entries and adding them to each textbox. I'm wondering if there's a more efficient way to load these comboboxes then having to individually add the table entry into each combobox, having to list 15 lines of code within the For loop.
I'm not seeing any performance issues with this, but figured I might as well work with the most efficient way possible rather than stick with what works. :)
You can create a list of the combo boxes, and then just loop through them. For instance:
Dim cbos() As ComboBox = {ComboBox1, ComboBox2, ComboBox3}
For Each cbo As ComboBox In cbos
' Load cbo from table
Next
Alternatively, if they are named consistently, you could find the combo box by name:
For i As Integer = 1 to 15
Dim cbo As ComboBox = DirectCast(Controls("ComboBox" & i.ToString())), ComboBox)
' Load cbo from table
Next
Since Combobox items are a collection, if their elements are the same, you can build and array with the objects you want to insert, and then just insert this array to each ComboBox with the method AddRange() (it's a method which exists inside the Combobox.items).
Getting an example from MSDN:
Dim installs() As String = New String() {"Typical", "Compact", "Custom"}
ComboBox1.Items.AddRange(installs)
Then you would only have to do a loop to add the array to each ComboBox. Of course, you will need to build your array first on your own, instead of this easy string array from the example.
Reference:
MSDN - AddRange
You could also do it this way since you mentioned that you already have a table.
Use a datatable
Change your table object into a datatable, which will assist in binding to the comboboxes. It might help if you add the datatable to a dataset too. That way you can attach all ComboBoxes (which are UI elements that let users see information) to the same DataSource, which is the datatable, in the dataset.
Binding
Now all you need to do is loop through all the comboboxes and set the datasource to the same table, that is if you decide to do it programmatically like so:
ComboBox1.DataSource = ds.Tables(0)
ComboBox1.ValueMember = "au_id"
ComboBox1.DisplayMember = "au_lname"
A further tutorial on this with the example above is found here
You can then also get the user selected value with ComboBox1.selectedValue.
On the other hand, if you did this with C# WPF, you can bind each comboBox in the XAML directly, I am unsure if this can be done in VB.net as I tried to look for the option but did not manage to do so, something you might want to try though.
Some very useful tutorials and guides on Data binding, which you might be interested:
~ denotes recommended reading for your question
MSDN: Connect data to objects
DotNetPerls on DataGridView (note this isn't a combobox, just displaying values)
~ VBNet DataTable Usage from DotNetPerls (this is in relation to 1.)
~ SO Q&A on Binding a comboBox to a datasource
Concepts of Databinding
How do I add new record to DataGridView control in VB.Net?
I don't use dataset or database binding. I have a small form with 3 fields and when the user clicks OK they should be added to the DataGridView control as a new row.
If you want to add the row to the end of the grid use the Add() method of the Rows collection...
DataGridView1.Rows.Add(New String(){Value1, Value2, Value3})
If you want to insert the row at a partiular position use the Insert() method of the Rows collection (as GWLlosa also said)...
DataGridView1.Rows.Insert(rowPosition, New String(){value1, value2, value3})
I know you mentioned you weren't doing databinding, but if you defined a strongly-typed dataset with a single datatable in your project, you could use that and get some nice strongly typed methods to do this stuff rather than rely on the grid methods...
DataSet1.DataTable.AddRow(1, "John Doe", true)
I think you should build a dataset/datatable in code and bind the grid to that.
The function you're looking for is 'Insert'. It takes as its parameters the index you want to insert at, and an array of values to use for the new row values. Typical usage might include:
myDataGridView.Rows.Insert(4,new object[]{value1,value2,value3});
or something to that effect.
When I try to cast data source from datagridview that used bindingsource it error accor cannot casting:
----------Solution------------
'I changed casting from bindingsource that bind with datagridview
'Code here
Dim dtdata As New DataTable()
dtdata = CType(bndsData.DataSource, DataTable)
If you want to use something that is more descriptive than a dumb array without resorting to using a DataSet then the following might prove useful. It still isn't strongly-typed, but at least it is checked by the compiler and will handle being refactored quite well.
Dim previousAllowUserToAddRows = dgvHistoricalInfo.AllowUserToAddRows
dgvHistoricalInfo.AllowUserToAddRows = True
Dim newTimeRecord As DataGridViewRow = dgvHistoricalInfo.Rows(dgvHistoricalInfo.NewRowIndex).Clone
With record
newTimeRecord.Cells(dgvcDate.Index).Value = .Date
newTimeRecord.Cells(dgvcHours.Index).Value = .Hours
newTimeRecord.Cells(dgvcRemarks.Index).Value = .Remarks
End With
dgvHistoricalInfo.Rows.Add(newTimeRecord)
dgvHistoricalInfo.AllowUserToAddRows = previousAllowUserToAddRows
It is worth noting that the user must have AllowUserToAddRows permission or this won't work. That is why I store the existing value, set it to true, do my work, and then reset it to how it was.
If your DataGridView is bound to a DataSet, you can not just add a new row in your DataGridView display. It will now work properly.
Instead you should add the new row in the DataSet with this code:
BindingSource[Name].AddNew()
This code will also automatically add a new row in your DataGridView display.