Threading functions with VB.net - vb.net

I'm trying to thread my functions so the main GUI of my application doesn't fail to respond while the tasks are running.
At the moment my form1.vb is something like this. I've cut it down as to reduce the text, but everything works fine:
Public Class MAIR
Private Sub InstallTheAgent_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles InstallTheAgent.Click
InstallAgentWorker.RunWorkerAsync()
End Sub
Private Sub InstallAgentWorker_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles InstallAgentWorker.DoWork
'Do some stuff
End Sub
Private Sub InstallAgentWorker_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles InstallAgentWorker.RunWorkerCompleted
' Called when the BackgroundWorker is completed.
MsgBox("Installation of the Agent Complete")
ProgressBar1.Value = 0
End Sub
End Class
From my understanding, when the button "Open" is pressed, it calls the function Install and it should start this off in a separate thread, however this doesn't work.
This seems to work, but the GUI still locks up, suggesting its not in a separate thread

I recommend using the BackgroundWorker Class to implement this kind of basic threading. You don't have a lot going on here so the basic implementation should suffice. Here is a snippet of how I would go about doing it. My method is a bit too complex (I have base classes and events wired up to catch a lot of worker events) to list here succinctly, so I'll abbreviate.
Public Class MAIR
Dim worker as BackgroundWorker
Private Sub Open_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Open.Click
worker.RunWorkerAsync()
End Sub
Protected Sub Worker_DoWork(ByVal sender as Object, ByVal e as DoWorkEventArgs)
Call Shell("psexec.exe", AppWinStyle.Hide, True)
End Sub
Protected Sub Worker_ProgressChanged(Byval sender as Object, ByVal e as ProgressChangedEventArgs)
' You can track progress reports here if you use them
End Sub
Protected Sub Worker_RunWorkerCompleted(Byval sender as Object, ByVal e as RunWorkerCompletedEventArgs)
' Report back to the main application that the thread is completed
End Sub
Private Sub Install_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
worker = New BackgroundWorker()
' Add the event wire-ups
' ### This event wire-up establishes the link
' ### that is used by RunWorkerAsync to initiate
' ### the code you want to run asynchronously
AddHandler worker.DoWork, AddressOf Worker_DoWork
AddHandler worker.ProgressChanged, AddressOf Worker_ProgressChanged
AddHandler worker.RunWorkerCompleted, AddressOf Worker_RunWorkerCompleted
End Sub
End Class
This is all translated from C#, so treat it as psuedo-code. The important piece is the documentation of the BackgroundWorker and its behaviors. There is a lot of terrific functionality in this class that takes the headaches of threads away for simple usages. This class is located in System.ComponentModel so you'll need the reference and an Imports statement.
Edit: Something I forgot to mention is that once the worker is fired asynchronously, the only manner of tracking it or communicating with the main application is through the ProgressChanged and RunWorkerCompleted events. No global variables or cross-thread items will be available, so you'll have to use the built-in properties to pass in any values (such as computerName it looks like) that you may need during the run.

Related

Working sample of Control.VisibleChanged Event in vb.net

I'm struggling to make the MSDN code sample for the Control.VisibleChanged event work: I don't see the MsgBox.
Private Sub Button_HideLabel(ByVal sender As Object, ByVal e As EventArgs)
myLabel.Visible = False
End Sub 'Button_HideLabel
Private Sub AddVisibleChangedEventHandler()
AddHandler myLabel.VisibleChanged, AddressOf Label_VisibleChanged
End Sub 'AddVisibleChangedEventHandler
Private Sub Label_VisibleChanged(ByVal sender As Object, ByVal e As EventArgs)
MessageBox.Show("Visible change event raised!!!")
End Sub 'Label_VisibleChanged
You need to "wire up" the events to the event handlers.
To start with, to get the code in HideLabel_Click to be called you need it to respond to a click on the button named "HideLabel".
There are two ways to do that: you can use AddHandler or the Handles clause.
To demonstrate the latter:
Option Strict On
Public Class Form1
Private Sub HideLabel_Click(sender As Object, e As EventArgs) Handles HideLabel.Click
myLabel.Visible = False
End Sub
Private Sub myLabel_VisibleChanged(sender As Object, e As EventArgs) Handles myLabel.VisibleChanged
MessageBox.Show("Visible change event raised!!!")
End Sub
End Class
However, you will notice that the message is shown even before the form appears. That is because of what goes on behind the scenes to create the form.
To avoid that happening, you can add the handler after the form has been shown:
Option Strict On
Public Class Form1
Private Sub HideLabel_Click(sender As Object, e As EventArgs) Handles HideLabel.Click
myLabel.Visible = False
End Sub
Private Sub myLabel_VisibleChanged(sender As Object, e As EventArgs)
MessageBox.Show("Visible change event raised!!!")
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
AddHandler myLabel.VisibleChanged, AddressOf myLabel_VisibleChanged
End Sub
End Class
Another way, in VB2015 and later, is to use a "lambda expression" instead of a separate method, although then you cannot disassociate the handler from the event with RemoveHandler:
Option Strict On
Public Class Form1
Private Sub HideLabel_Click(sender As Object, e As EventArgs) Handles HideLabel.Click
myLabel.Visible = False
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
AddHandler myLabel.VisibleChanged, Sub() MessageBox.Show("Visible change event raised!!!")
End Sub
End Class
Craig was kind enough to [and I quote verbatim] call attention to the importance of Option Strict when you add handlers manually using AddHandler. Without it, the "relaxed delegate convention" may allow adding handlers which don't exactly match the event signature that you won't be able to remove later.
Having said that, Option Strict On isn't a complete safeguard: notice how my last example compiles and works even with the wrong method signature for the handler.
[I suspect that the MSDN code sample was first created in C# as part of a larger example, so some parts have been lost in the translation and excerption.]
I get this is old but came across this post when looking for more information on VisibleChanged and couldn't help but notice that the accept answer may be misleading. If you are using a designer to create your Form and place objects on it, then the accepted answer will be fine. In fact you can get rid of the addHandler because the designer handles that for you. All you would need to do is use a handles clause with your label.
Private Sub Button_HideLabel(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
myLabel.Visible = False
End Sub 'Button_HideLabel
Private Sub Label_VisibleChanged(ByVal sender As Object, ByVal e As EventArgs) Handles myLabel.VisibleChanged
MessageBox.Show("Visible change event raised!!!")
End Sub 'Label_VisibleChanged
Where the issue lies with the accepted answer is if you arn't using a designer. Adding handle clauses to "wire up" simply won't work (we can make it work and if anyone is interested in that I'll be happy to post a code snippet of that, but it's not how the accepted answer lays it out). In your case all you need to do is call AddVisibleChangedEventHandler() to set up the handler. that's it. you could have done this by calling it in MyBase.Load
Private Sub Load_Form(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
AddVisibleChangedEventHandler()
End Sub
Private Sub Button_HideLabel(ByVal sender As Object, ByVal e As EventArgs)
myLabel.Visible = False
End Sub 'Button_HideLabel
Private Sub Label_VisibleChanged(ByVal sender As Object, ByVal e As EventArgs)
MessageBox.Show("Visible change event raised!!!")
End Sub 'Label_VisibleChanged
Private Sub AddVisibleChangedEventHandler()
AddHandler myLabel.VisibleChanged, AddressOf Label_VisibleChanged
End Sub
Once again I know this is dated but couldn't help but notice that (more or less assuming) that you are trying to get a msgBox to appear when you click a label. That is you click a label and then toggled the visibility of another label. The other label is the one where the event handler is on for visibility change. So that inevitably gets called when clicking the original label. IF you only want this msgBox to appear when clicking that label and not when the form loads as well, you should change the addHandler statement so that you are adding a handler on the click event.
Private Sub Load_Form(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
AddVisibleChangedEventHandler()
End Sub
Private Sub Label_VisibleChanged(ByVal sender As Object, ByVal e As EventArgs)
MessageBox.Show("Visible change event raised!!!")
End Sub 'Label_VisibleChanged
Private Sub AddVisibleChangedEventHandler()
AddHandler otherLabel.Click, AddressOf Label_VisibleChanged
End Sub 'AddVisibleChangedEventHandler
Also Option Strict On has nothing to do with addhandler (From my understanding, could be wrong. please enlighten me if that is the case). Option Strict On is only checking to see that you arn't implicitly typecasting. So for example:
Dim a As Double
Dim b As Integer
a = 10
b = a
results in an error when Option Strict is On but is totally legal if it is off. So in the case of you leaving off the handles clause, you'll never be implicitly typecasting and therefore is not needed.
Hope this helps anyone who sees this question

Access Main thread data from the Background thread

I´m using a backgroundworker to handle processing of data while the user is still free to for instance click another button that aborts the process.
However, the code in backgroundworker requires several pieces of data, for instance it needs to know whether a radio button is checked.
Is there a way to access data in another thread from the background thread? Or should I create global variables that hold this information?
Public Class Test
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Button1.Content = "Working..."
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
If RadioButton1.IsChecked Then
MsgBox("It works")
End If
End Sub
End Class
You can pass the RadioButton checked state to the RunWorkerAsync method like this:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync(RadioButton1.Checked)
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim checked As Boolean = CBool(e.Argument)
If checked Then
MessageBox.Show("It works")
End If
End Sub
Let me know if you need to be checking the RadioButton continuously from inside a loop, as that would be different.

Visual Basic 2010 Live Logger

Hello I am trying to make a live logger in my program so I can keep track of the progress.
Like this:
http://i.stack.imgur.com/cmcTp.png
How do I make something like this.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
You can use background worker, with a listbox to accomplish what you are asking. On the reports progress method of background worker, you can specify the "userstate" object to "log" to controls on the UI thread, just make sure to set 'worker reports progress' to true:
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'where you call your worker to do work
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'make sure to set worker reports progress to true
BackgroundWorker1.ReportProgress(0, "About to Messagebox") 'where 0 is a progress percent if you want it and the string is overloaded
MsgBox("This is to show how to report before an event!")
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
'
ListBox1.Items.Add(e.UserState)
End Sub
End Class

How to use Backgroundworker to load form?

When I try to load a form with a backgroundworker class, it throws an exception because I tried to access an object that was created by another thread, not by the current thread. How to load that form by backgroundworker ?
(I am looking for marquee progress bar while the form is loading)
You can't change any controls in the DoWork section of a backgroundworker since it's a separate thread. You'll want to process any data in the DoWork section, then update your form in the RunWorkerCompleted section.
Example:
Private WithEvents bgwTasks As New BackgroundWorker
Private Sub bgwTasks_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwTasks.DoWork
e.Result= GetDatabaseTable()
End Sub
Private Sub bgwTasks_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgwTasks.RunWorkerCompleted
If e.Result IsNot Nothing Then
DataGridView1.DataSource = e.Result
End If
End Sub
Private Sub Main_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If bgwTasks.IsBusy = False Then
bgwTasks.RunWorkerAsync()
End If
End Sub
This will disable checking for Cross Threads
Me.CheckForIllegalCrossThreadCalls = False
I still suggest you search for Cross Thread regarding the proper use of the BackgroundWorker Class.
There is no problem to initialize the second form in another thread, but if you want to use it in the context on your main form, you have to use original thread. The follows code creates and initializes new form in background worker and then shows it when initialization is completed in appropriate event handler:
Public Class Form1
Dim form2 As Form2
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
form2 = New Form2()
form2.TextBox1.Text = "Text"
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
form2.Show()
End Sub
End Class
You can use ProgressChanged event of background worker for the purpose of report to progress bar.
You cannot do this. Forms have Thread-Affinity. ie. they can ONLY be accessed by the thread that instantiated the Form. You background thread just became the UI thread for the form!
So what you MUST do is marshal the call onto the correct thread.
Test InvokeRequired. If true, instantiate a delegate, call BeginInvoke and IMMEDIATELY return.
Now the call will be marshaled to the correct thread.

Consolidate redundant code in VB.NET

Is there a more elegant or efficient way to write some of my code?
In particular, I have a toolstrip on all of my forms. Each form has its own set of methods that essentially is doing the same thing. Is there a way to delete all of the methods save one, and then just enclose it into a switch statement for my form specific variables?
An example of what I am talking about:
'Form 1
Private Sub ExitToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ExitToolStripMenuItem.Click
Application.Exit()
End Sub
'Form 2
Private Sub ExitToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ExitToolStripMenuItem.Click
Application.Exit()
End Sub
Could I delete one of them, and move the other to a separate module and have it act for both form1 and form2?
Private Sub ExitToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles [myWholeDangProject].ExitToolStripMenuItem.Click
Application.Exit()
End Sub
Yes, use Addhandler and AddressOf to hookup the events when the forms initialize.
AddHandler ExitToolStripMenuItem.Click, AddressOf MyModule.ExitHandler
and
Public Sub ExitHandler(ByVal sender As System.Object, ByVal e As System.EventArgs)
Application.Exit()
End Sub
There is no way to direct a WithEvents event to a shared module. Like you say, though, a shared class or VB.NET module can hold subroutines that can be called from both control event handlers.
You could make a shared event handler, but you have to use the AddHandler keyword to actually attach the code to the event though in most simple cases just a straight call to a sub would be easier.