回 帖 发 新 帖 刷新版面

主题:[原创]用户控件制作实例与讲解(下)

用户控件制作实例与讲解

        下篇

(附件中包括以下控件的工程文件:进度条控件、消息框控件、选项卡控件、菜单控件和立体字制作控
件共计10个控件)

三、方法
  找遍网上,所有的资料都只有如何定义用户控件的属性和事件,却没有说明如何定义用户控件的方
法。在用户控件页面点击“工具→添加过程”,在弹出的对话框中,“类型”单选按纽竟然也没有“方
法”!没有办法啊,笔者长叹一声,只好在一片茫然中做试验。经过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.时钟控件:
  其中酷时钟程序不是我的原创,它本是一个窗体程序,还使用了一个类。笔者在保持它的基本功能
和外观的前提下,将它作了大幅的压缩和删减(精减了一半以上的代码,删除了与钟无直接关系的所有
控件),翻译或添加注释,去掉了那个类(将其核心代码移植到用户控件代码中),改造成用户控件,
又添加了报时功能,足足费了笔者三整天的时间!从制作用户控件的角度来看,其中有关用户控件的代
码很简单。
  诸位在将窗体代码制作为用户控件时请注意一点:原程序 Form_Load 事件中的代码一般必须移植
到用户控件的 UserControl_Initialize 事件中,原因嘛,笔者前面已经讲过,用户控件运行时,首先
执行的就是这个 UserControl_Initialize 事件。

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.立体字制作控件:
  这个控件是昨天下午才准备将它由窗体程序改编为用户控件,今天上午完成。利用它你可以制作纯
背景色的立体字,也可以制作有背景图片的立体字。

回复列表 (共6个回复)

沙发

最后的建议
  如果你制作的用户控件不想发布,而只是供自己的程序使用,那么建议:

1.不要编译成 OCX 文件。发布程序时都要带上一个OCX,显得麻烦。不如将用户控件封装进程序。由于
是源代码级控件,编译时会与模块、窗体、类等等一起被编译进程序,既方便又提高了效率。

2.根据具体情况,可以删除用户控件中多余的功能或选项。例如按纽控件,如果在你的程序中,只使用
了其中的一种或几种外观,那么你就可以删除其它的枚举结构项目甚至整个结构,再修改相关的代码就
行了,这样你的程序就会更加精练而高效。

板凳

好,不错支持一下

3 楼

支持原创

4 楼

好,不错支持一下

5 楼

谢谢楼主了,讲的非常详细。

6 楼

支持,多发点

我来回复

您尚未登录,请登录后再回复。点此登录或注册