Using Managed Controls as ActiveX Controls
Can you use a managed usercontrol in an Office document in the same way that you can use a native ActiveX control – all without using VSTO? Some time ago, I posted about how to use native ActiveX controls within a doc-level VSTO solution, by wrapping them in managed usercontrols. A reader (Casey) asked the question, “what about going the other way?” The answer is “maybe”. Or, to be more precise, “up to a point, but the technique is unsupported and probably won't work in most scenarios”. If you still want to play with this a bit, here’s how to start…
First, create a managed usercontrol project – either a Windows Forms class library or control library project. Use the usercontrol designer to design your custom usercontrol the way you want it (using any standard controls you like).
Second, in the project properties, on the Build tab, select the “Register for COM interop” option. This will register any COM-visible classes in the project when you build it, and will also build a COM typelib and register it.
Third, attribute your usercontrol class to make it COM-visible. Also, specify a GUID, to avoid getting a fresh one on each build; and specify that you want the compiler to generate a dispatch class interface for the control.
[ComVisible(true)]
[Guid("0F2A2E9D-C79E-4b1b-9AF7-2D1487F29041")]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public partial class ManagedOcx : UserControl
{
Next, we need to provide some additional registry entries: Control, MiscStatus, TypeLib and Version. You can do this with a .REG script, but it’s generally better to write functions that will be called on registration/unregistration (attributed with ComRegisterFunction/ComUnregisterFunction).
Control is an empty subkey. TypeLib is mapped to the GUID of the TypeLib (this is the assembly-level GUID in the assemblyinfo.cs). Version is the major and minor version numbers from the assembly version. The only mildly interesting subkey is MiscStatus. This needs to be set to a value composed of the (bitwise) values in the OLEMISC enumeration, documented here. To make this enum available, add a reference to Microsoft.VisualStudio.OLE.Interop (and a suitable ‘using’ statement for the namespace).
[ComRegisterFunction]
static void ComRegister(Type t)
{
string keyName = @"CLSID\" + t.GUID.ToString("B");
using (RegistryKey key =
Registry.ClassesRoot.OpenSubKey(keyName, true))
{
key.CreateSubKey("Control").Close();
using (RegistryKey subkey = key.CreateSubKey("MiscStatus"))
{
// 131456 decimal == 0x20180.
long val = (long)
( OLEMISC.OLEMISC_INSIDEOUT
| OLEMISC.OLEMISC_ACTIVATEWHENVISIBLE
| OLEMISC.OLEMISC_SETCLIENTSITEFIRST);
subkey.SetValue("", val);
}
using (RegistryKey subkey = key.CreateSubKey("TypeLib"))
{
Guid libid =
Marshal.GetTypeLibGuidForAssembly(t.Assembly);
subkey.SetValue("", libid.ToString("B"));
}
using (RegistryKey subkey = key.CreateSubKey("Version"))
{
Version ver = t.Assembly.GetName().Version;
string version =
string.Format("{0}.{1}", ver.Major, ver.Minor);
subkey.SetValue("", version);
}
}
}
[ComUnregisterFunction]
static void ComUnregister(Type t)
{
// Delete the entire CLSID\{clsid} subtree for this component.
string keyName = @"CLSID\" + t.GUID.ToString("B");
Registry.ClassesRoot.DeleteSubKeyTree(keyName);
}
Build the project, then run Excel. From the Developer tab, go to the Controls group, and click the Insert button. This drops down a little gallery of available controls. The one in the bottom right-hand corner pops up the More Controls dialog, which offers a list of all suitably-registered ActiveX controls. You should find your custom control in this list.
Note: this seems to work OK for Excel (with the very limited testing I've done), partly works with PowerPoint, but fails miserably with Word. Possibly, some more of the OLEMISC values might improve this; possibly there are some messages we need to hook; possibly there are some more interfaces we need to implement – I haven’t tried. Of course, the VSTO runtime has a nice set of hosting controls that enable this behavior for Excel and Word, but these are not usable outside the context of a VSTO solution. As I said, this is an entertaining avenue to explore, but it remains unsupported. The fact that I’ve only barely got it to work in a very limited way should tell you that this is probably not a technique you want to use in any serious way.
Comments
Anonymous
November 24, 2008
PingBack from http://blog.a-foton.ru/index.php/2008/11/25/using-managed-controls-as-activex-controls/Anonymous
December 07, 2008
Hi andreww, I failed to found the customed control in the excel. Following is the steps I have done:run VS 2008 in administrator permission on vista OScopy your code in vs editor, and check the "register for com interop" Follow your guid to find the control in the more control dialog. and nothing found. search the regedit for the GUID, Nothing relative found I don't know what's the things goes wrong.Will you tell me what else I should set.any help is greate appreciated.Anonymous
December 26, 2008
jackson - what happens if you create a simple class library project, with one class, with the same ComVisible and Guid attributes, and with "Register for COM interop' checked?Do you see the ProgId and GUID in the registry after building the project?Anonymous
January 05, 2009
Hi Andrew,I tried this very long time ago and I stopped at one point:The Control crashed when the Resize event of the parent window was raised.The Control coordinates/structure seem to be different between native ActiveX controls and managed Controls. Is there a solution for this problem? Maybe to handle the resize event manualy.Greets - Helmut.Anonymous
January 06, 2009
Helmut - Thanks for your comment. As it happens, the simple control I built using this technique (with a MonthCalendar and a Button) doesn't crash when the parent's Resize is raised (for Excel and Word) - but I have no doubt that this might well be a problem in some scenarios. As I mentioned at the beginning of my post, this is really just an exercise for curiosity's sake - you'd have to do a huge amount of work to make this approach even half-way stable.Anonymous
February 17, 2009
Andrew, thanks for responding to my question! We looked into it and ultimately decided it would be easier for us to convert to VSTO. I'm glad your post helps validate our decision. Thanks again!Anonymous
May 06, 2009
Hi Andrew,Thank you for the GGGGreat tip!I have an additional question for this control.How to get the parent object(worksheet)?It's so difficult problem to me.please tell me how to do.thanks~Anonymous
May 07, 2009
inhyuk - I don't know of any easy way to get to the worksheet from the control. I suspect you'd have to do something like passing the control's Handle to some Win32 function like GetParent. Perhaps use FindWindow to find the current host app process, and then iterate child windows to match up the one hosting the worksheet. As I said earlier, this post was really for entertainment value - I don't see this as a recommended technique for real use.Anonymous
May 07, 2009
Thanks for your answer.I've already tried that, but I can't get the worksheet.So I'm finding a solution, but I'm pessimistic.Thanks again!