创建轻量控件

轻量控件有时也被称为“无窗口的控件”,它与常规的控件有一点显著的区别:它们不具有窗口句柄(hWnd 属性)。因此它们使用的系统资源相对而言要少一些,对于 Internet 应用程序、分布式应用程序,以及其他容易受系统资源制约的应用程序来说,这种性质是很有吸引力的。常见的轻量控件包括 Label 控件和 Image 控件。

在设计一个用户控件的时候,除非该控件符合下列条件之一,否则应该首先考虑创建轻量控件:

要创建一个轻量的用户控件,只需在设计时将 Windowless 属性设置为 True。轻量的用户控件只能够用来容纳其他的轻量控件:如果您试图将一个有窗口的控件放入一个轻量的用户控件,那么将会发生一个设计时错误。同样,如果一个用户控件中已经容纳了有窗口的控件,试图将其 Windowless 属性设置为 True 将会导致同样的错误。

并非所有的容器都支持轻量控件。如果轻量用户控件的宿主并不支持轻量方式,那么它将自动地以窗口模式运行。系统将在运行时赋予它一个 hWnd。目前已知的支持轻量控件的宿主包括: Visual Basic 4.0 及其后续版本,Internet Explorer 4.0 及其后续版本,以及 Visual Basic for Applications。

创建具有透明背景的轻量控件

轻量控件公开了几个新的属性和一个新事件,它们将有助于创建具有透明背景的控件。对于一个有窗口的控件,MaskPicture 和 MaskColor 属性被用于决定鼠标单击是否发生在控件窗口的有效区域之内;如果不在范围之内,那么事件被递交给窗体。由于轻量控件不具有窗口,单击事件由谁接收是由控件决定的。当光标位于控件上时,HitTest 事件将发出通知;可以通过设置若干属性的组合来控制 HitTest 事件的行为。

控制区

要理解轻量控件是如何管理单击测试的,首先需要熟悉用户控件的各种区域。一个控件可以有四种区域:

图 9.8

掩盖区域的实际组成取决于 BackStyle 属性的设置:

Visual Basic 本身并不能确定 Transparent(透明) 和 Close (临近)区域:您必须亲自定义这些区域并在 HitTest 事件中对其进行测试。

HitTest 事件

如果轻量控件的 BackStyle 属性被设置为 Transparent,那么每当光标移动到控件上的时候,HitTest 事件就被触发。可以在 HitTest 事件中添加代码,以便确定是由控件接收鼠标消息,还是将其传递到 ZOrder 序列中位于该控件之后的对象。HitTest 的触发要早于其他任何鼠标消息。

由于在设计时可能将几个控件放在相邻的位置上,位于控件透明区域下的其他控件的某些部分可能是可见的。在有的情况下,可能需要测试您的控件下面是否有其他控件。如果确实有其他控件,有时需要允许这个控件接收到鼠标事件;否则的话,您可以自己处理该鼠标事件。如果控件下面没有其他控件,或者下面的控件拒绝处理该事件,那么 HitTest 事件还会给您第二次(和第三次)处理该事件的机会。

HitTest 事件有三个参数:x y 坐标指定了光标的位置,HitResult 参数则对应于一个控制区域。HitResult 有四种可能的设置:

对于分层的多个控件,鼠标单击测试将按照下列顺序进行:

不过,这里还有一个问题:HitTest 事件返回的 HitResult 永远不会是 1 和 2,因为 Visual Basic 并不真正了解临近和透明区域。您需要自己在代码中定义这些区域;这样,如果 X 和 Y 坐标落在您定义的区域之中,那么您可以为 HitResult 设置相应的值。下面代码显示了中心有一个正方形“洞”的控件的 HitTest 事件:

Private Sub MyControl_HitTest(X As Single, _
   Y As Single, HitResult As Integer)
   
   ' 确定 X  Y 坐标是否落入
   ' Close (临近)区域。
   If (X > 200 And X < 210) Or (X > 390 And X < 400) Then
      If (Y > 200 And Y < 210) Or (Y > 390 And Y < 400) Then
         ' 坐标在 Close (临近)区域之内
         HitResult = vbHitResultClose
         ' 我们得到了一个点击事件,现在可以退出了。
         Exit Sub
      End If
   End If
   
   ' 现在检查 Transparent (透明)区域。
   If (X > 210 And X < 390) Then
      If (Y > 210 And Y < 390) Then
         ' 坐标在 Transparent (透明)区域中。
         HitResult = vbHitResultTransparent
      End If
   End If
End Sub

在上面的例子中,我们首先查看坐标是否落入我们的 Close 区域中,在本例中该区域是正方形洞的边缘附近的一个宽度为 10 缇的正方形框。如果在此范围内,我们可以将 HitResult 设置为 2 (vbHitResultClose) 然后退出。否则,我们需要检查坐标是否位于 Transparent 区域内,如果确实在其中,我们将 HitResult 设置为 1 (vbHitResultTransparent)。如果这两个测试都没有返回“点击”,我们将使用传递来的 HitResult。

当然,对于正方形形状的区域来说,在程序中进行区域检查是非常容易的。可以想象,对于圆形区域、形状不规则的区域以及同一控件内的多个区域,这一过程要复杂得多。

还有一点需要注意:由于当光标在控件上移动的时候要不断地触发 HitTest 事件,如果在 HitTest 事件中执行的代码比较复杂,那么我们有理由怀疑性能将不是最优。事实可能的确如此。

与 Hit (点击)测试有关的性能问题

从性能的角度来看,点击测试的代价是比较大的。考虑到这一操作的“幕后事务”,这并不会使我们感到奇怪。首先,Visual Basic 必须执行计算以剪裁控件到 MaskPicture 和 MaskColor 定义的掩盖区域。其次,如果您的控件中包含其他控件或者用户绘制的图形,那么 Visual Basic 还需要对这些控件和图形进行剪裁。最后,如果您在控件中定义了临近和透明区域,那么它需要执行代码对这些区域进行检查。由于 HitTest 事件重复地被触发,所以该过程可能导致控件的执行速度比较慢。

比较幸运的是,用户控件还有另外两个属性,ClipBehavior 和 HitBehavior,可以通过设置这两个属性来改善性能。下表显示了这些不同设置方法的结果:

0 ClipBehavior None1 ClipBehavior Use Region

0 无 HitBehavior点击测试总是返回 0 - 不进行裁剪。点击测试总是返回 0 - 裁剪成掩盖区域。

1 HitBehavior Use Region点击测试返回 0 或 3 - 不进行裁剪。(缺省设置) 点击测试返回 0 或 3 - 裁剪成掩盖区域。

2 HitBehavior Use Paint点击测试返回 0 或 3 - 裁剪成绘制图形。点击测试返回 0 或 3 - 裁剪成掩盖区域之内的绘制图形。

如果 HitBehavior 被设置为 0 (vbHitBehaviorNone),HitTest 事件将永远返回 HitResult = 0 (外部)。您可以添加代码来判断点击发生在什么位置,并相应地改变 HitResult 的值。如果 HitTest 事件过程中没有大量的代码,这样可能会改善性能。

如果将 ClipBehavior 设置为 0 (vbClipBehaviorNone) 通常能够改善性能,因为 Visual Basic 不需要判断掩盖区域与外部区域之间的边界。对于掩盖区域的形状比较复杂的情况,这一点的好处尤其明显。例如,当它包含 TrueType(R) 字体或不规则形状的时候。

如果您的控件需要使用“热点”(就是说,只有可见区域的某些部分可以接受单击),那么把 ClipBehavior 设置为 0,把 HitBehavior 设置为 1 (vbUseRegion) 可以改善性能。

对于用户绘制的控件,如果控件的可见部分与掩盖区域并不完全相同,那么将 HitBehavior 设置为 2 (vbUsePaint) 也许是必要的。这是代价最为昂贵的操作,因为每次执行 HitTest 事件的时候控件都需要重新进行绘制。

重点   如果一个轻量用户控件被放入不支持 Windowless 属性的容器内,那么某些 HitBehavior 和 ClipBehavior 设置可能会导致不可预测的结果。如果您的控件将被用于不支持轻量控件的容器,那么应该使用缺省的设置。