I have a main DataSet which contains several DataTables. These different DataTables are bound to their DataGridView's DataSource respectively.
My problem is whenever I modify something in the display area such as the description textbox below and then click on save....
<<< To >>>
The DataGridView doesn't have the replicated changes, as seen here:
I need a way to update the DataTable ... My save button successfully saves the information. I am quite new to DataSets and DataTables so this is my first time trying to update a DataTable. I doubt I need to reload the information in the DataTable, there must be something more efficient?
Updating a DataTable With Unknown Indexes
For more information: How to: Edit Rows in a DataTable
In order to edit an existing row in a DataTable, you need to locate the DataRow you want to edit, and then assign the updated values to the desired columns.
Update existing records in typed datasets (Row index not known)
Assign a specific DataRow to a variable using the generated FindBy method, and then use that variable to access the columns you want to edit and assign new values to them.
Dim Description As String = "Hello World Modified"
'Update DataTable
Dim Row As DataSet1.DataTableRow
Row = DataSet1.DataTableRow.FindByPrimaryKey(PK)
Row.Description = Description
Update existing records in untyped datasets (Row index not known)
Use the Select method of the DataTable to locate a specific row and assign new values to the desired columns
Dim Description As String = "Hello World Modified"
'Update DataTable
Dim Row() As Data.DataRow
Row = DataSet1.Tables("Table1").Select("PrimaryKey = '10'")
Row(0)("Description") = Description
Once this is done, I don't need to refresh anything else - my DataGridView has the latest information.
Related
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
I have a function which return a list of countries as data table. How do I add these row values to a combo box?
For Each row as DataRow in fooDataTable.Rows
combobox.Items.add(row)
Next
Some how this does not fill up the values on my combo box. The datatable returns 100 rows. Each row has 2 columns. I am trying to add all the 100 datarows with the first column. Any help would be greatly appreciated.
A DataRow is an object and while objects can be added to a Combo or ListBox, in this case the result is not very useful. When you store an Object in .Items, the result of the .ToString function is what displays. In this case it will just display System.Data.Datarow
The best solution is what LarsTech suggested and use the existing datatable as the datasource. This is economical because there is nothing new created for the CBO:
ComboBox.DataSource = fooDataTable
ComboBox.DisplayMember = "column name in DT"
Another way is to modify your loop:
combobox.Items.Add(row("col name").ToString)
This adds the data from the column name to the cbo. If this was something like peoples names and you wanted to show First and Last name:
combobox.Items.Add(String.Format("{0}, {1}", row("LastName").ToString,
row("FirstName").ToString))
This is similar except we are formatting the contents of both columns as we add it to the CBO.
Still another way is to create a class object for each row which returns what you want to display via the ToString override; and add them to the CBO, but that is overkill in this case, given the economy of the first alternative.
I am trying to use a bindingsource as the datasource for a combobox. The display and value members of the combobox will be the field names in my bindingsource's datasource.
Currently I use a process of populating a datatable and assigning it to the datasource of the combobox. Because I already have the bindingsource populated with data it would make sense to just set the binding up rather than continue to use this code below:
Dim dtfields As New DataTable
dtfields = mySqlref.sqlobj.SelectData(String.Format("select column_name from information_schema.columns where table_name = '{0}' order by ordinal_position", mydata.Table), SqlLibrary.SqlLibrary.SelectType.datatable)
cboField.DataSource = dtfields
cboField.ValueMember = "column_name"
cboField.DisplayMember = "column_name"
Can someone point me in the right direction? Thanks for reading.
The sort of answer I was looking for here was:
if your fieldnames are already in your bindingsource then you can use linq to create an array of those fieldnames instead of using a seperate query to call the database for field names
Here's the code I ended up using:
Dim arraynames = (From x As DataColumn In mydata.Table.Columns Select x.ColumnName).ToArray()
cboField.DataSource = arraynames
in this example "mydata" is a dataview. It is the object upon which my binding source is created. I extract column names into an array using the datatable object inside that dataview.
I've been using listviews successfully in a fairly complex app, but I really want to use a DataGridView, and I haven't been able to get it to work. I'm using classes or my own pseudo-middleware to read the data, and have avoided binding tables to controls. Here's a simplified example of code that doesn't work right.
Dim ds As New DataSet
Dim nrows as Integer
ds = dbio.ReadLocations("") 'Reads place names, with an optional search string
nrows = ds.Tables(0).Rows.Count
'Note: I can insert code here that successfully displays the data in a listview
Dim dtStates As DataTable = New DataTable("TableName")
Dim drStates As DataRow 'A component row of dtstates
Dim dr As DataRow 'A component row of the result set, ds
dtStates.Clear()
'There are just 3 columns in the data grid
dtStates.Columns.Add("ID", GetType(String))
dtStates.Columns.Add("StateName", GetType(String))
dtStates.Columns.Add("Country", GetType(String))
'Build the data table, row-by-row
For i = 1 To nRows
dr = ds.Tables(0).Rows(i - 1) 'Get a row from the populated data set
drStates = dtStates.NewRow 'Create a new row object
'Populate the new row object
drStates("ID") = dr("ID").ToString
drStates("StateName") = dr("StateName")
drStates("Country") = dr("Country")
'Put the new row object into the data table
dtStates.Rows.Add(drStates)
Next
'Bind the DataGridView to my data table
grdStates.DataSource = dtStates
My grid "lights up" as it were, and clearly has the appropriate number of rows, just no data. My best guess is that I need to define a DataMember for my DataGridView, but I can't imagine what value should be placed there. I've tried grdStates.DataMember="TableName" and other values (even ""), but nothing works.
I will make an assumption here, since I can't yet add a comment to your question, and that is that you created your DataGridView and added columns to it independently of the columns added to dtStates.
I recently experienced the same problem you have, which led me to your question.
My problem was that I had defined a DataGridView with 3 columns, then set its datasource to a dataTable which already had 3 columns. I thought it would map the datatable's columns to the DataGridView. In fact it only displayed the three columns I had added via the design interface for each row in the datatable, but they were empty.
My first clue to this was when I selected a row and hit Ctrl+C, then pasted the results in Notepad++. I saw 4 tab stops before the "first" column.
I deleted the manual column definitions from the DataGridView so that its only columns come from the datatable I assigned to it. Now the data shows as expected.
I still need to figure out how to format the columns to the width I want, but at least the data is there now. Hope that helps! Surf Wisely.
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.