将托管控件当作ActiveX 控件使用
发布时间: 星期一,11/24/2008, 11:31PM
作者: andreww
原稿地址: Using Managed Controls as ActiveX Controls
在不用VSTO 的情况下,你能够 在办公文档中像 使用本地的ActiveX 控件一样使用托管的用户控件 吗? 不久以前,我写了一篇介绍在文档级别的VSTO 解决方案中使用本地的ActiveX 的控件的文章,方法是把ActiveX 控件封装在托管的用户控件中。一个名叫Casey 的读者问到:“能不能通过另外的方法达到此目的呢?”。 答案是可能的,更准确地说,“我不能100% 保证你一定可以,因为很有可能技术上不支持, 或者很可能在大多数情况不可用。 如果你仍然想试一试,请继续向下看。
第一,创建一个托管用户控件的工程(译者注: 在windows vista 上请用administrator 权限 打开 VSTO ),即一个窗口表单类库或者控件类库工程。使用用户控件设计器 (IDE)来设计你的用户控件(你可以使用任何标准控件在该工程中。)
第二,在工程属性里, 找到Build table标签(编译选项卡), 在“Register for COM interop”( 中文译为: “为 COM Interop 注册组件”, Register for COM interop 项目属性指定托管应用程序是否将公开一个允许 COM 对象与托管应用程序进行交互的 COM 对象(COM 可调用的包装)) 复选框前打钩 , 这个步骤将会注册你工程中任何com 可见的类,同时会生成一个COM 类库并且自动注册该库。
第三, 添加属性使你的用户控件类为com 可见的(即能够被 com 发现或访问), 同样, 指定一个GUID, 这样能够避免在每次重新编译的时候得到一个新的不同的GUID, 指定编译器为你的控件生成一个自动派发类接口。
[ComVisible(true)]
[Guid("0F2A2E9D-C79E-4b1b-9AF7-2D1487F29041")]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public partial class ManagedOcx : UserControl
{
下一步, 我们需要提供一些额外的注册表键值: Control, MiscStatus,TypeLib, Version。 你可以通过使用.Reg 脚本来完成此操作,但是下面的方式更好:你可以写一些函数使它在注册和注销的时候都能够被调用(通过 加上属性 ComRegisterFunction/ComUnregisterFunction 来使用此功能)来完成此功能
Control 是一个空的子键。 TypeLib 这个键将映射到应用程序(或者类库)TypeLib 的GUID (这是一个程序集级别的GUID,在 assemblyinfo.cs中有对应的项)。Version 的键值来自程序集版本号中的主版本号和次版本号。稍微有点意思的是子键MiscStatus,它的值由枚举OLEMISC 中一些枚举值组合后的位运算得到的, 可以参考文档 OLEMISC Enumeration . 为了使用该枚举,你需要添加对Microsoft.VisualStudio.OLE.Interopd.dll的引用同时在代码文件中开头添加:(using Microsoft.VisualStudio.OLE.Interop;)引用.
[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);
}
生成工程后,启动Excel 。 从开发者选项卡, 到控件组栏, 然后双击 插入一个按钮, 这个级联菜单打开当前可用的控件的一个列表。点击右下角的图标将弹出一个显示更多控件的对话框, 这将展示一个更多当前可用已注册的ActiveX 控件。 在这里你能够发现自己刚刚创建的控件.
注意:这在excel 里 运行是没问题的(我只做了很小一部分测试,并且成功了)。 部分的能够用于power point , 但是在word 里全部的尝试都失败了。也许使用更多的OLEMISC值能够改变这种状况,或者有可能possibly there are some messages we need to hook;还有可能是我们需要实现更多的相关接口,但我没有继续去尝试。当然,VSTO 运行时有一套很好的宿主控件能够实现Excel 和Word 的这个功能, 但是一旦离开了VSTO 解决方案这个大环境, 他们就不可用了。这是一个有趣的尝试,但是它仍然不被支持。事实上, 我只能以有限的方式,使它在特殊的环境下才能够工作.但应该告诉你,在任何真正的场合,这绝对不是你想用的一个技术