RaiseEvent triggers a "Debugger.Runtime.CrossThreadMessagingException" - vb.net

I have a class in VB.NET that has a method (called CurrentValue) that returns a number. There is also an event that the class raises to indicate the number has changed. In the event handler on my form, I update a textbox using the exposed method.
Sort of like this:
Public WithEvents MyClass as New CustomClass
Private Sub MyClass_DataChanged() Handles MyClass.DataChanged
Text1.Text = MyClass.CurrentValue
End Sub
When I run this I get a "Debugger.Runtime.CrossThreadMessagingException" error. What could be doing this? I am instantiating MyClass in the same form that contains the textbox.
I can also set properties of the MyClass object without any trouble.

OK, here's what I did:
In the form I have this to handle the event:
Public Delegate Sub MyClassDataChangedDelegate()
Sub MyClassDataChanged() Handles MyClass.DataChanged
If Me.InvokeRequired Then
Me.Invoke(New MyClassDataChangedDelegate(AddressOf MyClassDataChanged))
Else
Me.Text1.Text = MyClass.CurrentValue
End If
End Sub
This seems to work. Thanks for the suggestion.

Related

Listbox breaks when setting it to a virtual instance from a class

I have a weird problem that I can't wrap my head around.
I have the following code:
Public Class Form1
Public WithEvents MyClass1 As New MyClass
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
End Sub
Private Sub MyClass_UpdateListbox() Handles MyClass1.UpdateListbox
For Each sItem as String In MyClass1.Listbox
MsgBox(sItem) 'an MsgBox shows correct items each time.
Next sItem
Me.Listbox = Me.MyClass1.Listbox 'doesn't work and breaks listbox.
Me.Listbox.Items.Clear() 'listbox is empty anyway, but has no effect.
Me.Listbox.Items.Add("event triggered") 'does nothing.
End Sub
End Class
Public Class MyClass
Public Listbox as new Listbox
Public Event UpdateListbox()
Public Sub New()
'Constructor. sub.
Me.AddItem("Populating listbox")
End Sub
Public Sub AddItem(sItem as String)
Me.Listbox.Items.Add(sItem)
RaiseEvent UpdateListbox()
End Sub
End Class
If I comment the following lines in above code, the listbox keeps adding event triggered, as expected. Of course, I don't have to remove the clear one. It will work, but then it just adds the same item. If I use a command button and call MyClass.AddItem("Something") that is correctly added too as long as the below is commented out. But if not, then once the listbox is in broken state, nothing can be added anymore.
Me.Listbox = Me.MyClass1.Listbox 'doesn't work and breaks listbox.
Me.Listbox.Items.Clear() 'listbox is empty anyway, but has no effect.
How can I use a virtual listbox and assign it to my real listbox?
Also, instead of assigning one listbox to the other, I can of course use that for each loop and add each item one by one which works, but that for each look was for debugging purpose in the first place.
EDIT:
My goal with this application is to build a Todo list with features that are not in a todolist. This is a project I build for work because there I need a tool like this. I already have a todolist that I use but I built it wrong in the past. Everything was condensed in form1, no modules no extra classes. As a result I got weird bugs that I patched with workarounds. I am now rebuilding the application from the ground up, separating tasks in its own classes so I can apply business logic and have a true OOP application. The todo list will become its own class, and managing the list etc will be handeled by this class. It interacts with controls on the form, such as buttons and listboxes. If I just use form1.listbox from the class, things break at program start. I started another question and the below code was a now deleted answer. At first I did not get it working because I did not realize the listbox crashes if I assign it the virtual instance.
So my goal is to have the todolist be handled entirely by the todolist class. It does need a way to interact with controls on form1, and that is the puzzle I'm currently trying to solve.
In the original code, the main problem is that the Field that hold the instance of a Control shown if a Form is reassigned to the instance of another ListBox Control defined in a custom class:
Me.Listbox = Me.MyClass1.Listbox
From now on, Me.Listbox points another ListBox that is not show on screen, so any attempt to update the Form's child ListBox fails, except when Me.Listbox.Items.Clear() is called - in the same procedure - after it's being reassigned, because the handle of the Owner of the ObjectCollection (the object that holds the Items shown in the ListBox) has not been updated yet. It's going to fail after the current method exits nonetheless.
As noted in comments, this is a simplified method to handle a Form and its child Controls using a handler class. The contract between the class handler and a Form is sealed by an Interface (named IFormHandler here).
A Form that implements this Interface exposes the methods defined by the Interface that allow to trigger Actions and specific behaviors, depending on the Type of Control and the implementation.
I suggest to take a look at the MVP or ReactiveUI (MVVM-derived) for WinForms Patterns.
How too proceed:
Open up the ApplicationEvents class object.
If you don't have it already, select Project -> Properties -> Application and click the View Application Events button. It will generate ApplicationEvents.vb. Find it in Solution Explorer and open it up.
It should look like this (plus a bunch of comments that explain what it's for):
Imports Microsoft.VisualBasic.ApplicationServices
Namespace My
Partial Friend Class MyApplication
End Class
End Namespace
Paste into MyApplication these lines of code:
Imports Microsoft.VisualBasic.ApplicationServices
Namespace My
Partial Friend Class MyApplication
Public SomeFormHandler As MyFormHandler(Of SomeForm)
Protected Overrides Function OnStartup(e As StartupEventArgs) As Boolean
SomeFormHandler = New MyFormHandler(Of SomeForm)
Return MyBase.OnStartup(e)
End Function
End Class
End Namespace
Add an Interface that defines the Actions (or Behaviors) that a Form must implement.
Here, the GetUsersList() method specifies that a Form that implements this Interface must return the instance of a child ListBox Control.
(To add an Interface, select Project -> Add -> New Item... and select the Interface template. Name the file IFormHandler)
Extend this Interface as needed, to add more Methods or Properties that define actions and behaviors.
Public Interface IFormHandler
Function GetUsersList() As ListBox
End Interface
A Form that implements the IFormHandler Interface implements and exposes the GetUsersList() method, which returns the instance of a ListBox Control (named usersList here)
There's nothing else to do with this Form, the control is handed over to the MyFormHandler object that is initialized with this Type.
Public Class SomeForm
Implements IFormHandler
Public Sub New()
InitializeComponent()
End Sub
Public Function GetUsersList() As ListBox Implements IFormHandler.GetUsersList
Return Me.usersList
End Function
End Class
Now, to show SomeForm, you can use the MyFormHandler class object show below.
' Set the Owner if called from another Form
My.Application.SomeFormHandler.Show(Me)
' Or without an Owner
My.Application.SomeFormHandler.Show()
To close SomeForm, you can either use its handler:
My.Application.SomeFormHandler.Close()
or close it as usual:
[SomeForm Instance].Close()
If MyFormHandler determines that the instance of SomeForm has been disposed, it creates a new one when you call its Show() method again later.
To update the ListBox Control of SomeForm, use the public methods exposed by the MyFormHandler class:
' Add a new element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.AddElement, "Some Item")
' Remove an element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.RemoveElement, "Some Item")
' Replace an element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.ReplaceElement, "New Item", "Some Item")
' Clears the ListBox
My.Application.SomeFormHandler.ClearUsersList()
All these actions generate an event that you can subscribe to when needed.
See also the example that shows how to raise a custom event when the ListBox raises one of its stardard events; SelectedIndexChanged is handled here.
See the implementation of MyFormHandler.
Generic Form handler:
A Form needs to implement the IFormHandler Interface for the MyFormHandler class to accept it as valid.
You can of course extend the Interface, to add more Actions, or build a MyFormHandler class object that uses a different Interface, or more than one.
Public Class MyFormHandler(Of TForm As {Form, IFormHandler, New})
Implements IDisposable
Private formObject As TForm
Private IsInstanceSelfClosing As Boolean = False
Public Event UsersListUpdate(item As Object, changeType As UpdateType)
Public Event UsersListIndexChanged(index As Integer)
Public Sub New()
InitializeInstance()
Dim lstBox = formObject.GetUsersList()
AddHandler lstBox.SelectedIndexChanged, AddressOf OnUsersListIndexChanged
End Sub
Private Sub InitializeInstance()
formObject = New TForm()
AddHandler formObject.FormClosing, AddressOf OnFormClosing
End Sub
Private Sub OnFormClosing(sender As Object, e As FormClosingEventArgs)
IsInstanceSelfClosing = True
Dispose()
End Sub
Public Sub UpdateUsersList(updateMode As UpdateType, newItem As Object, Optional oldItem As Object = Nothing)
If newItem Is Nothing Then Throw New ArgumentException("New Item is null")
Dim lstBox = formObject.GetUsersList()
Select Case updateMode
Case UpdateType.AddElement
lstBox.Items.Add(newItem)
Case UpdateType.RemoveElement
lstBox.Items.Remove(newItem)
Case UpdateType.ReplaceElement
If oldItem Is Nothing Then Throw New ArgumentException("Replacement Item is null")
Dim index = lstBox.Items.IndexOf(oldItem)
lstBox.Items.Remove(oldItem)
lstBox.Items.Insert(index, newItem)
Case Else : Return
End Select
RaiseEvent UsersListUpdate(newItem, updateMode)
End Sub
Public Sub ClearUsersList()
formObject.GetUsersList().Items.Clear()
End Sub
Private Sub OnUsersListIndexChanged(sender As Object, e As EventArgs)
RaiseEvent UsersListIndexChanged(DirectCast(sender, ListBox).SelectedIndex)
End Sub
Public Sub Show(Optional owner As IWin32Window = Nothing)
If formObject Is Nothing OrElse formObject.IsDisposed Then InitializeInstance()
If formObject.Visible Then
formObject.WindowState = FormWindowState.Normal
formObject.BringToFront()
Else
formObject.Show(owner)
End If
End Sub
Public Sub Close()
If formObject IsNot Nothing AndAlso (Not formObject.IsDisposed) Then
RemoveHandler formObject.FormClosing, AddressOf OnFormClosing
IsInstanceSelfClosing = False
Dispose()
End If
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overridable Sub Dispose(disposing As Boolean)
If disposing Then
If formObject Is Nothing OrElse formObject.IsDisposed Then Return
Dim lstBox = formObject.GetUsersList()
RemoveHandler lstBox.SelectedIndexChanged, AddressOf OnUsersListIndexChanged
RemoveHandler formObject.FormClosing, AddressOf OnFormClosing
If Not IsInstanceSelfClosing Then formObject.Close()
IsInstanceSelfClosing = False
End If
End Sub
End Class
Enumerator used in MyFormHandler:
Public Enum UpdateType
AddElement
RemoveElement
ReplaceElement
End Enum

Best Practices - Form Class Receiving Message from Class Modules

Hoping to get some best-practice advise with regards to capturing a returned message from an instantiated class on my form.
In my form (form1.vb), I have a label which reflects what is being done, with the code below.
Code in form1.vb to display message:
Public Sub DisplayMessage(ByVal Msg as String, ByVal Show as Boolean)
Application.DoEvents()
If Show Then
lblShow.Text = Msg
lblShow.Refresh()
End If
End Sub
I have came across three methods so far:
Direct Form Call. In this scenario the class directly calls the form's message routine:
form1.DisplayMessage("Show This Message", True)
RaiseEvent within class. In this scenario form1 is Friends WithEvents of the class sending the message, and the class raises the event to the form.
**Declared in Form1.vb**
Friend WithEvents Class1 as New Class1
**Declared in Class1.vb**
Public Event SetMessage(ByVal Msg As String, ByVal Show As Boolean)
**Used in Class1.vb**
RaiseEvent SetMessage("Show This Message", True)
Have an EventArgs class handle the event. In this scenario we have an EventArg.vb class which is instantiated whenever we raise the event.
**Declared in Form1.vb**
Friend WithEvents Class1 as New Class1
Private Sub class1_DisplayMessage(ByVal Msg As String, ByVal showAs Boolean, ByRef e As ProgressMessageEventArgs) Handles Class1.SetMessage
DisplayMessage(Msg, Show)
End Sub
**Declared in Class1.vb**
Public Event SetMessage(ByVal msg As String, ByVal Show As Boolean, ByRef e As ProgressMessageEventArgs)
Protected Sub CaptureMessage(ByVal msg As String, ByVal Show As Boolean)
RaiseEvent SetMessage(message, ShowList, New ProgressMessageEventArgs(message))
End Sub
**Used in Class1.vb**
RaiseEvent CaptureMessage("Show This Message", True)
**EventArg.vb created to handle ProgressMessageEventArgs class**
Public NotInheritable Class ProgressMessageEventArgs
Inherits System.EventArgs
Public txt As String
Public Sub New(ByVal txt As String)
MyBase.New()
Me.Text = txt
End Sub
End Class
Scenario 1 is seemingly the simplest, though I was advised against this and asked to raise an event instead. Over time I came across scenario 3 which involves an additional class vs scenario 2.
Therefore, the question is...
Between these three methods, which would be the "proper" way of returning a message from a class to the form? Is the additional EventArg class as per scenario 3 necessary since scenario 2 works fine as well?
Many thanks in advance.
My answer is none of the above. Consider this example
Public Class Form1
Private WithEvents myClass1 As New Class1()
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
myClass1.CountTo1000()
End Sub
Private Sub MyClass1_Updated(number As Integer) Handles myClass1.Updated
Me.Label1.Text = number.ToString()
End Sub
End Class
Public Class Class1
Public Event Updated(number As Integer)
Public Sub CountTo1000()
For i = 1 To 1000
System.Threading.Thread.Sleep(1)
RaiseEvent Updated(i)
Next
End Sub
End Class
You have a form and a class, and the form has a reference to the class (the class doesn't even know the form exists). Your business logic is performed in the class, and the form is used to input and display information. CountTo1000() is being called directly from the form, which is bad because basically the UI thread is being put to sleep 1000 times, while the class is trying to update the UI by raising the event after each sleep. But the UI never has time to allow the events to happen, i.e. to be updated. Placing an Application.DoEvents() after Me.Label1.Text = number.ToString() will allow the UI to update. But this is a symptom of bad design. Don't do that.
Here is another example with multi-threading
Public Class Form1
Private WithEvents myClass1 As New Class1()
' this handler runs on UI thread
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' make a new thread which executes CountTo1000
Dim t As New System.Threading.Thread(AddressOf myClass1.CountTo1000)
' thread goes off to do its own thing while the UI thread continues
t.Start()
End Sub
' handle the event
Private Sub MyClass1_Updated(number As Integer) Handles myClass1.Updated
updateLabel(number.ToString())
End Sub
' invoke on UI thread if required
Private Sub updateLabel(message As String)
If Me.Label1.InvokeRequired Then
Me.Label1.Invoke(New Action(Of String)(AddressOf updateLabel), message)
Else
Me.Label1.Text = message
End If
End Sub
End Class
Public Class Class1
Public Event Updated(number As Integer)
Public Sub CountTo1000()
For i = 1 To 1000
System.Threading.Thread.Sleep(1)
RaiseEvent Updated(i)
Next
End Sub
End Class
This simple example shows how a thread can be created and run some code off the UI. When doing this, any method call from the non-UI thread must be invoked on the UI if it must access a UI control (Label1). The program runs smoothly since the Thread.Sleep is done on a different thread than the UI thread, with no need for Application.DoEvents, because the UI thread is otherwise doing nothing, and can handle the events being raised by the other thread.
I focused more on threading, but in both examples the design has a form with a class, and the form knows about the class, but the class doesn't know about the form. More about that can be seen here.
See also:
Why we need to check for InvokeRequired, then invoke: Control.InvokeRequired
A better option than Thread nowadays: BackgroundWorker
An even cooler option, if you can wrap your head around it: Async/Await

I want main class event to be raised in supported form

I have public class, with event declared in it.
And have procedure which shows modal form.
The form has buttons, on whichs click I want the main event to be raised. How to achieve this?
for example:
Public class MyClass
Public Event MyEvent(arg as date)
Public Sub ShowForm()
using frm as new frmMyForm
frm.showDialog()
End Using
....
End Class
On forms button click event
RaiseEvent MyClass.MyEvent(Today)
I have passed egzemplar of class as property, but nothing helped.
I have solved problem. I have really created method in the main class with date parameter. In button click I'm calling above method of passed egzemplar with desired argument. and then raising event in a class. it works fine now.
In my situation code is more harder, therefore I provide only a simple example instead.
Public class MyClass
Public Event MyEvent(arg as date)
Public sub FromOtherPlanet(dt as Date)
RaiseEvent MyEvent(dt)
End sub
Public Sub ShowForm()
using frm as new frmMyForm
frm.Egz = Me
frm.showDialog()
End Using
End Sub
....
End Class
in the form
Public class MyForm
dim Temp_Egz as MyClass
public property Egz as Myclass
Get
.......
Set
....
End Property
private sub button1_click.....
egz.FromOtherPlanet(today)
end sub
end class

Catch event from item stored in a dependency property

How do you catch an event from an object raised in a dependency property?
For example, normally I'd write something like this
Public Property foo1 as foo
get
return _foo
end get
set (value as foo)
_foo = value
end set
end property
Private WithEvents _foo as foo
Public Sub Foo_Handler() Handles _foo.SomeEvent
'Do soemthing
End Sub
However you can't declare a dependency property as WithEvents. Any ideas
Here is the solution I came up with. I don't really like it and it doesn't feel right, but it works. If someone has a better solution please provide it as an answer. To provide a little more information about my use case, I have a dialog named OkCancelDialog. It's basically just a placeholder to present content to the end user. When the user clicks Ok, the Save method of ISaveView is called and conversely when the Cancel button is clicked, it calls the Cancel method. The problem I ran into is that the save method usually has to save the item asynchronously. Originally I had marked the save method as a function that returned a boolean (which closed the dialog if true), but the problem was that if something went wrong during the save, the OkCancelDialog would already be closed. So now, I call the Save method and provided everything saves properly in the View, it raises the event CloseView, which I then capture in the OKCancelDialog to close the dialog.
I would love to hear other options for accomplishing this.
Interface ISaveView:
Public Interface ISaveView
Sub Cancel()
Sub Save()
Event CloseView()
Event HasChanges()
End Interface
Class with DP:
DP:
Public ViewProperty As DependencyProperty = DependencyProperty.Register("View", GetType(ISaveView), GetType(OkCancelDialog), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnViewChanged)))
CLR Property:
#Region "View"
Public Property View As ISaveView
Get
Return DirectCast(GetValue(ViewProperty), ISaveView)
End Get
Set(value As ISaveView)
SetValue(ViewProperty, value)
End Set
End Property
Public Sub OnViewChanged()
_view = View
OnPropertyChanged("View")
End Sub
Private WithEvents _view As ISaveView
Private Sub View_HasChanges() Handles _view.HasChanges
okButton.IsEnabled = True
End Sub
Private Sub View_CloseView() Handles _view.CloseView
Me.Close()
End Sub
#End Region

Why is a value copy of MainForm created when method is called or invoked cross thread?

Update: I think it has something to do with lazy instantiation of the window handle for MainForm - but haven't been able to work out quite how that would result in the behavior seen here.
The application requests data via 3rd party COM interface providing a callback to process the results. In the callback, the UI needs to be updated - but the update doesn't work as expected. It's as if a value copy of MainForm had been created, when MainForm.DataReady is called or invoked directly cross thread, but UI update works as expected when executed from an event handler. Can you explain why?
(Note: AppDomain.CurrentDomain.Id is always 1 whether examined in MainForm or in ClassB.)
Initial Code - call to DataReady from ClassB instance without InvokeRequred /Delegate /Invoke logic in MainForm. Application UI change works as expected, MainForm SomeListControl.EmptyListMsg = "Not Available" change doesn't 'stick' (as if applied to a separate copy of MainForm)
Module AppGlobals
Public WithEvents A As ClassA
End Module
Partial Friend Class MyApplication
Private Sub MyApplication_Startup(ByVal sender As Object,
ByVal e As StartupEventArgs) Handles Me.Startup
A = New ClassA()
End Sub
End Class
Class MainForm
private sub getData
ToggleWait(True)
SomeListControl.Clear()
A.getData() 'Sets up the com object & callback
end sub
Public Sub DataReady()
ToggleWait(False)
' Do something with the data
End Sub
Private Sub ToggleWait(toggle as Boolean)
Application.UseWaitCursor = False
if toggle then
SomeListControl.EmptyListMsg = "Not Available"
else
SomeListControl.EmptyListMsg = "Please Wait"
end if
End Sub
End Class
Class ClassA
public sub getData()
Dim ComObj as New ComObject
Call ComObj.setClient(New ClassB)
End Sub
End Class
Class ClassB
Implements IComObjectClient
sub getdata_callback(results() as Object) handles IComObjectClient.getdata_callback
' Get the results
MainForm.DataReady()
end sub
End Class
Added InvokeRequred logic to DataReady, still called directly from ClassB. InvokeRequired is never true, Application UI change works as expected, MainForm SomeListControl.EmptyListMsg = "Not Available" change doesn't 'stick' (as if applied to a separate copy of MainForm)
Class MainForm
Public Delegate Sub DataReadyDelegate(ByVal toggle As Boolean)
...
Public Sub DataReady()
If InvokeRequired Then
Invoke(New DataReadyDelegate()
Else
ToggleWait(False)
' Do something with the data
End If
End Sub
...
End Class
Invoked MainForm.DataReady directly from ClassB Got exception: "Invoke or BeginInvoke cannot be called on a control until the window handle has been created." until I forced the window handle creation. Then it's the same behavior as before, namely, InvokeRequired is never true, Application UI change works as expected, MainForm SomeListControl.EmptyListMsg = "Not Available" change doesn't 'stick' (as if applied to a separate copy of MainForm)
Class ClassB
Implements IComObjectClient
Public Delegate Sub DataReadDelegate()
sub getdata_callback(results() as Object) handles IComObjectClient.getdata_callback
' Get the results
If Not MainForm.IsHandleCreated Then
' This call forces creation of the control's handle
Dim handle As IntPtr = MainForm.Handle
End If
MainForm.Invoke(New DataReadyDelegate(AddressOf MainForm.DataReady))
end sub
End Class
Executed from Event Handler Defined custom 'got data' events in ClassA and ClassB. ClassA listens for ClassB.got_data_event and raises ClassA.got_data_event, MainForm listens for ClassA.got_data_event and handles it by calling DataReady(). This works - InvokeRequired is true, Invoke is excuted, Application UI and MainForm UI changes work as intended.
Class MainForm
Public Delegate Sub DataReadyDelegate()
...
Public Sub DataReady()
If InvokeRequired Then
Invoke(New DataReadyDelegate()
Else
ToggleWait(False)
' Do something with the data
End If
End Sub
Public Sub _GotData_HandleEvent(ByVal resultMessage As String)
DataReady()
End Sub
Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles Me.Load
...
ToggleWait(False)
AddHandler A.GotData, AddressOf _GotData_HandleEvent
...
End Sub
...
End Class
Contrast:
A.getData()
with:
If Not MainForm.IsHandleCreated Then
You are using proper object-oriented programming syntax in the first statement. A is an object. The Form.IsHandleCreated property is an instance property, it requires an object name at the left side. You however used a type name. MainForm is not an object, it is a type in your code.
That this is possible is a very nasty VB.NET feature. It exists to help VB6 programmers move to VB.NET coding, VB6 strongly encouraged using the form's type name. Syntax inherited from VB1 before VB4 implemented anything resembling objects.
Now this is most certainly a convenience. You can refer to the form object in another class by simply using the type name. Note how you did not have that convenience with the A object. You solved it by making it a global variable, storing it in a Module. That doesn't win any prices either, but did allow you to reference A in any class.
Problem is, this convenience turns deadly when you start using the fake form object in another thread. What you didn't count on is that this object has <ThreadLocal> scope. In other words, when you use it in a worker thread then you get a new object of class MainForm. This form object is not visible, you never called its Show() method. Not that this would work, the thread does not pump a message loop so that form won't paint itself properly. Another side effect you observed is that its InvokeRequired property doesn't behave. It returns False. Correctly so, the form was created on the work thread so you don't actually have to use BeginInvoke(). Not that this would work either, it is still the wrong object, not the one that the user is looking at.
So one Q&D workaround is to do the same thing with the form object as you did with the A object, store it in a global variable:
Module AppGlobals
Public WithEvents A As ClassA
Public MainWindow As MainForm
End Module
And initialize it from the class constructor:
Class MainForm
Sub New()
InitializeComponent()
MainWindow = Me
End Sub
'' etc..
End Class
Now you can refer to MainWindow in your classes. And you get a reference to the actual instance of MainForm class that the user is looking at. And get the proper return value from MainWindow.InvokeRequired.
This will solve your problem, but it is still ugly and error prone. The right way looks like this:
Public Class MainForm
Private Shared MainWindow As MainForm
Public Shared ReadOnly Property Instance() As MainForm
Get
'' Return a reference to the one-and-only instance of MainForm
If MainWindow Is Nothing Then
'' It doesn't exist yet so create an instance
'' Creating one on a worker thread will never work, so complain
If System.Threading.Thread.CurrentThread.GetApartmentState() <> Threading.ApartmentState.STA Then
Throw New InvalidOperationException("Cannot create a window on a worker thread")
End If
New MainForm()
End If
Return MainWindow
End Get
End Property
Protected Overrides Sub OnFormClosed(ByVal e As System.Windows.Forms.FormClosedEventArgs)
'' Ensure that the one-and-only instance is now Nothing since it closed
MyBase.OnFormClosed(e)
MainWindow = Nothing
End Sub
Sub New()
'' Creating more than once instance of this form can't work, so complain
If MainWindow IsNot Nothing Then Throw New InvalidOperationException("Cannot create more than one instance of the main window")
InitializeComponent()
'' We need to keep track of this instance since the Instance property returns it
MainWindow = Me
End Sub
'' etc...
End Class
Now you can use MainForm.Instance anywhere in your classes, like MainForm.Instance.InvokeRequired. And you'll be reminded when you get it wrong with an exception.