I have two classes.
Public Class MainForm
Private Project As clsProject
Private Sub btnDo_Click
...
Backgroundworker.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject
End Sub
and two methods inside MainForm
Public Shared Sub setLabelTxt(ByVal text As String, ByVal lbl As Label)
If lbl.InvokeRequired Then
lbl.Invoke(New setLabelTxtInvoker(AddressOf setLabelTxt), text, lbl)
Else
lbl.Text = text
End If
End Sub
Public Delegate Sub setLabelTxtInvoker(ByVal text As String, ByVal lbl As Label)
end class
I want to update the labels of MainForm from the clsProject constructor.
MainForm.setLabelTxt("Getting prsadasdasdasdasdry..", MainForm.lblProgress)
but it does not update them.
What am I doing wrong?
The problem is that you are using the global MainForm instance to access the label in a background thread here:
Public Class clsProject
Public Sub New()
' When accessing MainForm.Label1 on the next line, it causes an exception
MainForm.setLabelTxt("HERE!", MainForm.Label1)
End Sub
End Class
It's OK to call MainForm.setLabelTxt, since that is a shared method, so it's not going through the global instance to call it. But, when you access the Label1 property, that's utilizing VB.NET's trickery to access the global instance of the form. Using the form through that auto-global-instance variable (which always shares the same name as the type) is apparently not allowed in non-UI threads. When you do so, it throws an InvalidOperationException, with the following error message:
An error occurred creating the form. See Exception.InnerException for details. The error is: ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.
I'm guessing that the reason you are not seeing the error is because you are catching the exception somewhere and you are simply ignoring it. If you stop using that global instance variable, the error goes away and it works. For instance, if you change the constructor to this:
Public Class clsProject
Public Sub New(f As MainForm)
' The next line works because it doesn't use the global MainForm instance variable
MainForm.setLabelTxt("HERE!", f.Label1)
End Sub
End Class
Then, in your MainForm, you would have to call it like this:
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject(Me) ' Must pass Me
End Sub
Using the global instance from the background thread is not allowed, but when we use the same label from the background thread, without going through that global variable it works.
So it's clear that you cannot use the global MainForm variable from a background thread, but what may not be clear is that it's a bad idea to use it ever. First, it's confusing because it shares the same name as the MainForm type. More importantly, though, it is a global variable, and global state of any kind is almost always bad practice, if it can be avoided.
While the above example does solve the problem, it's still a pretty poor way of doing it. A better option would be to pass the setLabelTxt method to the clsProject object or even better have the clsProject simply raise an event when the label needs to be changed. Then, the MainForm can simply listen for those events and handle them when they happen. Ultimately, that clsProject class is probably some sort of business class which shouldn't be doing any kind of UI work anyway.
You cannot execute any action on GUI-elements from the BackgroundWorker directly. One way to "overcome" that is by forcing the given actions to be performed from the main thread via Me.Invoke; but this is not the ideal proceeding. Additionally, your code mixes up main form and external class (+ shared/non-shared objects) what makes the whole structure not too solid.
A for-sure working solution is relying on the specific BGW methods for dealing with GUI elements; for example: ProgressChanged Event. Sample code:
Public Class MainForm
Private Project As clsProject
Public Shared bgw As System.ComponentModel.BackgroundWorker
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
bgw = BackgroundWorker1 'Required as far as you want to called it from a Shared method
BackgroundWorker1.WorkerReportsProgress = True
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Project = New clsProject
End Sub
Public Shared Sub setLabelTxt(ByVal text As String)
bgw.ReportProgress(0, text) 'You can write any int as first argument as far as will not be used anyway
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Me.Label1.Text = e.UserState 'You can access the given GUI-element directly
Me.Label1.Update()
End Sub
End Class
Public Class clsProject
Public Sub New()
MainForm.setLabelTxt("Getting prsadasdasdasdasdry..")
End Sub
End Class
Try:
Me.Invoke(...)
instead of lbl.Invoke(.... I had to do this. This is my implementation:
Delegate Sub SetTextDelegate(ByVal args As String)
Private Sub SetTextBoxInfo(ByVal txt As String)
If txtInfo.InvokeRequired Then
Dim md As New SetTextDelegate(AddressOf SetTextBoxInfo)
Me.Invoke(md, txt)
Else
txtInfo.Text = txt
End If
End Sub
And this worked for me.
Related
hello I have a problem to update a progress bar and a label inside a StatusStrip in the main form.
there are 2 controls in the form inside a StatusStrip:
Progressbar (ToolStripProgressBar)
ProgressLabel (ToolStripStatusLabel)
Basically I have this situation:
Public Class Main
Public Sub TEST(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles TEST.DoWork
Dim tmp as New NAMESPACE1.CLASS2(VALUES)
End Sub
End Class
Namespace NAMESPACE1
Public Class CLASS2
Public Sub New(VALUES)
Main.Progressbar.Value = 15
Main.ProgressLabel.Text = "hello!"
End Sub
End Class
End Namespace
The problem is that text or value of the controls are updated (I see it using breakpoints) in the code but not in the form in which progressbar is always a 0% and label always as nothing.
I think it's an update or refresh problem of the main form. i have tried to do Main.Refresh() and Main.Update() but it does not work anyway.
Thanks in advance.
You have 2 issues in play. The first is that Main is a class name, not a runtime reference or object variable. See Idle_Mind's answer for using Me to get the runtime object reference.
The second problem is that since Class2 is created in DoWork, it is created on the background thread, which will prevent it from accessing UI controls (which are created on the UI thread). You will get an illegal cross thread operation exception (even if you dont see it).
I'd suggest that Class2 does nothing useful which can't be done using the ReportProgress method. Getting rid of it also gets rid of the form reference issue since an event is raised on the same thread as the UI controls:
Private WithEvents bgw As BackgroundWorker
...
' in a button click or whatever starts the worker:
bgw = New BackgroundWorker
bgw.WorkerReportsProgress = True
bgw.RunWorkerAsync(5) ' times to loop
...
Private Sub bgw_DoWork(sender As Object,
e As DoWorkEventArgs) Handles bgw.DoWork
' NOTE
' This code executes on a different thread
' so do not reference UI controls!
' e.Argument is the value passed - amount of work
Dim max As Integer = CInt(e.Argument)
For n As Integer = 1 To max
Threading.Thread.Sleep(250) ' emulates work
' causes the ProgressChanged event to fire:
bgw.ReportProgress(n, String.Format("{0} of {1}", n.ToString, max.ToString))
Next
End Sub
Private Sub bgw_ProgressChanged(sender As Object,
e As ProgressChangedEventArgs) Handles bgw.ProgressChanged
'ProgressChanged fires on the UI thread, so it is safe to
' referenece controls here
TextBox4.Text = e.UserState.ToString
TextBox4.Refresh()
End Sub
Paste the code and you can see the message change in the TextBox. The same would work using your ProgressBar and ProgressLabel.
bgw.ReportProgress(n, arg)
The first argument will map to e.ProgressPercentage in the ProgressChanged event. The second is optional - UserState. I used it to pass a string for illustrative purposes (the form can already know the amount of work since it told the BGW what to do.)
If Class2 has some other purpose, you can use it as long as it is created on the UI thread (in the form) and used on that thread (ie in ProgressChanged event). You also need a method to talk to the controls so you dont have to create a new one each time:
Private myObj As Class2 ' declaration
...
myObj = New Class2(Me) ' instance with frm ref
In class2:
Public Sub Update(value As Integer, msg As String)
frmMain.Progressbar.Value = value
frmMain.ProgressLabel.Text = msg
End Sub
Then in the ProgressChanged event:
myObj.Update(x, y)
Where x and y are the value and message from whereever.
Here's an example of passing a reference to MAIN as suggested by Plutonix. I've intentionally left your pseudo-code style intact:
Public Class MAIN
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
TEST.RunWorkerAsync()
End Sub
Private Sub TEST_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles TEST.DoWork
Dim tmp As New NAMESPACE1.CLASS2(Me, VALUES) ' <-- Form reference being passed via 1st parameter
End Sub
End Class
Namespace NAMESPACE1
Public Class CLASS2
Private frmMain As MAIN
Public Sub New(ByVal frmMain As MAIN, VALUES)
Me.frmMain = frmMain
Me.frmMain.Progressbar.Value = 15
Me.frmMain.ProgressLabel.Text = "hello!"
End Sub
End Class
End Namespace
I try using delegate to pass info from dialog form to active MDI child form(not parent form) but it only accepts one data, how to I do this with multiple data like shown in the picture below:
this is I use so far: it only accepts one data from textbox
MDI Child Form:
Private Delegate Sub DoSearch(Msg As String)
Private PerformSearch As DoSearch
Private Sub InvokeFunc(Msg As String)
If PerformSearch IsNot Nothing Then
PerformSearch .Invoke(Msg)
End If
End Sub
Public Sub FuncDisplayMsg(Msg As String)
msg(Msg)
End Sub
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
PerformSearch = AddressOf Me.FuncDisplayMsg
FrmSecond.InvokeDel = AddressOf Me.InvokeFunc
FrmSecond.Show()
End Sub
Dialog Form
Public Delegate Sub SearchInvoke(Msg As String)
Public InvokeSearch As SearchInvoke
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Me.InvokeSearch IsNot Nothing Then
InvokeSearch .Invoke(Me.txtMsg.Text)
End If
End Sub
How do I pass the values of a control(textbox, combobox & checkbox) from a dialog form to an Active MDI Child Form (assuming many MDI Child is open) like shown in a picture, and perform the search within the MDI Child
You seem to be jumping through a few too many hoops. You don't need two delegates. You only need one. For instance, if your dialog window had code like this:
Public Delegate Sub SearchInvoke(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean)
Public Property InvokeSearch As SearchInvoke
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Me.InvokeSearch IsNot Nothing Then
InvokeSearch.Invoke(cboSelection.SelectedItem, txtMsg.Text, chkBox1.Checked, chkBox2.Checked)
End If
End Sub
Then you could simply have code in your main form that looked like this:
Public Sub FuncDisplayMsg(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean)
MessageBox.Show(msg)
End Sub
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.InvokeSearch = AddressOf Me.FuncDisplayMsg
FrmSecond.Show()
End Sub
Using a Class Instead of Multiple Parameters
Alternatively, you could package all of the data in a single object and then send that one object as a parameter to the delegate. For instance, if you had a class like this:
Public Class DialogData
Public Property Selection As Object
Public Property Msg As String
Public Property Chk1 As Boolean
Public Property Chk2 As Boolean
End Class
Then you could define your delegate and call it from the dialog form like this:
Public Delegate Sub SearchInvoke(data As DialogData)
Public Property InvokeSearch As SearchInvoke
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Me.InvokeSearch IsNot Nothing Then
InvokeSearch.Invoke(New DialogData() With
{
.Selection = cboSelection.SelectedItem,
.Msg = txtMsg.Text,
.Chk1 = chkBox1.Checked,
.Chk2 = chkBox2.Checked
})
End If
End Sub
And you could handle the delegate invocation in your main form like this:
Public Sub FuncDisplayMsg(data As DialogData)
MessageBox.Show(data.Msg)
End Sub
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.InvokeSearch = AddressOf Me.FuncDisplayMsg
FrmSecond.Show()
End Sub
Using an Event Instead of a Delegate
Technically, an event is just a special kind of delegate, so they effectively work in the same way. However, the VB.NET syntax for working with events is considerably different than working with standard delegates. Since handling events is commonplace, you may find it more "normal" to implement it as an event rather than a standard delegate. To do that properly, you'd want to make an EventArgs class that contains properties to hold the data (similar to the previously discussed DialogData class), for instance:
Public Class SearchSubmittedEventArgs
Inherits EventArgs
Public Property Selection As Object
Public Property Msg As String
Public Property Chk1 As Boolean
Public Property Chk2 As Boolean
End Class
Then, you could declare and raise the event from the dialog form like this:
Public Event SearchSubmitted As EventHandler(Of SearchSubmittedEventArgs)
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
RaiseEvent SearchSubmitted(Me, New SearchSubmittedEventArgs() With
{
.Selection = cboSelection.SelectedItem,
.Msg = txtMsg.Text,
.Chk1 = chkBox1.Checked,
.Chk2 = chkBox2.Checked
})
End Sub
And then you could handle the event on your main form like this:
Private WithEvents _dialog As frmSecondChild
Private Sub _dialog_SearchSubmitted(sender As Object, e As SearchSubmittedEventArgs) Handles _dialog.SearchSubmitted
MessageBox.Show(e.Msg)
End Sub
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
_dialog = New frmSecondChild()
_dialog.Show()
End Sub
Rather than using the WithEvents and Handles keywords, you could also choose to manually attach the event handler using the AddHandler and keyword. However, if you do that, don't forget to later detach it using RemoveHandler.
Passing a Business Object to the Dialog Instead of a Delegate
Another option would be to forgo having a delegate or event at all, and instead choose to give some business object to the dialog form. The dialog form could then just call a method on that business class to perform the search as needed. For instance, if you created a business class like this:
Public Class SearchBusiness
Public Sub PerformSearch(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean)
MessageBox.Show(msg)
End Sub
End Class
Then you could just call it, as necessary, from the dialog form like this:
Public Property Business As SearchBusiness
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Business IsNot Nothing Then
Business.PerformSearch(cboSelection.SelectedItem, txtMsg.Text, chkBox1.Checked, chkBox2.Checked)
End If
End Sub
And you could show the dialog form from the parent form like this:
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.Business = New SearchBusiness()
FrmSecond.Show()
End Sub
Although, in that case, unless there are different kinds of search business classes which all inherit from SearchBusiness, it seems silly to have the parent form be the thing creating the business object when the dialog could just create it itself. Which leads me to the next option...
Using an Interface to Make the Business Object Interchangeable
Since having the separate business class being used explicitly by the dialog form is a bit constricting, the preferable method, in my mind, would be to create an interface for the search business, like this:
Public Interface ISearchBusiness
Sub PerformSearch(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean)
End Interface
Public Class SearchBusiness
Implements ISearchBusiness
Public Sub PerformSearch(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean) Implements ISearchBusiness.PerformSearch
MessageBox.Show(msg)
End Sub
End Class
Then, you could call it from the dialog form like this:
Public Property Business As ISearchBusiness
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Business IsNot Nothing Then
Business.PerformSearch(cboSelection.SelectedItem, txtMsg.Text, chkBox1.Checked, chkBox2.Checked)
End If
End Sub
And you could give the applicable business object to the dialog from your main form, the same way as above, like this:
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.Business = New SearchBusiness()
FrmSecond.Show()
End Sub
Passing the Parent Form to the Dialog Instead of a Separate Business Object
If, due to an unwise limitation in your design, only the parent form is capable of performing the business logic, then you could give the dialog a reference to the parent form rather than to a separate business object. However, in that case, I would definitely stick with using an interface. That way, you could, at a later date, refactor the code to give the dialog a separate business object that implements the same interface rather than the parent form. You wouldn't want to cement that poor design in stone. So, if you had the same interface as above:
Public Interface ISearchBusiness
Sub PerformSearch(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean)
End Interface
Then you'd still call it from the dialog in the same way, as above:
Public Property Business As ISearchBusiness
Private Sub btnSubmit_Click(sender As Object, e As EventArgs)
If Business IsNot Nothing Then
Business.PerformSearch(cboSelection.SelectedItem, txtMsg.Text, chkBox1.Checked, chkBox2.Checked)
End If
End Sub
Then you could implement the interface in your parent form like this:
Public Class FrmParent
Implements ISearchBusiness
Public Sub PerformSearch(selection As Object, msg As String, chk1 As Boolean, chk2 As Boolean) Implements ISearchBusiness.PerformSearch
MessageBox.Show(msg)
End Sub
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.Business = Me
FrmSecond.Show()
End Sub
End Class
Displaying the Dialog Modally
When a form is displayed modally (i.e. using the ShowDialog method rather than the Show method), that means that execution does not continue in the parent form until the dialog form has been closed. Therefore, if you don't mind the dialog form stealing and holding onto the focus from the user until it is done (which is the typical behavior of a dialog window), then you could just show the dialog form modally and then read its properties once it is closed. For instance, in your main form you could just do something like this:
Private Sub FrmParentLoad(sender As Object, e As EventArgs)
Dim FrmSecond As New frmSecondChild()
FrmSecond.ShowDialog()
MessageBox.Show(FrmSecond.txtMsg.Text)
End Sub
It's not good practice, though, to access controls on another form directly like that. It would be better if the dialog form exposed properties for each datum and then the main form accessed the data through those properties.
This method is by far the simplest. Anywhere you can do it like this, it makes sense. This is, for instance the way the OpenFileDialog, ColorDialog, and other dialogs that are built-in to the .NET framework are designed. This design has one major drawback, though, which can limit its use. If you need to keep the dialog open until the work is complete, then you can't really do it this way. For instance, you may want to display some sort of progress bar on the dialog while the search was being performed. Or, you may want to allow for the fact that some validation error may occur in the business logic at which point you'd want the user to be able to make changes on the dialog and then try again. The latter is of particular concern in cases where the dialog is being used for data entry. For instance, if the dialog was being used to allow the user to submit a new sales order, then you don't want to close the dialog until the sales order has been successfully submitted. If some failure occurs while the data is being saved to the system, then you will likely want to let them fix the problem and then try submitting it again.
This is my situation, there are 2 Classes and my main form Form1:
Class1: has a method doSomethingAndCall(callback) which creates a new thread
Class2: has dynamic created controls with a button that fires Class1.doSomethingAndCall(newCallback)
in code it looks like this (it starts at Class2.Button_Click):
Class Class1
public shared sub doSomethingAndCallAsync(state as object)
Console.WriteLine(Form1.InvokeRequired) 'output: false
Console.WriteLine(Form1.IsHandleCreated) 'output: false
Form1.Invoke(state.callback) 'throws System.InvalidOperationException
end sub
public shared sub doSomethingAndCall(callback as object)
System.Threading.ThreadPool.QueueUserWorkItem(AddressOf doSomethingAndCallAsync, New With {.callback = callback})
end sub
End Class
Class Class2
Public Delegate Sub doSomethingDelegate()
Public Sub doSomething()
Console.WriteLine("success!")
End Sub
Public Sub Button_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Class1.doSomethingAndCall(New doSomethingDelegate(AddressOf doSomething))
End Sub
End Class
The exact exception I get is:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
and as I can see the console.WriteLine in line 4 shows me that the form is realy not created. So I added this handlers, and now it get's really confusing:
Private Sub Form1_HandleCreated(sender As Object, e As System.EventArgs) Handles Me.HandleCreated
Console.WriteLine("Handle created") 'Output: Handle created, when running program
End Sub
Private Sub Form1_HandleDestroyed(sender As Object, e As System.EventArgs) Handles Me.HandleDestroyed
Console.WriteLine("Handle destroyed") 'Will never Output!
End Sub
So it's created and never destroyed but if i click the button it's nevertheless not avaible? -Can anyone explain me what is going on and how to call a callback correct, thanks!
The instance of My.Forms.Form1 aka. Form1 will be different in each thread. You need a handle to the correct instance. Drop a button onto your Form1 and add the following code:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Threading.Tasks.Task.Factory.StartNew(Sub() Class1.Wrong())
Threading.Tasks.Task.Factory.StartNew(Sub() Class1.Correct(Me))
End Sub
End Class
Public Class Class1
Public Shared Sub Wrong()
Debug.WriteLine(String.Format("(Other thread, wrong) InvokeRequired={0}, IsHandleCreated={1}", Form1.InvokeRequired, Form1.IsHandleCreated))
End Sub
Public Shared Sub Correct(instance As Form1)
Debug.WriteLine(String.Format("(Other thread, correct) InvokeRequired={0}, IsHandleCreated={1}", instance.InvokeRequired, instance.IsHandleCreated))
End Sub
End Class
Output
(Other thread, correct) InvokeRequired=True, IsHandleCreated=True
(Other thread, wrong) InvokeRequired=False, IsHandleCreated=False
I have a form declared as s property WithEvents. If I add Handles formServers.FormClosing to a Sub declaration it works fine, but when I want to handle an event of a control within formServers I get the following error -
'Handles' in classes must specify a 'WithEvents' variable.
How do I correctly set this up? Thanks.
Private WithEvents formServers As New formServers
Private Sub txtServers_Closing(ByVal Sender As Object,
ByVal e As EventArgs) Handles formServers.txtServers.LostFocus
Me.SetServers()
If Me.ServersError Then
Dim Ex As New Exception("Error validating Servers.")
Dim ErrorForm = New formError(Ex, 101)
End If
End Sub
The error message is fairly misleading. The Handles keyword has several restrictions, it cannot work across different classes, it needs an object reference. You must use the more universal AddHandler keyword instead.
There are some additional problems in your scenario. Never use the LostFocus event, use Leave instead. And it is very important that you subscribe the event for the specific instance of the form, using As New gets you into trouble when you display the form multiple times, an ObjectDisposedException will be the outcome. Correct code looks like this:
Private formInstance As FormServers
Private Sub DisplayFormServer()
formInstance = new FormServers
AddHandler formInstance.txtServers.Leave, AddressOf txtServers_Closing
AddHandler formInstance.FormClosed, _
Sub()
formInstance = Nothing
End Sub
formInstance.Show()
End Sub
A much more elegant approach is to expose the event explicitly in your FormServers class. Make that look like this:
Public Class FormServers
Public Event ServersLeave As EventHandler
Private Sub txtServers_Leave(sender As Object, e As EventArgs) Handles txtServers.Leave
RaiseEvent ServersLeave(Me, EventArgs.Empty)
End Sub
End Class
The problem is that you do are not specifying WithEvents on the TextBox. Rather, you are specifying WithEvents on the Form. You can only use Handles on variables which you have declared directly with the WithEvents keyword. With the WithEvents being on the form, you will only be able to use Handles to handle events that are raised directly by the form itself. You will not be able to do so for events raised by any of its controls.
You can fix this in one of two ways. Either you can use AddHandler to register your event handler (rather than using the Handles keyword), or you can create a TextBox variable WithEvents and then set it to the appropriate TextBox object on the form, like this.
Private formInstance As New FormServers
Private WithEvents txtServers As TextBox
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
txtServers = formServers.txtServers
End Sub
Private Sub txtServers_LostFocus(Sender As Object, e As EventArgs) Handles txtServers.LostFocus
' ...
End Sub
The advantage of the latter approach, besides the more consistent, and possibly more elegant syntax, is that you don't have to remember to call RemoveHandler.
I have a BackgroundWorker that is used to carry a time consuming process while a form is shown. The form and the BackgroundWorker are in separate classes, and when the BackgroundWorker has finished what it has to do, I need to carry out some basic actions on the form.
However, the below does not work and produces the warning Reference to non-shared member requires an object reference.
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
mainForm.btnCancel.Enabled = False
mainForm.btnFinish.Enabled = True
End Sub
I researched the warning and it suggested that I had to ensure the object mainForm was declared, which for this scenario seems odd from the get go. Regardless, I changed my code to this, and the warning disappeared, but as suspected, it doesn't work. It seems that a new instance of the form would be referenced, which is not what I require.
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
Dim objForm As New mainForm
objForm.btnCancel.Enabled = False
objForm.btnFinish.Enabled = True
End Sub
Can someone please tell me how I can interact with a form from a BackgroundWorker? Thanks.
The problem is not that you need to declare a new mainForm object. The problem is that you need a reference to the right mainForm object. Since it is possible to create any number of mainForm objects, you need a reference to the particular mainForm object that you want to modify. Remember, mainForm is the class (a type of object). It is not, itself, an object.
The simplest way fix this would be to give a reference to the mainForm object to the class that is performing the work, like this:
Public Class MyBusiness
Public Property TheMainForm As mainForm
' ...
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
TheMainForm.btnCancel.Enabled = False
TheMainForm.btnFinish.Enabled = True
End Sub
End Class
Then, before starting the work, you need to make sure you set the TheMainForm property. For instance, something like this:
Dim business As New MyBusiness
business.TheMainForm = Me
business.DoWork()