Datagridview linked to datatable not getting updated - vb.net

This is a part of simultaneous url download program that i'm trying to make. It has the url list saved in a datatable named tbl and it is bound to a datagridview named dgvUrls. Evrytime it encounters a dead url, it removes it from the datatable.
I've reproduced the error using the code below. The Button3_Click adds 100 rows to the datatable, makes it as the datasource for datagridview. The q() removes the rows one at a time by removing the 1st row. The prob is that the datagridview don't reflect the changes made in the datatable
Dim tbl = New DataTable
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
'Add 100 urls, for simplicity i'm adding only integers
tbl.Columns.Add("Urls")
For i = 1 To 100
tbl.Rows.Add(i)
Next
'bind to datagridview so that the end user can see the urls being download/removed from the list
dgvUrls.DataSource = tbl
'start multithread download , for simplicited (of this question) we have only one
Dim t As Thread = New Thread(AddressOf Download)
t.Start()
t.Join()
dgvUrls.Refresh()
End Sub
Private Sub download()
'for simplicity, the 1st 80 urls were dead!
For i = 1 To 80
'we remove the dead urls
tbl.Rows.RemoveAt(0)
Next

In general, it is a good thing to Refresh the DataGridView, mainly if you are performing the modifications from another thread; something like this:
Dim t As Thread = New Thread(AddressOf q)
t.Start()
t.Join() 'Waits for the other thread to complete, such that the next line is reached on the right moment
dgvUrls.Refresh()
I deleted Dim ts As ThreadStart = New ThreadStart(AddressOf q) because is not necessary. Also you don't need the Sleep and DoEvents:
Private Sub q()
For i = 1 To 98
tbl.Rows.RemoveAt(0)
Next
End Sub
As a proof of concept (to understand how all this works) is OK; but you should review various ideas in your logic before going further: removing so many rows from the DataSource can provoke problems (you would see that it triggers errors); ideally, (at least, I prefer it) you should modify the DataGridView directly (if possible) to avoid info-synchronisation problems; if you deal with multiple threads you would have to set up a "more proper structure" (the proposed t.Join() should be seen as a temporary fix to make this work).

Related

Unable to add rows to datatable, datatable is nothing error

I am not really sure what went wrong, i declared dt on top as a class variable then declare it as new datatable in fill function used in pageload but when i pressed buttonadd, dt is nothing error pops up.
Private dt As DataTable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
fill()
End If
End Sub
Protected Sub fill()
dt = New DataTable
dt.Columns.Add("Status", GetType(String))
End Sub
Protected Sub btnadd_Click(sender As Object, e As EventArgs) Handles btnadd.Click
Dim R As DataRow = dt.NewRow
R("Status") = "Pending"
dt.Rows.Add(R)
'dt.Rows.Add("pending")
GridView1.DataSource = dt
GridView1.DataBind()
End Sub
The key concept is that web pages are "state-less".
That means for each event code stub, then the code is starting over from scratch.
And it means for each browser round trip, then the browser code starts from scratch EACH time. So, you only load up the data table one time (first time - this is GOOD!!!).
The problem of course, without a control on the web page. (say a data grid), or say a simple text box? Those controls survive that so called round trip. The MOST important concept, and if were going to learn ONE thing about asp.net web pages?
You must grasp the round trip concept, and the so called page state.
So, what happens in your case?
First time - page loads - code runs server side (often called code behind).
Your form instance class is created, your load event runs, you load up the table. The browser is THEN send down to the client side. The web page is just sitting here. Maybe you close the browser. Maybe you un-plug your computer. The server does not know, or care about this. In fact, the server does NOT even know the web page exists anymore!!!
So, your web page is sitting here, and you click on that button.
The web page is now sent up to the server, and the code behind starts running - but it STARTS FROM FRESH scratch each time!
So your form level variable called "data table" does NOT exist any more and does NOT have a value.
So, there are several solutions here. For starters, we need that "table" to persist and survive the rounds trip. As noted, most controls you drop into that page WILL keep their values. This is called the "viewstate". So, if we want that data table to survive and "exist" the next time the user does something? Then we need to save or persist that table variable.
Another way? You can use what is called the session(). Session() is a attempt and system to allow use to shove values into Session(). And the session() survives round trips.
Now, given your example?
Well, then our code would become this:
Private dt As DataTable
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
fill()
Session("MyCoolTable") = dt
else
' this is a page post back - re-load the active table into our
' forms level dt.
dt = Session("MyCoolTable")
end if
In a lot of cases, I would assume the table that drives the grid is a database. And thus I would add the row to the database, and then re-bind the data grid.
But the above use of session() will persist the dt table for you.

Multithreading in vb.net to simulate task

I have a program that is doing one task.
For Example i have one list box containing some links.
And on the other hand my program is opening them one by one but i want it to be done faster
i have used for-each loop for that purpose.
All what i want to do is i wanna give every 2 or 3 link to a different thread or if there is any other solution to make it Faster Kindly tell me.
This is a small piece of code from my program.
For value As Integer = 1 To TextBox1.Text
If (value = TextBox1.Text) Then
Exit For
End If
Dim page As String = "-p-" & value
Extractor.ScrapLinks(txturl.Text + page, lstbox)
lbllinks.Text = lstbox.Items.Count
Next
I don't know what Extractor.ScrapLinks actually do, but it seems that you need to access UI thread and you cannot create multiple UI threads so eventually you will process them sequentially
What you can do to improve the solution is to read the data you want from the UI controls and then process that data on a separate thread, after completion you can fill the results into the UI by invoking some method on the UI thread as shown below
Delegate Sub PerformOperationDel(value As Integer)
Sub PerformOperation(value As Integer)
Dim page As String = "-p-" & value
Extractor.ScrapLinks(txturl.Text + page, lstbox)
lbllinks.Text = lstbox.Items.Count
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
For value As Integer = 1 To CInt(TextBox1.Text) - 1
lstbox.BeginInvoke(New PerformOperationDel(AddressOf PerformOperation))
Next
End Sub
You can use backgroundworkers but notice that you cannot access UI controls in the DoWork but you can access them on work completed (Refer to: Background worker proper way to access UI)
Best of luck

my program is not adding items to listbox and is freezing up

im trying to add a very large amount of items to list box and what i need it for is i'm using to add selected items to itextsharp table report im using filters in the table just to display either the sales person who handled the customer or the date at which the incident occurred (or Issue was reported with the product) my filter system is as follows i have 4 categories which is 4 listboxes customer name, customer code(named listBox1 i have not got around to changing it yet) species name, and the error type my filter is placed under the searchBtn_Click event my filter and item adding code is as follows:
Private Sub searchBtn_Click(sender As Object, e As EventArgs) Handles searchBtn.Click
For Each obj As contactObject In custList
For Each item As speciesObject In speciesList
'loadLists()
If Trim(fromDate.Value) < Trim(obj.eventDate) Then
If Trim(toDate.Value) > Trim(obj.eventDate) Then
If Trim(fromDate.Value) < Trim(item.compDate) Then
If Trim(toDate.Value) > Trim(item.compDate) Then
End If
If Not customerListBox.Items.Contains(obj.customerName) Then
customerListBox.Items.Add(obj.customerName)
End If
If Not ListBox1.Items.Contains(obj.customer) Then
ListBox1.Items.Add(obj.customer)
End If
If Not speciesListBox.Items.Contains(item.name) Then
If ListBox1.Items.Contains(item.customerCode) Then
speciesListBox.Items.Add(Trim(item.name).ToUpper)
End If
End If
If Not errorListBox.Items.Contains(obj.issue + " - " + obj.issueDescription) Then
errorListBox.Items.Add(Trim(obj.issue + " - " + obj.issueDescription).ToUpper)
End If
End If
End If
End If
Next
Next
countErrors()
End Sub
then i have the query which is set up to get the customer info from the database system
Dim SqlText As String = "SELECT DISTINCT QEE.[EventID] ,QEE.[EventDate] ,QEE.[Employee] ,QEE.[Communication] ,QEE.[OtherCommunication] ,QEE.[Issue] ,QEE.[IssueDescription] ,QEE.[IssueComments] ,QEE.[Resolution] ,QEE.[ResolutionComments] ,QEE.[SalesOrderNumber] ,QEE.[CustomerPO] ,QEE.[SOStatus] ,QEE.[Customer] ,QEE.[CustomerName] ,QEE.[SalesPersonName] ,QEE.[IsResolved] ,QEE.[IssueValue] ,QEE.[DateAndTimeAdded] ,DATEDIFF(day, SOR.ReqShipDate, QEE.[EventDate]) AS Elapsed, SOR.ReqShipDate FROM [QualityTracking].[dbo].[tblQualityEventEntry] QEE INNER JOIN SysproCompanyC.dbo.SorMaster SOR ON QEE.SalesOrderNumber = SOR.SalesOrder COLLATE Latin1_General_CI_AS ORDER BY EventDate ASC, CustomerName ASC, SalesOrderNumber ASC;"
I could not fit all code on here
if you could also just general things to help as well i am new to vb.net but for other information things i have tried :
*listbox.startUpdate/endUpdate
*changing querys
*changing the sorted property (Right now its set for false)
*the issue happens when i choose select all and then hit search the database is holding almost 2Mil items and i need to be able to get it to move once i get it work stop freezing i will be able to increase the speed i just cant figure out totally where the issue is i know the query could be better probably (if you have any suggestions please feel free i'm learning)
*but i also see alot of people having this issue with listbox as being kinda a broken down way of listing items
*i have tried researching it and people have said use something else i cant do that for listbox is committed
Assuming Windows Forms.
You program might not be responding because of too many records to add, and each time you add an item into the ListBox.Items collection, the UI is refreshed.
You may either SuspendLayout while adding the lines into your Listbox, and ResumeLayout afterwards.
Private Sub searchBtn_Click(ByVal sender As Object, ByVal e As EventArgs)
customerListBox.SuspendLayout();
// Place your code to populate the ListBox control here...
customerListBox.ResumeLayout();
End sub
This shall avoid a lot of refreshes from occuring while adding the items one by one and allow the application to lighten the add of items, then you resume the layout so that it refreshes the controls to display adequate information to the screen.
OR
You may use the ListBox.Items.AddRange() method along with List.ToArray().
Private Sub searchBtn_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim custList As List(Of ConstactObject) = loadYourCustomers();
customerListBox.Items.AddRange(custList.ToArray());
Dim speciesList As List(Of SpeciesObject) = loadYourSpecies();
speciesListBox.Items.AddRange(speciesList.ToArray());
End sub
OR ELSE
I recommend using a DataGridView and setting its DataSource property to your list of objects.
So, in order to have the correct data, you'll have to:
Drop two DataGridView on your Form
Rename both DataGridView to a meaningful name (e.g. custListDataGridView, speciesListDataGridview)
Drop two BindingSource on your Form
Rename both BindingSource to a meaningful name (e.g. custListBindingSource, speciesListBindingSource)
In the designer, set the DataGridView.DataSource property to your respective BindingSource (e.g. custListDataGridview.DataSource = custListBindingsource, speciesListDataGridView.DataSource = speciesListBindingSource)
In the backing code, that is, in your searchBtn.Click event, you may set both your binding sources DataSource property
Private Sub searchBtn_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim custList As IList(Of ContactObject) = loadYourContactObjects();
custListBindingSource.DataSource = custList;
Dim speciesList As IList(Of SpeciesObject) = loadYourSpeciesObject();
speciesListBindingSource.DataSource = speciesList;
End Sub
And your information data should be listed automatically without you having to manually add each record.

Simultaneous OleDbDataAdapter.Fill Calls on Separate Threads?

First timer here, so go easy on me. Is it theoretically possible to execute two OleDBDataAdapter.Fill calls on separate threads simultaneously - or is that fundamentally flawed?
Consider a form with 2 buttons and 2 datagridviews. Each button click launches a worker thread using an Async \ Await \ Task.Run pattern that calls a method to return a populated datatable and assigns it to one of the datagridviews. The .Fill in the first thread takes 30 seconds to complete. The .Fill in the second thread takes 1 second to complete. When launched individually, both buttons work as expected.
However, if I launch the first worker thread (30 seconds to Fill), then launch the second thread (1 second Fill), the second DataGridView is not populated until the first .Fill call completes. I would expect the second datagridview to populate in 1 second, and the first datagridview to populate ~30 seconds later.
I have duplicated this issue in my sample code with both the OleDBDataAdapter and the SqlDataAdapter. If I replace the long running query with a simple Thread.Sleep(30000), the second datagridview is populated right away. This leads me to be believe that it is not an issue with my design pattern, rather something specific to issuing the .Fill calls simultaneously.
Private Async Sub UltraButton1_Click(sender As Object, e As EventArgs) Handles UltraButton1.Click
Dim Args As New GetDataArguments
Args.ConnectionString = "some connection string"
Args.Query = "SELECT LongRunningQuery from Table"
Dim DT As DataTable = Await Task.Run(Function() FillDataTable(Args))
If DataGridView1.DataSource Is Nothing Then
DataGridView1.DataSource = DT
Else
CType(DataGridView1.DataSource, DataTable).Merge(DT)
End If
End Sub
Function FillDataTable(Args As GetDataArguments) As DataTable
Dim DS As New DataTable
Using Connection As New OleDbConnection(Args.ConnectionString)
Using DBCommand As New OleDbCommand(Args.Query, Connection)
Using DataAdapter As New OleDbDataAdapter(DBCommand)
DataAdapter.Fill(DS)
End Using
End Using
End Using
Return DS
End Function
Private Async Sub UltraButton2_Click(sender As Object, e As EventArgs) Handles UltraButton2.Click
Dim DS As DataTable = Await Task.Run(Function() LoadSecondDGV("1234"))
DataGridView2.DataSource = DS
End Sub
Function LoadSecondDGV(pnum As String) As DataTable
Dim DX As New DataTable
Using xConn As New OleDbConnection("some connection string")
Using DataAdapter As New OleDbDataAdapter("Select name from products where PNUM = """ & pnum & """", xConn)
DataAdapter.Fill(DX)
End Using
End Using
Return DX
End Function
This depends on what the data source is. Some data sources (like Excel) will only allow one connection at a time. Other data sources (like Access) will allow multiple connections, but actually fulfill the results in serial, such that you don't gain anything. Other data sources, like Sql Server, will allow the true parallel activity that you're looking for.
In this case, you mention that you also tried with an SqlDataAdapter, which indicates to me that you're talking to Sql Server, and this should be possible. What's probably going on here is that your first query is locking some of the data you need for the second query. You can get past this by changing your transaction isolation level or through careful use of the with (nolock) hint (the former option is strongly preferred).
One other thing to keep in mind is that this can only work if you're using a separate connection for each query, or if you've specifically enabled the Multiple Active Result Sets feature. It looks like you're using separate connection objects here, so you should be fine, but it's still something I thought was worth bringing up.
Finally, I need to comment on your FillDataTable() method. This method requires you to provide a completed Sql string, which practically forces you to write code that will be horribly vulnerable to sql injection attacks. Continuing to use the method as shown practically guarantees your app will get hacked, probably sooner rather than later. You need to modify this method so that it encourages you to use parameterized queries.

Starting same thread from a foreach loop not working

Is there a way to start same Thread form foreach loop
Sub
For Each lvItem As ListViewItem In _ListView.SelectedItems
tThread = New Thread(AddressOf Me.myFunction())
tThread .Start()
Next
End Sub
Sub myFunction()
//Code
End Sub
In my case, when i select one item from list it is working fine...but when i select more than one files it odes not work.
I want to select multiple files (which are file paths) from ListView and convert them in mp3 files but with above solution. it converts first selected file successfully but then stops.
Creating a separate thread for each file is sub-optimal. In some cases, this can actually result in worse performance than doing them all sequentially, because your system will spend too much time switching back and forth between different threads. Instead, you want to choose a small number of threads and queue your items for time by those threads.
There are a lot of ways to implement this:
You can write custom code that bases the number of threads on the number of physical processor cores (greater of 2 or the number of cores - 1 is common). This is a lot of extra work, and is error prone.
You can use the built-in in ThreadPool.QueueUserWorkItem(). This is great, but it can be tricky to track progress of your items.
You can use the Task Parallel Library. This requires .Net 4, but it's probably your best option by far. The extra work upfront learning the concepts will also be a huge payoff, and it sounds like Microsoft is basing some of the more important features in .Net 5 around the Task concept.
I'm guessing that instead of having one variable tThread storing the thread, you probably meant to store all the threads, so instead have a List of them. So something like:
Sub
Dim threads as New List(Of Thread)
For Each lvItem As ListViewItem In _ListView.SelectedItems
tThread = New Thread(AddressOf Me.myFunction())
tThread .Start()
threads.Add(tThread)
Next
End Sub
Otherwise, if there's another issue, please supply more details.
Are you sure the items are selected? I tried the following with a few items in the listview and it worked as expected.
Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
For Each lvItem As ListViewItem In ListView1.SelectedItems
Dim t As Threading.Thread = New Threading.Thread(AddressOf myFunction)
t.Start(lvItem)
Next
End Sub
Private Sub myFunction(ByVal lvi As Object)
Dim lvItem As ListViewItem = CType(lvi, ListViewItem)
Debug.WriteLine(lvItem.Text)
End Sub