Gridview-Datasource: Datatable loses all its Rows - vb.net

I've come across a rather strange behavior.
Behind my form i have my constructor which looks like this:
Private Property SourceDatatable As DataTable
Public Sub New()
' This call is required by the designer.
InitializeComponent()
SourceDatatable = GetData()
'SourceDatatable now has 500 rows
AvailableObjects.DataSource = SourceDatatable
End Sub
Right now all 500 rows are visible inside the gridview.
When the user clicks a button, then the selected row is 'copied'
Private Sub CopyObject_Click(sender As System.Object, e As System.EventArgs) Handles AddNewObject.Click
Dim selectedRow As Integer = GridviewAvailableObjects.GetSelectedRows().FirstOrDefault()
If (selectedRow > 0) Then
Dim selectedDataRow As Integer = GridviewAvailableObjects.GetRowHandle(selectedRow)
SelectedRecords.Rows.Add(SourceDatatable.Rows(selectedDataRow))
End If
GridViewSelectedValues.RefreshData()
End Sub
The error occurs at SourceDatatable.Rows(selectedDataRow). All of a sudden it has 0 rows, Yet selectedDataRow refers to the correct row in the datasource(datatable). There is no interference of other Methods/Code as these 2 methods are the only ones present on the form and there is no other code on this form. Nor is the grid or any control accessible from outside the form.
What could cause this strange behavior? Does de Devexpress Gridview do anything with the datasource?

You should not directly add rows from one table to another due to DataTable restrictions - it will throw the System.ArgumentException ("This row already belongs to another table") exception. This issue is not specific to DevExpress GridView. And you can avoid it with easy via copying only "values" from the original row into another table.
This version of "copying" works to me, please review it:
void btnCopy_Click(object sender, EventArgs e) {
var selectedRowHandles = gridViewForAllData.GetSelectedRows();
for(int i = 0; i < selectedRowHandles.Length; i++) {
var selectedRow = gridViewForAllData.GetDataRow(selectedRowHandles[i]);
selectedRowsTable.Rows.Add(selectedRow.ItemArray);
}
gridViewForSelectedValues.RefreshData();
}
VB.Net:
Private Sub btnCopy_Click(ByVal sender As Object, ByVal e As EventArgs) Handles simpleButton1.Click
Dim selectedRowHandles = gridViewForAllData.GetSelectedRows()
For i As Integer = 0 To selectedRowHandles.Length - 1
Dim selectedRow = gridViewForAllData.GetDataRow(selectedRowHandles(i))
selectedRowsTable.Rows.Add(selectedRow.ItemArray)
Next i
gridViewForSelectedValues.RefreshData()
End Sub

Related

New row default values from datagridview dissappear when another control is selected

This is my first question here so please be merciful with me.
Purpose:
What I want to accomplish is to allow users to edit rows from a DataGridView (which is binded to a List of objects of custom class) in a Windows Forms application. Also, when a new row is generated in the DataGridView, I need to provide some default values, which I am implementing with the DefaultValuesNeeded event handler from the DataGridView.
Problem:
When editing a row, user must be able to navigate outside the DataGridView (for example, to a TextBox to provide extra info), but if the user leaves the new row before editing it, default values dissapear from the row. This is what I need to avoid. If user edits any cell of the new row and then clicks somewhere else in the form, all the values in the row remain there, which is correct and the desired behaviour.
I have created a little project to illustrate this.
Form:
Imports System.ComponentModel
Public Class Form1
Private Sub dgvAsientos_DefaultValuesNeeded(sender As Object, e As Windows.Forms.DataGridViewRowEventArgs) Handles DataGridView1.DefaultValuesNeeded
e.Row.Cells("ID").Value = Me.DataGridView1.Rows.Count
e.Row.Cells("Name").Value = "Test Name " & Me.DataGridView1.Rows.Count
e.Row.Cells("Description").Value = "Description " & Me.TextBox1.Text & " " & Me.DataGridView1.Rows.Count
Me.DataGridView1.BindingContext(Me.DataGridView1.DataSource, Me.DataGridView1.DataMember).EndCurrentEdit()
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim myList As New BindingList(Of ExampleClass)
For n = 0 To 5
Dim itemn As New ExampleClass
itemn.ID = n
itemn.Name = "Name_" & n
itemn.Description = "Description_" & n
itemn.OptionalField = "OptionalField_" & n
myList.Add(itemn)
Next
Dim bs As New BindingSource()
bs.DataSource = myList
Me.DataGridView1.DataSource = bs
End Sub
End Class
Example class:
Public Class ExampleClass
Public Property ID As Integer
Public Property Name As String
Public Property Description As String
Public Property OptionalField As String
End Class
Any help will be appreciated. I have found very little info regarding DefaultValuesNeeded + BindingSources + values lost when user focusing some other control; some of them made me add the line following line, but I didn't find that made any difference.
(...).EndCurrentEdit()
I also found suggestions to add a handler for the binding source AddingNew event which returned an instance of the object with the default values I need, again no difference.
Private Sub myBindingSource_AddingNew(sender As Object, e As AddingNewEventArgs)
e.NewObject = CreateNewExample()
End Sub
I hope the question and the format is correct. Thanks in advance,
MBD
When the user adds a new row in DataGridView by navigating to the last row and then leave the row before editing the cells; in this case, adding the row will be cancelled. It's because of some internal logic which cancels the new row when the row is not dirty.
To change this behavior, you can handle RowValidating or RowLeave events, then notify the current cell is dirty, and then end the current edit.
C# Example
private void dgv1_RowLeave(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex == dgv1.NewRowIndex)
{
dgv1.NotifyCurrentCellDirty(true);
dgv1.BindingContext[dgv1.DataSource, dgv1.DataMember].EndCurrentEdit();
dgv1.NotifyCurrentCellDirty(false);
}
}
VB.NET Example
Private Sub dgv1_RowLeave(sender As Object, e As DataGridViewCellEventArgs) _
Handles dgv1.RowLeave
If e.RowIndex = dgv1.NewRowIndex Then
dgv1.NotifyCurrentCellDirty(True)
dgv1.BindingContext(dgv1.DataSource, dgv1.DataMember).EndCurrentEdit()
dgv1.NotifyCurrentCellDirty(False)
End If
End Sub

How do I add a new row to a binding source

I am trying to programmatically add a new row to a binding source . I know calling the bsSource.AddNew() adds a new row which I cast as a DataRowView and I set my values. My problem is this - the DataRowView.Row shows a RowState of detached. I do not want it to be detached ; I believe it should show added - I also do NOT want it to commit the change to the database (There is a very valid reason for this). I want to pick the time for that later.
My code is as follows:
private Sub AddToRelationSource(binID As Integer, gradeID As Integer, IsChecked As Boolean)
Dim drv As DataRowView = DirectCast(bsBinGrades.AddNew(), DataRowView)
drv.Row("IsSelected") = IsChecked
drv.Row("BinID") = binID
drv.Row("GradeID") = gradeID
' I tried drv.EmdEdit(0 drv.Row.EndEdit() - Row State still shows detached
End Sub
The BindingSource AddNew method does not actually add a new record to the underlying datasource , it simply adds it to the bindingsource as a detached item. When using the datatabel as a datasource I needed to get the datatable and use the AddRow() method - this properly set the value in my bindingsource to added so that when the changes would be committed to the database on bindingsource.Update() method.
The code I used:
Dim drv As DataRowView = DirectCast(bsData.AddNew(), DataRowView)
drv.BeginEdit()
drv.Row.BeginEdit()
drv.Row("IsSelected") = IsChecked
drv.Row.EndEdit()
drv.DataView.Table.Rows.Add(drv.Row)
The last line is what actually added the item to the datasource - I misunderstood BindingSource.AddNew() .
The following may be in the right direction. First I used a few language extension methods e.g.
Public Module BindingSourceExtensions
<Runtime.CompilerServices.Extension()>
Public Function DataTable(ByVal sender As BindingSource) As DataTable
Return CType(sender.DataSource, DataTable)
End Function
<Runtime.CompilerServices.Extension()>
Public Sub AddCustomer(ByVal sender As BindingSource, ByVal FirstName As String, ByVal LastName As String)
sender.DataTable.Rows.Add(New Object() {Nothing, FirstName, LastName})
End Sub
<Runtime.CompilerServices.Extension()>
Public Function DetachedTable(ByVal sender As BindingSource) As DataTable
Return CType(sender.DataSource, DataTable).GetChanges(DataRowState.Detached)
End Function
<Runtime.CompilerServices.Extension()>
Public Function AddedTable(ByVal sender As BindingSource) As DataTable
Return CType(sender.DataSource, DataTable).GetChanges(DataRowState.Added)
End Function
End Module
Now load ID, FirstName and LastName into a DataTable, Datatable becomes the DataSource of a BindingSource which is the BindingSource for a BindingNavigator and are wired up to a DataGridView.
Keeping things simple I mocked up data, has no assertions e.g. make sure we have valid first and last name, instead concentrate on the methods.
First use a extension method to add a row to the underlying DataTable of the BindingSource.
bsCustomers.AddCustomer("Karen", "Payne")
Now check to see if there are detached or added rows
Dim detachedTable As DataTable = bsCustomers.DetachedTable
If detachedTable IsNot Nothing Then
Console.WriteLine("Has detached")
Else
Console.WriteLine("No detached")
End If
Dim AddedTable As DataTable = bsCustomers.AddedTable
If AddedTable IsNot Nothing Then
Console.WriteLine("Has added")
Else
Console.WriteLine("None added")
End If
Since we are not talking to the database table, the primary key is not updated as expected and since you don't want to update the database table this is fine. Of course there is a method to get the primary key for newly added records if you desire later in your project.
Addition
Private Sub BindingSource1_AddingNew(ByVal sender As System.Object, ByVal e As System.ComponentModel.AddingNewEventArgs) Handles BindingSource1.AddingNew
Dim drv As DataRowView = DirectCast(BindingSource1.List, DataView).AddNew()
drv.Row.Item(0) = "some value"
e.NewObject = drv
' move to new record
'BindingSource1.MoveLast()
End Sub
'This routine takes the AddForm with the various fields that the user
'fills in and calls the TableAdapter's Insert method.
'After that is done, then the table has be be reflected back to the
'various components.
Private Sub AddRecord()
'The following line did not work because I could not get
'the bs definition down.
'Tried the BindingSource but in gave an error on
'DataRowView so I came up with an alternate way of
'adding the row.
'Dim drv As DataRowView = DirectCast(bsData.AddNew(), DataRowView)
'Dim drv As DataRowView = DirectCast(RecTableBindingSource.AddNew(), DataRowView)
'drv.BeginEdit()
'drv.Row.BeginEdit()
'drv.Row("Title") = "Order, The"
'drv.Row.EndEdit()
'drv.DataView.Table.Rows.Add(drv.Row)
RecTableTableAdapter.Insert(pAddForm.tTitle.Text,
pAddForm.tCast.Text,
pAddForm.tAKA.Text,
pAddForm.tRelated.Text,
pAddForm.tGenre.Text,
pAddForm.tRated.Text,
pAddForm.tRelease.Text,
pAddForm.tLength.Text)
Validate()
RecTableBindingSource.EndEdit()
RecTableTableAdapter.Update(VideoDBDataSet.RecTable)
RecTableAdapterManager.UpdateAll(VideoDBDataSet)
RecTableTableAdapter.Fill(VideoDBDataSet.RecTable)
VideoDBDataSet.AcceptChanges()
End Sub
'Here is my Delete Record routine
Private Sub DeleteRecordToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles DeleteRecordToolStripMenuItem.Click
Dim RowIndex As Int32
If (dgvRec.SelectedRows.Count > 0) Then
RowIndex = dgvRec.SelectedRows(0).Index
'Now we have to delete the record
dgvRec.Rows.RemoveAt(RowIndex)
dgvRec.CommitEdit(RowIndex)
dgvRec.EndEdit()
Validate()
RecTableBindingSource.EndEdit()
RecTableTableAdapter.Update(VideoDBDataSet.RecTable)
RecTableAdapterManager.UpdateAll(VideoDBDataSet)
RecTableTableAdapter.Fill(VideoDBDataSet.RecTable)
VideoDBDataSet.AcceptChanges()
Else
'No row selected to work with
End If
End Sub
'The pAddForm MUST be open for this routine to work
Private Sub UpdateGridFromForm()
Dim RowIndex As Int32
Dim Index As Int32
Dim RecIndex As Int32
Dim dt As DataTable
If ((pAddForm Is Nothing) = False) Then
RowIndex = pAddForm.GridIndex
If (RowIndex >= 0) Then
Index = pAddForm.Index
If (Index = dgvRec.Rows(RowIndex).Cells(constRecGridColIndex).Value) Then
'OK, we have a match so we are good to go
Call PopulateGridFields(RowIndex)
Else
MsgBox("Unable to save data back to the Grid because the record is no longer the same")
End If
Else
'This must be a NEW record
Call AddRecord()
End If
Else
'No form to work with
End If
End Sub
'Populate the dgvRec fields from pAddForm
Private Sub PopulateGridFields(RowIndex As Int32)
dgvRec.Rows(RowIndex).Cells(constRecGridTitle).Value = pAddForm.tTitle.Text
dgvRec.Rows(RowIndex).Cells(constRecGridCast).Value = pAddForm.tCast.Text
dgvRec.Rows(RowIndex).Cells(constRecGridAKA).Value = pAddForm.tAKA.Text
dgvRec.Rows(RowIndex).Cells(constRecGridRelated).Value = pAddForm.tRelated.Text
dgvRec.Rows(RowIndex).Cells(constRecGridGenre).Value = pAddForm.tGenre.Text
dgvRec.Rows(RowIndex).Cells(constRecGridRated).Value = pAddForm.tRated.Text
dgvRec.Rows(RowIndex).Cells(constRecGridRelease).Value = pAddForm.tRelease.Text
dgvRec.Rows(RowIndex).Cells(constRecGridLength).Value = pAddForm.tLength.Text
dgvRec.CommitEdit(RowIndex)
dgvRec.EndEdit()
Validate()
RecTableBindingSource.EndEdit()
RecTableTableAdapter.Update(VideoDBDataSet.RecTable)
RecTableAdapterManager.UpdateAll(VideoDBDataSet)
RecTableTableAdapter.Fill(VideoDBDataSet.RecTable)
VideoDBDataSet.AcceptChanges()
End Sub
'This all works great.
'The only problem I have now is that the DataGridView will
'always'Repopulate the grid (including any changes with 'Add/Delete/Modify) sending the active
'row back to the top of the grid
'I will work on a solution to this now that I have the rest working

get ID from datagridview and show the data to another form in textboxes

Im kind of new in vb.net. I have a datagridview that shows the Delivery Number, Date and supplier. Now, I want the Admin to view the details of every delivery to another form. I just want to know how will I get the id of the selected row and then will be able to display the equivalent data of that selected ID. Thanks.
Here's my code for the Deliveries Form.
Private Sub dgvDeliveryReport_CellContentDoubleClick(sender As Object, e As DataGridViewCellEventArgs) Handles dgvDeliveryReport.CellContentDoubleClick
If e.RowIndex < 0 Then Exit Sub
Dim id As Int32 = dgvDeliveryReport.CurrentRow.Cells(0).Value
Dim viewDelivery As New frmDeliveryFormReport
frmDeliveryFormReport.Show()
End Sub
In your frmDeliveryFormReport class add a new field to store current row:
private _currentDeliveryReportRow as DataGridViewRow
Look for the constructor:
Public Sub New frmDeliveryFormReport()
...
End Sub
(If you cannot find it just proceed with the next step).
Change/Add the constructor so it takes the DataGridViewRow parameter and store the given row:
Public Sub New frmDeliveryFormReport(deliveryReportRow as DataGridViewRow)
_currentDeliveryReportRow = deliveryReportRow
End Sub
Adapt your existing dgvDeliveryReport_CellContentDoubleClick to call the new constructor:
Private Sub dgvDeliveryReport_CellContentDoubleClick(sender As Object, e As DataGridViewCellEventArgs) Handles dgvDeliveryReport.CellContentDoubleClick
If e.RowIndex < 0 Then Exit Sub
Dim viewDelivery As New frmDeliveryFormReport(dgvDeliveryReport.CurrentRow)
frmDeliveryFormReport.Show()
End Sub
You can then access all columns of the DeliveryReportRow in the frmDeliveryFormReport via
_currentDeliveryReportRow.Cells(<CellIndex>)
Additional information about this topic:
Passing variables between windows forms in VS 2010
VB.Net Passing values to another form
http://www.dreamincode.net/forums/topic/332553-passing-data-between-forms/
Try.
Dim newFrmName as new yourForm
For each row as DataGridViewRow in SampleGrid
if row.selected = true then
Dim whatValueYouWant as string = row.cells("ID").value.toString()
if newFrmName.NameOfTextBoxInForm.Text <> vbEmpty Then
'NameOfTextBoxInForm is textbox that existing in yourform
newFrmName.NameOfTextBoxInForm.text = ", " & whatValueYouWant
Else
newFrmName.NameOfTextBoxInForm.text = whatValueYouWant
End If
End IF
Next
newFrmName.Show()

Maintain listview row selected

I already check this question - prevent listview to lose selected item .
but I need this code for VB.Net. I already used c# to vb converter and the output is this.
Private Sub ListView_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
Dim listView As ListView = TryCast(sender, ListView)
If listView.SelectedItems.Count = 0 Then
For Each item As Object In e.RemovedItems
listView.SelectedItems.Add(item)
Next
End If
End Sub
And the error is : 'SelectionChangedEventArgs' is not defined.
I replaced the SelectionChangedEventArgs to ByVal e As System.EventArgs
and now the error is:
'RemovedItems' is not a member of 'System.EventArgs'.
Property: HideSelection = False also not working for me.
How will I able to maintain the row selected?

Double-click DataGridView row?

I am using vb.net and DataGridView on a winform.
When a user double-clicks on a row I want to do something with this row. But how can I know whether user clicked on a row or just anywhere in the grid? If I use DataGridView.CurrentRow then if a row is selected and user clicked anywhere on the grid the current row will show the selected and not where the user clicked (which in this case would be not on a row and I would want to ignore it).
Try the CellMouseDoubleClick event...
Private Sub DataGridView1_CellMouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs) Handles DataGridView1.CellMouseDoubleClick
If e.RowIndex >= 0 AndAlso e.ColumnIndex >= 0 Then
Dim selectedRow = DataGridView1.Rows(e.RowIndex)
End If
End Sub
This will only fire if the user is actually over a cell in the grid. The If check filters out double clicks on the row selectors and headers.
Use Datagridview DoubleClick Evenet and then Datagrdiview1.selectedrows[0].cell["CellName"] to get value and process.
Below example shows clients record upon double click on selected row.
private void dgvClientsUsage_DoubleClick(object sender, EventArgs e)
{
if (dgvClientsUsage.SelectedRows.Count < 1)
{
MessageBox.Show("Please select a client");
return;
}
else
{
string clientName = dgvClientsUsage.SelectedRows[0].Cells["ClientName"].Value.ToString();
// show selected client Details
ClientDetails clients = new ClientDetails(clientName);
clients.ShowDialog();
}
}
Use DataGridView.HitTest in the double-click handler to find out where the click happened.
I would use the DoubleClick event of the DataGridView. This will at least only fire when the user double clicks in the data grid - you can use the MousePosition to determine what row (if any) the user double clicked on.
You could try something like this.
Private Sub DataGridView1_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles DataGridView1.DoubleClick
For index As Integer = 0 To DataGridView1.Rows.Count
If DataGridView1.Rows(index).Selected = True Then
'it is selected
Else
'is is not selected
End If
Next
End Sub
Keep in mind i could not test this because i diddent have any data to populate my DataGridView.
You can try this:
Private Sub grdview_CellDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles grdview.CellDoubleClick
For index As Integer = 0 To grdview.Rows.Count - 1
If e.RowIndex = index AndAlso e.ColumnIndex = 1 AndAlso grdview.Rows(index).Cells(1).Value = "" Then
MsgBox("Double Click Message")
End If
Next
End Sub