This code is recording the inserted IDs in arraylist and when press on button it should create loop for this array and get the data of each ID from the array and put it in Datatable and add the new data while looping to the datatable and finaly show it in datagridview .
The problem in the result when I insert one record it works fine but when I insert more than one the datagridview shows just the last one , what the mistake that I Done ?!!
In Mainform
Public Inserted_record_hold_dt As New DataTable
Public Inserted_record_dt As New DataTable
Public Sub Addcolumnstodatagrid()
Inserted_record_dt.Columns.Add("ID")
Inserted_record_dt.Columns(0).AutoIncrement = True
Inserted_record_dt.Columns.Add("drawingname")
Inserted_record_dt.Columns.Add("serial")
End Sub
and call this in main_Load
Addcolumnstodatagrid()
And this in the show button when click to loop on the array list that already have the latest ID's that has been added
Private Sub show_btn_Click(sender As System.Object, e As System.EventArgs) Handles show_btn.Click
Dim InsertedID As Integer
Inserted_record_dt.Clear()
Dim R As DataRow = Inserted_record_dt.NewRow
'Loop For each ID in the array "Inserted_List_Array"
For Each InsertedID In mainadd.Inserted_List_Array
'MsgBox(InsertedID.ToString)
Dim cmd As New SqlCommand("select drawingname , serial from main where drawingid = '" & InsertedID & "'", DBConnection)
DBConnection.Open()
Inserted_record_hold_dt.Load(cmd.ExecuteReader)
Try
R("drawingname") = Inserted_record_hold_dt.Rows(0).Item(0)
R("serial") = Inserted_record_hold_dt.Rows(0).Item(1)
Inserted_record_dt.Rows.Add(R)
Catch
End Try
'MsgBox("added")
DBConnection.Close()
cmd = Nothing
Inserted_record_hold_dt.Clear()
Next
sendmail.Show()
sendmail.Mail_DGView.DataSource = Inserted_record_dt
End Sub
Please tell me what is the problem in my code .
Your mistake is in declaring the R variable just one time outside the loop. In this way you continuously replace the values on the same instance of a DataRow and insert always the same instance.
Just move the declaration inside the loop
For Each InsertedID In mainadd.Inserted_List_Array
......
Try
Dim R As DataRow = Inserted_record_dt.NewRow
R("drawingname") = Inserted_record_hold_dt.Rows(0).Item(0)
R("serial") = Inserted_record_hold_dt.Rows(0).Item(1)
Inserted_record_dt.Rows.Add(R)
Catch
....
Next
Another important thing to do is to remove the empty Try/Catch because you are just killing the exception (no message, no log) and thus you will never know if there are errors in this import. At the end you will ship a product that could give incorrect results to your end user.
Related
Apologies if this has already been asked. If so, I am unable to find a simple solution. I am trying to allow a user to copy/paste multiple records in a DataGridView (the in memory copy of the data, to be saved later when the user clicks the save button) and cannot find anything that works. It probably is because there is something I do not understand about all of this.
I set up a standard edit form with Visual Studio's drag/table into a form, so it's using a BindingSource control and all the other controls that come with doing that. It works just fine when manually entering something in the new row one by one, so it seems to be set up correctly, but when it comes to adding a record (or multiples) using code, nothing seems to work.
I tried a few things as outline in the code below. Could someone please at least steer me in the right direction? It cannot be that difficult to paste multiple records.
I run this when the user presses Control-V (the clipboard correctly holds the delimited strings):
Private Sub PasteClipboard()
If Clipboard.ContainsText Then
Dim sLines() As String = Clipboard.GetText.Split(vbCrLf)
For Each sLine As String In sLines
Dim Items() As String = sLine.Split(vbTab)
Dim drv As DataRowView = AdjustmentsBindingSource.AddNew()
drv.Item(1) = Items(0)
drv.Item(2) = Items(1)
drv.Item(3) = Items(2)
drv.Item(4) = Items(3)
'Error on next line : Cannot add external objects to this list.
AdjustmentsBindingSource.Add(drv)
Next
End If
End Sub
EDIT
(the bindingsource is bound to a dataadapter, which is bound to a table in an mdb file, if that helps understand)
I adjusted the inner part of the code to this:
If (RowHasData(Items)) Then
Dim drv As DataRowView = AdjustmentsBindingSource.AddNew()
drv.Item("FontName") = Items(0)
drv.Item("FontSize") = Items(1)
drv.Item("LetterCombo") = Items(2)
drv.Item("Adjustment") = Items(3)
drv.Item("HorV") = Items(4)
End If
It kinda works, but it also adds a blank row before the 2 new rows. Not sure where that is coming from, as I have even included your RowHasData() routine...
I would think that “attemp3” SHOULD work, however, it is unclear “what” the AdjustmentsBindingSource’s DataSource is. Is it a List<T> or DataTable?
If I set the BinngSource.DataSource to a DataTable, then attempt 3 appears to work. Below is an example that worked.
Private Sub PasteClipboard2()
If Clipboard.ContainsText Then
Dim sLines() As String = Clipboard.GetText.Split(vbCrLf)
For Each sLine As String In sLines
Dim Items() As String = sLine.Split(vbTab)
If (RowHasData(Items)) Then
Dim drv As DataRowView = AdjustmentsBindingSource.AddNew()
drv.Item("FontName") = Items(0)
drv.Item("FontSize") = Items(1)
drv.Item("LetterCombo") = Items(2)
drv.Item("Adjustment") = Items(3)
drv.Item("HorV") = Items(4)
End If
Next
End If
End Sub
This appears to work in my tests. I added a small function (RowHasData) to avoid malformed strings causing problems. It simply checks the size (at least 5 items) and also checks to make sure a row actually has “some” data. If a row is just empty strings, then it is ignored.
Private Function RowHasData(items As String())
If (items.Count >= 5) Then
For Each item In items
If (item <> "") Then Return True
Next
End If
Return False
End Function
I am guessing it would be just as easy to add the new rows “directly” to the BindingSource’s DataSource. In the example below, the code is adding the row “directly” to the DataTable that is used as a DataSource to the BindingSource. I am confident you could do the same thing with a List<T> by simply adding a new object to the list. Below is a complete example using a BindingSource and a DataTable. This simply adds the rows to the bottom of the table.
Dim gridTable1 As DataTable
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
PasteClipboard()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
gridTable1 = GetTable()
FillTable(gridTable1)
AdjustmentsBindingSource.DataSource = gridTable1
AdjustmentsDataGridView.DataSource = AdjustmentsBindingSource
End Sub
Private Function GetTable() As DataTable
Dim dt = New DataTable()
dt.Columns.Add("FontName", GetType(String))
dt.Columns.Add("FontSize", GetType(String))
dt.Columns.Add("LetterCombo", GetType(String))
dt.Columns.Add("Adjustment", GetType(String))
dt.Columns.Add("HorV", GetType(String))
Return dt
End Function
Private Sub FillTable(dt As DataTable)
For index = 1 To 10
dt.Rows.Add("Name_" + index.ToString(), "Size_" + index.ToString(), "Combo_" + index.ToString(), "Adjust_" + index.ToString(), "HorV_" + index.ToString())
Next
End Sub
Private Sub PasteClipboard()
If Clipboard.ContainsText Then
Dim sLines() As String = Clipboard.GetText.Split(vbCrLf)
Try
Dim dataRow As DataRow
For Each sLine As String In sLines
Dim Items() As String = sLine.Split(vbTab)
If (RowHasData(Items)) Then
dataRow = gridTable1.NewRow()
dataRow("FontName") = Items(0)
dataRow("FontSize") = Items(1)
dataRow("LetterCombo") = Items(2)
dataRow("Adjustment") = Items(3)
dataRow("HorV") = Items(4)
gridTable1.Rows.Add(dataRow)
End If
Next
Catch ex As Exception
MessageBox.Show("Error: " + ex.Message)
End Try
End If
End Sub
Private Function RowHasData(items As String())
If (items.Count >= 5) Then
For Each item In items
If (item <> "") Then Return True
Next
End If
Return False
End Function
Hope the code helps…
Last but important, I am only guessing that you may have not “TESTED” the different ways users can “SELECT” data and “how” other applications “copy” that selected data. My previous tests using the WIN-OS “Clipboard” can sometimes give unexpected results. Example, if the user selects multiple items using the ”Ctrl” key to “ADD” to the selection, extra rows appeared in the Clipboard if the selection was not contiguous. My important point is that using the OS clipboard is quirky IMHO. I recommend LOTS of testing on the “different” ways the user can select the data. If this is not an issue then the code above should work.
I have some code that is intended to:
1. Loop through a datagrid that is user populated
2. Search an access database using the input criteria
3. Return the results as a datatable
4. Merge additional results to that datatable
5. Bind the merged table results to a DataGridView
The query works, results return as expected with a single value search. The query will also return one of the two test values if I use the user input DataGridView. However, it will not combine the two results before outputting to the results_DataGridView. I have also tried using .Fill from the adapter to add onto my temp table and use that as the datasource.
Option Strict On
Dim dt_TempTable As New DataTable
Dim dt_ContentsTable As New DataTable
Dim SerialNumbers As New List(Of String)
Dim PanelIDs As New List(Of String)
Dim MultiSearch As Boolean
Private Sub bgwk_data_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles bgwk_data.DoWork
If Not (MultiSearch) Then
Retrieveinfo(Me.txt_SerialNumber.Text, Me.txt_PanelID.Text) 'Local
Else
Dim arrSN As String() = SerialNumbers.ToArray()
Dim arrPID As String() = PanelIDs.ToArray()
For i = 0 To arrSN.Count - 1
If i > 0 Then
Retrieveinfo(arrSN(i), arrPID(i)) 'Local - Run query and create a new temp table of results
dt_ContentsTable.Merge(dt_TempTable) 'Merge new temp table to current contents table
Else
dt_ContentsTable = Retrieveinfo(arrSN(i), arrPID(i)) 'Return the original temp table to the contents table (datagridview datasource)
End If
Next
End If
End Sub
'Gets the Data by SerialNumber and saves it to dt_TempTable to be bound afterwards
Private Function Retrieveinfo(SerialNumber As String, PanelID As String) As DataTable
Dim ds As New datasource
Dim tblAdapt As New datasourceTableAdapters.toHTML_SPC_DataTableAdapter
tblAdapt.GetData(dt_TempTable, SerialNumber, PanelID)
Return dt_TempTable 'Return the table
End Function
'Updates dg_data with the datatable from Retrieveinfo and refreshes the form
Private Sub bgwk_dataRunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgwk_data.RunWorkerCompleted
'Unlocks ability to search new things
Me.txt_PanelID.ReadOnly = False
Me.txt_SerialNumber.ReadOnly = False
Me.dg_data.ReadOnly = False
'Binds the backgroundworker results to the datagridview
Me.dg_data.DataSource = Nothing
Me.dg_data.DataSource = dt_ContentsTable
Me.dg_data.Refresh()
Me.Refresh()
End Sub
I understand that merge can be used on DataTables, but perhaps this is incorrect usage?
I'd go with a condition in the multi-search loop:
If dt_ContentsTable.Rows.Count = 0 Then
dt_ContentsTable = dt_TempTable.Clone()
End If
dt_ContentsTable.Merge(dt_TempTable)
This way, the first time, its at least creating the columns of the dt_ContentsTable.
I also don't see any code in the single search where dt_ContentsTable is assigned. So, in the single search, I recommend you assign dt_ContentsTable to dt_TempTable.
I have a Problem with my two DataGrids
The first DataGrid is used to Show Data from an SQL Database.
Now i want that, if i select one row and save it to the Datagrid2, it stays selected and won't Change.
The Problem is now that if i Change the Row, the DataGrid2 changes too.
I hope you understand my Problem.
Here's the Code
Public Sub SelectItem()
Try
Dim rows As List(Of Integer) = New List(Of Integer)
For Each cell As DataGridCellInfo In DataGrid1.SelectedCells
rows.Add(DataGrid1.Items.IndexOf(cell.Item))
DataGrid1.SelectedItems.Clear()
Next
For Each Item As Integer In rows
If (Item < DataGrid1.Items.Count) Then
DataGrid1.SelectedItems.Add(DataGrid1.Items.GetItemAt(Item))
DataGrid2.ItemsSource = DataGrid1.SelectedItems
End If
Next
Catch ex As Exception
GeneralMergeTools.ShowError(ex, GeneralMergeTools.FatalError.CriticalError, "ContentControl1.SelectedItem")
End Try
End Sub
Kind Regards
EDIT:
I'll add here my Code for Future People who have the same or a similar Problem.
Public Sub SelectItem() 'Auf Knopfdruck
Try
Dim dt As DataTable = CType(Me.DataGrid1.ItemsSource, DataView).Table.Clone
For Each r1 As System.Data.DataRowView In Me.DataGrid1.SelectedItems
Dim r2 As DataRow = dt.NewRow
For Each c As System.Data.DataColumn In dt.Columns
r2.Item(c.ColumnName) = r1.Row(c.ColumnName)
Next
dt.Rows.Add(r2)
DataGrid2.ItemsSource = dt.DefaultView
Next
Catch ex As Exception
GeneralMergeTools.ShowError(ex, GeneralMergeTools.FatalError.CriticalError, "ContentControl1.SelectedItem")
End Try
End Sub
You share the same instance of Items between 2 grids:
DataGrid2.ItemsSource = DataGrid1.SelectedItems
So basically when you change property IsSelected in first grid by selecting it, on the second grid it's exactly the same object having also this change, so second grid also change SelectedItem accordingly.
To fix this behavior you need to have different instances of objects in your ItemSource.
So this is my very well working Code for this matter:
Public Sub SelectItem()
Try
Dim dt As DataTable = CType(Me.DataGrid1.ItemsSource, DataView).Table.Clone
For Each r1 As System.Data.DataRowView In Me.DataGrid1.SelectedItems
Dim r2 As DataRow = dt.NewRow
For Each c As System.Data.DataColumn In dt.Columns
r2.Item(c.ColumnName) = r1.Row(c.ColumnName)
Next
dt.Rows.Add(r2)
DataGrid2.ItemsSource = dt.DefaultView
Next
I have a question about database values and how to determine the id of a value that has been changed by the user at some point.
As it is currently set up there is a combobox that is populated from a dataset, and subsequent text boxes whose text should be determined by the value chosen from that combobox.
So let's say for example you select 'Company A' from the combobox, I would like all the corresponding information from that company's row in the dataset to fill the textboxes (Name = Company A, Address = 123 ABC St., etc.,)
I am able to populate the combobox just fine. It is only however when I change the index of the combobox that this specific error occurs:
An unhandled exception of type 'System.Data.OleDb.OleDbException'
occurred in System.Data.dll
Additional information: Data type mismatch in criteria expression.
Here is the corresponding code:
Imports System.Data.OleDb
Public Class CustomerContact
Dim cn As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|datadirectory|\CentralDatabase.accdb;")
Dim da As New OleDbDataAdapter()
Dim dt As New DataTable()
Private Sub CustomerContact_Load(sender As Object, e As EventArgs) Handles MyBase.Load
cn.Open()
da.SelectCommand = New OleDbCommand("select * from Customers", cn)
da.Fill(dt)
Dim r As DataRow
For Each r In dt.Rows
cboVendorName.Items.Add(r("Name").ToString)
cboVendorName.ValueMember = "ID"
Next
cn.Close()
End Sub
Private Sub cboVendorName_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboVendorName.SelectedIndexChanged
cn.Open()
da.SelectCommand = New OleDbCommand("select * from Customers WHERE id='" & cboVendorName.SelectedValue & "'", cn)
da.Fill(dt)
Dim r As DataRow
For Each r In dt.Rows
txtNewName.Text = "Name"
txtAddress.Text = "Address"
Next
cn.Close()
End Sub
The error is caught at Line 24 of this code, at the second da.Fill(dt) . Now obviously from the exception I know that I am sending in a wrong datatype into the OleDbCommand, unfortunately I am a novice when it comes to SQL commands such as this. Also please keep in mind that I can't even test the second For loop, the one that is supposed to fill the Customer information into textboxes (for convenience I only copied the first two textboxes, of which there are nine in total). I am think I could use an If statement to determine if the row has been read, and from there populate the textboxes, but I will jump that hurdle when I can reach it.
Any guidance or suggestions would be much appreciated. Again I am a novice at managing a database and the code in question pertains to the project my current internship is having me write for them.
Since you already have all the data from that table in a DataTable, you dont need to run a query at all. Setup in form load (if you must):
' form level object:
Private ignore As Boolean
Private dtCust As New DataTable
...
Dim SQL As String = "SELECT Id, Name, Address, City FROM Customer"
Using dbcon = GetACEConnection()
Using cmd As New OleDbCommand(SQL, dbcon)
dbcon.Open()
dtCust.Load(cmd.ExecuteReader)
End Using
End Using
' pk required for Rows.Find
ignore = True
dtCust.PrimaryKey = New DataColumn() {dtCust.Columns(0)}
cboCust.DataSource = dtCust
cboCust.DisplayMember = "Name"
cboCust.ValueMember = "Id"
ignore = False
The ignore flag will allow you to ignore the first change that fires as a result of the DataSource being set. This will fire before the Display and Value members are set.
Preliminary issues/changes:
Connections are meant to be created, used and disposed of. This is slightly less true of Access, but still a good practice. Rather than connection strings everywhere, the GetACEConnection method creates them for me. The code is in this answer.
In the interest of economy, rather than a DataAdapter just to fill the table, I used a reader
The Using statements create and dispose of the Command object as well. Generally, if an object has a Dispose method, put it in a Using block.
I spelled out the columns for the SQL. If you don't need all the columns, dont ask for them all. Specifying them also allows me to control the order (display order in a DGV, reference columns by index - dr(1) = ... - among other things).
The important thing is that rather than adding items to the cbo, I used that DataTable as the DataSource for the combo. ValueMember doesn't do anything without a DataSource - which is the core problem you had. There was no DataSource, so SelectedValue was always Nothing in the event.
Then in SelectedValueChanged event:
Private Sub cboCust_SelectedValueChanged(sender As Object,
e As EventArgs) Handles cboCust.SelectedValueChanged
' ignore changes during form load:
If ignore Then Exit Sub
Dim custId = Convert.ToInt32(cboCust.SelectedValue)
Dim dr = dtCust.Rows.Find(custId)
Console.WriteLine(dr("Id"))
Console.WriteLine(dr("Name"))
Console.WriteLine(dr("Address"))
End Sub
Using the selected value, I find the related row in the DataTable. Find returns that DataRow (or Nothing) so I can access all the other information. Result:
4
Gautier
sdfsdfsdf
Another alternative would be:
Dim rows = dtCust.Select(String.Format("Id={0}", custId))
This would return an array of DataRow matching the criteria. The String.Format is useful when the target column is text. This method would not require the PK definition above:
Dim rows = dtCust.Select(String.Format("Name='{0}'", searchText))
For more information see:
Using Statement
Connection Pooling
GetConnection() method aka GetACEConnection
I searched many forums but didn't find any solution. I want to update access table cells from Vb.net. My table has fields:
[PanelNumber],[Date], [PVValue]
In Panel number field, there is some text like "Panel 1", "Panel 2" etc..
from vb, i will select that "Panel 1" after clicking a button, i need to fill that "PVValue" field with random numbers in given range, plz check my code below, when i try with this code, i am always getting same number in all rows
but need separate number (may be repeated in some rows)
LogTable2 is my table name
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
connString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=LoggedData.accdb;Jet OLEDB:Database Password=GodavarthiSuresh;"
myNewConnection.ConnectionString = connString
myNewConnection.Open()
Dim UpdateString As String = "update LogTable2 set [pvvalue]= #rndVal1 where panelnumber='" & panelnametxt.Text & "'"
Dim UpdateCmd As New OleDb.OleDbCommand(UpdateString, myNewConnection)
UpdateCmd.Parameters.Clear()
Randomize()
UpdateCmd.Parameters.AddWithValue("#rndVal1", GetRandom())
Try
UpdateCmd.ExecuteNonQuery()
UpdateCmd.Dispose()
myNewConnection.Close()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
' this is the function to get random number in given range
Public Function GetRandom() As Integer
Static Generator As System.Random = New System.Random()
Return Generator.Next(825, 850)
End Function
If you have multiple rows for each panel and you want them to have different values, you need to update them individually. Its is not true that update command will be "called" 5 times if there are five rows associated. It will be executed once per click event.
To do what it sounds like you want, you need a unique identifier for each such as an AutoIncrement ID column.
Private RNG As New Random()
Private Sub btnUpdate_Click(etc...
Dim sql = "SELECT ID FROM LogTable2 WHERE panelnumber = #pnl"
Dim pnlList As New List(Of Int32)
Using con As OleDbConnection = GetACEConnection()
Using cmd As New OleDbCommand(sql, con)
con.Open()
cmd.Parameters.AddWithValue("#pnl", panelnametxt.Text)
' get affected row IDs into a list;
Using rdr As OleDbDataReader = cmd.ExecuteReader
While rdr.Read
pnlList.Add(Convert.ToInt32(rdr.Item("ID")))
End While
End Using ' close, dispose of reader
End Using ' dispose of cmd
' not sure you need a new command object
sql = "UPDATE LogTable2 SET pvvalue = #rVal WHERE ID = #id"
Using pcmd As New OleDbCommand(sql, con)
' loop thru ID list and update each row with
' new random value 825-849 inclusive
For n As Int32 = 0 To pnlList.Count - 1
pcmd.Parameters.AddWithValue("#rVal", RNG.Next(825, 850))
pcmd.Parameters.AddWithValue("#id", pnlList(n))
pcmd.ExecuteNonQuery()
' clear for next iteration
pcmd.Parameters.Clear()
Next
End Using ' close and dispose of pcmd
End Using ' close and dispose of connection
End Sub
I dont like scattering the connection string in every method which opens a connection, so a method for that is nice to have.
Notes:
This depends on a unique ID column which is AutoIncrement (PK). If you have some other unique identifier, use it but you have to have some way to identify rows individually.
Rather than a method to create a random value, since it is just one line, it might be easier to just use your RNG directly as shown.
I cant test the code, but it should be close.
Use Using blocks to close and dispose of DBObjects like connections, command and reader otherwise you can run out of resources.
You can also initialize Command objects with the SQL and COnnection when you declare it rather than setting them as properties. It makes the code a little more compact and less likely that you forget them.
Randomize does nothing - it is meant to be used with the old VB6 Rnd(). You only need to [Escape] keywords in SQL, not every column name and pvvalue is not a keyword.
A DataTable instead of a Reader could be used to get the rows but I am not sure it is any simpler.
Finally, elements of a SQL WHERE clause can also be parameterized; there is no need to concat them just because it is a where rather than a column value.
you can do this in database level,add auto increment value to database field