属性页看起来很象窗体,而且设计属性页也类似于设计窗体。但是,属性页的工作方式却与窗体的工作方式并不相同。
例如,当“属性页”对话框创建属性页的一个实例时,PropertyPage 对象所得到的第一个事件是 Initialize 事件—这跟窗体的情况是一样的。但是,与窗体不同的是,PropertyPage 对象并不获得 Load 事件。PropertyPage 对象的关键事件是 SelectionChanged 事件。
本主题要检查 PropertyPage 对象所必须做的三件事:
当显示属性页并且当前选定控件的列表被改变时,就出现 SelectionChanged 事件。
例如,当选定一个控件实例并打开“属性页”对话框以后,开发者可能会意识到需要对两个控件示例的属性进行更改。在按下 CTRL 键的同时单击第二个示例,就可将第二个示例添加到已选定控件列表中。于是,每个属性页都可以接收到 SelectionChanged 事件。
重点 应该总是用这种方式处理 SelectionChanged 事件— 就好象第一次加载属性页那样。如所看到那样,改变选定就基本上改变了属性页的状态。
在 SelectionChanged 事件中要做的首要事情就是设置控件值,而控件则显示编辑属性值。例如,考虑 VirtualVelociraptor 控件的“通用”页(原图见图 10.1):

假设 VirtualVelociraptor 控件的 Age 属性使用下面的公共 Enum:
Public Enum DinoAge
vvHatchling
vvJuvenile
vvAdult
End Enum
属性页的 SelectionChanged 事件看起来是这样的:
Private Sub PropertyPage_SelectionChanged() '将第一个已选定控件的DinoName属性值放置到' txtDinoName文本框中,从而对其进行显示和编辑。txtDinoName = SelectedControls(0).DinoName'使用第一个已选定控件的Age属性值,从而'在Age框架中选定合适的选项按钮。optAge(SelectedControls(0).Age).Value = True'(以上代码是根据下面的事实决定的,即DinoAge Enum'的元素具有值0、1及2。)End Sub
提示 属性页向导将用文本框控件和复选框(对于 Boolean 属性来说)构造属性页,并为 SelectionChanged 事件生成缺省代码。
SelectedControls 集合包含当前在容器中选定的所有控件,而且开发者正在该容器上工作。该集合可能包含控件的许多实例;如果属性页由控件部件中的多个控件共享,则该集合可能包含多种类型的控件。
注意 不必担心集合包含了不属于您的控件(比如说文本框),因为“属性页”对话框仅仅显示被所有当前选定的控件所使用的那些页。
现在暂时忽略选中多个控件的可能性。上述 SelectionChanged 事件中的代码就是要在集合中对第一个控件的每个属性取值,并将此值赋给属性页上的合适控件。
对于选定单个控件的情况,这就会把控件的所有属性值放在用户可对它们进行编辑的区域内。
可用下拉菜单列表来显示枚举元素,而不必使 Age 属性的属性值以一组属性按钮的形象出现:

下拉菜单列表比选项按钮占用的空间少(随着数目的增加,这个优点会更突出),而且还会显示代码中将会用到的常量名。
下段代码说明如何对这样一个列表进行设置。
Private Sub PropertyPage_SelectionChanged() txtDinoName = SelectedControls(0).DinoName '创建一个下拉式列表,其中包含Age属性的Enum元素的值和名称',还要选定与Age属性的当前值相对应的'元素。cboAge.AddItem vvHatchling & " - vvHatchling"cboAge.AddItem vvJuvenile & " - vvJuvenile"cboAge.AddItem vvAdult & " - vvAdult"cboAge.ListIndex = SelectedControls(0).Age'(在下拉菜单列表中,每个Enum元素的索引与元素的'值相同。)End Sub
提示 尽管能够任意选择可编辑的对属性有意义的表示,但要记住,每个属性所占用的空间越多,所需要的选项卡就越多。减少选项卡的数目可以使控件的属性页易于使用。对于大多数枚举,下拉菜单列表将使空间得到最大效率的利用。
为确定是否选定了多个控件,可对 SelectedControls 集合的 Count 属性进行测试,看看该属性是否大于一。
处理多个选定控件的示例时,将控件的属性分为两组是很有用的:
无论何时,只要选定多个控件就使第二种属性的编辑区域无效,这是在 SelectionChanged 事件中可以采取的一种方法。在 ApplyChanges 事件的讨论中将指出另一种技巧。
如果工程中有多个控件,而且两个这样的控件共享一个属性页,那么,就要确保读取属性值的代码能够捕获错误。如果选定的第一个控件不包括该页显示的全部属性,那么,在试图读取属性值时就会出现错误。
当用户已经在属性页上编辑属性时,为了将此事告知 Visual Basic,必须把 PropertyPage 对象的 Changed 属性设置为 True。因为无法搞清决定要改哪个属性,所以对该页显示的每个属性都要如是处理。
例如,在上例的 DinoName 属性或 Age 属性中进行更改,为将此事通知 PropertyPage,可用下列代码:
Private Sub txtDinoName_Changed()
Changed = True
End
Private Sub cboAge_Change()
Changed = True
End
注意,这与 PropertyPage.Changed = True 的编码完全相同。
通知 PropertyPage 对象,值已发生变化,这就使“属性页”对话框上的“应用”按钮有效,而且在按下了“应用”按钮,或用户另选一个选项卡,或解除对话框时,都会导致 ApplyChanges 事件出现。
注意 可能希望跟踪已发生改变的属性,所以不必将它们全部写出来。
PropertyPage 对象中的第二重要的事件是 ApplyChanges 事件。在这个事件中,可将已编辑过的属性值复制回到当前选定的控件中。
当用户做下面这些事情时,就会出现 ApplyChanges 事件:
以下有关 ApplyChanges 事件的代码假定,SelectionChanged 事件用 Age 属性的下拉菜单列表来编制代码,这一点如前所示。
Private Sub PropertyPage_ApplyChanges() Dim vv As VirtualVelociraptor '只对第一个选定控件的DinoName属性进行设置。SelectedControls(0).DinoName = txtDinoNameFor Each vv In SelectedControls'将DinoAge属性的下拉菜单列表中的当前选定值'传送到所有已选定控件中。vv.DinoAge = cboAge.ListIndex'(上述代码可以正常工作,因为Enum的'每个元素的值都与该元素在cboAge中的索引号相同。)NextEnd Sub
一般来说,因为将同样的名字赋给所有虚拟的 velociraptors 并没有什么意义,所以 DinoName 属性只用于第一个选定控件,另一方面,Age 属性则用于所有选定控件。
注意 对于多个选定的控件来说,到底设置哪些属性是有意义的,这要由控件作者决定。
对于上述情况,ApplyChanges 事件中没有出错的可能性。文本属性只是简单的字符串,而下拉菜单列表则限制用户在输入 Age 属性时只能输入有效值。
如果属性页允许用户输入可能被 Property Let(或 Property Set)过程拒绝的值,则应在 ApplyChanges 事件中使用错误捕获。最简单的方案莫过于使用 On Error Resume Next,并在每个可能产生错误的属性之后对 Err.Number 进行测试。
当产生错误时:
重点 设置 Changed = True 有两个作用。首先重新使“应用”按钮有效,其次,当用户单击“确定”时,防止解除“属性页”对话框。这是防止对话框被关闭的唯一方法。