### ActiveX 深入浅出(二）

事件是用来通知使用控件的程序员，某件事情发生了，好让程序员可以相应的处理。事件在 VB 编程中比比皆是，例如，当鼠标点击控件事会发生 Click 事件，文本框内容改变了会发生 Change 事件，等等。但这儿要讲的不同于这些东西，也不同于 InitProperties, ReadProperties, WriteProperties 前面提到过的事件，那些是系统已经定义好了的。我们要做的是定义自己的事件。自定义的事件必须声明在模块的声明部分，然后就能够在任何地方任何时候，只要你觉得应该有事件了，就可以触发它。例如，一个代表眨眼的事件应该这样声明：
Public Event Blink()
在括号中，你可以放置任何你想要传递给事件的参数。对于一个 Click 事件，这个参数可能鼠标点击时 x 和 y 坐标。而这在理，应为只是眨眼，就用不到额外的参数了。
触发事件，得使用 RaiseEvent 方法。对于我们的眨眼事件，我们把它和一个计时器联系起来，这样它就能够时不时的眨眼了：
Private Sub Blinker_Timer()
RaiseEvent Blink
End Sub
就这样，一个事件就完成了，没有任何技巧或者秘密可言。现在留给程序原作的，只要编写相应的事件处理过程就行了，就像下面这样：
Private Sub BodyControl1_Blink()
Debug.Print "嘻嘻，我又眨眼了!"
End Sub
到这儿，大部分关于 ActiveX 控件的基本问题都讲了。后面的将是一些更高级的内容，包括图片和字体属性，“关于”对话框，运行时之读属性等等。在继续之前，再看看前面的内容，好好的在理解一下。好了吗？OK，现在开始更为激动的历程。
首先，我们先来看看对于属性，还有什么可以挖掘的东西，比如颜色或者图片之类的东西。
高级属性设计
色彩值被存贮在长整形变量中，但是如果你只是定义一个长整形变量，显然是不能够得到像 vb 中所提供的那个颜色选单：
这看起来很复杂，实际上做起来却一点也不难：所有你要做的只是把属性声明为 OLE_COLOR 类型，就像下面这段代码所作的那样：
Public Property Get BackColor() As OLE_COLOR
BackColor = UserControl.BackColor
End Property
Public Property Let BackColor(ByVal New_BackColor As OLE_COLOR)
UserControl.BackColor = New_BackColor
PropertyChanged "BackColor"
End Property
记得前面曾经提过除了 Let，Get，还有第三种属性过程吗？现在揭开迷底：它就是 Set 属性过程，当要给对象变量赋值的时候，是不能用 Let，而必须用 Set 来代替。这是因为保存在控件内部的对象变量，保存的并不是对象的拷贝，而只是对象的引用，也就是一个内存地址了。为了同一般变量的复制保存区别开来，VB 引入了 Set 属性过程。
或许你可能知道：字体和图片就是保存在对象中的，而且，它们都拥有各自的的对话框来设置相关属性。要用到这些对话框，所有我们要作的就是把图片或者字体申声明为 Picture 或者 Font 对象类型，并且为其设置 Set 属性过程。
Public Property Get Font() As Font
Set Font = lblText.Font
End Property
Public Property Set Font(ByVal New_Font As Font)
Set lblText.Font = New_Font
PropertyChanged "Font"
End Property
看看上面的代码，你是不是在想：也没有什么很难的啊。确实，就是这样简单啊。下面，我们来看看如何为控键建立只读属性。这也是在控件的设计中用的比较多的一个内容。
只读属性
最简单的方法，就是不要在 Let/Set 属性过程中加入任何东西。但通常，这并不能满足要求，有时，你可能需要一个运行时只读的属性。所谓运行时——和设计时相对应，是指控件最终在一个开发完成的程序中运行，而设计时指的是控件被使用在开发程序的过程中。
要实现运行时只读，要用到 Usercontrol 的 AmbientProperties 对象。它提供了很多关于控件容器的属性。中有一个 UserMode 属性，当控件处在运行时状态时，UserMode 值为真。通过在 Let/Get 过程中提供对 UserMode 的检测，就可以很容易的实现运行是只读属性了：
Public Property Get MultiLine() As Boolean
MultiLine = m_MultiLine
End Property
Public Property Let MultiLine(ByVal New_MultiLine As Boolean)
If Ambient.UserMode Then
Err.Raise 382
Exit Sub
EndIf
m_MultiLine = New_MultiLine
PropertyChanged "MultiLine"
End Property
这段代码保护属性只能够在设计时被修改，如果在运行时试图改变它， 就会产生“Property is read-only at run-time”错误。
和 AmbientProperties 对象相近的还有 Extender 对象。对于 Extender 对象，在开始编写控件前有必要好好的了解。Extender 对象是一个晚期绑定的借口，开发员可以通过它访问由控件容器（而不是控件本身）维护和控制的控件属性。它提供了一些属性，像 Name，Enable，Left，Top，Height，Width 等等，这些大多出现在一般的控件中，在编写控件属性前，应该看看是否已经存在在 Extender 对象中，一方面可以避免重复工作，另一方面也更有效率。
但是使用 Extender 对象还存在一些问题：并不是所有的容器都支持访问相同的 Extender 属性。所以选用 Extender 对象是必须十分小心，否这做出的控件只能为特定的容器所使用。但如果你只是为 VB 开发控件，那就不用有这些顾虑了，尽量的用吧。
还有一点要注意的是，Extender 对象不能在 usercontrol 的 initialize 事件中访问它，但是可以在 initProperties 和 ReadProperties 事件中使用。
枚举
在控件中设置属性时，使用枚举是一种很常见的的方式。它提供了一个下拉列表和若干选项让你选择。这样方便了用户的操作，又不用考虑过多的兼容性和错误处理问题，简化了属性设置，而且更加安全。
首先，必须建立一个枚举结构，放在声明部分。然后给出一系列的常量和对应的字符串。常量值可以零，或者是任何比它前面一个常量值大的整数。如果没有给出常量，那么 VB 自动为其赋值，第一个未指定的赋值为零，其它的值为前面一个数加一：
Public Enum eDirection
Left
Right = 1
Up
Down
End Enum
要实现枚举属性，必须创建一个带有 Let 和 Get 属性过程的标准属性。这里的技巧是将属性的类型声明为给出的枚举类型：
Public Property Get Direction() As eDirection
Direction = m_Direction
End Property
Public Property Let Direction(ByVal New_Direction As eDirection)
m_Direction = New_Direction
PropertyChanged "Direction"
End Property
唯一要注意的是，只能够在设计时修改属性列表，而不允许在运行时这么做。其它的，像读，写，保存和检索，都和使用标准属性是一样的。
这就是所有的技巧了。难以置信的简单，是吗？你的控件是不是看起来更为专业了呢？
Usercontrol 对象
用 Visual Basic 所创建的 ActiveX 控件总是由 UserControl 对象加上选中放到 UserControl 上的任何控件（称为子控件或则组成控件）所组成。就象 Visual Basic 窗体一样，UserControl 对象具有代码模块以及可视化的设计器。将组成控件放到 UserControl 对象的设计器上，就象把控件放到窗体上一样。 在窗体上放置 ActiveX 控件的实例时，就创建了 UserControl 对象，以及放在 UserControl 设计器上的所有子控件的实例。这些对象都被封装在控件中。
UserControl 对象有自己的属性、方法和事件。对于一些属性，像 BorderStyle，BackColor 等等，与其自己写代码来实现 BackColor 属性，不如就直接使用 UserControl 对象的。这就意味着 ActiveX 控件的 BackColor 属性只需要简单地调用 UserControl 对象的 BackColor 属性就行了。同样地，也可以在 UserControl 对象现有的 Click 事件的基础上设计您的控件的 Click 事件。
事实上在前面的内容中，我们时时都在和这个对象打交道，但是它有一些特别的东西，是应该值的多一些注意的。它的大部分属性应该在控件的设计时来完成设置。在这儿我解释一些比较晦涩难懂得属性的含义，还有一些一望既知，就不多费口舌了。
Alignable 属性，当被设置成真的时候，VB 将自动为控件添加一个新的属性：align。这样就能够像放置工具条那样安排控件在容器中的位置，而且这还意味着你的控件能够被放置在 MDI 程序中。
CanGetFocus 属性，能够决定用户控件是否能够在运行时获得焦点。当要创建一个图形控件，或者像 Timer 那样在运行是不可见的控件时，就要设置这个值为 False。要注意的是：只要控件至少包含一个设置为能够接收焦点的子控件，CanGetFocus 属性就不能设置为 False。如果 CanGetFocus 设置为 False，则其所有的子控件都不能设置为接收焦点。
ControlContainer 属性定义一个控件是否能够像 frames 或者 PictureBoxes 控件那样作为控件容器包含其它的控件。
DefaultCancel 属性可以为控件添加 Default 和 Cancel 属性。在添加 Default 和 Cancel 属性后，控件就可充当标准命令按钮。也就是说当 Default 被设为 Ture 后，按下回车键会触发控件的 click 事件，而当 Cancel 属性被设为 Ture 时,Esc 键按下也会触发 click 事件。
你可以通过检查 AmbientProperties 对象的 DisplayAsDefault 属性来知道控件是不是缺省控件。
InvisibleAtRuntime 能够让你建立像 Timer 之类的控件，在运行的时候是不可见的。
最后，ToolboxBitmap 属性用来指定放在 VB 工具箱上的图标的。微软建议的大小是 32x32，但是实践证明 23x23 或 24x24 工作起来更好，32x32 是被缩放到那么大了再显示出来的。
属性的属性
说起来有点绕口，也有点费解，是吗？。VB 允许为控件的每个成员设置属性。这里面包含了一些较高级的内容，能够让你建造更为专业化的控件。在菜单栏中点击工具|过程属性，会出现下面的对话框：
你可以在描叙框内输入一段对控件的说明，在帮助上下文标识符内输入一个帮助的关联 ID 号，将你的控件与一个帮助文件关联起来，这样当点了属性后再按 F1 键就可以给出这个属性的帮助内容。
利用“在属性浏览其中使用本页”字段，可以给控件的定制属性页分配选定的号码。这样当用户从 VB 的属性浏览器中选择该成员时，VB 将直接线是属性页。 “属性分类”字段能够让属性在 VB 的属性浏览器的“按分类序”模式中出现在特定的类别下。这些类别包括外观，字体，位置，杂项等等，只要选择一项就行了。“隐藏该成员”可以让属性不在属性浏览器中显示出来，这对于一些不想让用户看到得公有成员有用，但是要记住，它只是隐藏而不是不许被使用。而利用“在属性浏览器中不显示”可以在控件的设计时（而不是在运行时）把属性从属性浏览器中去掉。作为一般的原则，任何用 ReadProperties 和 WriteProperties 实现的永久的属性，都应当被属性浏览器显示出来，反之，任何非永久性的属性就不应被显露出来。
“缺省用户界面”用来设置控键的缺省属性和方法。比如，因为 Caption 属性是 Lable 控键的缺省属性，那么就可以对代码进行一些简化，把
Label1.Caption = "Hello"
可以简写为：
Label1 = "Hello"
最后是关于数据绑定的部分。在 VB 中，数据库的应用是很常见的。利用 MS 的数据访问功能，可以很容易的把控件属性和数据库的字段联系起来。当选择了“属性为数据绑定”和“绑定属性到数据字段”两个复选框后，该属性就可以以标准绑定控键的方式运行，这意味着你可以在为该属性选择数据库控件和字段名称。
最后的话
到这儿，几乎大部分和用户控件有关的内容都讲完了。你现在就可以开始为自己的程序定制合适的控件，还可以让别人分享你的成果。虽然内容不是很多，但要完全吃透也不是件容易的事。所以，多看多练还是必要的，学无止境嘛。
当然，没有十全十美的东西，对于 ActiveX 控件也是这样的。合理的使用用户控件是必须要注意的事情。ActiveX 控件很容易就会变得很复杂，一不小心的话，你所付出的会远远多于你所得到的。
如果你只是想要一个带有属性和方法的对象，那么类或许是更好的选择。用户控件与应用程序间相对复杂的接口，会占用较多的资源。而在编程方面，控件也要比类复杂一些。但是如果你要求属性，方法和事件实现永久的数据存贮，后者要在程序的外部实现组件更新，那么就要用到用户控件了。总之，不要以为这是一件容易的是。虽然我在这儿讲得很简单，但是对一个完整的用户控件的设计，编写，以及测试和调试所作的工作，绝对不会比一个一般的 VB 应用程序来的少。