显露子控件属性

按照缺省规定,UserControl 对象的属性,以及它的子控件的属性,对最终用户是不可见的。这样控件的制作者就可以充分自由地确定控件的接口。

然而,控件的属性经常需要通过简单地委派给 UserControl 对象或其子控件的现有属性来实现。本主题将解释用手工方式显露 UserControl 对象或其子控件属性的技术。

理解委派和属性映射将有助于更好地运用 ActiveX 控件接口向导 (ActiveX Control Interface Wizard),它的设计目的是自动地完成尽可能多的处理。这也使您能够处理连向导也不能应付的复杂工作。

通过委派显露属性

假设需要创建一个允许最终用户以某种特殊格式编辑字段的控件,例如 Driver's License Number。先放一个文本框到 UserControl 上,给它起一个好记的名称,如 txtBase。

因为新控件是对某个 Visual Basic 控件的改进,所以也得调整 txtBase 的尺寸来适合 UserControl。这可在 UserControl 的 Resize 事件中进行,如本章前面的“用子控件提供外观”。

为了创建控件的 BackColor 属性,可以简单地显露文本框的 BackColor 属性,下面的代码取自 UserControl 代码模块:

Public Property Get BackColor() As OLE_COLOR
   BackColor = txtBase.BackColor
End Property

Public Property Let BackColor(ByVal NewColor _
      As OLE_COLOR)
   ' . . . 属性验证代码 . . . 
   txtBase.BackColor = NewColor
   PropertyChanged "BackColor"
End Property

关于 PropertyChanged 的作用和重要性,请参阅本章前面的“在控件中添加属性”。

为控件创建的 BackColor 属性只是把值保存在文本框控件的 BackColor 属性中。显露的方法与此类似,可以把工作委派给被改进控件的相应方法。关于委派的详细内容,请参阅《程序员指南》的“用对象编程”中的“合成对象”。

提示 当把 OLE_COLOR 数据类型用于颜色属性时,“属性”窗口会自动显示出用来打开标准颜色选择对话框的椭圆按钮。关于标准属性类型的内容,请参阅相关主题“使用标准控件属性类型”。

注意   用户控件不支持 动态数据交换(Dynamic Data Exchange)(DDE)。如果您删除象LinkMode 的与 DDE 相关的属性,或者删除象 LinkOpen 的事件,则它们将载设计时显示,但是将会造成在运行时的错误。

重点 因为 UserControl 对象和子控件的属性是通过委派显露的,所以不能显露仅在设计时的属性,如 Appearance 和 ClipControls。为这种属性选择的设定值在 ActiveX 控件中是固定不变的。

将属性映射到多个控件

经常需要把多个子控件属性映射到控件的同一个属性。委派提供了处理这种情况的方法。

例如,假设已经在 UserControl 上放置一些复选框、选项按钮和标签,建立了控件的外观。那么以 UserControl 的 BackColor 作为控件的背景色是有意义的。然而,使子控件的 BackColor 属性与该颜色一致也是有意义的。

以下代码说明了这样一种实现:

Public Property Get BackColor() As OLE_COLOR
   BackColor = UserControl.BackColor
End Property

Public Property Let BackColor(ByVal NewColor _
      As OLE_COLOR)
   Dim objCtl As Object
   ' . . . 属性验证代码 . . . 
   UserControl.BackColor = NewColor
   For Each objCtl In Controls
      If (TypeOf objCtl Is OptionButton) _
            Or (TypeOf objCtl Is CheckBox) _
            Or (TypeOf objCtl Is Label) _
         Then objCtl.BackColor = NewColor
   Next
   PropertyChanged "BackColor"
End Property

在读属性值时,值通常由 UserControl 对象的 BackColor 属性提供。Property Get 的数据源最好是唯一的。

注意 当给控件添加某个属性,而底层的 UserControl 对象已经拥有该属性时,在代码中使用该属性名时将引用新定义的属性,除非用 UserControl 对属性名加以限定,如上例所示。

详细信息 关于使用 Controls 集合遍历所有子控件的内容,请参阅本章前面的“控件创建术语”。关于 PropertyChanged 的作用和重要性的内容,请参阅本章前面的“在控件中添加属性”。

多个 BackColor 属性

上面的实现方式引出了一个有趣的问题。怎样允许用户设置控件上所有文本框的背景色?BackColor 已经被映射到它最自然的用法,但是,通过属性名称总可以获得些创造性。

例如,能以上面代码为基础添加一个 TextBackColor 属性,用来设置控件上所有文本框的 BackColor 属性。再选择一个文本框作为 Property Get 的 TextBackColor 数据源,这就行了。(把 UserControl 对象的 BackColor 属性用于这种用途不会有什么意义。)

映射到多个对象属性

作为多个属性映射的另一个例子,可以为上述控件实现 TextFont 和 LabelFont 属性。一个属性控制所有标签的字体,另一个控制所有的文本框。

在实现多个映射的对象属性时,可以利用多对象引用带来的便利。例如可用如下代码来实现 LabelFont 属性:

Public Property Get LabelFont() As Font
   Set LabelFont = UserControl.Font
End Property

'为对象属性使用 Property SetPublic Property Set LabelFont(ByVal NewFont As Font)
   Set UserControl.Font = NewFont
   SyncLabelFonts
   PropertyChanged "LabelFont"
End Property

Private Sub SyncLabelFonts()
   Dim objCtl As Object
   For Each objCtl In Controls
      If TypeOf objCtl Is Label Then
         Set objCtl.Font = UserControl.Font
      End If
   Next
End Sub

SyncLabelFonts 辅助函数中的代码将把 UserControl 对象的 Font 对象的引用赋予每个 Label 控件的 Font 属性。因为所有的控件都具有到同一个 Font 对象的引用,所以字体的变化会在所有的选项卡中反映出来。

使用辅助函数,是因为在控件被初始化和读取被保存的属性时,也需要执行同一段代码。

注意 关于 PropertyChanged 的作用和重要性的内容,请参阅本章前面的“在控件中添加属性”。

用来初始化、保存和获取 LabelFont 属性的代码见下。作为选项,可以把 UserControl 对象的字体特性设置成与容器的字体特性一致,如“使用环境对象与容器保持一致”所述。

Private Sub UserControl_InitProperties()
   SyncLabelFonts
End Sub

Private Sub UserControl_ReadProperties(PropBag As _
      VB.PropertyBag)
   On Error Resume Next
   Set LabelFont = PropBag.ReadProperty("LabelFont")
End Sub

Private Sub UserControl_WriteProperties(PropBag As _
      VB.PropertyBag)
   PropBag.WriteProperty "LabelFont", LabelFont
End Sub

因为 Font 对象是标准对象,所以可以通过 PropertyBags 来保存和获取它。

使用控件的开发者现在就可以通过“属性”窗口为 LabelFont 属性设置字体。假定控件的名称是 MultiEdit,也可以在运行时使用类似如下的代码进行设置:

Private Sub Command1_Click()
   YourControl1.LabelFont.Bold = True
   YourControl1.LabelFont.Name = "Monotype Sorts"
End Sub

这段代码的好处在于,它从不为 LabelFont 属性调用 Property Let,这是可以检验出来的。把上述代码加到具有若干子 Label 控件的 UserControl 对象中,并在 Property Get 和 Property Let 中添加断点即可。

当 Visual Basic 执行上述代码的第一行时,它要调用 Property Get,并返回一个对 UserControl 对象的 Font 对象的引用。使用该引用可以设置 Font 对象的 Bold 属性。因为所有的子 Label 控件都具有到 UserControl 对象的 Font 对象的引用,所以变化会立即得到反映。

不要把子控件作为属性显露出来

有人也许会问:把子控件完全显露出来不是更加简单吗?例如,如果 UserControl 对象上有一个名称为 Text1 的文本框控件,可以试着写入下面的代码:

'孩子,别在家里试这个。
Property Get Text1() As TextBox
   Set Text1 = Text1
End Property

这样,控件的使用者就能够访问 Text1 的所有属性和方法了,制作者只写了一行代码。

以上代码不会被编译,因为 TextBox 不是公有数据类型。但这还并不是称它为“馊主意”的真正原因。

相对于有选择地显露子控件的属性和方法,显露所有的属性和方法也许会简化工作,但是请考虑一下控件的使用者会干些什么!他现在具有了对 Text1 的 Text 属性的直接访问权,能够跳过写在 Property Let 中的验证代码。还可以调整文本框的高度和宽度,这可能使 UserControl_Resize 事件过程中所写的代码完全瘫痪。

总的来说,开发者可能会得出结论说:这个控件漏洞太多,没有使用价值。但是且慢,还有更坏的地方。如果开发者通过其它开发工具使用该控件,如 Microsoft Excel 或 Microsoft Access,那么子控件的类型库信息将是不可使用的。所有对 Text1 的引用将都是后期约束,所以这个控件不仅漏洞多,而且速度慢。

显露子控件也限制了将来改变控件实现方式的能力。例如,将来可能想把控件的下一版本构建在其它子控件的基础上,而不是内在的 TextBox 控件。除非这个 SuperTextBox 的属性和方法与内在的 TextBox 精确匹配,否则用户不重写其代码就不能升级到新的版本。

只显露控件操作需要的子控件属性是良好的编程习惯。例如,如果上面提到的文本框中保存了一个用户名,可以通过 UserName 的一个属性显露 Text1.Text 的值。

使用 ActiveX 控件接口向导

如果有大量的子控件需要显露,或者一个子控件有很多属性需要显露,“ActiveX 控件接口向导”可以显著地减少显露子控件属性所需的编码工作量。

关于使用 “ActiveX 控件接口向导”的内容,请参阅相关主题“应该提供的属性”。