为什么要在 ActiveX 控件中使用窗体?
想象一个场景:你正在开发一个 ActiveX 控件,用于配置一个复杂的图表,如果直接在控件的表面放置几十个配置选项(如坐标轴、图例、数据系列等),会使得控件界面臃肿不堪,并且难以管理。

更好的方法是:
- 控件本身只显示一个简单的图表或一个“设置”按钮。
- 当用户点击“设置”按钮时,弹出一个独立的窗体(对话框)。
- 这个对话框窗体包含了所有复杂的配置选项。
- 用户在对话框中进行设置,点击“确定”后,对话框关闭,并将设置结果传递回 ActiveX 控件,控件根据新设置重新绘制自身。
这样做的好处显而易见:
- 界面清晰:控件保持简洁,复杂的配置逻辑被封装在对话框中。
- 代码解耦:控件的绘制代码和配置对话框的界面/逻辑代码分离,便于维护。
- 功能强大:可以利用窗体的所有功能,如菜单、工具栏、多页面等。
核心原理:控件与窗体的通信
实现这一功能的核心在于建立 ActiveX 控件 和 对话框窗体 之间的双向通信。
-
控件 -> 窗体 (单向)
(图片来源网络,侵删)- 时机:当控件需要显示窗体时(响应按钮点击事件)。
- 方式:控件创建一个对话框窗体的实例,并可能需要向窗体传递一些初始数据,将当前的配置状态传递给对话框,以便对话框在打开时显示当前的设置。
-
窗体 -> 控件 (单向)
- 时机:当用户在对话框中点击“确定”或“应用”按钮,并关闭对话框时。
- 方式:对话框需要将用户在界面上修改的新数据“返回”给控件,这通常通过以下两种方式实现:
- 方式一(推荐):通过对话框类的成员变量,这是最标准、最清晰的方式,对话框类定义成员变量来绑定到对话框上的控件(如编辑框、复选框),当用户点击“确定”时,这些成员变量中就保存了最新的值,控件在创建对话框时,将自己的数据设置到对话框的成员变量中;在对话框关闭后,再从这些成员变量中读取新数据。
- 通过控件的公共方法,对话框可以调用 ActiveX 控件暴露给外部(宿主程序,如 VB, C# 或网页)的公共方法来传递数据,控件可以定义一个
ApplySettings(newSettings)方法,对话框在关闭前调用这个方法。
详细实现步骤 (以 MFC ActiveX 控件为例)
假设我们要创建一个名为 MyDialogCtrl 的 ActiveX 控件,它包含一个按钮,点击后弹出 MySettingsDialog 对话框。
步骤 1:创建对话框资源
- 在你的 MFC ActiveX 控件工程中,插入一个新的对话框资源。
- 使用 Resource View -> 右键点击 Dialog -> Insert Dialog...。
- 设计你的对话框界面,添加所需的控件(如编辑框、复选框、列表框等)。
- 为对话框资源创建一个类:
- 双击对话框资源,或者右键点击对话框 -> Add Class...。
- 选择 MFC Class,点击“Add”。
- 为类命名,
CMySettingsDialog,并确保基类是CDialogEx。 - 关键步骤:为对话框上的每个控件(ID 为
IDC_EDIT_NAME的编辑框)添加 成员变量,在“Add Member Variable”向导中,选择“Value”类型,并指定变量类型(如CString)和变量名(如m_strName),这些变量将用于在控件和对话框之间传递数据。
步骤 2:在 ActiveX 控件类中添加成员变量和方法
-
添加成员变量:在你的控件类(
CMyDialogCtrl)的头文件(.h)中,添加两个成员变量:- 一个用于存储当前配置的数据结构(或简单变量)。
- 一个指向对话框对象的指针。
// MyDialogCtrl.h class CMyDialogCtrl : public COleControl { // ... 其他代码 public: // ... 其他方法 // 对话框指针 CMySettingsDialog* m_pSettingsDialog; // 存储配置数据的成员变量 CString m_strCurrentName; BOOL m_bIsEnabled; protected: // ... 其他代码 }; -
添加方法:在控件类中添加一个公共方法,用于触发显示对话框,这个方法通常被控件的某个事件(如按钮点击)调用。
(图片来源网络,侵删)// MyDialogCtrl.h class CMyDialogCtrl : public COleControl { // ... public: // ... 其他方法 // 声明一个公共方法来显示对话框 void ShowSettingsDialog(); // ... };
步骤 3:实现控件逻辑
-
初始化:在控件类的构造函数中,将对话框指针初始化为
NULL。// MyDialogCtrl.cpp CMyDialogCtrl::CMyDialogCtrl() : m_pSettingsDialog(NULL) { // TODO: 初始化成员变量 m_strCurrentName = _T("Default Name"); m_bIsEnabled = TRUE; } -
实现
ShowSettingsDialog方法:这是核心部分,我们将创建对话框、传递数据、显示对话框,并在关闭后获取新数据。// MyDialogCtrl.cpp #include "MySettingsDialog.h" // 确保包含对话框类的头文件 void CMyDialogCtrl::ShowSettingsDialog() { // 1. 创建对话框对象 // 注意:使用 new 创建,因为 DoModal 会阻塞,对象必须存在 if (m_pSettingsDialog == NULL) { m_pSettingsDialog = new CMySettingsDialog(); } // 2. 将控件的当前数据传递给对话框 // 假设对话框类 CMySettingsDialog 有 SetInitialData 方法 // 或者直接设置其成员变量 m_pSettingsDialog->m_strName = m_strCurrentName; m_pSettingsDialog->m_bEnabled = m_bIsEnabled; // 3. 以模态方式显示对话框 // DoModal 会阻塞,直到对话框关闭 INT_PTR nResult = m_pSettingsDialog->DoModal(); // 4. 对话框关闭后,检查用户点击的是“确定”还是“取消” if (nResult == IDOK) { // 用户点击了“确定”,从对话框中获取新数据 m_strCurrentName = m_pSettingsDialog->m_strName; m_bIsEnabled = m_pSettingsDialog->m_bEnabled; // 5. (可选) 数据更新后,通知控件重绘或触发事件 InvalidateControl(); // 使控件无效,导致 OnDraw 被调用 FireClick(); // 假设你定义了一个 Click 事件来通知宿主 } // 6. 清理对话框对象 // 注意:对于模态对话框,DoModal 返回后会自动销毁窗口, // 但我们是用 new 创建的,所以需要手动 delete if (m_pSettingsDialog != NULL) { delete m_pSettingsDialog; m_pSettingsDialog = NULL; } }
步骤 4:将方法与控件事件关联
为了让宿主程序(如 VB6, C# 或网页中的 JavaScript)能够触发这个对话框,你需要将 ShowSettingsDialog 方法暴露出去。
-
添加方法到
.odl文件:打开你的工程中的.odl文件(MyDialogCtrl.odl),在dispinterface中添加你的方法。// MyDialogCtrl.odl [ uuid(...), // 你的控件 GUID helpstring("MyDialogCtrl 1.0 Type Library"), version(1.0) ] library MYDIALOGCTRLTLB { // ... 其他代码 importlib("stdole2.tlb"); [ uuid(...), // 你的接口 GUID helpstring("_IMyDialogCtrlEvents") ] dispinterface _IMyDialogCtrlEvents { // ... 这里是事件,不是方法 }; [
