I am trying to declare a variable in a VB6 module as follows:
Public WithEvents MyObject As MyClass
The help files say that WithEvent
A Case for One Simple Approach
It is sometimes very useful for a Class or even a UserControl (which is really just a special sort of Class) to have properties and methods of wide general use throughout a program.
If the main concern is wide use of "utility" methods that are stateless (e.g. someting Web related that has methods for URL encoding, entity encoding, etc.) it can be fairly cheap just to have an additional global instance declared "without" events that you don't cause to load up heavy internal state (arrays, Collections, etc.). Since your program needs all of the code anyway and only one copy sits in memory the second instance can be fairly cheap.
Another option is to factor those "common" operations and even property values out into a separate Class or static (BAS) module.
But sometimes you have a fairly complex UserControl or Class that you don't want to customize by refactoring into separate modules or otherwise alter. Maybe you maintain a common, standard versions for use in different projects. Maybe it's a 3rd party library you don't even have the source for. Whatever.
Something to Consider
This brings us to another technique that might work for you even though in the immortal words of Irving Mainway it's "not for blind kids." If you are experienced enough to be using this then it should already have occurred to you - but none of us has perfect memory (if indeed we have ever taken the time to Read That Fine Manual).
So perhaps this won't work for you for some reason and you've already considered and discarded the idea.
Your container object (Form, UserControl, parent Class, etc.) can carefully set a global reference to the instance of the object in question when it initializes and remove the reference when it terminates. This should be done with some care to avoid circular or orphaned references that can keep your other objects or even your entire program from unloading.
This should not be taken likely or used carelessly by Johnny GoTo. But if you know what you are doing in VB and have actually read and understood the parts of the manual that discuss this very technique and its pitfalls it can be useful.
Say you have some Form1 as your program's Startup Object. In Form1 you have an instance of a UserControl "WebWiz" you wrote, named WebWiz1. But elsewhere (another three or twelve Forms?) you want to call a WebWiz method that converts a set of common MIME type string values to file extensions. Only Form1 needs to handle WebWiz1's events, and WebWiz is fairly heavy on internal state so you don't want to create additional instances just for this method.
In a static module you can simply declare a:
Public gWebWiz As WebWiz
When Form1's Initialize or Load events run (or for a Class, after you create the WithEvents
instance) you can:
Set gWebWiz = WebWiz1
In Form1's Unload event handler:
Set gWebWiz = Nothing
Then your program has a global reference it can use for miscelleneous purposes.
Looks easy right?
Well if your programs using this are written as a proper "tree" of code starting at a Sub Main
or a main Form you generally don't have to exercise much extra caution. But if you are one of those people who code like the Pinball Wizard and find yourself asking questions like "How do I find all of my open Forms and unload them?" then you:
I'm not sure how large your application is, but there are several ways to do this. Here's what I would do, though this may take you 15 minutes or so to refactor your app to use it.
First copy all of the Public and Global methods and members from your Module into a class called (for example) clsApplication.
Second, in the now-empty Module (lets call it modGlobal), declare Public Property Get Application() As clsApplication
. Go ahead and add a setter too.
Third, in your Sub Main()
that starts your program, add this as your first line Set modGlobal.Application = New clsApplication
.
Now you have replaces your module with a global class that can listen to events that occur on an application-wide basis. In the rest of your app, you can acccess your global state like this Application.Config
or Application.GetUser()
or whatever you else you keep at the global level.
You could of course apply this to just the variable that you wish to use WithEvents on, but you should really look to getting your code out of modules anyway.
Just saw the comments to @Mark. The minumal approach is what I refrered to last. If you event class is MyEventSource then create a class called MyEventSourceListener with a property called Target
that you pass the object into and is declared privately WithEvents. Then MyEventSourceListener can receive the events and forward them back to your module.
I don't like it because its a hack and puts the code back into the module, but it might be the most expedient approach.
Events in VB are a wrapper around a fairly complicated COM mechanism. Essentially in this mechanism, everything involved has to be a COM class implementing the interfaces IConnectionPoint or IConnectionPointContainer. A BAS module is a part of legacy BASIC, and is not implemented as a COM class, unlike the VB class. I suppose that they could have reimplemented a BAS file in COM as a singleton type object (like they do with Global MultiUse classes), but they didn't. So, unfortunately BAS files cannot use WithEvents.
As for the simplest solution to your problem - you need to take advantage of the fact that object variables are just references to the object, and not the object themself. I imagine you want your event messages to get to the highest level object that is necessary, in order for your to have the means to control the bit of the application you are interested in. Identify this place. For instance, it could be that you have a main form that you are sure will be the first object loaded, and the last unloaded. As part of initialisation of this object, pass in a reference to your global object, and set this to a WithEvents module level variable. You are now in control.
BUT!! And this is very important!! You must ensure that you clear down this reference in good time before the application closes, just in case you have any circular references to your form (or whatever). The Form_Unload event would be ideal in this case.
Write a class that accepts your global object as a parameter and sinks its events.
' Class MySink
Private WithEvents m_oSink As MyClass
Friend Sub frInit(oSink As MyClass)
Set m_oSink = oSink
End Sub
Private Sub m_oSink_MyEvent()
'--- implement event
End Sub
Create an instance of this class in your .bas
module.
Public g_oMyObject AS MyClass
Private m_oMySink As MySink
Sub Main()
Set g_oMyObject = New MyClass
Set m_oMySink = New MySink
m_oMySink.frInit g_oMyObject
End Sub