Somebody asked an interesting question today on how to automate a Windows Form from a COM client application. .NET doesn’t support building EXE COM servers so it’s not as easy as it was in say Visual FoxPro or VB to simply create a COM public form and have it exposed as a COM object and fire away at the methods and properties.
I was actually thinking about this scenario as it came up because I have been toying with the idea of moving Help Builder to .NET for a while now and keep hitting a few areas that are, uh, shall we say less than optimal for a port. This is another issue that complicates matters.
Well, while .NET can’t create COM Exes it can create Windows Forms in DLLs and you can actually access these forms directly if you compile your DLL to be COM creatable (Register for COM Interop):
[ClassInterface(ClassInterfaceType.AutoDual)]
[ProgId("WinFormCom.Test")]
public class WinFormCom : System.Windows.Forms.Form
The key here is that you HAVE TO stick this form into a DLL project, not into the EXE where it can’t be directly instantiated via COM. So what I did is create a second project for the WinForms app (Windows Control Project) and added the Windows Form to be automated into this project. This is the project that needs to be marked for COM interop (Register for COM Interop in the Build Properties of the project).
Once that’s done you can now instantiate your WinForm directly. From VFP:
o = CREATEOBJECT("WinformCom.Test")
o.Show()
This works fine if all you want to do is display the form MODALLY and run it. The problem is that you can’t run the form in the background this way, because the Show() method blocks the call when no event loop (Application.Run() and the like) is present.
If you want the form to run and keep active while control returns to your client COM application, you will have to resort to using a new thread in the server and starting a full event loop on that new thread. That way the form will run independently of a reference that you’ve created and you get a reference to the form passed back to you.
To do this you need two new methods on your form (actually I’d move these off to a controller COM object but for demonstration these methods are on the form).
private WinFormCom LiveForm = null;
public WinFormCom RunForm()
{
this.LiveForm = new WinFormCom();
Thread t = new Thread( new ThreadStart( this.RunThread ));
t.ApartmentState = ApartmentState.STA;
t.Start();
return this.LiveForm;
}
public void RunThread()
{
this.LiveForm.Show();
Application.Run();
}
Note that you need an instance field for the LiveForm so that we have a persistent reference that the thread routine can work with.
You can now run the Windows form from the COM client like this:
o = CREATEOBJECT("WinformCom.Test")
loForm = o.RunForm()
loForm.Top = 0
loForm.Left = 0
Voila, you can now access the WinForm directly via COM. Note that you can’t get at the controls of the form directly because controls are defined private by default, so the public interface will be the form’s public interface of methods plus whatever public interface you slap onto it.
Surprisingly, when you get the WinForm reference returned to you from the factory method, Intellisense does not work. Not sure why that is as the reference is exposed to COM and of the same type as the master form reference. Odd…
A couple of things that you need to be aware of as well. You need be very careful about object lifetime in this scenario. Killing the top level reference will not automatically kill the factory created form reference. So the proper sequence of getting rid of the form is:
loForm.Close()
loForm = null
o = null
But even that will not really clean up everything. If you use Task Manager you can watch the number of threads created in the application. As you call RunForm() each time the thread count goes up. But when you close the form the thread count does not go away. The reason for this is that we called Application.Run() without a parameter, without a specific form it is watching for termination. So we need to explicitly tell the application to stop processing the message loop with this code in the form:
private void WinFormCom_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Application.ExitThread();
}
This will shut down the message loop and properly clean up if the above sequence is used.
Note that COM clients have very little control as it is over the lifetime of the .NET COM subsystem. Even with the above code and a CLEAR ALL, CLOSE ALL, CLEAR DLLS etc in VFP, the DLL remains loaded in the VFP process. As far as I know there’s no way to completely unload the DLL once it’s been loaded. This means if you make changes to the DLL (as I did while testing) you have to shut down the client app and restart it frequently. There’s more info on this and other issues when dealing with COM interop from a COM client in an article I put out a while back.
So summing up, yeah it’s a bit more work to call a Windows Form from a COM client application, but it’s definitely possible. Note that if you already have an EXE and all of your WinForms are compiled into it, you can easily expose those forms through a wrapper DLL. The DLL can then simply set a reference to the EXE and expose forms using this same approach mentioned above using a thread method.
Other Posts you might also like