理解控件的存活期和关键事件

设计 ActiveX 控件需要根本性地转变观念。需要响应的关键事件是不同的,例如,经常需要同 Resize 事件打交道,并且不再需要处理 QueryUnload 等事件。但这仅仅是开始。

本章开始部分的“控件创建术语”介绍了控件不是窗体的一种永久性的固定物。事实上,当窗体打开和关闭时,或者运行工程时,设计时和运行时的控件实例将不断地被生成和销毁。

每当生成和销毁 ActiveX 控件的实例时,它所在的 UserControl 对象以及对象的所有子控件也随着被生成和销毁。(本章开始部分的“UserControl 对象”中阐述了用 Visual Basic 创建 ActiveX 控件的基础知识。)

下面以“创建一个 ActiveX 控件”中分步创建的 ShapeLabel 控件为例进行说明。

  1. 通过双击工具箱或打开一个放置了 ShapeLabel 实例的窗体,创建 ShapeLabel 的实例。

  2. 创建了 Shape 和 Label 子控件。

  3. 创建了 UserControl 对象,Shape 和 Label 控件被放置在它的上面。

  4. 执行 UserControl_Initialize 事件过程。

  5. ShapeLabel 控件被放置在窗体上。

  6. 如果用户往窗体上放置了新的 ShapeLabel 控件,就将发生 UserControl 对象的 InitProperties 事件,并用缺省值设置控件的属性值。如果打开现存的窗体,就会发生 ReadProperties 事件,控件的属性值就使用存储的值。

  7. 执行 UserControl_Resize 事件过程,子控件就根据用户为新的控件实例设置的大小,或是在窗体关闭前的大小,来调整自身的大小。

  8. 发生 Show 和 Paint 事件。如果没有子控件,UserControl 对象就绘制自己。

  9. 用户按 F5 键运行工程。Visual Basic 关闭窗体。

  10. 发生 UserControl 对象的 WriteProperties 事件,控件的属性值被存入 .frm 文件的内存副本中。

  11. 控件已被卸下。

  12. 发生 UserControl 对象的 Terminate 事件。

  13. UserControl 对象和它的子控件销毁。

接下来,现在创建了窗体以及 ShapeLabel 控件的运行时实例。当关闭窗体并回到设计模式时,ShapeLabel 被销毁并重新创建。

本主题后面的部分解释了 UserControl 对象存活期间的关键事件,并提供在几个重要场合接收到的事件的参阅列表中。

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 事件。

World Wide Web 页面上的控件

用 HTML <OBJECT> 和 </OBJECT>标记指定 HTML 主页上的控件。在处理 HTML 时创建并定位控件。如果 </OBJECT>标记包括任何 <PARAM NAME> 属性,则使用这一主题所讨论的标准 PropertyBag 对象将这些属性支持的属性值传递到控件的 ReadProperties 事件。

一旦 HTML 页为活动的,就可用附加到事件上的脚本来设置控件属性值,而且事件出现在该页上。

注意 如果除了设置扩展属性的 <PARAM NAME> 属性外再没有这样的属性,则控件可能接收到 InitProperties 事件而不是 ReadProperties 事件。这种特性取决于浏览器的实现,但不是依赖这一实现。

UserControl 对象接收不到的事件

在使用窗体时十分熟悉的某些事件,在 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 的特殊事件

UserControl 的 GotFocus 和 LostFocus 事件通知用户绘制控件,何时需要显示或停止显示焦点矩形。控件的使用者不需关心这些事件,因为容器会对焦点事件做出反应。

如果 UserControl 具有能够接收焦点的子控件,那么当第一个子控件收到焦点时会产生 EnterFocus 事件,当焦点离开最后一个子控件时会发生 Ambient ExitFocus 事件。请参阅本章后面的“怎样在控件中处理焦点”。

如果允许开发者为控件设置访问键,那么用户一按访问键就会产生 AccessKeyPress 事件。请参阅本章后面“允许开发者为控件设置访问键”。如果控件是缺省按钮或取消按钮,则也会产生 AccessKeyPress 事件。这将在本章后面的“把控件设置为缺省或取消按钮”中讨论。

当控件所在的容器的环境属性发生改变时,就会产生 AmbientChanged 事件。请参阅本章后面的“使用 AmbientProperties 对象与容器保持一致“。