### ActiveX 深入浅出(一)

纲要
可以这样说，Active 平台代表了 Microsoft 的世界观。使用 ActiveX 控件,来构筑包括从与用户交互和适应 COM 的事务处理监视器，到 Web 服务器，全部实现自动化的机构，这就是 Microsoft 的打算了。桌面用的、基于 COM 的组件叫做 ActiveX 控件。所谓 ActiveX 控件不过是遵从一定的标准、与客户端交互的 COM 对象而已。基于组件的应用开发,其方法和组装电子装置一样,可以用已制作好的组件部件来构筑应用。这确实是一项很令人神往的技术。虽然说起来高深，但随着 ActiveX 的广泛应用，越来越多的编程工具都支持创建 ActiveX 控件了。这其中就包括了 VB。在 VB 中设计一个 ActiveX 并不比一个普通的 VB 应用程序难上许多。这篇文章，就是一步步教你如何在 VB 中创建一个 ActiveX 控件。
ActiveX 深入浅出(一）
作 者 : 扬眉
什么是 ActiveX 控件？
一个简单的回答就是：拥有图形界面的类。你可能曾经用过类来编程，那是一种实现代码重用的的好方法。当然，它也提供了很多其它的好处，这儿并不打算一一列举出来。ActiveX 控件将这个概念近一步的深化了，能够让你编写一个窗口小部件（widget），然后把它打包起来，在以后的程序里用到它，或者作为特定的问题的解决方法，给程序员提供更简便的编程方法。 用 ActiveX 你可以建造“复合式”控件，由其它的几种控件组合而成。也就是说，在 ActiveX 控件中你不但可以使用 VB 中诸如文本框，图片框之类的普通控件，你还可以用其它的 ActiveX 控件来构成你自己的控件，实现你想要的功能，并把它打包以背后用。
ActiveX 控件的组成
一个 ActiveX 控件由它的一些成员组成：属性，方法以及事件。它们之间有什么样的逻辑关联呢？还是用我们的身体来做个例子吧，将身体就看成一个 ActiveX 控件。 这个控件首先应该有一些属性，比如：眼是否睁开属性。显然，这个属性值应该有两种情况：开或则闭。使用的时候能够告诉“身体控件”让这个属性换一个新的值，以决定睁眼或者闭眼，或者得到当前的属性值，以知道目前的身体状况。
方法是控件中过程和函数的统称，同其它任何的 VB 函数和过程并没有什么不同，你同样可以向它们传递参数，并返回想要的值。假设有一个类描叙了身体这个对象，它应该有“Look”这个方法，并且应该能接受“Direction”这个参数，那么，这个方法就应该写成这样：
Public Sub Look(Direction As Integer)
Select Case Direction
Case 0
'向左看
Case 1
'向右看
Case 2
'向前看
Case 3
'向后看
End Select
End Sub
若要求返回值，我们就举一个“读”的例子吧。这时你必须把“Read”这个方法声明成函数而不是过程 ：
Public Function Read() As String
'读的一些操作
Read = "Hello from the World"
End Function
在身体控件中，我们用的还是“Look”这个方法。当调用这个方法的时候，就是指定眼睛应该去“看”了。同样，我们还指定了另外一个方法，“Read”。这一个方法将用来返回看到的东西。
“方法”这个概念是不是很简单呢？如果你还不能够理解，也可以这样来想：你的控件就像是一台机器，你拨动控制开关（输入参数），转动把手（调用方法），然后机器的灯闪动，运行起来（执行方法），最后从机器里蹦出一些东西（返回值），就是这样了。但等等……如果你的机器想告诉你什么事情，它该怎么做呢？这时就该是“事件”出场了。
最后，身体控件还会提供一个“眨眼”的事件，用来在进行眨眼这个动作的时候，通知开发者发生了这个事件，但是不必知道身体内部的工作方式和为什么这个事件会被触发。
来动手吧，作一个控件。不要以为作一个控件是非常难的事，虽然它和一般应用程序的编写是有一点不同的地方。在上面我们已经知道，一个 ActiveX 控件是由属性，方法和事件组成的，我们先看看如何在程序中实现这些东西，然后再把它们联系起来，组合成一个右机的整体。属性最简单的形式是用 public 声明的公用变量。例如，如果把下面这段代码放到你的控件工程的声明部分：
Public EyeOpen As Boolean
这样，你就可以在后边的代码中使用这个属性了。但是，这种属性所能够做的事实在太少了。它几乎是不能够正常工作的。因为在程序的设计期间，属性值的任何变化都必须通知 Visual Basic，以便把控件实例标记为需要保存。而又因为属性值可能显示在多个地方，因此当属性值发生改变时必须通知开发环境，以便使它能够同步显示“属性”窗口、“属性页”对话框等位置上的属性值。
上面说的是不是有点难理解呢？要是不明白说的什么，不要急，你先打开一个工程，添加一个控件，试着改改这个控件的一些属性，在来看看上面的话，是不是明白了呢？我们现在的任务，就是要作一个这样的控件啊。由此可以看出，控件编程和一般的编程还是有一些区别的。那么，该怎么实现数行呢？这要使用到属性过程。
当一个属性值被引用或者设置的时候，属性过程自动的被调用。下面我们就来添加一个这样的属性：打开代码窗口，再点击“工具”菜单，选择“添加过程”子菜单，弹出的对话框中，填入过程名“EyeOpen”，然后再把类型设置为“属性”。当你点了确定之后，VB 自动的为你创建了一个属性过程的原型，代码会被添加到代码窗口中：
Public Property Get EyeOpen() As Variant
End Property
Public Property Let EyeOpen(ByVal vNewValue As Variant)
End Property
剩下的你要做的，是写入属性处理代码，把这个骨架填满。
你可以看到，VB 实际上为你写了两个，“Get”和“Let”属性过程，有点疑惑吗？实际上很简单：“Get”是当属性值被引用的时候调用的过程，而“Let”则是当属性值被写入的时候调用的。(事实上还有第三种类型的过程，我们将在后面遇到，这里先提个醒)要让一个属性过程能够正确的工作，必须还要有一个变量来保存真正的属性值。把下面这一行添加到声明部分：
Private m_EyeOpen As Boolean
注意到那个'm_'前缀，一般是放在用户控件的内部变量之前。 现在该是填充过程框架的时候了。先看看 Let 过程，这个过程带有一个参数：缺省的是名字是 vNewValue，variant 类型。但我们想要一个布尔类型的变量而不是 variant，因为眼睛在我们的模型中只有开和闭两种状态。所以把这个参数改成“New_EyeOpen As Boolean”。如果你想要这个属性作为只读属性出现，那么就不要再改动 Let 过程，让过程体空在那儿。否则，当想要设置这个属性值的时候，应该执行这样的代码：
Public Property Let EyeOpen(New_EyeOpen As Boolean)
m_EyeOpen = New_EyeOpen
PropertyChanged "EyeOpen"
'后面还可以写一些相关的代码，对属性的设置做出反应
End Property
当要在程序中写入属性值的时候，就会调用这个函数，先把属性值保存在一个私有变量里面，然后执行 PropertyChanged 这个内部方法，它用来告诉 Visual Basic 属性值有了变化，并触发一个 WriteProperties 事件。关于这一点的具体的内容，在后面还会提到。
而 Get 过程更为简单！它和标准函数没有两样：
Public Property Get EyeOpen() As Boolean
EyeOpen = m_EyeOpen
End Property
做完这些就完了吗？不！忘了前面说的属性值是需要保存的，因此当编程会话发生转换的时候，它们能够保持下来。那么怎么样来保存和取出属性值呢？这时就要用到 PropertyBag 对象了。
使用 PropertyBag
PropertyBag 对象包含两个方法：一个用来读出，一个用来写入。前面提到了，当任何的属性发生变化得时候，会触发控件的 WriteProperties 事件。这时就可以把属性值保存在属性包中了。下面的代码实现这个功能：
PropBag.WriteProperty "EyeOpen", m_EyeOpen, True
PropBag 是 PropertyBag 对象的一个实例。WriteProperty 函数包含三个参数，第一格是属性名称，后面的是要保存的值，最后的参数是在无用户定义属性的情况下，将写入的默认值。把这个和 ReadPropertiy 方法的默认值设置结合起来，就可以为属性值设置缺省值了。如果属性值和缺省值相同，那么属性值就不会真正的别保存。当要读出的时候，ReadProperty 函数发现在属性包中没有内容，就会返回缺省值。这样可以节省一些系统开销。要注意的是：必须将一个成员属性的名称作为字符串传递。当你将控件作国际化处理时，不要改变这一字符串的名称，它必须与该属性的申明的名称相匹配。
当控件被重启动后，你必须重载如所有保存的属性值。当每次读取 PropertyBag 中的数据时， ReadProperties 事件被触发。在这个事件的处理过程中，你要做的任务就是载入保存在属性包中的属性值，调用 ReadProperty 函数来实现值一点。要注意的是：在读和写两个函数中的缺省值设置要一样。例如：
m_EyeOpen = PropBag.ReadProperty("EyeOpen", True)
一个同步缺省值的方法是为其设置常量，在需要的时候直接使用常量来代替具体的值，这样就不用担心出问题了。例如：
Private Const m_def_EyeOpen = True
“m_def_”前缀通常放在缺省值常量的前面。
当控件被启动的时候，一般要为属性设置初始值。这一般是在 usercontrol 的 InitProperties 事件中实现的。这个事件发生于控件实例首次实体化时，也就是把控件实例放置到窗体上的时候，它在以后的整个设计期间里都不会再发生。Usercontrol 还有一个 Initialize 事件，它是每次控件实例被创建时触发的。显然，初始化过程如果放在 Initialize 事件中就太过频繁了，没有这个必要。设计 ActiveX 控件需要根本性地转变一些观念。需要响应的关键事件是往往不同于一般的程序设计。下面是我们想要实现的代码：
Private Sub UserControl_InitProperties()
m_EyeOpen = m_def_EyeOpen
End Sub
好，到现在关于属性的部分基本就结束了，下一篇中我们要讲到的是另外的一个成员：方法。