VB.net ListView Adding Items Multiple Times - vb.net

I am having an issue where I get multiple entries in my ListView for the same item if I run my action more than once.
I am creating a simple network scanner/hostname grabber that will add the items to the listview as they come back alive to my ping test.
When I run it the first time it runs fine and creates one entry as it should.
When I run it subsequent times it creates the item as many times as I have ran the code ex. 3rd time hitting start it creates each entry 3 times when it should just create the entry once.
Here is my go button code:
Private Sub Go_Click(sender As Object, e As EventArgs) Handles Go.Click
Dim verifyIP
ListView1.Items.Clear()
chkDone = 0
verifyIP = ipChk(ipAdd.Text)
If verifyIP = 1 Then
ipAddy = Split(ipAdd.Text, ".")
pingTest1.WorkerReportsProgress = True
pingTest1.WorkerSupportsCancellation = False
AddHandler pingTest1.ProgressChanged, AddressOf pingTest1_ProgressChanged
pingTest1.RunWorkerAsync()
pingTest2.WorkerReportsProgress = True
pingTest2.WorkerSupportsCancellation = False
AddHandler pingTest2.ProgressChanged, AddressOf pingTest2_ProgressChanged
pingTest2.RunWorkerAsync()
pingTest3.WorkerReportsProgress = True
pingTest3.WorkerSupportsCancellation = False
AddHandler pingTest3.ProgressChanged, AddressOf pingTest3_ProgressChanged
pingTest3.RunWorkerAsync()
pingTest4.WorkerReportsProgress = True
pingTest4.WorkerSupportsCancellation = False
AddHandler pingTest4.ProgressChanged, AddressOf pingTest4_ProgressChanged
pingTest4.RunWorkerAsync()
While chkDone < 4
wait(25)
End While
Else
MsgBox("IP Invalid")
End If
MsgBox("Done")
End Sub
Here is the code from one of the background workers I am using:
Private WithEvents pingTest1 As BackgroundWorker = New BackgroundWorker
Private Sub pingTest1_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles pingTest1.DoWork
Try
Dim hostCheck
pingResult1 = 0
pingTestDone1 = 0
tryIP1 = ipAddy(0) & "." & ipAddy(1) & "." & ipAddy(2) & ".1"
If My.Computer.Network.Ping(tryIP1) = True Then
'Dim pingsender As New Net.NetworkInformation.Ping
'If pingsender.Send(tryIP).Status = Net.NetworkInformation.IPStatus.Success Then
Try
'Dim host As System.Net.IPHostEntry
hostCheck = ""
'host = System.Net.Dns.GetHostByAddress(tryIP3)
'MsgBox(host.HostName)
'host3 = host.HostName
'hostCheck = System.Net.Dns.GetHostEntry(tryIP3).HostName
hostCheck = System.Net.Dns.GetHostByAddress(tryIP1)
'get the hostname property
hostCheck = hostCheck.HostName
pingTest1.ReportProgress("1", hostCheck)
Catch f As Exception
'MsgBox("Error: " & f.Message)
pingTest1.ReportProgress("1", "No Hostname Found")
End Try
Else
pingResult1 = 2
End If
Catch d As Exception
MsgBox("There was an error trying to ping the IP Address: " & d.Message)
End Try
End Sub
Private Sub pingTest1_ProgressChanged(e.ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
MsgBox("Hey")
Dim str(2) As String
Dim itm As ListViewItem
str(0) = tryIP1 & " Is Alive!!!"
str(1) = e.UserState
itm = New ListViewItem(str)
ListView1.Items.Add(itm)
str(0) = ""
str(1) = ""
itm = Nothing
End Sub
Private Sub pingTest1_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles pingTest1.RunWorkerCompleted
chkDone = chkDone + 1
End Sub
I added the Hey box and sure enough the ProgressChanged event gets triggered the amount of times I have hit the Go button. Is it something I have coded incorrectly?

It's most likely because you're adding, but not removing your handlers for the progress changed, so you're handling the event multiple times.
Try adding your Progress Changed Event Handlers when you're instantiating your Background workers, rather than every time you click your button. This way they will only handled once.

Related

VB.NET Form.Show closes the application for unknown reason

VB.NET Framework 4.7.2
Visual Studio Community 2019 V16.11.2
I have a WinForms application and, for the most part, I start with a form to display the loading of datatables. This is handled in the Application Events module:
Private Sub MyApplication_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
If String.IsNullOrEmpty(My.Settings.TheCompany) Then
MainForm = FormConnection
Else
MainForm = FormDataLoad
End If
End Sub
When the datatables are all loaded the FormDataLoad has a close button which is enabled by the procedure which loads all the datatables:
With FormDataLoad
.ButtonClose.Visible = True
End With
When the button is pressed it calls another form which displays various charts etc based on the datatables which have been loaded:
Private Sub ButtonClose_Click(sender As Object, e As EventArgs) Handles ButtonClose.Click
Me.Close()
End Sub
Private Sub FormDataLoad_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
With FormOverview
.Show()
End With
End Sub
When FormOverview loads, its labels, charts etc are all initialised and drawn BUT, mysteriously, the form closes and the application ends. The Application Shutdown event is raised:
' Shutdown: Raised after all application forms are closed. This event is not raised if the application terminates abnormally.
Private Sub MyApplication_Shutdown(sender As Object, e As EventArgs) Handles Me.Shutdown
MessageBox.Show("Closing the application for some unknown reason")
End Sub
When this message box is displayed it means that the application has closed but not as a result of any unhandled exception. Nowhere in the project is there any code to call FormOverview.close so I am at a loss to understand what is happening.
The form load code is here:
Private Sub FormOverview_Load(sender As Object, e As EventArgs) Handles Me.Load
With Me
'Set the form caption and back colour and font
.Text = "Retail Management Hero Data Visualisation Overview"
.BackColor = Color.GhostWhite
.Font = U.BasicFontSmall
'Add event handler for the DAL
AddHandler D.ConnectionOpened, AddressOf Connected
'Form level variables for the KPJs
Dim ITO As String = ""
Dim ATV As String = ""
Dim CRR As String = ""
Dim GMROI As String = ""
Dim OOS As String = ""
Dim StocksSales As String = ""
With KPIData.Rows(0)
Me.Text = My.Settings.TheCompany & " " & Me.Text
ITO = .Item("ITO").ToString
CRR = .Item("CRR").ToString
ATV = "€" & .Item("ATV").ToString
GMROI = "€" & .Item("GMROI").ToString
OOS = .Item("OOSRatio").ToString
StocksSales = .Item("StocksToSales").ToString
End With
For Each CTL As Control In .Controls
If TypeOf CTL Is Label Then
With CTL
If .Name.Contains("Value") Then
.Font = U.BasicFontSuperLarge
.BackColor = Color.Yellow
Else
.BackColor = U.Spurs
.ForeColor = Color.GhostWhite
End If
End With
End If
Next
With .LabelReportDate
.Text = "Reports As Of " & ReportDate
End With
With .LabelATVValue
.Text = U.DecimalToSuperSubFormat(ATV, True)
End With
With .LabelGMROIValue
.Text = U.DecimalToSuperSubFormat(GMROI, True)
End With
With .LabelCRRValue
.Text = U.DecimalToSuperSubFormat(CRR, True) & "%"
End With
With .LabelITOValue
.Text = U.DecimalToSuperSubFormat(ITO, True)
End With
With .LabelOOSValue
.Text = U.DecimalToSuperSubFormat(OOS, True) & "%"
End With
With .LabelStocksSalesValue
.Text = U.DecimalToSuperSubFormat(StocksSales, True) & "%"
End With
'Extract the names of the X and Y axes
Dim Xname As String = ""
Dim Yname As String = ""
With WineData
Xname = .Columns("Department").ColumnName
Yname = .Columns("Day").ColumnName
End With
'Draw the charts
DrawColumnChart(WineData, .ChartWine, "Wine sales", Xname, Yname, 250, False)
DrawColumnChart(OtherData, .ChartNonWine, "Other sales", Xname, Yname, 25, False)
'Centre on the screen
.CenterToScreen()
End With
End Sub
So, what should happen is that this form displays centre screen with charts etc all displayed correctly.
If anyone has any ideas I would be most grateful.
Dermot
I found out what was causing the problem.
In the Project's properties, in the Application tab, there Combo selector, Shutdown mode.
The option I had selected was When startup form closes - by changing this to When last form closes the issue went away.
Like many problems, when you look at it too long you can't see the woods for the trees.

WebBrowser won't fire DocumentCompeted event or print

So I've been looking around for a solution to this for a little over a week to no avail. I have a program that needs to be able to print htm(l) files and I'm having a terrible time getting it to comply.
This is the code I'm using at the moment:
Private Sub HtmlPrinterLaunch(i As Integer)
'Dim htmlWBPrinter As New WebBrowser()
'AddHandler htmlWBPrinter.DocumentCompleted, New WebBrowserDocumentCompletedEventHandler(AddressOf HtmlPrinter)
'htmlWBPrinter.Visible = True
'htmlWBPrinter.ScriptErrorsSuppressed = False
'htmlWBPrinter.Show()
''frmHTMLPrint.wbPrintHtml.AllowNavigation = True
''AddHandler frmHTMLPrint.wbPrintHtml.DocumentCompleted, AddressOf HtmlPrinter
''frmHTMLPrint.wbPrintHtml.Visible = False
''frmHTMLPrint.wbPrintHtml.Navigate("file:///" & IO.Path.GetFullPath(_prints(i).SourcePathFileName))
''Application.Run(frmHTMLPrint)
''Dim appPath As String = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase)
''Dim reportPath As String = Path.Combine(appPath, Path.GetFileName(_prints(i).SourcePathFileName))
''htmlWBPrinter.Url = New Uri(reportPath) 'New Uri(Path.Combine("file:///", reportPath)) 'New Uri("file://" & IO.Path.GetFullPath(_prints(i).SourcePathFileName))
'htmlWBPrinter.Url = (New Uri(Path.Combine("file:///" & IO.Path.GetFullPath(_prints(i).SourcePathFileName))))
'While ((htmlWBPrinter.DocumentText = ""))
' Thread.Sleep(10000)
'End While
'htmlWBPrinter.ShowPrintDialog()
'' htmlWBPrinter.Dispose()
Dim wb As New WebBrowser
AddHandler wb.DocumentCompleted, Sub() If wb.ReadyState = WebBrowserReadyState.Complete Then wb.Print()
wb.ScriptErrorsSuppressed = True
Dim url As New Uri(Path.Combine("file:///" & IO.Path.GetFullPath(_prints(i).SourcePathFileName)))
wb.Navigate(url)
End Sub
Private Sub HtmlPrinter(sender As Object, e As WebBrowserDocumentCompletedEventArgs)
state = 4
Dim wbPrinter As WebBrowser = CType(sender, WebBrowser)
wbPrinter.Print()
wbPrinter.Dispose()
'frmHTMLPrint.BeginInvoke(New Action(Sub() frmHTMLPrint.Close()))
End Sub
As you can see I have a couple attempts in there (kept some older code that sort of worked but I'd rather not us it as I kept getting pop ups)
Some likely related issues:
-the WebBrowser state stays in loading (1)
-the Url never updates with the file:///path even if I pass it directly
So to put it in brief, my in code WebBrowser control won't hit the DocumentCompleted event, nor will it print out files. I need this code to print documents with no input from the user. What am I missing here?
Edit:
So I've messed around with this some more. I have the webbrowser control on its own form and I can get it to load/print when called from the main thread, but I'm having no luck invoking it. My current code for invocation:
If wbPrintHtml.InvokeRequired Then
If url.SourcePathFileName = "about:blank" Then
wbPrintHtml.Invoke(CType(Sub()
wbPrintHtml.Navigate(url.SourcePathFileName)
End Sub, MethodInvoker))
Else
wbPrintHtml.Invoke(CType(Sub()
wbPrintHtml.Navigate("file:///" & url.SourcePathFileName)
End Sub, MethodInvoker))
End If
Else
If url.SourcePathFileName = "about:blank" Then
wbPrintHtml.Navigate(url.SourcePathFileName)
Else
wbPrintHtml.Navigate("file:///" & url.SourcePathFileName)
End If
End If
I finally got this to work as intended. It's not the prettiest thing in the world, but it functions, and I'm ok with that.
Public Class frmHTMLPrint
Public Shared formHandle As frmHTMLPrint
Private Sub wbPrintHtml_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles wbPrintHtml.DocumentCompleted
Dim wbPrinter As WebBrowser = CType(sender, WebBrowser)
If wbPrinter.ReadyState = WebBrowserReadyState.Complete AndAlso Not wbPrinter.Url.ToString() = "about:blank" Then
wbPrinter.Print()
End If
End Sub
Shared Function setURL(url As Reporter.ReportServer.PrintMessageType) As Boolean
If formHandle.wbPrintHtml.InvokeRequired Then
If url.SourcePathFileName = "about:blank" Then
formHandle.wbPrintHtml.Invoke(CType(Sub()
formHandle.wbPrintHtml.Navigate(url.SourcePathFileName)
End Sub, MethodInvoker))
Else
formHandle.wbPrintHtml.Invoke(CType(Sub()
formHandle.wbPrintHtml.Navigate("file:///" & url.SourcePathFileName)
End Sub, MethodInvoker))
End If
Dim wbReady As WebBrowserReadyState
formHandle.wbPrintHtml.Invoke(CType(Sub()
wbReady = formHandle.wbPrintHtml.ReadyState
End Sub, MethodInvoker))
While ((Not wbReady = WebBrowserReadyState.Complete))
Application.DoEvents()
formHandle.wbPrintHtml.Invoke(CType(Sub()
wbReady = formHandle.wbPrintHtml.ReadyState
End Sub, MethodInvoker))
End While
Return wbReady = 4
Else
If url.SourcePathFileName = "about:blank" Then
formHandle.wbPrintHtml.Navigate(url.SourcePathFileName)
Else
formHandle.wbPrintHtml.Navigate("file:///" & url.SourcePathFileName)
End If
While ((Not formHandle.wbPrintHtml.ReadyState = WebBrowserReadyState.Complete))
Application.DoEvents()
End While
Return formHandle.wbPrintHtml.ReadyState = 4
End If
End Function
Private Sub frmHTMLPrint_Load() Handles Me.Load
InitializeComponent()
Dim wbInitializer As New Reporter.ReportServer.PrintMessageType
formHandle = Me
wbInitializer.SourcePathFileName = "about:blank"
setURL(wbInitializer)
End Sub
End Class

Windows Forms Modal Form Closes instantly when Base form is not Active

I have a Winforms Application which should notify the user when something in a database changes. For that i use Sql-Dependencys, which works fine.
When the Dependency fires i am able to show a form with some Buttons so the user can decide what he wants to do.
After one Button i want to show a Dialog, but the first Dialog always gets closed instantly.
The only fix i found until now is restoring the base form and activating it, but that is not the solution i am looking for.
For the code i am doing the Following:
This Method gets called when something changes in the Database
Private Sub NutzerBenachrichtigen(Aenderung As Aenderung)
If InvokeRequired Then
Me.BeginInvoke(New MethodInvoker(Sub()
ErzeugeBenachrichtigung(Aenderung)
End Sub))
Else
ErzeugeBenachrichtigung(Aenderung)
End If
End Sub
This Method Displays the first form (HeadsUp is taken from this here :https://github.com/glm9637/MaterialWinforms/blob/master/MaterialWinforms/Controls/HeadsUp.cs)
Private Sub ErzeugeBenachrichtigung(ByVal Aenderung As Aenderung)
If Aenderung.istAktuellerBenutzer Then
Dim objHeadsUp As New HeadsUp()
objHeadsUp.Titel = "Neue Aenderung"
If Aenderung.EventTyp.ToLower = "alter" Then
objHeadsUp.Text = String.Format("Du hast etwas an {0} {1} {2} geändert. {3}Willst du etwas dazu schreiben?", _
If(Aenderung.BetroffenesObjekt.EntitaetTyp.Name = New EntitaetTyp.Trigger().Name, "dem", "der"), _
Aenderung.BetroffenesObjekt.EntitaetTyp.Name, Aenderung.BetroffenesObjekt.Name, vbNewLine)
Else
objHeadsUp.Text = String.Format("Du hast {0} {1} {2} erstellt. {3}Willst du etwas dazu schreiben?", _
If(Aenderung.BetroffenesObjekt.EntitaetTyp.Name = New EntitaetTyp.Trigger().Name, "den", "die"), _
Aenderung.BetroffenesObjekt.EntitaetTyp.Name, Aenderung.BetroffenesObjekt.Name, vbNewLine)
End If
objHeadsUp.Tag = Aenderung.BetroffenesObjekt
Dim objButtonSchliessen = New MaterialFlatButton
objButtonSchliessen.Tag = objHeadsUp
objButtonSchliessen.Text = "Schliessen"
AddHandler objButtonSchliessen.Click, AddressOf SchliesseHeadsUp
objHeadsUp.Buttons.Add(objButtonSchliessen)
Dim objButtonHistorie = New MaterialFlatButton
objButtonHistorie.Tag = objHeadsUp
objButtonHistorie.Text = "Historieneintrag"
AddHandler objButtonHistorie.Click, AddressOf HistorienEintragHinzufuegen
objHeadsUp.Buttons.Add(objButtonSchliessen)
Dim objButtonDokumentation = New MaterialFlatButton
objButtonDokumentation.Tag = objHeadsUp
objButtonDokumentation.Text = "Dokumentation"
AddHandler objButtonDokumentation.Click, AddressOf DokumentationBearbeiten
objHeadsUp.Buttons.Add(objButtonSchliessen)
objHeadsUp.Buttons.Add(objButtonHistorie)
objHeadsUp.Buttons.Add(objButtonDokumentation)
objHeadsUp.Show()
ElseIf Aenderung.EventTyp = "CLOSE_MESSAGE" Then
Dim objHeadsUp As New HeadsUp()
objHeadsUp.Titel = "Achtung"
objHeadsUp.Text = "Die Anwendung muss für eine Aktualisierung geschlossen werden."
Dim objButtonSchliessen = New MaterialFlatButton
objButtonSchliessen.Tag = objHeadsUp
objButtonSchliessen.Text = "Anwendung Schliessen"
AddHandler objButtonSchliessen.Click, AddressOf AnwendungSchliessen
objHeadsUp.Buttons.Add(objButtonSchliessen)
objHeadsUp.Show()
Else
If mtc_UebersichtTabControl.SelectedTab.Text = "Aenderung" Then
mAenderungenUebersicht.Aktualisieren()
End If
End If
End Sub
Finally when the "Historieneintrag" Button is pressed this Method gets called:
Private Sub HistorienEintragHinzufuegen(sender As Object, e As EventArgs)
Dim blnNachDialogVerstecken As Boolean = False
Dim objFlatButton As MaterialFlatButton = sender
Dim HeadsUp As HeadsUp = objFlatButton.Tag
Dim Objekt As Entitaet = HeadsUp.Tag
Dim objOldLocation As System.Drawing.Point = Location
HeadsUp.Close()
Dim objDialogContent As New HistorienEintrag()
''Hacky: Ansonsten wird der Dialog sofort geschlossen
If WindowState = FormWindowState.Minimized Or Not Visible Then
Location = New System.Drawing.Point(-Width * 2, -Height - 2)
Me.Show()
blnNachDialogVerstecken = True
End If
Activate()
If MaterialDialog.Show("Neuer Historien Eintrag", objDialogContent, MaterialWinforms.Controls.MaterialDialog.Buttons.OKCancel, MaterialDialog.Icon.Info) = DialogResult.OK Then
Objekt.HistorieSpeichern(objDialogContent.Ergebniss)
End If
If blnNachDialogVerstecken Then
Me.Hide()
Location = objOldLocation
End If
End Sub
In here the first modal Form, so a MessageBox.Show("") or any other form closes Instantly if i don't do the show and activate part.
What am i doing wrong here?
Project > (Project) Properties > Application > Shutdown mode
change from when startup form closes to when last form closes

Updating and changing the data source of combobox VB.Net

I'm creating a multiclient-server application (chat room). I 'store' every connection (actually the connection-objects) in a hashtable. I also want to see and select one client from a combobox. I managed to bind the combobox to the hashtable but when I try to update the hashtable and then the combobox it changes every item to:
System.Collections.DictionaryEntry
to
user_selector is the combobox
registered_clients is the hashtable(clients connected through tcp to the server)
DB_clients is another hashtable (it takes users from a database).The combobox will show users from this hashtable.
The Form Code
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
form = Me ''The variable is used in the module
conn_listener.Start()
conn_listener.BeginAcceptTcpClient(New AsyncCallback(AddressOf new_client), conn_listener)
End Sub
Private Sub new_client(ByVal ar As IAsyncResult)
total_logged_clients += 1
temp_client = New Client(conn_listener.EndAcceptTcpClient(ar))
AddHandler temp_client.Connected, AddressOf Connected
AddHandler temp_client.Disconnected, AddressOf Disconnected
AddHandler temp_client.New_Message, AddressOf New_Message
Update_Log_Data("New user found & added. Waiting for details...")
conn_listener.BeginAcceptTcpClient(New AsyncCallback(AddressOf new_client), conn_listener) ''Loop
End Sub
Private Sub Connected(ByVal user As Client_Info)
total_registered_clients += 1
registered_clients.Add(user.ID, temp_client) '' Register each client based on its ID (in the hashtable)
Update_Log_Data("Confirmed client:" & user.Computer_Name & "," & user.ID)
RaiseEvent Update_GUI() ''Function from the module
End Sub
Private Sub Disconnected(ByVal user As Client_Info, ByVal reason As System.Exception)
Dim class_for_disposal As Client
total_logged_clients -= 1
If Not user.Connection_Status = Enum_Connection_Status.NO_INFO Then
total_registered_clients -= 1
End If
''Dispose this class
If registered_clients.ContainsKey(user.ID) Then
class_for_disposal = registered_clients.Item(user.ID)
registered_clients.Remove(user.ID)
class_for_disposal.Dispose()
End If
Update_Log_Data("Deconnected:" & user.Computer_Name & "," & user.ID & " because:" & reason.Message, 1)
RaiseEvent Update_GUI()
End Sub
Private Sub user_selector_SelectionChangeCommitted(ByVal sender As Object, ByVal e As System.EventArgs) Handles user_selector.SelectionChangeCommitted
Dim temp_selection As New DictionaryEntry
temp_selection = user_selector.SelectedItem
If registered_clients.ContainsKey(temp_selection.Key) Then
'' If the selected user is in the hastable then
''set "selected_user" to the object created by the user
'' With this object I can sendand receive from the selected user.
selected_user = registered_clients.Item(temp_selection.Key)
Else
selected_user = Nothing
End If
''Update_Data_GUI()
RaiseEvent Update_GUI()
End Sub
The Module
Private Sub Update_GUI() Handles form.Update_GUI
''Called often by controls
Interface_DB_clients()
Interface_bottom_conn_status()
End Sub
Private Sub Interface_bottom_conn_status()
With form
If .bottom_band.InvokeRequired Then
.Invoke(New Repeat(AddressOf Interface_bottom_conn_status))
Else
If selected_user Is Nothing Then
.bottom_client_status.ForeColor = Color.Red
.bottom_client_status.Text = "Offline"
Else
.bottom_client_status.ForeColor = Color.Green
.bottom_client_status.Text = "Online"
End If
End If
End With
End Sub
Private Sub Interface_DB_clients()
Dim preserve_item As DictionaryEntry
Dim bind As New BindingSource
With form
If .user_selector.InvokeRequired Then
.Invoke(New Repeat(AddressOf Interface_DB_clients))
Else
'' Here it's being made the refresh
preserve_item = .user_selector.SelectedItem
bind.DataSource = DB_clients
.user_selector.DataSource = Nothing ''clear first
.user_selector.DataSource = bind
.user_selector.ValueMember = "Value"
'' Selected old value
.user_selector.SelectedItem = preserve_item
End If
End With
End Sub
I've created special classes for the clients and that's why you can see properties like user.id and structures like Header_Info and Client_Info. I tried overlaping the data sources ,here
.user_selector.DataSource = Nothing ''clear first
.user_selector.DataSource = bind
by simply removing the .user_selector.DataSource = Nothing part.It worked when I added a new element but what if I remove a user? And why is it showing that error?
Where is the problem?

vb.net update progress bar multithread

Long time reader, first time poster. Usually I'm able to find the answer and make it work. Not this time..... I'm using VB.NET in VS2013. I am trying to update a progress bar with work done in a secondary thread. Easy right? No. I had to make it more complicated. The progress bar (ToolStripProgressBar1) is on the main form (frmMain), the MDI of the project. A secondary form (frmShipping) has a button which initiates a second thread to do some COMM Port communications in a class (cApex). I can get the progress bar to update on the frmMain from the main UI thread (frmShipping button).
This is the code from button on frmShiping and the multithread procedure:
Private Sub btnreadScanner_Click(sender As Object, e As EventArgs) Handles btnreadScanner.Click
Dim thrReadScanner As New System.Threading.Thread(AddressOf ReadScanner)
thrReadScanner.IsBackground = True
thrReadScanner.Start()
End Sub
Private Sub ReadScanner()
Dim strRowCount As String
ShipmentMsg(2)
strRowCount = objShipping.RecordsExisit.ToString()
Try
objApex.ImmediateMode()
If objApex.FileDownload = False Then
Throw New Exception(Err.Description)
End If
Catch ex As Exception
ShipmentMsg(1)
MessageBox.Show("No Data downloaded from Scanner. Try Again. Error#: " & Err.Number & " : " & Err.Description)
Exit Sub
End Try
RecordCount()
DataGridUpdate()
btnProcessShipment.Enabled = True
ShipmentMsg(5)
ScanErrors()
End Sub
This all works great. As expected. The call to objApex.FileDownload in class cApex is where progress bar is to be updated from (actually in another function called from FileDownload). So here is the code there.
Try
GetHeaderRecord()
If Count <> 0 Then intTicks = Math.Round((100 / Count), 1)
For intcount As Integer = 1 To Count
Dim intLength As Integer = Length
Do While intLength > 0
literal = Chr(_serialPort.ReadChar.ToString)
If literal = ">" Then Exit Do
strRecord = strRecord & literal
intLength = intLength - 1
Loop
REF = strRecord.Substring(0, 16).TrimEnd
SKID = strRecord.Substring(16, 16).TrimEnd
REEL_BC = strRecord.Substring(32, 15).TrimEnd
ScanDate = strRecord.Substring(47, 8).TrimEnd
ScanDate = DateTime.ParseExact(ScanDate, "yyyyMMdd", Nothing).ToString("MM/dd/yyyy")
ScanTime = strRecord.Substring(55, 6).TrimEnd
ScanTime = DateTime.ParseExact(ScanTime, "HHmmss", Nothing).ToString("HH:mm:ss")
strRecordTotal = strRecordTotal & strRecord & CRLF
Dim strSQL As String
strSQL = "INSERT INTO tblScanData (PONo,Barcode,SkidNo,ScanDate,ScanTime) " & _
"VALUES (" & _
Chr(39) & REF & Chr(39) & _
"," & Chr(39) & REEL_BC & Chr(39) & _
"," & Chr(39) & SKID & Chr(39) & _
"," & Chr(39) & ScanDate & Chr(39) & _
"," & Chr(39) & ScanTime & Chr(39) & ")"
objData.Executecommand(strSQL)
strRecord = ""
Next
And finally this is how I was calling the progress bar update.
Dim f As frmMain = frmMain
System.Threading.Thread.Sleep(100)
DirectCast(f, frmMain).ToolStripProgressBar1.PerformStep()
I really need to put the PerformStep in the For loop. Each time around the loop will step the progress bar the percentage of steps needed to make bar fairly accurate (done by the math code before loop). Also I setup the properties of the progress bar control on frmMain. So, am I crazy, or is there a way to accomplish this? I tried using a delegate; Me.Invoke(New MethodInvoker(AddressOf pbStep)) to make code cross thread safe. I don't get an error about cross thread calls, but the progress bar doesn't update either. Sorry it's a long one but I'm lost and my ADHD won't let me scrap this idea.
EDIT AS REQUESTED:
Public Sub pbStep()
Dim f As frmMain = frmMain
If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(AddressOf pbStep))
Else
DirectCast(f, frmMain).ToolStripProgressBar1.PerformStep()
System.Threading.Thread.Sleep(100)
End If
End Sub
Both responses helped lead me to the correct answer I was needing. The code provided by James was a great starting point to build on, and Hans has several post explaining the BackgroundWorker. I wanted to share the "Answer" I came up with. I'm not saying its the best way to do this, and I'm sure I'm violating some rules of common logic. Also, a lot of the code came from a MSDN example and James's code.
Lets start with the form from which I am calling the bgw, frmShipping. I added this code:
Dim WithEvents bgw1 As New System.ComponentModel.BackgroundWorker
Private Sub bgw1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles bgw1.RunWorkerCompleted
If e.Error IsNot Nothing Then
MessageBox.Show("Error: " & e.Error.Message)
ElseIf e.Cancelled Then
MessageBox.Show("Process Canceled.")
Else
MessageBox.Show("Finished Process.")
End If
End Sub
Private Sub bgw1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) _
Handles bgw1.ProgressChanged
DirectCast(Me.MdiParent, frmMain).ToolStripProgressBar1.Maximum = 1960
DirectCast(Me.MdiParent, frmMain).ToolStripProgressBar1.Step = 2
Dim state As cApex.CurrentState =
CType(e.UserState, cApex.CurrentState)
DirectCast(Me.MdiParent, frmMain).txtCount.Text = state.LinesCounted.ToString
DirectCast(Me.MdiParent, frmMain).txtPercent.Text = e.ProgressPercentage.ToString
DirectCast(Me.MdiParent, frmMain).ToolStripProgressBar1.PerformStep()
End Sub
Private Sub bgw1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) _
Handles bgw1.DoWork
Dim worker As System.ComponentModel.BackgroundWorker
worker = CType(sender, System.ComponentModel.BackgroundWorker)
Dim objApex As cApex = CType(e.Argument, cApex)
objApex.CountLines(worker, e)
End Sub
Sub StartThread()
Me.txtCount.Text = "0"
Dim objApex As New cApex
bgw1.WorkerReportsProgress = True
bgw1.RunWorkerAsync(objApex)
End Sub
Next I added the following code the my cApex class.
Public Class CurrentState
Public LinesCounted
End Class
Private LinesCounted As Integer = 0
Public Sub CountLines(ByVal worker As System.ComponentModel.BackgroundWorker, _
ByVal e As System.ComponentModel.DoWorkEventArgs)
Dim state As New CurrentState
Dim line = ""
Dim elaspedTime = 20
Dim lastReportDateTime = Now
Dim lineCount = File.ReadAllLines(My.Settings.strGenFilePath).Length
Dim percent = Math.Round(100 / lineCount, 2)
Dim totaldone As Double = 2
Using myStream As New StreamReader(My.Settings.strGenFilePath)
Do While Not myStream.EndOfStream
If worker.CancellationPending Then
e.Cancel = True
Exit Do
Else
line = myStream.ReadLine
LinesCounted += 1
totaldone += percent
If Now > lastReportDateTime.AddMilliseconds(elaspedTime) Then
state.LinesCounted = LinesCounted
worker.ReportProgress(totaldone, state)
lastReportDateTime = Now
End If
System.Threading.Thread.Sleep(2)
End If
Loop
state.LinesCounted = LinesCounted
worker.ReportProgress(totaldone, state)
End Using
End Sub
I also added a couple of text boxes to my main form to show the current line count from the file being read from and the overall progress as a percentage of a 100. Then on the Click event of my button I just call StartThread(). It is not 100% accurate, but its close enough to give the user a very good idea where the process stands. I have a little more work to do to add it to the "ReadScanner" function, where I originally was wanting to use the progress bar. But this function it the longer of the two that I perform on the scanner, writing almost 2,000 lines of code through a COMM Port. I'm happy with the results.
Thank you guys for helping out!
P.S. I have also now added variables to set the pbar.Maximum and the pbar.step since those can change if the scanner file is changed.
Background workers are useful for this purpose. Just use it in combination with a delegate. All the threaded work is done within the DoWork event of the worker. As progress is made, progress is reported within the DoWork event. This in turn fires the ProgressedChanged event of the worker class which is on the same thread as the progressbar. Once the DoWork has completed and is out of scope, the RunWorkerCompleted event is fired. This can be used to do inform the user that the task is complete, etc. Here is a working solution that I threw together. Just paste it behind an empty form and run.
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Threading
Public Class Form1
Private _progressBar As ProgressBar
Private _worker As BackgroundWorker
Sub New()
' This call is required by the designer.
InitializeComponent()
Initialize()
BindComponent()
End Sub
Private Sub Initialize()
_progressBar = New ProgressBar()
_progressBar.Dock = DockStyle.Fill
_worker = New BackgroundWorker()
_worker.WorkerReportsProgress = True
_worker.WorkerSupportsCancellation = True
Me.Controls.Add(_progressBar)
End Sub
Private Sub BindComponent()
AddHandler _worker.ProgressChanged, AddressOf _worker_ProgressChanged
AddHandler _worker.RunWorkerCompleted, AddressOf _worker_RunWorkerCompleted
AddHandler _worker.DoWork, AddressOf _worker_DoWork
AddHandler Me.Load, AddressOf Form1_Load
End Sub
Private Sub Form1_Load()
_worker.RunWorkerAsync()
End Sub
Private Sub _worker_ProgressChanged(ByVal o As Object, ByVal e As ProgressChangedEventArgs)
_progressBar.Increment(e.ProgressPercentage)
End Sub
Private Sub _worker_RunWorkerCompleted(ByVal o As Object, ByVal e As RunWorkerCompletedEventArgs)
End Sub
Private Sub _worker_DoWork(ByVal o As Object, ByVal e As DoWorkEventArgs)
Dim worker = DirectCast(o, BackgroundWorker)
Dim value = 10000
SetProgressMaximum(value)
For x As Integer = 0 To value
Thread.Sleep(100)
worker.ReportProgress(x)
Next
End Sub
Private Sub SetProgressMaximum(ByVal max As Integer)
If _progressBar.InvokeRequired Then
_progressBar.Invoke(Sub() SetProgressMaximum(max))
Else
_progressBar.Maximum = max
End If
End Sub
End Class