[网摘]VB用户控件制作讲解与实例
制作用户控件,主要就是进行以下三项代码编写工作:
1.定义控件的属性、事件和方法,其中属性是最常使用的。
2.保存和读取中间用户设置的属性值。
3.为达到你的预定目的而调用的各种技术手段。
在用户控件中定义的属性、事件、方法,其性质都必须是公用的,也就是说,只有用 Public 来定
义,这样你才能在主程序代码中使用这些事件和方法,以及设置或获取这些属性值,也只有公用的属性
才会在窗体页面相关控件的属性窗口显示出来。
一、属性
属性是用户控件最基本的东东,用户控件可以没有事件,可以没有方法,但不能没有属性(当然,
技术上来说是可以没有属性的,但这样的控件使中间用户无法进行任何设置,是没有什么意义的)。那
么,如何定义用户控件的属性呢?为用户控件添加属性有两种办法:
1.公用变量法:
public 变量名称 as 类型
这里的变量名称就是属性名称。这样定义的属性一般不会保存属性值,所以常常用作只读属性,在
笔者的用户控件中,用于对主程序返回一个必要的值。例如“四则运算”控件中的“ComputeAnswer”
属性:
Public ComputeAnswer As String
它返回的是计算结果,而计算结果是不需要保存在控件中的,所以把它用公用变量法定义。再例如
消息框控件中的 FeedValue 属性:
Public FeedValue As Integer '返回值
它返回最终用户选中的消息框按纽的编号,这个编号也只需要在主程序中处理,而无需保存在控件
中,所以也用公用变量法定义成只读属性。
2.property 过程法:
public property Get 过程名称() as 类型
……
end property
public property Let 过程名称(new值 as 类型)
……
end property
这里的过程名称就是属性名称。
而 property 过程法又有两种:一种是如上所述的标准过程法,另一种就是枚举法。
㈠标准过程法
这是用得最多的一种属性定义方法。在用户控件的代码页面选中“工具→添加过程”,会跳出一个
对话框,然后在单选按纽中选择“属性”,再在“名称”栏中输入属性名,点击确定,VB 就会自动生成
上述的几行代码,你将“类型”改为你所需要的,再输入相关代码即可。
标准过程法中,Get 过程和 Let 过程一般是成对出现的。例如“闪烁标签”控件中定义闪烁时的
前景颜色 FlickerForeColor 属性的代码:
Public Property Get FlickerForeColor() As OLE_COLOR '闪烁时的文字色
FlickerForeColor = mGlintForeColor
End Property
Public Property Let FlickerForeColor(ByVal newColor As OLE_COLOR)
mGlintForeColor = newColor
PropertyChanged "FlickerForeColor"
End Property
这两段代码中的“OLE_COLOR”是颜色数据类型,实质上也是长整形的数据类型,但它会自动调出
颜色对话框。“FlickerForeColor”是属性名称,在窗体界面相关控件的属性窗口中显示的就这个属性
名称。而“mGlintForeColor”是中间变量,中间变量是私用的,用 Dim 定义即可。在用过程法定义属
性时通常都需要中间变量。中间变量的身份是“代表”(代表属性名),作用有两个,一是上传下达,
在 Get/Let 过程与 ReadProperties/WriteProperties 过程中充当“邮递员”;二是参与,在许多别
的过程中都要与中间变量打交道。
Get 过程的作用是获取相关的属性值,并将属性名称和属性值显示在属性窗口(如果你去掉这个过
程,在属性窗口就不会出现相关的属性名称和属性值了)。它在三种情况下被激活:①中间用户在窗体
页面刚刚把焦点移到窗体上的控件时(例如点击该控件),在属性窗口显示出原先设置的属性值;②中
间用户在属性窗口修改了属性值,在属性窗口显示出修改后的属性值;③程序运行中用代码获取属性值
时,假设窗体代码有这么一句:RGB = Cipher.BackColor,那么也会激活该过程。
Let 过程的作用是设置相关的属性值,它在两种情况下被激活:①中间用户在属性窗口修改了控件
的属性值(执行顺序是:Get 过程→Let 过程→Get 过程);②程序运行中用代码设置新的属性值时,
例如:Cipher.BackColor = RGB。变量 NewValue 是被赋的新值(这个变量名是可以改变的),你可以
把得到的 NewValue 的值按自己的需求作任何用途。
要注意的是,如果这个属性是一个对象,那么就不能用 Let 过程而必须用 Set 过程了,这是因为
保存在控件内部的对象变量,保存的并不是对象的拷贝,而只是对象的引用(也就是一个内存地址)。
所以在它的 Get 和 Set 两个属性过程中,均须在等式的前面加上“Set”关键字。来看看“四则运算”
控件中的有关代码:
Public Property Get Font() As Font
Set Font = Text1.Font
End Property
Public Property Set Font(ByVal newFont As Font)
Set Text1.Font = newFont
PropertyChanged "Font"
End Property
这是设置文本框的 Font 属性的,而 Font 本身也是一个对象,所以必须使用 Set 了。
还有 Picture 属性也是如此,它也必须使用 Set 过程。然而,它这个对象却有一点特殊之处:如
果你想在程序运行当中使用 LoadPicture 语句动态加载图片的话,你就必须给它增加一个 Let 过程,
否则,你将只能在设计模式时在属性窗口加入图片。而新增的 Let 过程中不需要任何代码,只要一个注
释符就行了。以“酷时钟”控件中的 Picture 属性为例:
Public Property Get Picture() As Picture
Set Picture = UserControl.Picture
End Property
Public Property Set Picture(ByVal NewPic As Picture)
Set UserControl.Picture = NewPic
PropertyChanged "Picture"
End Property
Public Property Let Picture(ByVal NewPicture As Picture)
'
End Property
㈡枚举法
枚举是一种很常见的的方式,它提供了一个下拉列表和若干选项让用户选择。这样既方便了用户的
操作,又不用考虑过多的兼容性和错误处理问题,简化了属性设置,而且更加安全。
要实现枚举法,首先必须建立一个枚举结构,放在声明部分,然后在结构中给出一系列的常量和对
应的字符串。后面的常量值必须是比前面常量值大的整数。如果没有给出常量,那么 VB 会自动为其赋
值,第一个字符串赋值为零,其它的值则为前面一个数加一。例如在“特效标签”控件中,字体打印特
技的枚举结构声明:
Public Enum cTxtEffect
雕刻 '自动赋值=0
立体 '自动赋值=1
浮雕 '自动赋值=2
End Enum
如果你要为项目赋值为从 1 开始,也是可以的(当然有关代码要改一下):
Public Enum cTxtEffect
雕刻 = 1
立体 = 2
浮雕 = 3
End Enum
要实现枚举属性,还必须创建一个带有 Let 和 Get 属性过程的标准属性,但必须将属性的类型声
明为枚举类型。仍以“特效标签”控件为例,你还必须有这样两个过程:
Public Property Get TxtEffect() As cTxtEffect
TxtEffect = mTxtEffect
End Property
Public Property Let TxtEffect(ByVal NewValue As cTxtEffect)
mTxtEffect = NewValue
PropertyChanged "TxtEffect"
End Property
注意这两个过程中的数据类型都改为了 cTxtEffect。
枚举属性的读、写、保存和检索,都和标准属性是一样的。
3.保存或读取属性值
上述的 Let 过程中的 PropertyChanged 方法是用户控件特有的方法,其作用是,通知系统某个属
性发生了改变,系统根据具体情况决定是否将改变后的属性值保存到属性包(或 .frm 文件)中。所谓
具体情况是指:在设计模式(正在被中间用户使用)就保存,在运行模式(正在被最终用户使用)就不
保存。比如你的程序代码中有这么一句:Cipher.BackColor = RGB,那么运行该程序到这一句时,就会
将 Cipher 控件的背景色改变为 RGB 所代表的颜色,但不会将这个 RGB 值 保存到属性包中,所以,下
次你运行程序时,只要没有运行到这一句,Cipher 控件的背景色依然是你设计时的颜色。
保存属性值是由 WriteProperties 事件过程执行的,在销毁创建的控件之前,该事件会根据 Prop
ertyChanged 方法的提示,以及当时的运行模式,来决定是否通知 PropertyBag 对象把数据写入属性包
(或 .frm 文件)中。保存时,所有在该事件过程中的属性值都会同时保存,而不仅仅是保存某一个提示
改变的属性值。
读取属性值是由 ReadProperties 事件过程执行的,在创建控件之前,该事件会通知 PropertyBag
对象从属性包(或 .frm 文件)中把保存的所有属性值都同时读取出来,读出的数据由 Get 过程使用。
PropertyBag 对象是具体实施保存或读取功能的,它有两个方法:WriteProperty 方法用来写属性
值,ReadProperty 方法用来读属性值。
特别提醒:对于新手来说,一定要搞清楚 cTxtEffect、mTxtEffect、TxtEffect 和“TxtEffect”
这四个东东的意义:
cTxtEffect:是结构名。
mTxtEffect:是代表 cTxtEffect 结构中某个项目值的中间变量,如果没有枚举结构,则是代表属
性值的中间变量,它是模块级的变量。
TxtEffect:是属性名,它会出现在窗体页面的属性窗口中。
“TxtEffect”:是保存到属性包时所用的名称,笔者为了方便,把它与属性 TxtEffect 取了同一
个名称,但并不是同一个概念,它只出现以下在三个方法中:PropertyChanged 方法、ReadProperty方
法以及 WriteProperty 方法中,你完全可以另外取个名,但在这三个方法中必须是完全同名的。你可以
这样理解:“TxtEffect”是一个文件名,而 mTxtEffect 则是一个变量,你要从“TxtEffect”中读出
文件内容并赋值给 mcTxtEffect,或者你要将赋了值的 mcTxtEffect 保存到“TxtEffect”去。
还有一个需要注意的地方: 使用 ReadProperty/WriteProperty 方法读写数据时,被读写的变量不
能是数组。例如,在 MyMenu 菜单控件中,mCaption 是一维数组,但"sCaption"不可能是数组,所以你
不能这样编写代码:
For i = 1 To mItemSum: .WriteProperty "sCaption"(i), mCaption(i), "": Next
而只能这样:
For i = 1 To mItemSum: .WriteProperty "sCaption" & i, mCaption(i), "": Next
实际上就是:
.WriteProperty "sCaption1", mCaption(1), ""
.WriteProperty "sCaption2", mCaption(2), ""
……
.WriteProperty "sCaptionN", mCaption(N), "" 'N= mItemSum
对于另一个菜单控件 muchMenu 控件来说,mCaption 是二维数组,所以只能这样编写:
For j = 1 To sRep
For i = 1 To mItemSum(j)
.WriteProperty "sCaption" & j * 10 & i, mCaption(j, i), ""
Next
Next
实际上就是:
.WriteProperty "sCaption101", mCaption(1, 1), ""
.WriteProperty "sCaption102", mCaption(1, 2), ""
……
.WriteProperty "sCaption10N", mCaption(1, N), "" 'N=mItemSum(j)
.WriteProperty "sCaption201", mCaption(2, 1), ""
.WriteProperty "sCaption202", mCaption(2, 2), ""
……
.WriteProperty "sCaption20N", mCaption(2, N), ""
……
.WriteProperty "sCaptionM01", mCaption(M, 1), "" 'M=sRep
.WriteProperty "sCaptionM02", mCaption(M, 2), ""
……
.WriteProperty "sCaptionM0N", mCaption(M, N), ""
4.只读属性
前面已经说到,用公用变量法定义的属性一般是只读的,而且一般不论在设计模式还是运行模式都
是只读的。用标准过程法定义的属性也可以定义为在运行时只读的属性(当然也可以定义为在设计时只
读或者在设计和运行时都只读,不过那有什么意义呢?故我们不加讨论)。
最简单的方法,就是不在 Let 或 Set 属性过程中加入任何代码,但通常这会带来诸多不便之处,
一般不宜采用。比较适宜的办法就是使用 AmbientProperties 对象。这个对象共有16个属性,都是用户
控件的环境信息。比如该对象的 DisplayName 属性就是取得控件的默认名称,中间用户将用户控件画到
窗体上时,系统就会自动为控件的 Caption 属性赋值这个默认名称。我们要实现运行时只读属性,要用
到的是该对象的 UserMode 属性。当控件处于运行模式时,UserMode=True,当控件处于设计模式时,
UserMode=False。我们在 Let 过程中对 UserMode 属性加以检测,就可以很容易地实现运行时的只读
属性了。选项卡控件中有一段代码:
Public Property Let Tabs(ByVal newVal As Integer)
If newVal > 2 And newVal < 9 And Ambient.UserMode = False Then '如果是设计模式
If newVal = 5 Then newVal = 6
If newVal = 7 Then newVal = 8
propTabCount = newVal
ReDim Preserve propCaption(1 To propTabCount)
PropertyChanged "Tabs"
DrawTabs
End If
End Property
Tabs 属性表示的是选项卡的按纽数目,按纽数目只允许在设计模式时修改,在运行模式时不允许修
改。从代码中可以看出,如果运行时企图修改 Tabs 属性是不可能的,换言之,该属性在运行时只读。
5.属性说明
中间用户把用户控件画到窗体后,想在属性窗口查看它的属性说明,却发现只有简单的“Tabs”之
类的几个英文字符,就会弄得头大了。所以,我们有必要对属性加以描述。在用户控件页面,点击“工
具→过程属性”菜单项,这时会跳出一个对话框,我们在“名称”下拉框中选中需要说明的属性,在下
面的“描述”框中就可以输入对这个属性的说明文字了。你可以仿照微软控件的“返回/设置……”之类
的说明词加以描述,然后点“确定”就行了。再到窗体页面的属性窗口看看,呵呵,正是我们刚才输入
的那几个字!
后面我还会讲到控件的事件和方法,对于它们的描述也照此办理,不过事件和方法的描述要在“对
象浏览器”中才看得到。如果没有描述,“对象浏览器”中有关项目就只有“****工程的成员”的简单
说明,为了使中间用户明白你定义的属性、事件和方法的意义,我建议用汉字将所有的属性、事件和方
法都进行描述,免去中间用户翻译、猜测、反复试验的麻烦。
二、事件
就象定义属性一样,我们首先要在用户控件页面代码窗口的 Option Explicit 节中声明要产生的
事件(注意声明事件也必须是公用的)。单击“工具→添加过程”,在弹出的对话框名称栏中输入事件
名称(例如“Click”),在“类型”单选按纽中选择“事件”,点击“确定”,于是声明节中就多了这
么一行:
Public Event Click()
然后再输入对此事件的处理过程代码(以后你为控件添加的任何事件都必须有类似的代码):
Private Sub UserControl_Click()
RaiseEvent Click '触发Click事件
End Sub
RaiseEvent的功能是把用户控件或其上的子控件的事件进行转发。上面的代码的意思是:当你单击
窗体上的用户控件时,VB 就转发出一个单击事件,这个事件发给谁呢?呵呵,当然是发给窗体代码页中
相关控件的 Click 事件啦,你再在这个 Click 事件过程中编写代码就行了。是不是很简单?
要是你还想让这个事件携带参数,那也很容易实现,以 MyMenu 菜单控件为例:
Public Event Click(SelectedItem As Integer) '在声明节定义菜单项单击事件
Private Sub mLabel_Click(Index As Integer)
If left(mLabel(Index).Caption, 1) <> "-" Then RaiseEvent Click(Index) '转发单击事件
End Sub
菜单项的文本是显示在标签上的,而标签是一个控件数组,mLabel_Click 过程代码中的 Index 是
选中的控件数组的编号,也就是菜单项的编号,这个号码必须返回给主程序中的相应变量,以便作进一
步的处理,所以这个编号作为 Click 的参数就被传送出去了。
现在,我们在窗体的代码窗口上面的下拉框中找到该菜单控件的单击事件,点击一下,窗口中出现
了以下过程代码:
Private Sub MyMenu1_Click(SelectedItem As Integer)
End Sub
看看,返回参数的变量名与在控件代码页声明节中定义的单击事件中的变量名完全一样。在这个过
程中,再输入对返回参数的处理代码,你可以用 X=SelectedItem 的句式获取其值,但最好采用下面这
样的代码:
Private Sub MyMenu1_Click(SelectedItem As Integer)
MyMenu1.Visible = False '使菜单控件不可见,这是必须的
Select Case SelectedItem
Case 1: '去打开文件模块
Case 2: '去保存文件模块
.....
End Select
End Sub
三、方法
找遍网上,所有的资料都只有如何定义用户控件的属性和事件,却没有说明如何定义用户控件的方
法。在用户控件页面点击“工具→添加过程”,在弹出的对话框中,“类型”单选按纽竟然也没有“方
法”!没有办法啊,笔者长叹一声,只好在一片茫然中做试验。经过N多次屡败屡试不折不挠的试验,
终于获得了成功!
其实非常简单:只要在控件代码中增加一个独立的 Sub 公用过程,就定义了用户控件的方法,过程
名也就是方法名。方法既可以带输入参数也可以不带,这个参数是传送到控件内部来的(事件的参数是
传送到控件外部去的)。
例如,在“四则运算”控件中,Start 方法是这样定义的:
Public Sub Start()
……
End Sub
这是不带输入参数的方法。在窗体中的调用代码是:
Cipher.Start
再如“图片特技”控件定义的带输入参数的方法:
Public Sub Start(Optional ByVal StuntModus As Integer = -1)
……
End Sub
不但带了参数,而且还是可选参数,这样可以大大方便用户,用户在使用 Start 方法时,就可根据
具体情况来决定是否输入参数了,调用语句示例如下:
PicStunt1.Start i '输入参数为变量 i
PicStunt1.Start '无输入参数,则控件内部使用缺省参数 -1
上面说的是带一个输入参数,其实只要你需要,可以带N多个参数,例如“消息框”控件的 Msg 方
法:
Public Sub Msg(ByVal mInfoStr As String, Optional ByVal mCompages As Integer = 0, _
Optional ByVal mCaption As String = "马路消息")
……
End Sub
一共带了三个参数,其中第一个参数是必需的(消息内容),第二个参数(组合值)和第三个参数
(消息标题)是可选的,基本上与系统的消息框调用方式是一致的。
四、属性页
很多控件在其属性窗口上方第二个属性之处有“自定义”的属性,点击右边的“…”按纽,就打开
了属性页。
如果某个属性有若干个不能确定数量的子项目,通常就需要使用属性页了。例如系统自带的“工具
栏”控件,其中的按纽个数及其标题文本都是不能确定的,这时就要让中间用户根据自己的具体情况,
通过属性页来设置了。笔者发布的控件实例中,有三个控件需要使用属性页,这三个控件是:选项卡控
件、MyMenu 菜单控件、muchMenu 菜单控件。就拿菜单控件来说,如果你设计的菜单控件仅仅只是为了
给自己使用,菜单项数目是固定的,那当然可以多弄几个 Caption 属性来设置菜单文本,比如说你可以
定义 Caption1、Caption2、Caption3……等等,但这显示不是一个好办法;而如果你设计的菜单控件是
打算编译成 OCX 文件发布,那这样就更不行了,固定了菜单项的数目不能满足中间用户的需求。这时,
你唯一的办法就是利用属性页了。
用户控件的属性页是要我们自己来制作的,制作步骤如下(以 MyMenu 菜单控件为例):
1.右键点击“工程资源管理器→添加→添加属性页→VB 属性页向导→打开”,弹出“属性向导页-介
绍”,点击“下一步”,在列表框中选中“工程1:MyMenu”→“下一步”→“添加(A)”→弹出“属性
页名称”对话框,修改名称为“myMenuPage”(也可默认),点“确定”后会自动选中 myMenuPage,
再点击“下一步”,页面变成“添加属性”,将左边“可用属性”下的“Caption”选入右边的列表框
→“下一步”→“完成”→“确定”。
现在,你打开窗体代码页面的 MyMenu1 控件的属性窗口,就可以看到一个名称为“(自定义)”的
新属性项目了。
但是这样的属性页是不能使用的,页面上空空荡荡,就象一个没有添加任何控件的窗体界面,所以
我们必须为它添加若干用得着的控件,并编写相应的代码。
2.在工程资源管理器中选中 myMenuPage,再点击“工程→对象窗口”菜单项,打开属性页的窗口,要添
加的控件及其属性设置如下:
-----------------------------
控件名 Caption Index
-----------------------------
Label1 菜单项名称
Text1
ListBox1
Command 添加 0
Command 删除 1
Command 上移 2
Command 下移 3
------------------------------
说明:如果你从来没有接触过属性页的制作,你可以把它当作一个窗体,你的工作就是要编写实现
某个功能的小程序(对于菜单控件来说,这个功能就是可以随意增删菜单项),而这个小程序在运行时
,窗体上是需要一些控件的,因此,要根据你的需要来决定添加哪些控件。
在上表中,Label1 的作用是显示提示信息,是可选的,下面六个控件就是必需的了:文本框用来
输入菜单的文本,列表框用来显示你已经输入的所有文本条目,四个按纽用来增加、删除或调整文本条
目的上下位置。
现在的属性页上面只有我们添加的这几个控件,而没有我们所熟知的【确定】、【取消】以及【应
用】按钮,这是怎么回事呢?原来,在设计器中,PropertyPage 对象不会显示这三个按纽,这些都是由
属性页对话框自动提供的,当你从属性窗口进入属性页之后,就会看到这三个按纽了。
3.添加了控件后,你就要为这个小程序编写代码了。点击“工程→代码窗口”调出属性页的代码页添加
代码。代码中有两个关键的事件过程是每个属性页都必需的:
㈠SelectionChanged 事件
当我们点击控件的属性窗口中的“自定义”按纽时,就打开了属性页,这时,PropertyPage 对象所
产生的第一个事件是 Initialize 事件,这跟窗体的情况是一样的。但是,与窗体不同的是,Property
Page 对象并不获得 Load 事件。PropertyPage 对象的关键事件是 SelectionChanged 事件。对我们这
个菜单控件而言,执行流程是这样的:
(用户控件页的)UserControl_ReadProperties 过程→Property Get ItemS 过程→(属性页的)
PropertyPage_SelectionChanged 过程
在 SelectionChanged 事件中,获得要编辑的属性值,也就是说,这个事件携带了从用户控件页对
应项目的 Property Get 过程中获得的属性值数据,你可以编写代码将这些数据显示出来。
㈡ApplyChanges 事件
PropertyPage 对象中的第二重要的事件是 ApplyChanges 事件。在这个事件中,可将已编辑过的
属性值返回到当前选定的控件中。
当按下属性页上的“确定”或“应用”按钮时,或者由于选择选项卡切换了属性页时,才可能产生
ApplyChanges 事件,为什么说“才可能”呢?因为你还要通知系统,某个项目发生了改变,系统才会激
活这个事件(这就象用户控件页中,你改变了某个属性的值以后,必须要使用 PropertyChanged 方法通
知系统,系统才会保存这个被改变了的属性值一样),所以修改了某个项目的值以后,要将 Changed 属
性设置为 True,这就达到了通知系统的目的。并且,如果你不使 Changed=True 的话,属性页下方的
“确定”、“应用”两个按钮是灰色不可用的。
从属性页返回的数据,是由对应的 Property Let 过程来接收的, Property Let 过程接收了数据
后,发出 PropertyChanged 通知,系统就保存了你在属性页中修改过的项目值。对我们这个菜单控件而
言,执行流程是这样的:
(属性页的)PropertyPage_ApplyChanges 过程→(用户控件页的) Property Let ItemS 过程
(以及 Property Let Caption 过程)→UserControl_WriteProperties 过程
㈢SelectedControls 集合
在 ApplyChanges 事件和 SelectionChanged 事件中,我们都看到了 SelectedControls 集合的身
影,它允许对对象中当前选定的所有控件进行访问。它有两个属性:Item 和 Count,前者是我们在创
建属性页时与之关联的项目(在我们这个菜单控件中是 Caption),后者是该项目的总数。但在属性页
代码中,这两个属性的名称并不是 Item 和 Count,而是与用户控件相关联的属性的名称,例如,与
MyMenu1 控件关联的属性页,这两个属性名称就是 Caption 和 ItemS,切记切记!
这里还有一个问题,经过笔者的试验,Item 属性似乎只能是一维数组,如果与该属性相关的是一个
二维数组或多维数组,那么你就要在用户控件页面的有关代码中加以转换处理,请参看 muchMenu 控件
的有关代码。
五、用户控件重要过程执行顺序
UserControl_Initialize:执行顺序=1
UserControl_InitProperties:执行顺序=2,该过程仅在将控件刚画到窗体上时执行一次。
UserControl_Resize:执行顺序=3
UserControl_Show:执行顺序=4
六、用户控件的其它重要属性
1.AutoRedraw 属性
与窗体、图片框的同名属性作用相同。如果要在控件体上直接绘图或打印,一般要设置它为真。
2.Alignable 属性
当该属性被设置为真时,VB 将自动为控件添加一个新的属性:Align,这样就能够像放置工具条那
样安排控件在容器中的位置,而且这还意味着你的控件能够被放置在 MDI 程序中。
3.CanGetFocus 属性
决定用户控件是否能够在运行时获得焦点。当要创建一个图形控件,或者像计时器控件那样在运行
是不可见的控件时,就要设置这个值为 False。要注意的是:只要控件至少包含一个设置为能够接收焦
点的子控件,CanGetFocus 属性就不能设置为False,如果 CanGetFocus 设置为 False,则其所有的子
控件都不能设置为接收焦点。
4.ControlContainer 属性
运行时只读。它决定用户控件是否能够像窗体或者 PictureBox 控件那样作为控件容器包含其它的
控件。
5.DefaultCancel 属性
运行时不可用。它决定用户控件是否可以充当标准的命令按钮。笔者的按纽控件中,就设置了这个
属性为真。还可以通过检查 AmbientProperties 对象的 DisplayAsDefault 属性来知道控件是不是缺
省控件。
6.InvisibleAtRuntime 属性
运行时不可用。它能够让你创建像计时器那样在运行时不可见的控件。
7.ToolboxBitmap 属性:
运行时不可用。用来指定放在VB工具箱上的图标。微软建议的大小是16×15像素×16色,如果你不
指定图标,那么所有的用户控件在工具箱中的图标都是千篇一律的,所以最好弄一个有个性的图标。
七、过程属性对话框中的功能项目
在用户控件页面菜单栏点击工具→过程属性,出现过程属性对话框,再点击【高级】按纽展开。先
在“名称”下拉框选定一个属性,接下来,你就可以:
1.在“描叙”框内输入对本属性的说明。
2.在“帮助上下文标识符”框内输入一个帮助的关联 ID 号,将控件与一个帮助文件关联起来,这样当点
了属性后再按【F1】键就可以给出这个属性的帮助内容。
3.在“在属性浏览器中使用本页”下拉框中给控件的定制属性页分配选定的号码,这样当用户从VB的属
性浏览器中选择该成员时,VB将直接显示属性页。
4.在“属性分类”下拉框中给本属性选择一个类别,以后VB的属性浏览器的“按分类序”模式中,本属
性就会归类于指定的类别下。这些类别包括外观,字体,位置,杂项等等,你还可以创建一个新类别,
这只要输入一个新类名就行了。
5.勾选“隐藏该成员”可以让属性不在属性浏览器中显示出来,这对于一些不想让用户看到的公有成员有
用,但是要记住,它只是隐藏而不是不许被使用。而勾选“在属性浏览器中不显示”可以在控件的设计
时(而不是在运行时)把属性从属性浏览器中去掉。作为一般的原则,任何用 ReadProperties 和
WriteProperties 实现的永久的属性,都应当被属性浏览器显示出来,反之,任何非永久性的属性就不
必被显示出来。
6.“缺省用户界面”复选框用来设置控件的缺省属性和方法。例如,设置 Caption 属性为 Lable 控件
的缺省属性,那么在窗体代码中就可以将 Label1.Caption = "Hello" 简写为 Label1 = "Hello"。
八、关于本次发布的用户控件实例的说明
1.时钟控件:
其中酷时钟程序不是我的原创,它本是一个窗体程序,还使用了一个类。笔者在保持它的基本功能
和外观的前提下,将它作了大幅的压缩和删减(精减了一半以上的代码,删除了与钟无直接关系的所有
控件),翻译或添加注释,去掉了那个类(将其核心代码移植到用户控件代码中),改造成用户控件,
又添加了报时功能,足足费了笔者三整天的时间!从制作用户控件的角度来看,其中有关用户控件的代
码很简单。
2.混合四则运算控件:
在输入计算式时,二进制数据以字母“B”打头,八进制数据以字母“O”打头,16进制数据以字母
“H”打头,大小写字母均可。
3.选项卡控件:
定义按纽数目时,最少为2个按纽,最多为8个按纽,且数目须为偶数
4.消息框控件:
这东东是很伤脑筋的玩艺,碰到了一个难以解决的问题:如何得到返回值?
系统的消息框属于模式窗体,用户没有按下消息框上的按纽以前,程序就挂在那儿,直到按下某个
按纽才会继续住下执行。可是用户控件不行,用户控件好象是异步执行方式,还没等用户按键,程序已
经执行到调用用户控件的代码后面去了,这种情况下没法取到返回值。无奈之下,想到一个不是办法的
办法:在窗体中增加一个计时器,调用用户控件消息框以后,开启计时器,利用计时器的自动执行功能
来取得返回值。
窗体代码示例如下:
Private Sub Cmd1_Click()
MegBox1.Visible = True
MegBox1.Msg "内容已经改变,要保存吗?", 51, "提醒"
Timer1.Enabled = True
End Sub
Private Sub Timer1_Timer()
Dim msg As String, k As Integer
k = MegBox1.FeedValue
If k Then
Select Case k
Case 1: '执行代码
Case 2: '执行代码
Case 6: '执行代码
Case 7: '执行代码
End Select
Timer1.Enabled = False
MegBox1.Visible = False
End If
End Sub
这显然比直接调用系统的消息框麻烦多了,所以用户控件制作的消息框应用场合受到很大限制。不
过,笔者的目的只是提供给各位做实验,探讨制作用户控件的技巧,它能否广泛使用倒在其次。如果哪
位有创建象模式窗体那样的用户控件的办法,请不吝赐教,在此先行谢过。
另外,为了简单,笔者没有使用终止、重试、忽略三个按纽(如有必要,请自行添加),组合值中
不要包括相关的常数,也不要包括系统模式常数 40960。
5.菜单控件:
MyMenu 控件和 muchMenu 控件是笔者根据自己的原创《又漂亮又实用的记事本界面》中的 Form6
窗体代码改编的(原文请见笔者的旧贴)。本控件的缺憾是:由于用户控件的活动范围无法超出窗体,
所以如果最终用户将窗体拉小了而菜单又很长的话,那么菜单体将会有一部分显示不出来,这是用户控
件的遗憾,没法解决的,所以,菜单控件只适用于窗体大小固定的场合。
MyMenu 控件只适用于一个主菜单的情况,而不适用多个主菜单的情况。例如,窗体上有文件、编辑
、查看、选项、帮助等5个主菜单,难道要用上5个 MyMenu 控件不成?所以,我又编写了muchMenu控
件。
muchMenu 控件可以适用10个主菜单、每个主菜单有20个菜单项的情况(当然还可以扩展),这样,
菜单项文本变量 mCaption 就必须使用二维数组,第一维是“层”数(笔者将一个主菜单称为一层),
第二维是“项”数。在窗体的属性窗口,你必须先设置 RepeatCount 属性(当前总层数),再设置当前
层数(RepeatCurrent 属性),再点击“(自定义)”进入属性页对当前层的菜单项进行设置。
三个菜单控件都是利用单击事件返回用户选中的菜单项编号 SelectedItem。
6.立体字制作控件:
利用这个控件,你可以制作纯背景色的立体字,也可以制作有背景图片的立体字。
九、将窗体代码的功能改编为用户控件时的注意事项
1.最好只将某个单一的功能改编成用户控件,多功能的用户控件过于复杂也没有必要。
2.原程序 Form_Load 事件中的代码一般必须移植到用户控件的 UserControl_Initialize 事件中,原
因嘛,笔者前面已经讲过,用户控件运行时,首先执行的就是这个 UserControl_Initialize 事件。
3.原程序 Form_Resize 事件中的代码一般可以移植到用户控件的 UserControl_Resize 事件中。
4.原程序中的单选按纽或用组合下拉框选择的功能,一般必须在用户控件中定义成枚举形式的属性。
5.原程序中用复选框选择的功能,一般必须在用户控件中定义成标准形式的属性。
十、最后的建议
如果你制作的用户控件不想发布,而只是供自己的程序使用,那么建议:
1.不要编译成 OCX 文件。发布程序时都要带上一个OCX,显得麻烦。不如将用户控件封装进程序。由于
是源代码级控件,编译时会与模块、窗体、类等等一起被编译进程序,既方便又提高了效率。
2.根据具体情况,可以删除用户控件中多余的功能或选项。例如按纽控件,如果在你的程序中,只使用
了其中的一种或几种外观,那么你就可以删除其它的枚举结构项目甚至整个结构,再修改相关的代码就
行了,这样你的程序就会更加精练而高效。