属性页的工作方式

属性页看起来很象窗体,而且设计属性页也类似于设计窗体。但是,属性页的工作方式却与窗体的工作方式并不相同。

例如,当“属性页”对话框创建属性页的一个实例时,PropertyPage 对象所得到的第一个事件是 Initialize 事件—这跟窗体的情况是一样的。但是,与窗体不同的是,PropertyPage 对象并不获得 Load 事件。PropertyPage 对象的关键事件是 SelectionChanged 事件。

本主题要检查 PropertyPage 对象所必须做的三件事:

SelectionChanged 事件

当显示属性页并且当前选定控件的列表被改变时,就出现 SelectionChanged 事件。

例如,当选定一个控件实例并打开“属性页”对话框以后,开发者可能会意识到需要对两个控件示例的属性进行更改。在按下 CTRL 键的同时单击第二个示例,就可将第二个示例添加到已选定控件列表中。于是,每个属性页都可以接收到 SelectionChanged 事件。

重点 应该总是用这种方式处理 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
   ' 的元素具有值 01  2。)
End Sub

提示 属性页向导将用文本框控件和复选框(对于 Boolean 属性来说)构造属性页,并为 SelectionChanged 事件生成缺省代码。

SelectedControls 集合

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

提示 尽管能够任意选择可编辑的对属性有意义的表示,但要记住,每个属性所占用的空间越多,所需要的选项卡就越多。减少选项卡的数目可以使控件的属性页易于使用。对于大多数枚举,下拉菜单列表将使空间得到最大效率的利用。

对多个控件的 SelectionChanged 事件进行编码

为确定是否选定了多个控件,可对 SelectedControls 集合的 Count 属性进行测试,看看该属性是否大于一。

处理多个选定控件的示例时,将控件的属性分为两组是很有用的:

无论何时,只要选定多个控件就使第二种属性的编辑区域无效,这是在 SelectionChanged 事件中可以采取的一种方法。在 ApplyChanges 事件的讨论中将指出另一种技巧。

共享的属性页

如果工程中有多个控件,而且两个这样的控件共享一个属性页,那么,就要确保读取属性值的代码能够捕获错误。如果选定的第一个控件不包括该页显示的全部属性,那么,在试图读取属性值时就会出现错误。

通过设置 Changed = True 使“应用”按钮有效

当用户已经在属性页上编辑属性时,为了将此事告知 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 事件出现。

注意 可能希望跟踪已发生改变的属性,所以不必将它们全部写出来。

ApplyChanges 事件

PropertyPage 对象中的第二重要的事件是 ApplyChanges 事件。在这个事件中,可将已编辑过的属性值复制回到当前选定的控件中。

当用户做下面这些事情时,就会出现 ApplyChanges 事件:

以下有关 ApplyChanges 事件的代码假定,SelectionChanged 事件用 Age 属性的下拉菜单列表来编制代码,这一点如前所示。

Private Sub PropertyPage_ApplyChanges()
   Dim vv As VirtualVelociraptor
   '只对第一个选定控件的 DinoName 属性进行设置。
   SelectedControls(0).DinoName = txtDinoName
   
   For Each vv In SelectedControls
      ' DinoAge 属性的下拉菜单列表中的当前选定值
      '传送到所有已选定控件中。
      vv.DinoAge = cboAge.ListIndex
      '(上述代码可以正常工作,因为 Enum '每个元素的值都与该元素在 cboAge 中的索引号相同。)
   Next
End Sub

一般来说,因为将同样的名字赋给所有虚拟的 velociraptors 并没有什么意义,所以 DinoName 属性只用于第一个选定控件,另一方面,Age 属性则用于所有选定控件。

注意 对于多个选定的控件来说,到底设置哪些属性是有意义的,这要由控件作者决定。

处理 ApplyChanges 中的错误

对于上述情况,ApplyChanges 事件中没有出错的可能性。文本属性只是简单的字符串,而下拉菜单列表则限制用户在输入 Age 属性时只能输入有效值。

如果属性页允许用户输入可能被 Property Let(或 Property Set)过程拒绝的值,则应在 ApplyChanges 事件中使用错误捕获。最简单的方案莫过于使用 On Error Resume Next,并在每个可能产生错误的属性之后对 Err.Number 进行测试。

当产生错误时: