Faux Databinding a Recordset to a Datagridview - vb.net

I am updating a very old vb6 program that includes recordsets bound to an old third party grid control. The recordset functionality is so ingrained into the program that it is not an option to replace them. So, I replaced the unfunctional grid with a datagridview and I fill it using a dataadapter and a dataset. The problem is that the recordset was originally bound to the grid and using a dgv breaks the binding.
So this is what I am trying to do. I have a function that passes in the old recordset and the new dgv, and fills it. I would like to create a dynamic handler to the dgv's selectionchanged event to update the rs with the current position on the dgv (rs.aboluteposition = dgv.row), thus updating the rs cursor to the current position in the dgv, making a sort of faux data binding.
Something like this....
AddHandler dgv.SelectionChanged, AddressOf RefreshRecordset
Public Sub RefreshRecordset()
myRS.AbsolutePosition = dgv.Row
End Sub
A couple things though. I have to track if the event handler was already created, and the associated recordset that goes with this specific datagridview. Also, since this is a global function to update many dgvs with many rs, it needs to have a way to track the recordset. I was thinking of somehow using the tag of dgv? Maybe create a dictionary of all the recordsets then look it up by the name of the dgv?

Below is my original method that worked pretty well. I eventually refined it and put it in a class for each recordset. I also updated it to accept true dbgrids. By wrapping it in a class, I was able to remove the dictionaries. In the New function, I pass in either grid and a recordset. From there, I set up event handlers to handle both the grid row change and also the recordset MoveComplete event. I had to use the MoveComplete because the RecordsetChangeComplete has a bug and doesn't always fire correctly. I just check in the MoveComplete if the record count had changed. If so, refresh the grid. Lastly, I added a removehandler for each of the events in the finalize function.
////// The Original Answer //////
For instance, like this... (I CAN'T believe this worked...)
Public dict as new Dictionary(Of String, ADODB.Recordset)
Public Sub FillGrid(ByRef dgv as DataGridView, ByRef rs as ADODB.RecordSet)
'.... Fill the grid with the tableadapter, blah blah.
If Not dict.ContainsValue(rs) Then
dict.add(dgv.Name, rs)
AddHandler dgv.SelectionChanged, AddressOf RefreshRecordset)
' To make the event fire correctly, I think
dgv.SelectionMode = DataGridViewSelectionMode.FullRowSelect
End if
End Sub
Public Sub RefreshRecordset(sender As Object, e As System.EventArgs)
Dim dgv as DataGridView = Ctype(sender, DataGridView)
If dict.ContainsKey(dgv.Name) then
Dim rs as ADODB.Recordset = dict(dgv.Name)
rs.AbsolutePosition = dgv.CurrentCell.RowIndex
End if
End Sub
Now I need to create a handler to the recordset to refresh the dgv everytime the recordset is updated.
And btw, I know there are probably other ways to do this and I would absolutely would love to hear them! Please feel free!
Thanks!

Related

Split Form creates a separate collections for each of its parts

I have a split Form in MS Access where users can select records in the datasheet and add it to a listbox in the Form thereby creating custom selection of records.
The procedure is based on a collection. A selected record gets transformed into a custom Object which gets added to the collection which in turn is used to populate the listbox. I have buttons to Add a Record, Remove a Record or Clear All which work fine.
However I thought, that pressing all these buttons is a bit tedious if you create a Selection of more then a dozen records, so i reckoned it should be simple to bind the actions of the Add and Remove buttons to the doubleclick event of a datasheet field and the listbox respectively.
Unfortunately i was mistaken as this broke the form. While a doubleclick on a field in the datasheet part of the form added the record to the listbox it was now unable to remove an item from the underlying collection giving me Run-time error 5: "Invalid procedure call or argument". Furthermore the clear all Button doesn't properly reset the collection anymore. When trying to Clear and adding a record of the previous selection the code returns my custom error that the record is already part of the selection and the listbox gets populated with the whole previous selection, which should be deleted. This leaves me to believe, that for some Reason the collection gets duplicated or something along these lines. Any hints into the underlying problem is apprecciated. Code as Follows:
Option Compare Database
Dim PersColl As New Collection
Private Sub AddPerson_Click()
AddPersToColl Me!ID
FillListbox
End Sub
Private Sub btnClear_Click()
Set PersColl = Nothing
lBoxSelection.RowSource = vbaNullString
End Sub
Private Sub btnRemovePers_Click()
PersColl.Remove CStr(lBoxSelection.Value)
FillListbox
End Sub
Private Sub FillListbox()
Dim Pers As Person
lBoxSelection.RowSource = vbaNullString
For Each Pers In PersColl
lBoxSelection.AddItem Pers.ID & ";" & Pers.FullName
Next Pers
lBoxSelection.Requery
End Sub
Private Function HasKey(coll As Collection, strKey As String) As Boolean
Dim var As Variant
On Error Resume Next
var = IsObject(coll(strKey))
HasKey = Not IsEmpty(var)
Err.Clear
End Function
Private Sub AddPersToColl(PersonId As Long)
Dim Pers As Person
Set Pers = New Person
Pers.ID = PersonId
If HasKey(PersColl, CStr(PersonId)) = False Then
PersColl.Add Item:=Pers, Key:=CStr(PersonId)
Else: MsgBox "Person Bereits ausgewählt"
End If
End Sub
This works alone, but Simply Adding this breaks it as described above.
Private Sub Nachname_DblClick(Cancel As Integer)
AddPersToColl Me!ID
FillListbox
End Sub
Further testing showed that its not working if i simply remove the Private Sub AddPerson_Click()
Edit1:
Clarification: I suspected that having 2 different events calling the same subs would somehow duplicate the collection in memory, therefore removing one event should work. This is however not the case. Having the subs called by a button_Click event works fine but having the same subs called by a double_click event prompts the behaviour described above. The issue seems therefore not in having the subs bound to more than one event, but rather by having them bound to the Double_Click event.
Edit2: I located the issue but I Haven't found a solution yet. Looks like Split-Forms are not really connected when it comes to the underlying vba code. DoubleClicking on the record in the datasheet view creates a Collection while using the buttons on the form part creates another one. When trying to remove a collection item by clicking a button on the form, it prompts an error because this collection is empty. However clicking the clear all button on the form part doesn't clear the collection associated with the datasheet part.
Putting the collection outside into a separate module might be a workaround but i would appreciate any suggestions which would let me keep the Code in the form module.
The behavior is caused by the split form which creates two separate collections for each one of its parts. Depending from where the event which manipulates the collection gets fired one or the other is affected. I suspect that the split form is in essence not a single form, but rather 2 instances of the same form-class.
A Solution is to Declare a collection in a separate module "Coll":
Option Compare Database
Dim mColl as new Collection
Public Function GetColl() as Collection
Set GetColl= mColl
End Function
And then remove the Declaration of the Collection in the SplitFormclass and Declare the Collection in every Function or Sub by referencing the collection in the separate Module like in the following example:
Option Compare Database
Private Sub AddPersToColl(PersonId As Long)
Dim Pers As Person
Dim PersColl as Collection
Set PersColl = Coll.GetColl
Set Pers = New Person
Pers.ID = PersonId
If HasKey(PersColl, CStr(PersonId)) = False Then
PersColl.Add Item:=Pers, Key:=CStr(PersonId)
Else: MsgBox "Person Bereits ausgewählt"
End If
End Sub
This forces the form to use the same Collection regardless if the event is fired from the form or datasheet part of the split-form. Any Further information is appreciated but the matter is solved for now.
Thanks everyone for their time

Open Form faster & remove "System.Data.DataRowView" flicker from Combobox data binding

I have plenty Comboboxes on my form (around 20), and all of them are displaying items from different tables of my DB. If I put all code on Form_Load event then Form opens very slow. So I tried to paste code in different varieties, and currently I'm stuck at Combobox_Enter event - now Form loads fast, but when I click on drop-down of combobox I see sometimes "System.Data.DataRowView" flickering before items are loaded in Combobox. Is there any way to achieve both - fast Form opening & Combobox loading Items without flickering ?....So far I tested with Form_Activate,Form_GotFocus(not working) and Combobox_GotFocus,Combobox_MouseHover,Combobox_Click(not exactly perfect). This is an example of how I bind my Comboboxes:
Private Sub Combobox1_Enter(sender As Object, e As EventArgs) Handles Combobox1.Enter
Dim SQL As String = "SELECT Name from MyTable"
Dim dtb As New DataTable()
Using con As OracleConnection = New OracleConnection("Data Source=MyDB;User Id=Lucky;Password=MyPassword;")
Try
con.Open()
Using dad As New OracleDataAdapter(SQL, con)
dad.Fill(dtb)
End Using
Combobox1.DataSource = dtb
Combobox1.DisplayMember = "Name"
con.Close()
Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
con.Dispose()
End Try
Combobox1.SelectedIndex = -1
End Using
End Sub
I also tried with declaring " Public con As OracleConnection", but output is same as I have It now.
Any help appreaciated !
When binding a ComboBox or the like, you should pretty much ALWAYS set the DataSource last. You are not and that's why you see "System.Data.DataRowView" displayed.
When you bind a list to a ComboBox, the control will display data from the column or property specified in the DisplayMember if there is one, otherwise it will call ToString on each item. In your code, you first set the DataSource and, at that point, the DisplayMember is not set so the control calls ToString on each item. The result of that is "System.Data.DataRowView". When you then set the DisplayMember, those values that the control just went to the trouble of generating and displaying are discarded and the DisplayMember is used to get new values.
Even if you weren't seeing that effect, you'd still be wasting your control's time generating values that you don't want. ALWAYS set the DisplayMember, ValueMember or the like before setting the DataSource in code unless you have a specific reason not to. The only reason that I'm aware of is when you're binding a CheckedListBox, which has an issue when DataSource is set last.
By the way, shouldn't you have a test there to only retrieve data if there is no data already loaded? You don't want to reload data if the user returns to the same control, do you?

Declaring WebBrowser in VB.NET Other Form

I am working on a project that has WebBrowsers in Other Forms;
I wrote the code below to control these WebBrowsers; but I need the code to recognize (Declare) the WebBrowsers of these forms.
Dim openForm As Form = Nothing
For Index As Integer = My.Application.OpenForms.Count - 1 To 0 Step -1
openForm = My.Application.OpenForms.Item(Index)
If openForm IsNot Me Then
MyWebBrowser.navigate("http://www.google.com/") ' PROBLEM IN THIS LINE
End If
Next
My Module created them as below:
Module MMMBrowser
Dim frmNew As New Form
Dim MekdamBrowser As New WebBrowser
Other info gleaned from comments:
there is form factory of some sort which creates new frmNew
there are many of these open at a time, which is the reason for the backwards loop thru OpenForms to find the last one.
The MekdamBrowser reference is an attempt to refer to the browser on the form.
The easy things is to provide a way for outsiders to tell the form to navigate somewhere using a new Sub, and let the form drive the browser control. This probably eliminates the need for a global MekdamBrowser reference. In the browser form add something like this:
Public Sub GotoNewURL(url As String)
myWebBrowserName.navigate(url)
End Sub
This procedure only exists for Form1 not the generic Form type, so we need to change how you find the form to use. Your existing loop is wonky. It will only ever find the last instance of a form which is not the current form. If you add a third form type, it wont work well:
Dim lastBrowserFrm As Form1 ' use the class name!
' this will try to get the last Instance of Form1
lastBrowserFrm = Application.OpenForms.OfType(Of Form1)().LastOrDefault
' LastOrDefaultcan return nothing if there are none,
' so test
If lastBrowserFrm IsNot Nothing Then
lastBrowserFrm .GotoNewUrl("www.stackoverflow.com")
Else
' create a new one, I guess
End If
Your loop was not considering that there could be other form types in the collection which are not Form1 or even if a new browser form was the last one created! This is more important now because GotoNewURL is only available on Form1 instances.
I changed the name to lastBrowserFrm to reflect what is really going one - it will just find the last one created. If you are trying to work with a specific instance, you need to provide a way to track the ones you create such as with a List(of Form1) or use the Name property so you can tell one from the other. As is, you do not a way to get back a specific form instance.

refreshing datagridview after populate existing datatable in vb.net

I am working on a windows form application using VB.net. I have populated a Datagridview1( dataset1.existingtable is the datatable). Now i wish to get distinct values from one column of its datatable and then populate another Datagridview2(dataset2.uniquerecords is the datatable).
PROBLEM: Not able to refresh data in Datagridview2 using design mode. However i am able to refresh data when dynamically creating a datatable at runtime.
The below sub is called after an event after my form has loaded completely.
The below code does NOT work
Private Sub loaddistinctrecords()
uniquerecords = existingtable.DefaultView.ToTable(True, "column_name")
Datagridview2.Refresh()
End Sub
The below code works
Private Sub loaddistinctrecords()
Dim newuniquerecords As New DataTable()
newuniquerecords = existingtable.DefaultView.ToTable(True, "column_name")
Datagridview2.DataSource = newuniquerecords.DefaultView
End Sub
well it looks like one cannot directly assign a datatable to another datatable and expect the datagridview to update automatically if the datatable was created from design mode.
What one can do is simply clear the existing records and then merge records from the source datatable.
Private Sub loaddistinctrecords()
uniquerecords.Clear()
uniquerecords.Merge(existingtable.DefaultView.ToTable(True, "table_name"))
End Sub

New added items on the datagridview cannot be seen at runtime

I have a program that inserts data in access database and the user can view the newly added items in a data grid view. After i add new items to the database it cannot be seen in the datagridview while the program is running. I have to stop the program and run it again just to see the changes i made.
Here is how i load the datagridview:
Public Class frmSupplies
Private Sub frmSupplies_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'TODO: This line of code loads data into the 'SuppliesDataSet.product_info' table. You can move, or remove it, as needed.
Me.Product_infoTableAdapter.Fill(Me.SuppliesDataSet.product_info)
End Sub
How can i view the newly added items while the program is still running?
I created a module that will call the "refresh" function for the datagridview. I added a new module to my project and added this codes:
Imports System.Data.OleDb
Module Module1
Dim con As New OleDbConnection("Provider=Microsoft.Jet.Oledb.4.0;Data Source= database path")
Sub REFRESHDGV()
Dim sql As String
sql = "SELECT * FROM [product info]"
Dim adapter As New OleDbDataAdapter(sql, con)
Dim dt As New DataTable("product info")
adapter.Fill(dt)
Form1.dgv1.DataSource = dt
End Sub
End Module
Hope this helps others!
Try wrapping around beginedit ... end edit
gridview.BeginEdit();
----
gridview.EndEdit();
I am not familiar with access (I use oracle). Oracle database can notify you if something has changes in your database, so you can refresh the datagridview. In your case you can have a button (for refresh the datagridView) or a timer and refresh it every x time (you know the time):
'I imagine that you have a bindingSource
bindingSource1.DataSource = Product_infoTableAdapter.GetData()
bindingSource1.ResetBindings(false)
Maybe you want to take a look in DataGridView DataBinding
When you bind an Access table to a grid on a Windows form and then change the data outside that application, the form or the grid simply do not have a way of knowing that data was changed, unless 1) they receive some kind of notification from Access, or 2) the application keep querying the data source to see if changes were made.
Option 1 there is not applicable with MS Access.
Your only option is to put a timer on the form to regularly check for changes (on a fixed interval) and reload the table and refresh the grid if changes were found.