大多数的部件都有若干属性;通常,在类的 Initialize 事件中需要为这些属性建立缺省值。在对部件进行编译的时候,这些缺省值被“冻结”起来,那么,怎样才能允许使用这些部件的开发者改变这些缺省值,以便满足他们各自的特殊需要呢?为了解决这个问题,类提供了一个特殊的属性 Persistable,它可以用来保存同一个部件的不同实例的值。
假设现在有一个用于贷款计算的 ActiveX DLL,其中提供了一个与计算有关的 InterestRate 属性。在初始化 InterestRate 的时候可以将其设置成某个值,但是,由于利率不可避免地要发生波动,因此在每次运行部件的时候可能需要对 InterestRate 属性进行修改。利用类的持久保持功能,InterestRate 的数值将被保存起来,只有当利率发生变化的时候才对其进行修改。每次运行部件的时候,部件可以取出存储的 InterestRate,因此部件总是能够提供最近期的利率。
尽管 ActiveX 控件总是能够使它们的数据成为持久保存的;ActiveX 部件的数据的持久保存特性略有不同。控件将其属性设置值保存在它的 .cls 文件,但是部件不能够这样处理。部件使用的是一个 PropertyBag 对象,该对象能够被保存在任何地方,如文件、数据库、电子数据表格中的单元格,甚至系统注册表中。
详细信息 如果需要了解与 ActiveX 控件持久保存有关的更多信息, 请参阅“建立 ActiveX 控件”中的“保存控件属性”。
要成为一个具有持久保存特性的类,它必须满足两个条件:它必须是公有的,而且必须是可创建的。仔细考虑一下就会明白,这些条件是有意义的,因为持久保存对私有的部件是没有必要的。如果一个类满足了这两个条件,那么“属性”窗口中将出现 Persistable 属性。
在缺省情况下,Persistable 属性被设置为 0 (NotPersistable)。如果将该值设置为 1 (Persistable),三个新的事件将被加到类中:ReadProperties、WriteProperties 和 InitProperties。显然,这些事件被用于读、写和初始化类的属性。
通过在 Property Let 或 Property Set 过程中实现 PropertyChanged,可以将一个属性标记为持久保存的,如下面的例子所示:
Private mInterestRate As Single
Public Property Let InterestRate(newRate As Single)
mInterestRate = newRate
PropertyChanged "InterestRate"
End Sub
通过调用 PropertyChanged 方法,InterestRate 属性被标记为无用的。如果类中的某个属性被调用了 PropertyChanged,那么在类终止运行的时候 WriteProperties 事件将被触发。
当一个类终止向一个称为 PropertyBag 对象的私有存储区域写入当前属性值时,WriteProperties 事件过程被使用下列代码将一个属性保存在内部的 PropertyBag 中:
Private Sub Class_WriteProperties(PropBag As PropertyBag)
PropBag.WriteProperty "InterestRate", mInterestRate, conDefaultRate
End Sub
在上面代码中,Property Bag 的 WriteProperty 方法有三个参数:需要保存的属性的名称 ("InterestRate")、需要保存的值 (mInterestRate)、以及一个缺省值 (DefaultRate)。如果新的值与常数 conDefaultRate 一致,那么 WriteProperty 方法可以不把新值写出。
ReadProperties 事件在进行类初始化的时候被触发,而且只有当 PropertyBag 中有数据的时候才被触发。如果 PropertyBag 是空的,那么触发的将是 InitProperties 事件。ReadProperties 和 InitProperties 事件中的代码被用于设置初始的属性值:
Private Sub Class_ReadProperties(PropBag As PropertyBag)
mInterestRate = PropBag.ReadProperty("InterestRate", conDefaultRate)
End Sub
Private Sub Class_InitProperties ()
mInterestRate = conDefaultRate
End Sub
需要注意的是,常数 conDefaultRate 被用于上面两个过程中,以提供缺省值。使用常数来提供缺省值是值得推荐的,因为它可以避免由于在不同过程中定义不同的缺省值而导致的危险。
在第一次使用 New 关键字创建 Load 类的实例时,InitProperties 事件将被触发;在类成为持久保存的之后,再次创建类的时候触发的将是 ReadProperties 事件。
为了使 ActiveX 部件成为持久性的,需要创建 PropertyBag 对象的一个实例。这看起来可能有些多余,类不是已经有了自己的 PropertyBag 吗?为什么不能使用那个 PropertyBag 呢?原因很简单,当对象的生命周期结束的时候,它的 PropertyBag 也将消失。它只存在于内存中;为了保证持久保存,需要在某个合适的位置保存该对象的一份副本,以便在将来需要的时候能够再次将其取出。
PropertyBag 可以被看作一个“大麻袋”,它可以被装满东西,然后藏在某个安全的地方。到底藏在什么地方将完全取决于实现者。下面的窗体代码演示了如何使一个对象持久性地保存在一个文本文件中:
Private pb As PropertyBag '
声明一个PropertyBag
对象。Private LoanObject As Loan '
声明一个Loan
对象。Private Sub Form_Unload(Cancel As Integer)
Dim varTemp as Variant
' PropertyBag
对象的实例化。Set pb = New PropertyBag
'
使用WriteProperty
将对象保存在PropertyBag
中。pb.WriteProperty "MyLoanObject", LoanObject
'
将PropertyBag
的内容赋予一个Variant
。varTemp = pb.Contents
'
将其保存到一个文本文件中。Open "C:\Loandata.txt" For Binary As #1
Put #1, , varTemp
Close #1
End Sub
PropertyBag 对象的 Contents 属性中包含了以字节数组形式保存的 Loan 对象。为了将其保存到一个文本文件,必须首先将其转换成一种文本文件可以理解的数据类型,例如本例中的 Variant。
当对象已经被保存在文本文件中之后(或者其他类型的存储空间中),它可以被方便地转移到其他位置。请想象 Loan 对象中不仅包含了 InterestRate,还包含了贷款应用程序中所需的其他所有属性值。那么 Loandata.txt 文件可以被递交到中心部门进行验证。如果窗体需要再次使用 Loan 对象,那么代码将类似于下面的样子:
Private pb As PropertyBag '
声明一个PropertyBag
对象。Private LoanObject As Loan '
声明一个Loan
对象。Private Sub Form_Load()
Dim varTemp As Variant
Dim byteArr() as Byte
' PropertyBag
对象的实例化。Set pb = New PropertyBag
'
将文件的内容读入一个Variant
中。Open "C:\Loandata.txt" For Binary As #1
Get #1, , varTemp
Close #1
'
将Variant
赋值到一个Byte
数组中。ByteArr = varTemp
'
赋予PropertyBag Contents
属性。Pb.Contents = ByteArr
'
使用PropertyBag
进行对象实例化。Set LoanObject = pb.ReadProperty("MyLoanObject")
End If
有人可能已经注意到,对象需要被赋值三次:首先从文本文件到 Variant,然后从 Variant 到 Byte 数组,最后才赋值到 Contents 属性。这是因为,Contents 属性只能接受 Byte 数组,如果试图将其他数据类型赋予它,得到的将是一条错误信息。
那么,到底发生了什么呢?我们是否真的在甲地创建了一个对象,而在乙地重新使用它呢(完全保持了它的数据)?其实,不完全是这样的。实际上,原始的对象已经被消灭了,我们在 PropertyBag 中实际传递的其实只是对象的一份精确副本,而不是对象本身。这种“克隆”一个对象以便进行复用的能力是一种功能非常强大的概念,它对于工作流应用程序的设计尤为重要。