为什么要在 ActiveX 控件中使用窗体?
想象一下,一个 ActiveX 控件本身就像一个“黑盒子”,它可以在容器(如 Word 或 VB6 窗体)上绘制界面并响应事件,但这个界面通常很有限,比如只能是一个按钮、一个文本框或一个绘图区域。

当你的控件需要提供一个更复杂的、独立的用户界面时,比如一个设置面板、一个数据配置向导、一个属性对话框,或者一个复杂的编辑器界面时,就需要一个独立的窗体。
引入窗体技术的核心目的就是:
- 提供丰富的用户交互界面: 独立的窗体可以使用标准的 Windows 控件(如 TextBox, ListBox, TreeView, TabStrip 等),构建出比控件本身主界面复杂得多的 UI。
- 封装复杂的逻辑: 将与这个 UI 相关的所有逻辑、数据操作和状态管理都封装在窗体内,保持 ActiveX 控件本身的简洁。
- 提供模态或非模态的交互体验:
- 模态窗体: 用户必须先操作完这个窗体(比如点击“确定”或“取消”)才能返回到主容器应用程序,常用于设置、确认等场景。
- 非模态窗体: 窗体打开后,用户可以同时与主容器和其他应用程序交互,常用于工具窗口、实时监控面板等。
- 解耦界面与控件: 窗体可以独立于 ActiveX 控件进行设计、修改和测试,提高了代码的可维护性。
实现步骤详解 (以 Visual Basic 6.0 为例)
VB6 是实现这项技术最经典、最直观的工具,下面我们以创建一个名为 MyOCX 的 ActiveX 控件为例,讲解如何为其添加一个设置窗体。
步骤 1:创建 ActiveX 控件工程
- 打开 VB6,选择 "ActiveX 控件" (ActiveX Control)。
- 保存工程,
- 工程名:
MyOCXProj - 控件文件名:
MyOCX.ctl - 工程组名:
MyOCXGroup
- 工程名:
步骤 2:设计 ActiveX 控件的主界面
在 MyOCX.ctl 的设计视图中,放置一个简单的命令按钮 cmdShowSettings,这个按钮将作为触发设置窗体的入口。

' MyOCX.ctl
Private Sub cmdShowSettings_Click()
' 在这里我们将编写代码来显示窗体
End Sub
步骤 3:添加标准窗体
- 在 "工程" 菜单中,选择 "添加窗体" (Add Form)。
- 选择 “标准 EXE” 窗体 (Standard EXE),点击“打开”。
- 保存这个新窗体,
frmSettings.frm。 - 在
frmSettings上设计你的设置界面,- 一个文本框
txtUserName - 一个复选框
chkAutoSave - 两个命令按钮
cmdOK和cmdCancel
- 一个文本框
步骤 4:建立 ActiveX 控件与窗体的连接
这是最关键的一步,我们需要让 MyOCX 控件能够创建并显示 frmSettings 窗体,并且能够从窗体中获取用户设置的数据。
直接创建(简单直接)
在 MyOCX 控件的代码中,直接实例化并显示窗体。
' MyOCX.ctl 的代码
' 需要在控件的通用声明部分添加一个对设置窗体的引用
' Private m_frmSettings As frmSettings
Private Sub cmdShowSettings_Click()
' 检查窗体是否已经实例化,避免重复创建
If m_frmSettings Is Nothing Then
Set m_frmSettings = New frmSettings
End If
' 将当前控件的设置传递给窗体,以便窗体能显示正确的初始值
m_frmSettings.txtUserName.Text = m_strUserName
m_frmSettings.chkAutoSave.Value = m_blnAutoSave
' 以模态方式显示窗体
' vbModal 表示这是一个模态窗体
m_frmSettings.Show vbModal
' 当窗体关闭后(用户点击了确定或取消),可以在这里获取新值
If m_frmSettings.DialogResult = vbOK Then ' 假设我们在窗体中定义了 DialogResult
m_strUserName = m_frmSettings.txtUserName.Text
m_blnAutoSave = (m_frmSettings.chkAutoSave.Value = vbChecked)
' 可以在这里触发一个事件,通知容器控件的属性已更改
PropertyChanged "UserName"
PropertyChanged "AutoSave"
End If
' 释放窗体对象
Set m_frmSettings = Nothing
End Sub
' 控件的属性(需要用 Public Property Get/Let 声明)
Private m_strUserName As String
Public Property Get UserName() As String
UserName = m_strUserName
End Property
Public Property Let UserName(ByVal vNewValue As String)
m_strUserName = vNewValue
End Property
Private m_blnAutoSave As Boolean
Public Property Get AutoSave() As Boolean
AutoSave = m_blnAutoSave
End Property
Public Property Let AutoSave(ByVal vNewValue As Boolean)
m_blnAutoSave = vNewValue
End Property
在 frmSettings.frm 中需要添加的代码:

' frmSettings.frm 的代码
' 在窗体级别声明一个变量来记录用户点击了哪个按钮
Public DialogResult As VbMsgBoxResult
Private Sub cmdOK_Click()
' 用户点击了“确定”,设置 DialogResult 并关闭窗体
DialogResult = vbOK
Unload Me
End Sub
Private Sub cmdCancel_Click()
' 用户点击了“取消”,设置 DialogResult 并关闭窗体
DialogResult = vbCancel
Unload Me
End Sub
' 可选:当用户点击窗体右上角的 'X' 时,也视为取消
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
If UnloadMode = vbFormControlMenu Then
DialogResult = vbCancel
End If
End Sub
通过外接程序(更专业)
VB6 有一个 "外接程序" -> "外接程序管理器" -> "VB 6 ActiveX 控件接口向导",这个工具可以帮你更规范地定义控件的属性、方法和事件,当你通过向导添加一个属性时,它会自动生成 Property Let/Get 的骨架,你只需要在 Let 方法中调用 PropertyChanged 即可,这与方法一的核心逻辑类似,但流程更标准化。
关键技术与注意事项
-
线程模型:
- ActiveX 控件和它创建的窗体必须在同一个线程上运行。
- 在 VB6 中,这通常是自动处理的,因为默认是单线程单元模型。
- 在更现代的 .NET WinForms 或 WPF 中,如果你要将一个 ActiveX 控件嵌入其中,或者反之,必须特别注意跨线程调用问题,通常需要使用
Control.Invoke或Dispatcher来确保 UI 操作在正确的线程上执行。
-
生命周期管理:
- 谁创建,谁销毁:ActiveX 控件负责创建窗体实例,也必须在窗体关闭后负责将其设置为
Nothing,以释放内存,防止内存泄漏。 - 避免全局窗体:不要将窗体声明为
Global或Public在整个工程级别,这会导致多个控件实例共享同一个窗体对象,引发混乱,每个控件实例都应该拥有自己独立的窗体实例。
- 谁创建,谁销毁:ActiveX 控件负责创建窗体实例,也必须在窗体关闭后负责将其设置为
-
数据通信:
- 属性传递:最常见的方式是,在显示窗体前,将控件的内部属性值传递给窗体的控件作为初始值,在窗体关闭后,再将窗体控件的新值回传给控件的属性。
- 自定义事件:当窗体中的操作需要影响容器或进行复杂通知时,可以在 ActiveX 控件中定义自定义事件,窗体通过一个对控件的引用来触发这个事件,窗体中的“应用”按钮可以触发一个
ApplySettings事件。
-
部署问题:
- ActiveX 控件(
.ocx文件)和它依赖的所有窗体、模块、图片等资源,最终都会被编译进.ocx文件中。 - 部署时只需要将
.ocx文件和它的依赖库(如MSVBVM60.DLL)一起打包,并正确注册即可,用户不需要额外安装你的 VB6 工
- ActiveX 控件(
