如果准备自己做绘制工作,则放置绘制代码的唯一位置是 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事件。RefreshEnd 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。确定何时显示边框的其它方法都将导致错误。
详细信息 请参阅本章前面的“理解容器的扩展对象”和“使用环境对象与容器保持一致”。
如果您希望在用户绘制控件上显示文本,可以在 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 对象”。