保存控件的属性

正如本章前面“理解控件的存活期和关键事件”所述,控件实例会被频繁地创建和销毁,例如打开和关闭窗体设计器、打开和关闭工程、工程进入运行模式时,等等。

在这种销毁和重建过程中,如何保存控件实例的属性,例如 Label 控件的 Caption 属性。Visual Basic 把控件实例的属性值保存在它所在的容器所属的文件中;对于窗体这将是 .frm/.frx 文件,对于 UserDocument 对象这将是 .dob/.dox 文件,对于 UserControls 是 .ctl/.ctx 文件,对于属性页面是 .pag/.pgx 文件。请参阅图 9.13 的说明。

Figure 9.13 包含了控件属性保存信息的 .frm 文件

在制作控件时,必须在控件实例被销毁之前用代码保存属性值,并在控件实例被再次创建时读回其值。请参阅图 9.14 中的说明:

图 9.14 保存和提取属性值

图 9.14 有点过于简化了。事实上,并不一定要关闭窗体来执行控件实例的 WriteProperties 事件过程。在保存窗体文件到磁盘时,将为窗体上的所有控件产生 WriteProperties 事件。

提示 本主题解释了用代码保存和提取属性值的机制,但一般不需编写这里描述的所有代码。“ActiveX 控件接口向导”可以生成用来保存和提取属性值的大部分代码。

保存属性值

可以使用 PropertyBag 对象来保存和提取属性值。PropertyBag 是作为保存属性值的标准接口提供的,它不依赖于容器用来保存其源数据的数据格式。

以下的示例代码使用了 Masked 属性,它是一个 Boolean 属性,在相关主题“在控件中添加属性”中曾经描述过它:

Private Sub UserControl_WriteProperties(PropBag As _
      PropertyBag)
   '保存 Boolean Masked 属性的值。
   PropBag.WriteProperty "Masked", Masked, False
   ' . . . 其它属性 . . . 
End Sub

PropertyBag 对象的 WriteProperty 方法有三个参数。第一个是字符串,标识需要保存的属性,第二个参数是需要保存的值,通常是以访问属性的形式提供的,如在上例中所示。

注意 如果保存的值包含在子控件的缺省属性中,而且该属性具有 Variant 类型,例如具有文本框中的 Text 属性,则必须指定属性名。也就是说,必须用 Text1.Text 代替 Text1

最后的参数是属性的缺省值。本例中给出的是关键字 False。在一般情况下,需要创建一个形如 PROPDEFAULT_MASKED 这样的全局常数来存放该值,因为它需要被用于三个不同的地方,即 WriteProperties、ReadProperties 和 InitProperties 事件过程中。

提供缺省值的重要性

初看起来,可能会感到奇怪,为什么在保存属性值时还要提供缺省属性值?这样对控件用户是有好处的,因为它可以减少控件所在的容器的 .frm、.dob、.pag、或 .ctl 文件的大小。

仅当属性值不同于缺省值时,Visual Basic 才会为属性写出一行,这样就可以节省空间。假定 Masked 属性的缺省值是 False,WriteProperty 方法仅当属性值设置为 True 的时候才会为属性写出一行。

可以很容易地看到这种技术是怎样减少 .frm 文件规模的:打开一个新的 Standard EXE 工程,在 Form1 上添加一个 CommandButton,然后保存 Form1.frm。使用 Importancepad 或 Wordpad 等文本编辑器打开 Form1.frm,再比较为 Command1 写入到文件中的属性数和 Command1 在“属性”窗口中的属性数。

只要有可能,在初始化、保存和获取属性值时,都应该说明控件属性的缺省值。

获取属性值

可以在 UserControl 对象的 ReadProperties 事件中获取属性值,如下所示:

Private Sub UserControl_ReadProperties(PropBag As _
      PropertyBag)
   On Error Resume Next
   '获取 Masked 属性的值。
   Masked = PropBag.ReadProperty("Masked", False)
   ' . . . 其它属性 . . . 
End Sub

PropertyBag 对象的 ReadProperty 方法需要两个参数:一个字符串用来保存属性的名称,另一个为缺省值。

如果存在原先保存的属性值,则 ReadProperty 方法将返回该属性值,否则返回缺省值。把 ReadProperty 方法的返回值赋给属性,以便执行 Property Let 语句中的验证代码,如上例中所示。

在控件运行时,如果把属性值直接赋给保存该属性值的私有数据成员或子控件属性,而跳过了 Property Let,那么必须在 ReadProperties 事件中复制 Property Let 的验证代码。

提示 在 UserControl_ReadProperties 事件过程中总是应该添加错误捕获处理,以避免控件因为用户用文本编辑器输入 .frm 文件的非法属性值而受到影响。

运行时只读的属性

如果创建的属性可以由用户在设计时设置,但在运行时是只读的,那么 ReadProperties 事件中会有一个小问题。因为在运行时必须设置一次属性值,即将属性设置为用户在设计时选择的值。

解决这一问题的明显办法是跳过 Property Let,但是这样就失去了对设计时从源文件装载的非法属性值的防护。这个问题正确的解决方法请参阅“创建只在设计时、只在运行时或运行时只读的属性”。

初始化属性值

可以在 UserControl 对象的 InitProperties 事件中为属性赋初值。每个控件实例的 InitProperties 仅发生一次,即当实例第一次被放到容器上时。

此后,当由于关闭和打开窗体、卸载和加载工程、运行工程操作等而销毁和重建控件实例时,控件实例只会收到 ReadProperties 事件。关于内容的讨论请参阅本章前面的“理解控件的存活期和关键事件”。

应确保初始化属性时的缺省值与保存和提取属性值时的缺省值相同。否则就会失去使用缺省值可以得到的好处,请参阅本主题前面的“提供缺省值的重要性”。

提示 保证缺省值一致的最简单方法是为其创建一个全局常数。

保存和获取二进制数据

除了象 Picture 这样的标准对象,ReadProperty 和 WriteProperty 方法只有有限的能力来存储和获取二进制数据。由于 Unicode 转换和 .frm 文件中线长度的限制,在字符串中存储二进制数据是有问题的,但两个方法都将接受 Byte 类型的数组。

如果已经在 Byte 数组中保存了属性的数据,就可通过将 Byte 数组传递给 WriteProperty 方法的值参数把数据保存起来:

' Private storage for the Blob property.
Private mbytBlob(0 To 1023) As Byte

Private Sub UserControl_WriteProperties(PropBag As _
      PropertyBag)
   '保存 Blob 属性的二进制数据。
   PropBag.WriteProperty "Blob", mbytBlob
   ' . . . 更多的属性 . . .
End Sub

将数据保存在 .frx 文件中。在获取数据时,指定从 ReadProperty 方法到 mbytBlob 的返回值。如果 mbytBlob 具有可变长度,则必须用诸如“BlobSize”这样的名称将它的大小当作单独项保存起来,使您能够获取大小,并在获取数据之前 ReDim 数组。(对此,不一定要具有 BlobSize 属性。)

同样可用这一技术存储任何二进制属性数据,您可设法将这些数据复制到 Byte 数组。