Load a big silverlight treeview (question for silverlight masters!) - silverlight-4.0

I have a table with 6400 rows. This is a parent-children table, it has a foreign key to it self (a self-association reference in EF).
For this project, all tree must be loaded (load data when node is expanded is not a solution for me)
I have tried to load all nodes/tree at a time, but it exceed maximun items: "The InnerException message was 'Maximum number of items that can be serialized or deserialized in an object graph is '65536'."
Then I have tried to load data recursively, but maximun simultanius connections are maded (http://msdn.microsoft.com/en-us/library/cc304129%28VS.85%29.aspx). This is because loadoperation is asyncronous and recursion runs un parallel way.
Finaly I have write this code to load the data, it works (slowly) but I think that this is dirty solution:
Private Sub loadOperation_Completed(ByVal sender As Object, ByVal e As EventArgs)
Dim localloadOperation As LoadOperation(Of dimActivitats) = DirectCast(sender, LoadOperation(Of dimActivitats))
If Not localloadOperation.HasError Then
Dim llista = localloadOperation.Entities.ToList
If llista.Any AndAlso llista.First.idSubrogatPare Is Nothing Then
activitatsTree = llista
TreeViewTaula.DataContext = activitatsTree
End If
For Each i In llista
elementsWaitingForExpand.Push(i)
Next
End If
Dim take10 = 10
Dim IdsParentListOfElementsToProcessNow As New List(Of Integer)
While elementsWaitingForExpand.Count > 0 And take10 > 0
take10 -= 1
IdsParentListOfElementsToProcessNow.Add(elementsWaitingForExpand.Pop.idSubrogat)
End While
Dim q2 = CActx.GetDimActivitatsListChildQuery(IdsParentListOfElementsToProcessNow)
loadOperation = CActx.Load(Of dimActivitats)(q2)
AddHandler loadOperation.Completed, AddressOf loadOperation_Completed
ProgressBarTaula.Value = CActx.dimActivitats.Count
End Sub
I'm looking for some thing more elegant (semaphor? monitor? queue? ).
Here you can see a previeus solution that raise error due simultanius connections:
Private Sub loadOperation_Completed(ByVal sender As Object, ByVal e As EventArgs)
Try
Dim localloadOperation As LoadOperation(Of dimActivitats) = DirectCast(sender, LoadOperation(Of dimActivitats))
If Not localloadOperation.HasError Then
Dim llista = localloadOperation.Entities.ToList
If llista.Any AndAlso llista.First.idSubrogatPare Is Nothing Then
activitatsTree = llista
TreeViewTaula.DataContext = activitatsTree
End If
TreeViewTaula.Dispatcher.BeginInvoke(New loadLlistaDelegate(AddressOf loadLlista), llista)
End If
Catch ex As Exception
End Try
End Sub
Private Delegate Sub loadLlistaDelegate(ByVal llista As List(Of dimActivitats))
Private Sub loadLlista(ByVal llista As List(Of dimActivitats))
For Each item In llista
For Each fill In item.dimActivitatsChildren
Dim q2 = CActx.GetDimActivitatsChildQuery(fill.idSubrogat)
Dim newloadOperation = CActx.Load(Of dimActivitats)(q2)
AddHandler newloadOperation.Completed, AddressOf loadOperation_Completed
Next
Next
End Sub
Do you have any idea to rewrite this code in more elegant way?

Do you have the maxItemsInObjectGraph setting in your service behavior in the web.config set to the maximum ? e.g
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<!--The service behavior for the RIA Service-->
<behavior name="RIAServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<dataContractSerializer **maxItemsInObjectGraph="2147483647"** />

Related

How to save checkbox value in xml using vb.net

I know, maybe this is a question that comes up quite often, but I haven't been able to work on it.
How can I save this checkbox values in an XML file using VB.Net ?
Private Sub ChkVul_Click(sender As Object, e As EventArgs) Handles ChkVul.Click
If ChkVul.Checked = True Then
Me.pnlInsert.Visible = True
Else
Me.pnlInsert.Visible = False
End If
End Sub
Before I provide you with an answer, I wanted to recommend a slight code change. Since you are setting the Boolean value of pnlInsert.Visible based on a condition, which itself returns a Boolean value, simply get rid of the conditional check in the first place:
pnlInsert.Visible = ChkVul.Checked
Now to your question. What you are essentially asking is how to write a value to an XML file. Something to consider is that XML is only a markup language. At the end of the day, an XML file is simply a file that contains formatted text.
If you do not already have an XML file to read from, simply create a new instance of a XDocument (documentation). If you do have an XML file, then create a new instance of a XDocument by calling the static XDocument.Load method (documentation). Here is a function that takes in a file location and attempts to load a XDocument, if it is unable to then it returns a blank XDocument with a single <root> element:
Private Function LoadOrCreateXml(filename As String) As XDocument
Dim document = New XDocument()
document.Add(New XElement("root"))
Try
If (Not String.IsNullOrWhiteSpace(filename) AndAlso IO.File.Exists(filename)) Then
document = XDocument.Load(filename)
End If
Catch ex As Exception
' for the sake of this example, just silently fail
End Try
Return document
End Function
Now that you have an XDocument, it is just a matter of writing the value. You did not provide any details as to where the value should go or what the tag name should be, so I am going to assume that it should be a child of the <root /> element and the value would look something like this: <ChkVul>true/false</ChkVul>.
To do this, we will need to get the <root /> element by calling the Element method (documentation) on the XDocument to get the element and then call the Add method (documentation) on the resulting XElement to add our node with the value:
Dim document = LoadOrCreateXml("my-xml-file.txt")
document.Element("root").Add(New XElement("ChkVul", ChkVul.Checked))
The final piece of all this is to write the in-memory XDocument back to the file. You can leverage the XDocument.Save method (documentation):
Dim filename = "my-xml-file.txt"
Dim document = LoadOrCreateXml(filename)
document.Element("root").Add(New XElement("ChkVul", ChkVul.Checked))
document.Save(filename)
Maybe this is what you have in mind. I tested this with other controls,
Public Class Form1
Private SavePath As String = IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"MyControls.xml") 'some valid path <-----------<<<
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
SaveCheckedState(ChkVul, "ChkVul", True)
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
If IO.File.Exists(SavePath) Then
Try
MyControls = XElement.Load(SavePath)
For Each el As XElement In MyControls.Elements
Dim ctrl() As Control = Me.Controls.Find(el.#name, True)
If ctrl.Length > 0 Then
Dim chkd As System.Reflection.PropertyInfo = ctrl(0).GetType().GetProperty("Checked")
Dim chkdV As Boolean = Boolean.Parse(el.#checked)
chkd.SetValue(ctrl(0), chkdV)
End If
Next
Catch ex As Exception
'todo
End Try
End If
End Sub
Private Sub ChkVul_CheckedChanged(sender As Object, e As EventArgs) Handles ChkVul.CheckedChanged
If ChkVul.Checked Then
Me.pnlInsert.Visible = True
Else
Me.pnlInsert.Visible = False
End If
SaveCheckedState(ChkVul, "ChkVul")
End Sub
Private Sub RadioButton1_CheckedChanged(sender As Object, e As EventArgs) Handles RadioButton1.CheckedChanged
SaveCheckedState(RadioButton1, "RadioButton1")
End Sub
Private Sub CheckBox1_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBox1.CheckedChanged
SaveCheckedState(CheckBox1, "CheckBox1")
End Sub
Private MyControls As XElement = <controls>
</controls>
Private Sub SaveCheckedState(Ctrl As Control,
Name As String,
Optional Save As Boolean = False)
If Ctrl.GetType().GetProperty("Checked") IsNot Nothing Then
Dim chkd As System.Reflection.PropertyInfo = Ctrl.GetType().GetProperty("Checked")
Dim chkdV As Boolean = CBool(chkd.GetValue(Ctrl))
Dim ie As IEnumerable(Of XElement)
ie = From el In MyControls.Elements
Where el.#name = Ctrl.Name
Select el Take 1
Dim thisXML As XElement
If ie.Count = 0 Then
thisXML = <ctrl name=<%= Name %>></ctrl>
MyControls.Add(thisXML)
Else
thisXML = ie(0)
End If
thisXML.#checked = chkdV.ToString
End If
If Save Then
Try
MyControls.Save(SavePath)
Catch ex As Exception
'todo
' Stop
End Try
End If
End Sub
End Class

VB.NET: Add\remove row indexes of datagridview checkboxes rows

I am working with forms in VB.NET
There is a DatagridView table with a checkbox column.
See the picture below:
I am interested in the question: how to add the line index to the list when clicking in the checkbox (when we activate the checked status), and remove it from the list when we uncheck the checkbox?
Tried the following but this is not the correct solution:
If e.ColumnIndex = chk_column.Index Then
If e.RowIndex >= 0 Then
Try
For Each row As DataGridViewRow In dataGridNames.Rows
Dim cell As DataGridViewCheckBoxCell = TryCast(row.Cells(5), DataGridViewCheckBoxCell)
If cell.Value Is cell.FalseValue Then
bList_indexes.Add(DataGridnames.CurrentCell.RowIndex)
Exit For
Else 'If cell.Value Is cell.TrueValue Then
bList_indexes.RemoveAt(DataGridnames.CurrentCell.RowIndex)
End If
Next
Catch ex As Exception
'Show the exception's message.
'MessageBox.Show(ex.Message)
'Throw New Exception("Something happened.")
End try
End If
End If
Using DataSources allows you to take the logic out of mucking around in DataGridView events. You shouldn't perform [much] business logic on the UI anyways.
Here is the class I used to represent your data.
Public Class ClassWithSelect
Public Property [Select] As Boolean
Public Property Name As String
Public Sub New(s As Boolean, n As String)
Me.Select = s
Me.Name = n
End Sub
End Class
And all the code to set DataSources
Private myDataSource As List(Of ClassWithSelect)
Private selectedIndices As List(Of Integer)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
myDataSource = Enumerable.Range(65, 10).Select(Function(i) New ClassWithSelect(False, Chr(i).ToString())).ToList()
DataGridView1.DataSource = myDataSource
updateSelectedIndices()
End Sub
Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
updateSelectedIndices()
End Sub
Private Sub DataGridView1_CellContentClick(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick
DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
End Sub
Private Sub updateSelectedIndices()
selectedIndices = New List(Of Integer)()
For i = 0 To myDataSource.Count - 1
If myDataSource(i).Select Then selectedIndices.Add(i)
Next
ListBox1.DataSource = selectedIndices
End Sub
And the end result
Now you don't need to access the UI to get the indices for further processing as they are in the class-level variable selectedIndices. The UI is meant for user I/O, NOT for storing state.
Note: The event handler was taken from this answer but this answer is also linked as an improvement to the check change handler, but I felt the complexity would distract from my answer. If you find you need to click fast, look into the latter.
Also Note: The method updateSelectedIndices() should have inside it an InvokeRequired check if you plan to perform work off the UI thread

after using background worker also my application get stuck,,how i can resolve

I am working on windows form application :
in my form am filling my datagrid view in some interval.so some time my application getting stuck..so i used back ground worker and timer
in back ground worker i am calling my function to fill the my data grid view.i set Timer Interval as 10000. in background worker i given code like this:
Private Sub BackgroundWorker1_DoWork
Call Fetch_Info()
End Sub
in Timer click event i given code like thise:
If Not BackgroundWorker1.IsBusy Then
BackgroundWorker1.RunWorkerAsync()
End If
my Fetch_Info() function like this:
Dim cnt As Integer
Dim tbarcodedgv As String
Dim totaltbarcode As String
cnt = DGVall.RowCount
Dim tbar As String
Dim locTable As New DataTable
locTable.Columns.Add("carid", GetType(String))
If cnt > 0 Then
For i = 0 To cnt - 2
tbarcodedgv = DGVall.Rows(i).Cells(0).Value
locTable.Rows.Add(tbarcodedgv)
Next
End If
Dim flag As Boolean = False
Dim dcnt As Integer = DGVall.RowCount
Dim trid As Integer
Dim tbarcode As String
Dim keyloc As String
Dim cmd23 As New SqlCommand("IBS_fetchrequested", con.connect)
cmd23.CommandType = CommandType.StoredProcedure
cmd23.Parameters.Add("#tid", SqlDbType.Int).Value = tid
If cnt > 1 Then
Dim tvp1 As SqlParameter = cmd23.Parameters.AddWithValue("#Tbaroced", locTable)
tvp1.SqlDbType = SqlDbType.Structured
tvp1.TypeName = "dbo.TBarcode"
End If
dr = cmd23.ExecuteReader
While dr.Read
flag = False
tbarcode = dr("TBarcode")
If flag = False Then
If dr("keyloc") Is DBNull.Value Then
keyloc = ""
Else
keyloc = dr("keyloc")
End If
Dim row0 As String() = {tbarcode, keyloc, "", "Release"}
DGVall.Rows.Add(row0)
AxWindowsMediaPlayer1.URL = "C:\Beep.mp3"
End If
End While
dr.Close()
con.disconnect()
While your background worker runs in another thread than your GUI you are manipulating the Datagridview that's running in the GUI's thread. This should usually not work at all but it is probably the reason, why your GUI hangs while the BGW is running.
Try splitting the work: The time consuming fetching of data from the database is carried out in the Backgroundworker's DoWork event handler and you set the results as the e.Result value of the EventArgs variable in the DoWork function.
Then you handle the Backgroundworker's RunWorkerCompleted event and there you quickly update your datagridview with the results you set in the DoWork method. That way your GUI has nothing to do with the actual time consuming task and will only be affected by the quick update of your datagridview.
The code example for this is:
Public Class Form1
Private WithEvents LazyBGW As New System.ComponentModel.BackgroundWorker
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'This code runs in the UI-Thread
LazyBGW.RunWorkerAsync()
End Sub
Private Sub LazyBGW_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles LazyBGW.DoWork
'This code runs in the BGW-Thread
Dim a As Integer = 0
For i = 1 To 5
a += 1
'I'm a lazy worker, so after this hard work I need to...
Threading.Thread.Sleep(1000) 'This locks up the BGW-Thread, not the UI-thread
Next
'Work is done, put results in the eventargs-variable for further processing
e.Result = a
End Sub
Private Sub LazyBGW_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles LazyBGW.RunWorkerCompleted
'This code runs in the UI-Thread
Dim results As Integer = CInt(e.Result) 'e.Result contains whatever you put into it in the DoWork() method
MessageBox.Show("Finally the worker is done and our result is: " & results.ToString)
End Sub
End Class

WP7 - Updating a listbox

I've finally got around to starting windows phone dev. I'm not very good at it yet, but anyway, I hope you guys understand what I want to do here.
From what I've learnt from other programmers, an ObservableCollection can be updated in live time whilst it is databound to an object, such as a listbox. All changes to the ObservableCollection will cause the UI of the object it's databound to update it's items.
So what I'm trying to do, is download a file from my server, parse it with json, then update the ObservableCollection with the new data. However, the webclient doesn't seem to be downloading the new data until the app is re opened!
Here's a gif showing how the app works at the moment:
And here's my code (cut down a bit):
Dim aList As New ObservableCollection(Of classes.consoles)
Private Sub PhoneApplicationPage_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
checkforconsoles()
End Sub
Public Sub checkforconsoles()
Dim wc As New WebClient()
AddHandler wc.DownloadStringCompleted, AddressOf downloaded
wc.DownloadStringAsync(New Uri("http://localhost/api/?function=GetConsolesForUser&userid=" & id))
End Sub
Private Sub downloaded(sender As Object, e As DownloadStringCompletedEventArgs)
aList.Clear()
'MessageBox.Show(e.Result)
Dim o As JObject = JObject.Parse(e.Result)
Dim jarray As JArray = DirectCast(o("results"), JArray)
Try
Dim i As Integer = jarray.Count()
For i = 0 To jarray.Count() - 1 Step 1
Dim id As String = jarray(i)("id").ToString
Dim name As String = jarray(i)("name").ToString
Dim image As String = jarray(i)("image").ToString
MessageBox.Show(name)
Dim c As classes.consoles = New classes.consoles()
c.categoryimage = New Uri(image)
c.categoryname = name
c.categoryid = id
aList.Add(c)
Next
listBoxview.ItemsSource = aList
StackPanel1.Visibility = Windows.Visibility.Collapsed
StackPanel2.Visibility = Windows.Visibility.Visible
Catch ex As Exception
StackPanel2.Visibility = Windows.Visibility.Collapsed
StackPanel1.Visibility = Windows.Visibility.Visible
End Try
End Sub
Private Sub ApplicationBarIconButton_Click_1(sender As System.Object, e As System.EventArgs)
checkforconsoles()
End Sub
Does anybody have any clue what's wrong? :(
Thanks in advance.
It's a cachine issue with the WebClient. You can append a random query string to ensure that the URL is always unique so that the WebClient doesn't cache the results. One way to do this is to add a random GUID value since it's very unlikely to generate two of the same GUIDs in a short time frame.
wc.DownloadStringAsync(New Uri("http://localhost/api/?function=GetConsolesForUser&
userid=" & id & "&random=" + Guid.NewGuid().ToString()))

VB.Net Multiple background workers - Only last task completes

I have been pulling my hair out trying to get this to work. If I step through the code in debugger it all works great.
My problem is if I just run it, only the last task responds. I'm guessing I am overwriting the background working or something. I am sure I am doing a few things wrong but my code is now messy as I tried many way while searching. I know of the threadpool and .Net 4.0 tasks but having a hard time getting to do what I need.
Basicly I am writing a program (trying more likely) that takes a list of computers and pings then, then checks their uptime and reports back.
This works fine in the UI thread (Obviously that locks up my screen). I can have the background worker just do this, but then it does each computer 1 by one, and while the screen is responsive it still takes a long time.
So my answer was to have a for loop for each server launching a new background worker thread. My solution does not work.
I have seen other threads that I could do it, but I need to use with events to call code to update to UI when each is done.
What is the most simple way to do this?
Here is my code. Most is just copy paste + modify till I get it working right.
So In the main class I have the testworker.
(I tried using Testworker() but it said I could not do that WithEvents)
When I click the button the list loads.
Private WithEvents TestWorker As System.ComponentModel.BackgroundWorker
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
Button1.IsEnabled = False
Dim indexMax As Integer
indexMax = DataGridStatus.Items.Count
For index = 1 To (indexMax)
Dim Temp As ServerInfo = DataGridStatus.Items(index - 1)
Temp.Index = index - 1
Call_Thread(Temp)
Next
End Sub
Private Sub Call_Thread(ByVal server As ServerInfo)
Dim localserver As ServerInfo = server
TestWorker = New System.ComponentModel.BackgroundWorker
TestWorker.WorkerReportsProgress = True
TestWorker.WorkerSupportsCancellation = True
TestWorker.RunWorkerAsync(localserver)
End Sub
Private Sub TestWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles TestWorker.DoWork
Dim iparray As IPHostEntry
Dim ip() As IPAddress
Dim Server As ServerInfo
Server = e.Argument
Try
'Get IP Address first
iparray = Dns.GetHostEntry(Server.ServerName)
ip = iparray.AddressList
Server.IPAddress = ip(0).ToString
'Try Pinging
Server.PingResult = PingHost(Server.ServerName)
If Server.PingResult = "Success" Then
'If ping success, get uptime
Server.UpTime = GetUptime(Server.ServerName)
Else
Server.PingResult = "Failed"
End If
Catch ex As Exception
Server.PingResult = "Error"
End Try
TestWorker.ReportProgress(0, Server)
Thread.Sleep(1000)
End Sub
Private Sub TestWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles TestWorker.ProgressChanged
Dim index As Integer
Dim serverchange As ServerInfo = DirectCast(e.UserState, ServerInfo)
index = DataGridStatus.Items.IndexOf(serverchange)
' index = serverchange.Index
DataGridStatus.Items.Item(index) = serverchange
' ProgressBar1.Value = e.ProgressPercentage
DataGridStatus.Items.Refresh()
End Sub
You are only getting the last result because you are blowing away your BackgroundWorker each time you call TestWorker = New System.ComponentModel.BackgroundWorker. Since the processing is being done asynchronously, this line is being called multiple times within your for loop before the previous work has finished.
Something like the following might work. (Sorry, my VB is rusty; there are probably more efficient ways of expressing this.)
Delegate Function PingDelegate(ByVal server As String) As String
Private _completedCount As Int32
Private ReadOnly _lockObject As New System.Object
Dim _rnd As New Random
Private _servers As List(Of String)
Private Sub GoButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles GoButton.Click
_servers = New List(Of System.String)(New String() {"adam", "betty", "clyde", "danny", "evan", "fred", "gertrude", "hank", "ice-t", "joshua"})
_completedCount = 0
ListBox1.Items.Clear()
GoButton.Enabled = False
BackgroundWorker1.RunWorkerAsync(_servers)
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim servers As List(Of System.String) = DirectCast(e.Argument, List(Of System.String))
Dim waitHandles As New List(Of WaitHandle)
For Each server As System.String In servers
' Get a delegate for the ping operation. .Net will let you call it asynchronously
Dim d As New PingDelegate(AddressOf Ping)
' Start the ping operation async. When the ping is complete, it will automatically call PingIsDone
Dim ar As IAsyncResult = d.BeginInvoke(server, AddressOf PingIsDone, d)
' Add the IAsyncResult for this invocation to our collection.
waitHandles.Add(ar.AsyncWaitHandle)
Next
' Wait until everything is done. This will not block the UI thread because it is happening
' in the background. You could also use the overload that takes a timeout value and
' check to see if the user has requested cancellation, for example. Once all operations
' are complete, this method will exit scope and the BackgroundWorker1_RunWorkerCompleted
' will be called.
WaitHandle.WaitAll(waitHandles.ToArray())
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
ListBox1.Items.Add(String.Format("{0} ({1}% done)", e.UserState, e.ProgressPercentage))
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
GoButton.Enabled = True
End Sub
Private Function Ping(ByVal server As System.String) As System.String
' Simulate a ping with random result and duration
Threading.Thread.Sleep(_rnd.Next(1000, 4000))
Dim result As Int32 = _rnd.Next(0, 2)
If result = 0 Then
Return server & " is ok"
Else
Return server & " is down"
End If
End Function
Private Sub PingIsDone(ByVal ar As IAsyncResult)
' This method is called everytime a ping operation completes. Note that the order in which
' this method fires is completely independant of the order of the servers. The first server
' to respond calls this method first, etc. This keeps optimal performance.
Dim d As PingDelegate = DirectCast(ar.AsyncState, PingDelegate)
' Complete the operation and get the result.
Dim pingResult As String = d.EndInvoke(ar)
' To be safe, we put a lock around this so that _completedCount gets incremented atomically
' with the progress report. This may or may not be necessary in your application.
SyncLock (_lockObject)
_completedCount = _completedCount + 1
Dim percent As Int32 = _completedCount * 100 / _servers.Count
BackgroundWorker1.ReportProgress(percent, pingResult)
End SyncLock
End Sub
Update: I posted this answer focusing on exactly what you were trying to do from a technical standpoint (use many background workers) without really putting much thought into whether or not this was a good way to accomplish your real objective. In fact, I think you could achieve what you're going for much more easily with a single BackgroundWorker and something like a Parallel.ForEach loop in its DoWork event handler (this takes care of a lot of the nitty gritty work in, e.g., Dave's solution).
When you declare WithEvents TestWorker As BackgroundWorker in VB it wraps it up something like this (not exactly—this is just to illustrate the idea):
Private _TestWorker As BackgroundWorker
Private Property TestWorker As BackgroundWorker
Get
Return _TestWorker
End Get
Set(ByVal value As BackgroundWorker)
' This is all probably handled in a more thread-safe way, mind you. '
Dim prevWorker As BackgroundWorker = _TestWorker
If prevWorker IsNot Nothing Then
RemoveHandler prevWorker.DoWork, AddressOf TestWorker_DoWork
' etc. '
End If
If value IsNot Nothing Then
AddHandler value.DoWork, AddressOf TestWorker_DoWork
' etc. '
End If
_TestWorker = value
End Set
End Property
When you realize this, it becomes clear that by setting TestWorker to a new BackgroundWorker on every call to Call_Thread, you are removing any attached handlers from the object previously referenced by the field.
The most obvious fix would simply be to create a new local BackgroundWorker object in each call to Call_Thread, attach the handlers there (using AddHandler and RemoveHandler), and then just let it do its thing:
Private Sub Call_Thread(ByVal server As ServerInfo)
Dim localserver As ServerInfo = server
' Use a local variable for the new worker. '
' This takes the place of the Private WithEvents field. '
Dim worker As New System.ComponentModel.BackgroundWorker
' Set it up. '
With worker
.WorkerReportsProgress = True
.WorkerSupportsCancellation = True
End With
' Attach the handlers. '
AddHandler worker.DoWork, AddressOf TestWorker_DoWork
AddHandler worker.ProgressChanged, AdressOf TestWorker_ProgressChanged
' Do the work. '
worker.RunWorkerAsync(localserver)
End Sub
Creating the worker right there in the method should be fine as long as you do so from the UI thread, since BackgroundWorker automatically attaches to the current SynchronizationContext in its constructor (if I remember correctly).
Ideally you should use only 1 backgroundworker and use it like this:
Assemble all the work that needs to be done: in your case a list of ServerInfo
Do the work in the background: ping all the servers and keep the result
Report progress: for example after each server pinged
Put results back in DoWorkEventArgs.Result
Display the results back in your UI.
You need to attach TestWorker_DoWork and TestWorker_ProgressChanged to the DoWork and ProgressChanged events within Call_Thread. I haven't yet examined the rest of the code, but that is why it isn't doing anything now.
TestWorker = New System.ComponentModel.BackgroundWorker
TestWorker.WorkerReportsProgress = True
TestWorker.WorkerSupportsCancellation = True
AddHandler TestWorker.DoWork, AddressOf TestWorker_DoWork
AddHandler TestWorker.ProgressChanged, AddressOf TestWorker_ProgressChanged
TestWorker.RunWorkerAsync(localserver)