设计 ActiveX 控件需要根本性地转变观念。需要响应的关键事件是不同的,例如,经常需要同 Resize 事件打交道,并且不再需要处理 QueryUnload 等事件。但这仅仅是开始。
本章开始部分的“控件创建术语”介绍了控件不是窗体的一种永久性的固定物。事实上,当窗体打开和关闭时,或者运行工程时,设计时和运行时的控件实例将不断地被生成和销毁。
每当生成和销毁 ActiveX 控件的实例时,它所在的 UserControl 对象以及对象的所有子控件也随着被生成和销毁。(本章开始部分的“UserControl 对象”中阐述了用 Visual Basic 创建 ActiveX 控件的基础知识。)
下面以“创建一个 ActiveX 控件”中分步创建的 ShapeLabel 控件为例进行说明。
接下来,现在创建了窗体以及 ShapeLabel 控件的运行时实例。当关闭窗体并回到设计模式时,ShapeLabel 被销毁并重新创建。
本主题后面的部分解释了 UserControl 对象存活期间的关键事件,并提供在几个重要场合接收到的事件的参阅列表中。
下面是对 UserControl 对象存活期内的关键事件的解释:
除了以上所列出的事件以外,Show 和 Hide 事件对于控件也是比较重要的。图 9.5 说明了 Show 和 Hide 如何发生。
图 9.5 Show 和 Hide 事件
为了在 Windows 中将控件绘制到屏幕上,任何控件都必须有一个窗口,无论是暂时的还是永久性的。Visual Basic 的 ActiveX 控件具有永久性的窗口。在控件被定位到窗体上之前,它的窗口不在容器上。当该窗口被添加或删除时,UserControl 对象接受到 Show 和 Hide 事件。
当控件的窗口已经在窗体上时,如果控件的 Visible 属性变为 False,UserControl 就会接收到 Hide 事件;如果变为 True,UserControl 就会接收到 Show 事件。
当隐藏窗体被显现,或最小化的窗体恢复时,UserControl 对象不会接受到 Hide 和 Show 事件。在这些操作中,控件的窗口始终在窗体上,而且 Visible 属性值不改变。
如果控件出现在 internet 浏览器上,那么当该控件所在页移入“历史”列表时,就会产生 Hide 事件,如果再回到该页时,将产生 Show 事件。
注意 如果控件用于早期版本的 Visual Basic,那么在设计时 UserControl 对象接收不到 Show 和 Hide 消息。这是因为早期版本的 Visual Basic 在设计时不把任何可视窗口放到窗体上。
详细信息 在“创建一个 ActiveX 控件”的“UserControl 对象的存活期”中,演示了控件存活期中的关键事件,并说明了控件实例创建和销毁的频度。
让我们看一看创建控件的全过程:从控件实例被放置到窗体上开始,经过几个开发阶段,最终被编译到应用程序。在揭开帷幕之前,先假定控件已经开发完成并被编译成 .ocx 文件。
后面的几个场合中都要提到 Resize 和 Paint 事件。控件的制作者所关心的事件取决于控件的创建模式,如本章前面的“创建 ActiveX 控件的三种方式”中所述。
如果控件使用子控件来提供外观,则需要使用 Resize 事件来设置子控件的大小。另外,对于用户绘制控件,可以忽略子控件的 Resize 事件和有关说明。用户绘制控件在 Paint 事件中绘制外观。这将在本章后面的“绘制控件”中说明。
注意 在所有这些场合中,Resize 和 Paint 事件的次序和编号也许会有所不同。
双击工具箱中的控件图标,控件的设计时实例就被放置在正在设计的窗体上了。在 UserControl 对象中,在控件实例的核心发生了下列事件:
事件 | 完成什么 |
Initialize | 创建了子控件,但控件未在窗体上定位。 |
InitProperties | 控件实例设置属性的缺省值。控件已被定位,所以 Extender 和 AmbientProperties 对象是可用的。仅在此时实例才能得到本事件。 |
Resize, Paint | 如果有子控件,则控件实例根据缺省值调整其子控件的大小。用户绘制控件绘制自身。 |
至此,窗体的开发者可以看到控件,并在“属性”窗口中设置属性。此后,可按 F5 键来运行工程。
按 F5 键后,窗体上控件的设计时实例就销毁了。在运行时装载窗体时,控件就作为运行时实例重新创建。
事件 | 完成什么 |
WriteProperties | 在控件的设计时实例销毁之前,将属性值存储到 .frm 的内存副本中。 |
Terminate | 子控件仍然存在,但设计时控件实例不再定位在窗体上。它即将销毁。 |
Initialize | 子控件被创建,运行时控件实例尚未被定位到窗体上。 |
ReadProperties | 控件实例从 .frm 的内存副本中读取属性值。控件定位到窗体的运行时实例上,于是 Extender 和 AmbientProperties 对象成为可用的。 |
Resize, Paint | 如果有子控件,则控件实例根据缺省值调整其子控件的大小。用户绘制控件绘制自身。 |
开发者可以通过单击控件,或其它方法,使控件的属性、方法和事件得到测试,从而得以测试窗体。
最后,开发者关闭窗体退回到设计模式。控件的运行时实例就销毁了,并创建了设计时实例:
事件 | 完成什么 |
Terminate | 运行时实例没有机会保存属性值。在程序运行时,改变的属性值被丢弃了。 |
Initialize | 创建了子控件的设计时实例,但控件的设计时实例尚未定位到窗体上。 |
ReadProperties | 控件从 .frm 文件的内存副本中读取保存的属性值。控件被定位到窗体的设计时实例上,于是 Extender 和 AmbientProperties 对象成为可用的。 |
Resize, Paint | 如果有子控件,则控件实例根据缺省值调整其子控件的大小。用户绘制控件绘制自身。 |
如果开发者不再需要在窗体上工作,就可以关闭它。在停止工作的时候,可以关闭整个工程。在这两种情况下,窗体上的控件实例都会销毁。
事件 | 完成什么 |
WriteProperties | 在控件的设计时实例销毁之前,将属性值保存到 .frm 的内存副本中。 |
Terminate | 子控件仍存在,但控件实例不再定位在窗体上。它即将销毁。 |
注意 以上各种情况下,控件实例把属性值保存到 .frm 文件的内存副本中。在工程关闭时,如果开发者选择不保存,那么设置的属性值就被丢弃了。
当开发者重新打开工程,并打开窗体继续工作时,控件就被重新作为设计时实例被实体化。它接收 Initialize、ReadProperties、Resize、Paint 和 WriteProperties 事件。
注意 发生了 WriteProperties 事件吗?确实如此。当工程被打开之后,Visual Basic 创建 .frm 文件的内存副本。在创建窗体上的每一个控件时,控件将收到 ReadProperties 事件,用来从 .frm 文件中读取为它保存的属性值,同时用 WriteProperties 事件把这些属性值保存到 .frm 文件的内存副本中。
当工程被编译成应用程序或部件之后,Visual Basic 以不可见的方式逐个载入窗体文件,其目的是把它们包含的信息写入编译后的文件。控件实例将收到 Initialize、ReadProperties 和 WriteProperties 事件。控件属性的设置值将被编译进最终的可执行文件中。
当用户运行已安装的应用程序或部件,并且窗体已被载入时,控件就会收到 Initialize、ReadProperties 和 Resize 事件。当窗体被卸载时,控件将收到 Terminate 事件。
用 HTML <OBJECT> 和 </OBJECT>标记指定 HTML 主页上的控件。在处理 HTML 时创建并定位控件。如果 </OBJECT>标记包括任何 <PARAM NAME> 属性,则使用这一主题所讨论的标准 PropertyBag 对象将这些属性支持的属性值传递到控件的 ReadProperties 事件。
一旦 HTML 页为活动的,就可用附加到事件上的脚本来设置控件属性值,而且事件出现在该页上。
注意 如果除了设置扩展属性的 <PARAM NAME> 属性外再没有这样的属性,则控件可能接收到 InitProperties 事件而不是 ReadProperties 事件。这种特性取决于浏览器的实现,但不是依赖这一实现。
在使用窗体时十分熟悉的某些事件,在 UserControl 中是找不到的。例如,没有 Activate 和 Deactivate 事件,因为控件与窗体在激活和禁用的方式上是不同的。
更令人吃惊的是没有熟悉的 Load、Unload 和 QueryUnload 事件。Load 和 Unload 是由于不适合 UserControl 的工作方式;与窗体不同,控件实例并不是在创建后的某个时刻被载入的,当 UserControl 对象的 Initialize 事件发生时,子控件就已经被创建了。
UserControl 对象的 Initialize 和 ReadProperties 事件提供了窗体的 Load 事件所提供的功能。两者主要的区别在于:当 Initialize 事件产生时,控件还未被定位到容器上,所以无法利用容器的 Extender 和 AmbientProperties 对象。当 ReadProperties 事件产生时,控件才被定位。
注意 在控件实例首次放置在容器上时,不会产生 ReadProperties 事件,而是发生 InitProperties 事件。
在 UserControl 的事件中与窗体的 Unload 事件最接近的是 Terminate 事件。此时子控件仍然存在,但是因为控件已被卸下,所以不能再访问容器。
WriteProperties 事件与 Unload 事件是毫不相同的,因为它只在设计时产生。
因为控件只是窗体的一部分,所以 UserControl 对象没有 QueryUnload 事件;不需要由控件来决定是否关闭包含它的窗体。控件只需要在得到通知的时候销毁掉自身。
UserControl 的 GotFocus 和 LostFocus 事件通知用户绘制控件,何时需要显示或停止显示焦点矩形。控件的使用者不需关心这些事件,因为容器会对焦点事件做出反应。
如果 UserControl 具有能够接收焦点的子控件,那么当第一个子控件收到焦点时会产生 EnterFocus 事件,当焦点离开最后一个子控件时会发生 Ambient ExitFocus 事件。请参阅本章后面的“怎样在控件中处理焦点”。
如果允许开发者为控件设置访问键,那么用户一按访问键就会产生 AccessKeyPress 事件。请参阅本章后面“允许开发者为控件设置访问键”。如果控件是缺省按钮或取消按钮,则也会产生 AccessKeyPress 事件。这将在本章后面的“把控件设置为缺省或取消按钮”中讨论。
当控件所在的容器的环境属性发生改变时,就会产生 AmbientChanged 事件。请参阅本章后面的“使用 AmbientProperties 对象与容器保持一致“。