用户绘制控件

如果准备自己做绘制工作,则放置绘制代码的唯一位置是 UserControl_Paint 事件过程。当容器重新绘制控件所处的区域时,UserControl 对象会收到一个 Paint 事件。

如果 UserControl 对象内置的图形方法,如 Line 和 Circle,不能满足绘制需要,那么还可以使用 Windows API 调用来绘制控件。您还可以使用 Print 方法在控件上“绘制”文本。不论采用何种绘制技术,代码总是放在 UserControl_Paint 事件过程中。

如果控件必须根据用户的行为改变其外观,例如需要针对控件的单击事件进行处理,那么可以通过调用 UserControl 的 Refresh 方法来产生 Paint 事件。

注意 当不能在具有 BackStyle = Transparent 的透明部分上绘图时,可以创建具有透明背景的控件。参阅本章前面的“给您的控件以透明背景”。

在编写用户绘制控件时不要设置 BackStyle = Transparent。如果 UserControl 是透明的,那么 Paint 事件将不会产生,图形方法也就不会工作。

重点 在 Paint 事件中,不要使用 DoEvents,或任何会将控制权转移给其它程序的代码。这样做会引起错误。

下面的示例演示了三态按钮的基本工作原理,三态中的每一态由不同的位图表示。要试用此例,需要打开一个新的 Standard EXE 工程,然后用“工程”菜单为此工程添加一个 UserControl。在 UserControl 上放置一个 PictureBox,接着按下表设置对象的属性:

对象 属性 设定值
UserControl Name TripleState
PictureBox AutoRedraw True
  Name picStates
  Picture (任意的位图)
  Visible False

注意 如果为 Picture 属性选择的位图能够从左到右明显地改变颜色,那么本例的效果会更好。

在 UserControl 代码窗口的声明部分添加如下代码:

Option Explicit
'用于跟踪当前状态的私有变量。
Private mintState As Integer

使用一种简单的机制来提供三种状态:每次单击控件时增加私有状态变量的值,从而使控件的状态发生轮换。如果该值变的太大,就将其重置为零。在 UserControl 的 Click 事件过程中添加这段代码:

Private Sub UserControl_Click()
   mintState = mintState + 1
   If mintState > 2 Then mintState = 0
   '下一程序行产生 Paint 事件。
   Refresh
End Sub

在 UserControl 的 Paint 事件过程中添加如下代码。当 Paint 事件发生时,此段代码会把存于 PictureBox 中的不可见位图的三分之一复制到 UserControl。具体哪三分之一将被复制将取决于私有状态变量 mintState 的当前值。

Private Sub UserControl_Paint()
   PaintPicture picStates.Picture, 0, 0, ScaleWidth, _
      ScaleHeight, mintState * picStates.Width / 3, _
      0, picStates.Width / 3
End Sub

注意 使控件的每一种状态具有不同外观的另外一种方法是使用 Select...Case 语句。

当 mintState 为零,即其初值时,隐藏位图的前三分之一会被复制到 UserControl 上,如图 9.12 所示:

图 9.12 复制隐藏位图的前三分之一到 UserControl

在 UserControl 设计器上单击“关闭”框,激活它在“工具箱”中的图标。双击此图标将控件的一个复本放置在窗体上,再按 F5 键运行工程。单击本控件改变其状态。

可以把该窗体隐藏在另一个窗口后面,或者先将其最小化后再还原此窗体,以此来验证该控件能够正确地保持它的状态,并进行相应的重绘。

提示 在绘制控件时,为了达到更好的性能,请确信 AutoRedraw 属性是设置为 False 的。

详细信息 请参阅“创建一个 ActiveX 控件”中的“绘出 ShapeLabel 控件”。

关于使用 Windows API 调用,请参阅《部件工具指南》的“访问 Microsoft Windows API”部分。关于 PaintPicture 方法,请参阅《程序员指南》的“使用文本和图形”。

处理其它事件

以类似的方式,可以模拟其它的事件驱动的外观变化,如按钮压下时的动画。要模拟按钮压下动作,可以在 MouseUp 和 MouseDown 事件过程中添加改变状态的代码。不论使用什么事件,原理都是一样的:改变状态,并调用 Refresh 方法。

显示具有焦点的用户绘制控件

如果控件能够获得焦点,就需要另外一个状态变量来跟踪当前控件是否具有焦点,这样,每当控件重绘自己时,它将显示出自己是否拥有焦点。

使用 Windows API DrawFocusRect,可以绘制出 CommandButton 控件的那种单像素点虚线型焦点指示。对于非矩形的焦点指示,没有功能相当的 API 可以使用。

详细信息 请参阅本章前面的“怎样处理控件中的焦点”。

显示禁用状态的用户绘制控件

如果实现了 Enabled 属性,就有必要跟踪控件是否是允许使用的,这样当控件被禁用时就可以为使用者提供可视化的提示。

如果按本章前面“控件的激活与禁用”中所述的方法实现控件的 Enabled 属性,那么只需检查 UserControl.Enabled 就可以确定是否应该把控件绘制成活动状态。

以何种方式绘制禁用状态的控件,完全取决于控件的制作者。

可作为缺省按钮的用户绘制控件

把 UserControl 对象的 DefaultCancel 属性设置为 True,即可通知容器该控件可作为缺省或取消按钮,这样扩展对象将显示出 Boolean Default 和 Cancel 属性。

检查 AmbientProperties 对象的 DisplayAsDefault 属性值可以确定控件是否应该显示额外的黑色边框,以此来通知最终用户该控件是缺省按钮。仅当 DisplayAsDefault 属性为 True 才需显示此边框。

重点 在 Windows 中,按钮的正确行为应该是这样的,仅当控件被指定为缺省按钮,并且没有其它的按钮具有焦点时,它才显示代表缺省的边框。仅当这两个条件都满足时,DisplayAsDefault 才为 True。确定何时显示边框的其它方法都将导致错误。

详细信息 请参阅本章前面的“理解容器的扩展对象”和“使用环境对象与容器保持一致”。

处理 FontChanged 事件

如果您希望在用户绘制控件上显示文本,可以在 UserControl_Paint 事件中使用 Print 方法。例如,下面的代码将在前例中的 TripleState 控件上显示“Push Me”:

Private Sub UserControl_Paint()
   Cls      ' 清除原来的文本
   Print Spc(3); "Push Me"
End Sub

不过上面的例子还有一个问题 — 文本将总是被显示为缺省的字体,即使您已经显露控件的 Font 属性也是如此。由于控件不知道字体已经改变了,它也就不会触发 Paint 事件。

为了解决这个问题,需要为您的用户控件增加一个 Font 对象,并在 Font 对象的 FontChanged 事件中调用用户对象的 Refresh 方法。Font 必须使用 WithEvents 关键字进行声明,以便显露该事件:

Option Explicit
Private WithEvents mFont as stdFont

关于 Font 对象和 FontChanged 事件和完整示例,请参阅“ActiveX 控件功能”中的“在用户控件中添加 Font 对象”。