如何将 WPF
控件包装为 ActiveX
组件
前面的一篇文章中介绍了 如何将WPF控件嵌入Win32程序中,其要求 Win32
程序支持 C++/CLR
(托管代码)。然而在我们实际工程中,要使现有的 Win32/MFC
项目支持 CLR
,可能涉及到较大的改动。那么,能不能在 纯非托管程序
中嵌入 WPF
控件呢?答案当然是肯定的,那就是 COM/ActiveX
组件。
COM是 Component Object Model(组件对象模型)的缩写,Microsoft的许多技术,如 ActiveX , DirectX 以及 OLE 等都是基于 COM 而建立起来的。请自行百度了解
COM/ActiveX
的基本概念。
实现原理
理论上 COM/ActiveX
可以搭建起 WPF
和 MFC
之间的桥梁,实际操作时发现 WPF
生成的 ActiveX
并不是有效的 COM
组件,无法集成到 MFC
界面中。好在 WinForm
能够生成有效的 COM
组件,我们可以先将 WPF
控件集成到 WinForm
控件中,然后将 WinForm
控件编译成 ActiveX
组件。
开发环境
- Visual Studio 2017
- .NET Framework 4.5
- MFC / C++
- WPF / WinForm / C#
基本步骤
- 创建 WPF & WinForm 控件工程
- 将 WinForm 控件配置成 COM/ActiveX 组件
- 编写 COM 组件自动注册代码
- 在 MFC 中集成 ActiveX 组件
1. 创建 WPF & WinForm 控件工程
此处不用赘述,先在 Visual Studio
中创建 WPF User Control Library
工程和 Windows Forms Control Library
工程;然后在 WPF
工程中创建名为 MyWpfControl
的 User Control (WPF)
控件,在 WinForm
工程中创建名为 MyWinFormControl
的 User Control
控件;最后将 MyWpfControl
添加到 MyWinFormControl
中,效果如下图所示。
2. 将 WinForm 控件配置成 COM/ActiveX 组件
首先,进入 MyWinFormControl
的 项目属性
设置界面,找到 Application
-> Assembly Infomation
,勾选底部的 Make assembly COM-visible
选项。
然后,为 MyWinFormControl
添加 ProgId
和 ClassInterface
两个特性,分别用作标识组件名称和指定接口生成策略。
[ProgId("WinFormControl.MyWinFormControl")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public partial class MyWinFormControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
......
}
3. 编写 COM 组件注册代码
通过如上配置,编译出来的 MyWinFormControl
便是一个 COM/ActiveX
组件,我们可以通过 regSvr32
命令来手动注册。为了方便调试,也可编写如下代码来实现编译后的自动注册,以及清理后的自动反注册。由于 COM
组件的注册涉及到注册表操作,所以需要以管理员权限运行 Visaul Studio
。
首先,在 项目属性
界面下的 Build
栏中勾选 Register for COM interop
选项。
然后,在 MyWinFormControl
所在内中添加 RegisterClass
和 UnregisterClass
两个静态方法,注意分别标记 ComRegisterFunction
和 ComUnregisterFunction
特性。
/// <summary>
/// Register the class as a control and set it's CodeBase entry
/// </summary>
/// <param name="key">The registry key of the control</param>
[ComRegisterFunction()]
public static void RegisterClass(string key)
{
// Strip off HKEY_CLASSES_ROOT\ from the passed key as I don't need it
StringBuilder sb = new StringBuilder(key);
sb.Replace(@"HKEY_CLASSES_ROOT\", "");
// Open the CLSID\{guid} key for write access
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);
if (k == null)
{
throw new NullReferenceException();
}
// And create the 'Control' key - this allows it to show up in
// the ActiveX control container
RegistryKey ctrl = k.CreateSubKey("Control");
ctrl?.Close();
// Next create the CodeBase entry - needed if not string named and GACced.
RegistryKey inprocServer32 = k.OpenSubKey("InprocServer32", true);
inprocServer32?.SetValue("CodeBase", Assembly.GetExecutingAssembly().CodeBase);
inprocServer32?.Close();
// Finally close the main key
k.Close();
}
/// <summary>
/// Called to unregister the control
/// </summary>
/// <param name="key">Tke registry key</param>
[ComUnregisterFunction()]
public static void UnregisterClass(string key)
{
StringBuilder sb = new StringBuilder(key);
sb.Replace(@"HKEY_CLASSES_ROOT\", "");
// Open HKCR\CLSID\{guid} for write access
RegistryKey k = Registry.ClassesRoot.OpenSubKey(sb.ToString(), true);
if (k == null)
{
return;
}
// Delete the 'Control' key, but don't throw an exception if it does not exist
k.DeleteSubKey("Control", false);
// Next open up InprocServer32
RegistryKey inprocServer32 = k.OpenSubKey("InprocServer32", true);
// And delete the CodeBase key, again not throwing if missing
inprocServer32?.DeleteSubKey("CodeBase", false);
inprocServer32?.Close();
// Finally close the main key
k.Close();
}
编译 MyWinFormControl
项目,一个名为 WinFormControl.MyWinFormControl
的 AxtiveX
组件将被自动注册到系统中。
4. 在 MFC 中集成 ActiveX 组件
进入 MFC
项目,在界面上单击鼠标右键,选择 Insert ActiveX Control...
,在弹出的 Insert ActiveX Control
窗口中选择 WinFormControl.MyWinFormControl
。
另外,还可以使用 tstcon32.exe
来测试 ActiveX
组件。
方案总结
要实现 Win32/MFC
内嵌 WPF
控件,除了使用前面文章中介绍的 C++/CLR
方案外,还可以使用本文的 ActiveX
方案。比较而言,ActiveX
方案更加灵活自由,对 Win32/MFC
项目无特殊要求。
源码:https://github.com/Iron-YeHong/WPFInterop