Rowstate remains always unchanged even if data changes - vb.net

I have a datagrid bound to Oracle database. I would like to update my fields through the gridview. I used Update method which was not successful. After advanced diagnose I came up with the following observations when executing this code.
Private Sub MainGridView_ColumnChanged(ByVal Sender As Object, ByVal e As DataColumnChangeEventArgs) Handles DataTable.ColumnChanged
e.Row.AcceptChanges()
e.Row.EndEdit()
DataTable.AcceptChanges()
BindingSource.EndEdit()
End Sub
When I change a row, the value changes in memory. It's checked using break points and Watchs ( e.Row.Item("Field") has a different value )
e.Row.RowState remains Unchanged in the watch in all the steps of the excution.
Here is the code for binding the data to the database:
Public Sub FillForm()
SQL = "SELECT * FROM ARCHITECT.ARCH_TASKS"
Command = New OracleCommand(SQL, Connection)
DataAdapter = New OracleDataAdapter(Command)
DataSet.Tables.Add(DataTable)
DataAdapter.Fill(DataTable)
BindingSource.DataMember = "Table1"
BindingSource.DataSource = DataSet
Me.GridControl1.DataSource = BindingSource
End Sub
Here is a video preview Preview
Is there anything else I should take into consideration ?
Appreciate your help.

Related

ComboBox shows the first item when loaded into new form VB.Net

I have two forms which are connected, the first one has a datagridview(DGV) that will show list of staff.
When I double click the row the data from the FGV will transfer into second form for user to edit the data. Each column from the DGV will insert at textbox combobox etc.
But the combobox on second form are using database when I double click the DGV; the others is fine but for combobox it will show the first data in database.
This is the code for the double click on the first form:
Private Sub DataGridView1_CellDoubleClick(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellDoubleClick
Dim frmEdit As New Edit
frmEdit.lblEENo.Text = DataGridView1.CurrentRow.Cells(1).Value.ToString
frmEdit.txtName.Text = DataGridView1.CurrentRow.Cells(2).Value.ToString
frmEdit.txtAge.Text = DataGridView1.CurrentRow.Cells(3).Value.ToString
frmEdit.lblAgeCategory.Text = DataGridView1.CurrentRow.Cells(4).Value.ToString
frmEdit.txtGender.Text = DataGridView1.CurrentRow.Cells(5).Value.ToString
frmEdit.cbEthnicity.Text = DataGridView1.CurrentRow.Cells(6).Value.ToString
frmEdit.cbGrade.Text = DataGridView1.CurrentRow.Cells(7).Value.ToString
frmEdit.cbCategory.Text = DataGridView1.CurrentRow.Cells(8).Value.ToString
frmEdit.cbDepartment.Text = DataGridView1.CurrentRow.Cells(9).Value.ToString
frmEdit.cbPosition.Text = DataGridView1.CurrentRow.Cells(10).Value.ToString
frmEdit.txtReporting.Text = DataGridView1.CurrentRow.Cells(11).Value.ToString
frmEdit.DateTimePicker1.Value = DataGridView1.CurrentRow.Cells(12).Value.ToString
frmEdit.DateTimePicker2.Value = DataGridView1.CurrentRow.Cells(13).Value.ToString
If DataGridView1.CurrentRow.Cells(14).Value.ToString = "Y" Then
frmEdit.rbYes.Checked = True
ElseIf DataGridView1.CurrentRow.Cells(14).Value.ToString = "N" Then
frmEdit.rbNo.Checked = True
End If
frmEdit.cbStatus.Text = DataGridView1.CurrentRow.Cells(15).Value.ToString
frmEdit.txtNote.Text = DataGridView1.CurrentRow.Cells(16).Value.ToString
frmEdit.lblMody.Text = tslblLoginAs.Text
frmEdit.ShowDialog()
load_data()
End Sub
This is second form's code for connecting to the database table for the combobox:
Private Sub Edit_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim frmMasterList As New MasterStaff
Dim poscmd As New SqlCommand("SELECT * from MasterPositionList", conn)
Dim posadapt As New SqlDataAdapter(poscmd)
Dim postable As New DataTable
posadapt.Fill(postable)
cbPosition.DataSource = postable
cbPosition.DisplayMember = "PositionName"
Dim depcmd As New SqlCommand("SELECT * from MasterDepartmentList", conn)
Dim depadapt As New SqlDataAdapter(depcmd)
Dim deptable As New DataTable
depadapt.Fill(deptable)
cbDepartment.DataSource = deptable
cbDepartment.DisplayMember = "DeparmentName"
End Sub
The form load event fires too late to populate the combobox. The event is raised almost immediately when you create the form instance in the DataGridView1_CellDoubleClick() method, but the event handler can't actually run until control is returned to the messaging loop, and there's no chance for that to happen until the frmEdit.ShowDialog() line, which is after you have set the combobox values. Therefore the Load event runs after you have populated your desired value and will overwrite that work.
To fix this, move that Load code to the constructor, just after the InitializeComponent() method.
Additionally, since you want to select from the items provided by the database you should look at using the SelectedItem or SelectedValue property, instead of the Text property.
Finally, it's really poor practice to reuse the same connection object throughout an application or form. Yes, database connections are expensive objects to create and manage, but the ADO.Net library already handles this for you. The SqlConnection object you work with in code is a light-weight wrapper over a pool of the more-expensive actual raw connections. When you try to reuse one SqlConnection, you gain efficiency in the cheap thing while losing efficiency in the more expensive thing.
Instead, create (and promptly Dispose()!) a new SqlConnection object every time you need to use the database, and think about reducing connections by reducing round-trips to the database. In this case, you can combine the two SELECT statements into a single string and fill two DataTables in the same database trip:
Private Sub New ' Replaces the current Edit_Load() method
InitializeComponent()
Dim results As New DataSet
'Using block will make sure the connection is closed, **even if an exception is thrown**
' Do NOT(!) try to reuse the connection. Only reuse the connection string.
Using conn As New SqlConnection(connString), _
' Two SELECT queries in one string
cmd As New SqlCommand("SELECT * from MasterPositionList;SELECT * from MasterDepartmentList", conn), _
da As New DataAdapter(cmd)
da.Fill(results)
End Using 'Dispose the database objects as quickly as possible
cbPosition.DisplayMember = "PositionName"
cbDepartment.DisplayMember = "DeparmentName"
cbPosition.DataSource = results.Tables(0) 'Filled two tables in one database call
cbDepartment.DataSource = results.Tables(1)
End Sub
I am wondering why you need a separate form when you could edit the data directly in the DGV. When you think of it, all the data you're trying to edit is already found in the DGV (or should exist in its datasource). You can even have combo boxes inside the DGV, with their own bound datatables. Here is an example that shows how it could be done.
In Edit_Load there is unnecessary repetition. You load the same dataset twice. You could reuse it or even load it at startup and keep it in cache if it's not changing during application runtime.
Plus, rather than fetch values from the DGV, it would better to fetch the selected datarow instead. For this you just need the record ID...
There is no need to access UI attributes, use the underlying datatable instead.
You shouldn't be using index numbers. Imagine the mess and potential for bugs if you want to insert columns or move columns. Also, the user probably can reorder columns in the DGV by drag & drop... thus your logic is ruined and the program will not behave like it should.
Anyway, to answer your issue: you've loaded a datatable, now all you have to do is bind it to the combo (which you did). You have used the DataSource and DisplayMember attributes. You could use SelectedValue to preselect a value in the list like this:
With cbPosition
.DisplayMember = "PositionName"
.ValueMember = "PositionName"
.SelectedValue = "The text value that comes from the DGV"
.DataSource = postable
End With
As long as the value from the DGV is present in the datatable this should work.
Again, I think you should simplify your UI. All the data could be edited in-place in the DGV.

datagridview last programmatically changed cell does not get included in changedrows

VB.net app changes values in a datagridview programmatically. The values are all as they should be, but the save routine (dtShipments is the datatable that is the source for the datagridview)
Dim dtChanges As DataTable = dtShipments.getchanges()
If more than one has changed, dtChanges is always missing the last row.
In the routine that changes the cell values, I have tried DatagridView1.EndEdit and DatagridView1.CommitEdit, but the behavior is the same. I even tried adding a SendKeys.Send(vbTab) line, since hitting the tab key when making the changes manually is enough to get all the changes to show up in .GetChanges.
What am I missing?
code per request:
Private Sub btnAssign_Click(sender As Object, e As EventArgs) Handles btnAssign.Click
strModErr = "Form1_btnAssign_Click"
Dim sTruck As String = ""
Try
sTruck = Me.DataGridView2.SelectedCells(0).Value.ToString
For Each row As DataGridViewRow In DataGridView1.SelectedRows
row.Cells("Truck").Value = sTruck
Next
DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
Catch ex As Exception
WriteErrorToLog(Err.Number, strModErr + " - " + Err.Description)
End Try
End Sub
Private Function SaveChanges() As Boolean
strModErr = "Form1_SaveChanges"
Dim Conn As New SqlConnection(My.Settings.SQLConnectionString)
Dim sSQL As String = "UPDATE fs_Shipments SET Truck = #Truck, Stop = #Stop WHERE SalesOrder = #SO"
Dim cmd As New SqlCommand(sSQL, Conn)
Dim sSO, sTruck As String
Dim iStop As Integer = 0
Try
DataGridView1.EndEdit()
DataGridView1.ClearSelection()
Dim dtChanges As DataTable = dtShipments.getchanges() 'DataRowState.Modified
If Not dtChanges Is Nothing Then
Conn.Open()
For Each row As DataRow In dtChanges.Rows
sSO = row("SalesOrder").ToString
sTruck = row("Truck").ToString
iStop = CInt(row("Stop").ToString)
With cmd.Parameters
.Clear()
.AddWithValue("#SO", sSO)
.AddWithValue("#Truck", sTruck)
.AddWithValue("#Stop", iStop)
End With
cmd.ExecuteNonQuery()
Next
End If
Return True
Catch ex As Exception
WriteErrorToLog(Err.Number, strModErr + " - " + Err.Description)
Return False
End Try
End Function
I am not exactly 100% sure why this happens. The problem appears to be specific to when the user “selects” the cells in the first grid, and then calls the SaveChanges code “BEFORE” the user has made another selection in the first grid. In other words, if the user “selects” the rows to “assign” the truck to in grid 1, then, “AFTER” the “selected” cells have been filled with the selected truck, THEN, the user selects some other cell in grid 1, THEN calls the save changes code. In that case the code works as expected.
All my attempts at committing the changes, either failed or introduced other issues. I am confident a lot of this has to do with the grids SelectedRows collection. IMHO, this looks like a risky way to set the cell values, I would think a more user-friendly approach may be to add a check box on each row and assign the truck values to the rows that are checked. But this is an opinion.
Anyway, after multiple attempts, the solution I came up with only further demonstrates why using a BindingSource is useful in many ways. In this case, it appears the DataTable is not getting updated with the last cell change. Again, I am not sure “why” this is, however since it appears to work using a BindingSource, I can only assume it has something to do with the DataTable itself. In other words, before the “Save” code is executed, we could call the tables AcceptChanges method, but then we would lose those changes. So that is not an option.
To help, below is a full (no-fluff) example. In the future, I highly recommend you pull out the parts of the code that do NOT pertain to the question. Example, all the code that saves the changes to the DB is superfluous in relation to the question… so remove it. The more unnecessary code you add to your question only increases the number of SO users that will “ignore” the question. If you post minimal, complete and working code that reproduces the problem so that users can simply copy and paste without having to add addition code or remove unnecessary code will increase you chances of getting a good answer. Just a heads up for the future.
The solution below simply adds a BindingSource. The BindingSource’s DataSource is the DataTable dtShipments then the BindingSource is used a DataSource to DataGridView1. Then in the SaveChanges method, “before” the code calls the dtShipment.GetChanges() method, we will call the BindingSources.ResetBinding() method which should complete the edits to the underlying data source, in this case the DataTable dtShipments.
If you create a new VS-VB winforms solution, drop two (2) grids and two (2) buttons onto the form as shown below, then change the button names to “btnAssign” and “btnApplyChanges.” The assign button will add the selected truck in grid two to the selected rows in grid one. The apply changes button simply resets the binding source and displays a message box with the number of rows that were changed in the dtShipments DataTable. It should be noted, that the code calls the dtShipments.AcceptChanges() method to clear the changes. Otherwise, the code will update changes that have already been made.
Dim dtShipments As DataTable
Dim dtTrucks As DataTable
Dim shipBS As BindingSource
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dtShipments = GetShipmentsDT()
shipBS = New BindingSource()
shipBS.DataSource = dtShipments
dtTrucks = GetTrucksDT()
dtShipments.AcceptChanges()
DataGridView1.DataSource = shipBS
DataGridView2.DataSource = dtTrucks
End Sub
Private Function GetShipmentsDT() As DataTable
Dim dt = New DataTable()
dt.Columns.Add("ID", GetType(String))
dt.Columns.Add("Truck", GetType(String))
dt.Rows.Add(1)
dt.Rows.Add(2)
dt.Rows.Add(3)
dt.Rows.Add(4)
Return dt
End Function
Private Function GetTrucksDT() As DataTable
Dim dt = New DataTable()
dt.Columns.Add("Truck", GetType(String))
For index = 1 To 10
dt.Rows.Add("Truck" + index.ToString())
Next
Return dt
End Function
Private Sub btnAssign_Click(sender As Object, e As EventArgs) Handles btnAssign.Click
Dim sTruck = DataGridView2.SelectedCells(0).Value.ToString
'Dim drv As DataRowView
For Each row As DataGridViewRow In DataGridView1.SelectedRows
'drv = row.DataBoundItem
'drv("Truck") = sTruck
row.Cells("Truck").Value = sTruck
Next
'DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
'shipBS.ResetBindings(True)
'DataGridView1.CurrentCell = DataGridView1.Rows(0).Cells(0)
End Sub
Private Function SaveChanges() As Boolean
Try
shipBS.ResetBindings(True)
Dim dtChanges As DataTable = dtShipments.GetChanges()
If (dtChanges IsNot Nothing) Then
MessageBox.Show("There are " & dtChanges.Rows.Count & " rows changed in the data table")
' update sql DB
dtShipments.AcceptChanges()
End If
Return True
Catch ex As Exception
Return False
End Try
End Function
Private Sub btnApplyChanges_Click(sender As Object, e As EventArgs) Handles btnApplyChanges.Click
Dim ChangesMade As Boolean
ChangesMade = SaveChanges()
End Sub
Lastly, since you are using VB… I highly recommend that you set the “Strict” option to “ON.” When this option is ON, it will flag possible errors that you may be missing. To do this for all future VB solutions, open VS, close any solutions that may be open, then go to Tools->Options->Projects and Solutions->VB Defaults, then set “Option Strict” to “On.” The default setting is off.
I hope this makes sense and helps.

What is the correct event to use to catch a new row when the user has finished editing?

I need to add several bits of data to my record such as created date and createdby when a user adds a new row in a datatable.
I am looking for the correct event on the bindingsource to catch this so i can add the information then save the record before the user moves onto the next row.
C# seems to have a RowEditEnding event on the datagrid but a)i'm not using C# and b) I can see from searching that its better to work on the datasource, which in this case is a bound datagrid so i presume i should be looking at the bindingsource object but there is not an obvious event to choose.
I think need something like currentchanged with condition if isdirty then...
Please can someone point me in the right direction here.
thanks
john
this seems to work
Private Sub TblOppQuoteDetailBindingSource_CurrentChanged(sender As Object, e As EventArgs) Handles TblOppQuoteDetailBindingSource.CurrentChanged
If sender.current IsNot Nothing Then
If sender.current.IsNew Then
Dim nr As DataRowView = sender.current
nr.Item("OppQuoteID") = 2
nr.Item("Created") = Now
nr.Item("CreatedBy") = G_UserName
ElseIf sender.current.isedit Then
Dim nr As DataRowView = sender.current
nr.Item("OppQuoteID") = 2
nr.Item("Updated") = Now
nr.Item("UpdatedBy") = G_UserName
End If
End If
End Sub
is this the correct way?

how to delete selected row from database in GridControl

I am new on DevExpress. I retrieve data from database to gridview and can display them so far. However, I have a button which makes several modifications when user clicks on it.
I can remove the selected row from gridview.
So my obvious question is how can I delete selected rows from database in gridview.
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim cevap As DialogResult = DevExpress.XtraEditors.XtraMessageBox.Show("Are you sure?", "Dikkat!", MessageBoxButtons.YesNo)
If cevap = Windows.Forms.DialogResult.No Then Exit Sub
Dim command As New SqlCommand()
command.Connection = spcc.SqlCon
'command.CommandText = "DELETE FROM EBARPARAMETER WHERE ID = '" & GridView1.what? & "' "
Dim read As SqlDataReader
read = command.ExecuteReader()
GridView1.DeleteRow(GridView1.FocusedRowHandle)
Take a look at the Posting Data to a Connected Database help-article:
The GridView.DeleteRow method removes a row from a grid's View and also deletes the object which represents this row in the GridControl's data source, but not from a database. To accomplish your task, you need to post changes to the database manually, for example using corresponding the methods of a Data Adapter object or EF-context.
GridView1.what?
To get row objects that correspond to specific row handles, use the ColumnView.GetRow or ColumnView.GetDataRow method.
In specific cases, you may need to obtain indexes of rows in the bound data source that correspond to specific row handles in Views. To do this, use the ColumnView.GetDataSourceRowIndex method.

Limiting my result set in VB

So I can fill the combobox I have going in Visual Studio just how I want with ALL results with the following:
Dim pnum As New List(Of String)
For Each polnumber As InsuredDataSet.Claims_InsuredRow In Me.InsuredDataSet.Claims_Insured
pnum.Add(polnumber.Policy_Number)
Next
pnum.Reverse()
Me.Policy_NumberComboBox.DataSource = pnum
Awesome. Now I want to limit the pnum by taking what was input/selected from Insured_NameTextBox on the form and only returning the Policy_Number with a matching Insured_Name. I figure this can be performed with an If statement, but everything I try (stringcompare, InsuredName_TextBox = Me.InsuredDataSet.ClaimsInsured, etc.) either doesn't limit the results OR limits the results entirely so nothing shows up. Any idea where to put the If statement and what should be compared?
UPDATE:
I think there is some confusion so I'm including the entire load sub below:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'TODO: This line of code loads data into the 'IncidentsDataSet.Claims_Incidents' table. You can move, or remove it, as needed.
Me.Claims_IncidentsTableAdapter.Fill(Me.IncidentsDataSet.Claims_Incidents)
'TODO: This line of code loads data into the 'InsuredDataSet.Claims_Insured' table. You can move, or remove it, as needed.
Me.Claims_InsuredTableAdapter.Fill(Me.InsuredDataSet.Claims_Insured)
'textbox autocomplete mode
Dim Iname As New AutoCompleteStringCollection()
For Each insname As InsuredDataSet.Claims_InsuredRow In Me.InsuredDataSet.Claims_Insured
Iname.Add(insname.Insured_Name)
Next
Me.Insured_NameTextBox.AutoCompleteCustomSource = Iname
'combobox autocomplete code (now sorting by last included!)
Dim pnum As New List(Of String)
For Each polnumber As InsuredDataSet.Claims_InsuredRow In Me.InsuredDataSet.Claims_Insured
pnum.Add(polnumber.Policy_Number)
Next
pnum.Reverse()
Me.Policy_NumberComboBox.DataSource = pnum
End Sub
Try something like this:
Me.Policy_NumberComboBox.DataSource = InsuredDataSet.Claims_Insured.Where(Function(r) r.Insured_Name = Insured_NameTextBox.Text).Select(Function(r) r.Policy_Number).Reverse()
We're getting closer. Based on the update to your question, you're running this code when the form loads. However, at the point where the form loads, your textbox will always be empty. What do you do when the value in the textbox changes, to re-filter your data?
This is C#
Me.InsuredDataSet.Claims_Insured.Where(x => x.Insured_Name == Insured_NameTextBox.Text);