I always wanted to post an article in CodeProject. I was always "taking" not "giving" anything back to this community and that made me feel guilty. I think its time to "give" back something useful to this community.
Introduction
Throughout my humble experience with COM and .NET interoperability (let's narrow it to VB6, and further to COM client and .NET objects!), I have only found too few resources on how to handle forms between the two technologies�rather than methods and properties. Issues like accessing the database through different forms�causing .NET forms to be closed upon closing the main form in VB6 (.NET forms won't close upon closing the main form in VB6 in the ordinary state!)� and handling the events raised in .NET forms with methods in VB6� all these consumed considerable time and effort with some irritating errors!
In this article, I will try to explain how to handle the last two issues and leave the first one for the readers (maybe only suggest making the .NET form have its own DB connection rather passing the data from VB6 to .NET form).
First of all, we need to understand how exactly we can hook VB6 objects (forms here) to events raised in .NET forms, and then we can solve the issue of closing .NET forms when we close the VB6 main form.
Hooking VB6 forms to events raised in .NET forms
The main idea
Note: The credit for handling events this way goes to Adam Nathan's book ".NET and COM the Complete Interoperability Guide". We will not be using the connection point VB6 provides for automatic event handling. To handle events this way we need to declare the .NET forms in VB6 code with the "WithEvents
" keyword. As you know, this keyword can�t be used within methods; only in the declaration part, and many of you, I bet, will need to declare and create .NET forms within methods at runtime whenever you need one, rather than have fixed number of .NET forms in the declaration part of VB6.
The main idea is to create in each .NET form a Hashtable
. This Hashtable
will hold references to every VB6 object (specifically Form) you wish to handle events for. Now when the .NET event is raised a method within the .NET form (an event handler) will loop through each item in the Hashtable
(which represents the reference to the VB6 form) and call the proper method (the VB6 handler) through the reference.
An interface containing event handlers
Of course, by now you will conclude that each COM object (VB6 form in our case) in the Hashtable
should have some methods and these methods should have the same name and same signature as each of the objects in the Hashtable
. OK, now we can make an interface containing handlers (some methods) for the .NET Form�s events, which each COM object wishes to handle.
public interface IDotNetEventsHandler
{
void NewFormCreated( ref DotNetForm sender );
}
As the name of the event handler implies, the VB6 form (the COM client, the main form) wants to be informed about each DotNetForm
newly created (of course, we can make it a "Form
" rather than "DotNetForm
", but then we need to reference the .NET Framework main DLL in the VB6 application. For the sake of simplicity, we will stick to DotNetForm
). The parameter is a reference to the newly created DotNetForm
object.
Hooking the VB6 form to the event handlers
Now, to hook the VB6 form to the .NET form events, we define an interface for the .NET form to implement. The interface will contain two methods: Add
and Remove
.
public interface IDotNetEventsHookup
{
int Add(IDotNetEventsHandler comObject);
void Remove(int ComObjectIndex);
}
The Add
method will take an object that implements the interface IDotNetEventHandler
, in our case it will be the VB6 form. In this method, a reference of the VB6 form will be added to the Hashtable
, to be notified of any new DotNetForm
created in this .NET form. The reference will be assigned to a key to distinguish it. The Remove
method simply removes the reference from the Hashtable
.
This code in the VB6 application shows how these methods are used:
Dim netForm As NetEvents.DotNetForm
Set netForm = New NetEvents.DotNetForm
Dim Ihookup As NetEvents.IDotNetEventsHookup
Set Ihookup = netForm
Dim cookie As Long
cookie = Ihookup.Add(Me)
We now have the VB6 form hooked to the DotNetForm
events (it's being referenced in the Hashtable
).
This code will be executed when the event is raised in .NET; its main goal is to traverse all references of VB6 forms in the Hashtable
to call the proper methods for this event (the event handler):
private void OnNewFormCreated()
{
for(int counter=1; counter<=this.comObjects.Count; counter++)
{
IDotNetEventsHandler obj =
(IDotNetEventsHandler)comObjects[counter];
DotNetForm form = new DotNetForm();
form.Show();
obj.NewFormCreated( ref form );
}
}
Note: Of course, the new DotNetForm
s should be created outside of the event handler and then passed to the event handler, but if we do this the VB6 event handler will confuse the reader so, we will keep it like this and leave the alteration process to the reader.
Closing .NET forms by closing the main VB6 form
Now after learning (hopefully) how the events are handled, the rest is really simple. All we need is a dynamic array in VB6 form to hold DotNetForm
references passed to the event handlers (that are newly created), and then hook this object to the passed DotNetForm
s.
Private Sub IDotNetEventsHandler_NewFormCreated(_
sender As NetEvents.DotNetForm)
FormsCounter = FormsCounter + 1
ReDim Preserve DotNetForms(FormsCounter)
Set DotNetForms(FormsCounter) = sender
Dim Ihookup As NetEvents.IDotNetEventsHookup
Set Ihookup = sender
Dim cookie As Long
cookie = Ihookup.Add(Me)
MsgBox ("Created " + sender.Text)
End Sub
And then traverse these references to close them as needed (usually in the QueryUnload
method in VB6 form):
Dim tempForm As NetEvents.DotNetForm
While FormsCounter >= 1
Set tempForm = DotNetForms(FormsCounter)
If Not tempForm Is Nothing Then
tempForm.Close
End If
FormsCounter = FormsCounter - 1
Wend
Conclusion
In each .NET form we hold references of all the COM objects that want to handle a particular event raised by the .NET form. Then, when the event is raised in a private event handler (not exposed) we traverse the COM object's references and call the proper methods. As you can see, we don't expose the .NET events to the COM object; it only uses those events to call the proper methods.
Using this we can inform the parent VB6 form of all the newly created .NET forms and store references of those in a dynamic array, so when we close the parent VB6 form we traverse all the .NET forms created that can be closed using the Close
method.