打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Excel VBA教程
Excel知识交流,VBA应用实例!这是Excel吧的口号!一起学习!一起进步!今天开始,我将写一些自己学习与使用VBA的心得体会,希望大家能在此有所收获。
Excel——在各个相关网站中,常常被分为四部份:基础应用、函数应用、图表应用与VBA应用。四个部份只是大概的分类,相互相成,不可分割。而在这里,我要说的是关于VBA这一部份。可能你没有想到还可以这想解决!但——真的可以!
VBA能带给我们意想不到的方便和惊喜!您会了吗?您用了吗?
让我们一起学习!一起进步吧!
VBA可以方便处理一些有一定量的数据,但不是大量;有一定规律的数据,但规律是可以通过学习不断发现的东西!可以让OFFICE方便,自动帮我们处理一些重复性的工作!
VBA是OFFICE和一部份,当然也是Excel的一部份,OFFICE为编程留下很多接口,还可以用“录制宏”的方法,自动将你的操作转化为代码,当然,你也可以自已编写代码。VBA是VB的一个子集,也可以算是一种语言吧,你可以按ALT F11调出VBA编辑器,这时按F1,也就是帮助,如果在你有安装VBA帮助的话,还可以了解一下VBA的一些情况,如果没有安装会提示需要安装盘。我以前在不少地方有说过,为什么我不爱国不爱用WPS的一个最重要的原因就是在众多OFFICE软件中,微软的OFFICE是第一个支持宏,除了OFFICE外,好像只有永中OFFICE支持宏。如果你了解了宏,理解了宏,直到利用好宏,和实实在在会编程宏,那你就可以体验出宏给你带来的巨大的益处,和VBA的强大之处!!有人说,VB能做什么,回答是,什么都能做!而VBA又能做什么?我只能说VBA也是强大的,虽然我目前达不到想到什么就做什么的地步,但在我工作中,VBA给我带来的不只只是省点时间和方便而与,而是可以帮我独当一面的工具与助手!也是我一直非常喜爱Excel的地方!一起来吧,不为了什么,为了你自己,为了平常中的方便,VBA,一定会对你有帮助的。
VBA是什么?VBA是Visual Basic for Application的简写,它以VB语言为基础,经过修改并运行在Microsoft Office的应用程序,如Excel,Word中,它是不能像VB一样能生成可执行程序的。
什么是Excel应用程序?利用Excel和VBA为平台,开发出来的电子数据表即称Excel应用程序。
什么是宏?宏是一系列的命令与函数,存储于 Visual Basic 的模块中,并在在需要执行该项任务时可随时运行。如果经常在重复某项任务,那么可以用宏自动执行该任务。
上面是VBA的三个概念,那为什么要用VBA?为什么要以Excel为平台来开发程序?
这里说几个简单的理由!
1、当使用Excel为平台时,你的程序就可以利用Excel现有的功能,可以站在一个小巨人的肩膀上,这就可大大减少开发的周期。
2、几乎所有的电脑中都有Excel,也有大量的人正在使用Excel,但并不是每个人都会使用VBA,当你了解VBA后,以前的很多问题就可能在这就迎韧而解。
3、Excel开发程序分发很容易,只要电脑中有Excel,基本不需要在其它的文件,简简单单的复制与粘贴,就完成了文件的分发。
4、VBA的语言是相对容易学的语言,很容易上手,如果你熟悉VB,那你发现它们在语言方面是相通的,而如果你对Excel比较了解,那你也就很容易理解Excel的各种对象了。
最后,任何东西都不是万能的Excel与VBA也是一样。
如,Excel是一个电子表格程序,如果你把它强加成数据库软件是不公平的,在处理较少的数据,比如几千行的,用Excel是比较理想的,而大量的数据时,你就应该考虑用数据库了,比如Microsoft Office中的Access等,或者将两个相结合。
充分了解Excel的基本知识与VBA的编程技巧,你就会从中获得乐趣,让我们一起来吧,走进VBA的世界中。
功欲善其事,必先利其器!在我们要学习VBA与用VBA编写代码时,必须先熟悉开发的环境——VBE。
VBE——Visual Basic Edirtor,如果大家对微软的一门编程语言有所了解的话,那对VBE的样子也就不会陌生的。那怎么进入VBE呢?
从Excel菜单中,选择工具——宏——Visual Basic 编辑器,或者按快捷键Alt F11即可进入,不少朋友可能都没有接触过VBE,甚至连Excel中包含这个编辑器都一无所知,下面这张图就展现出VBE最常用的七个窗口,而图片也让这几个窗口更容易说明。

上个文章已用图片给出了七个常用窗口的图片,图片中可以很清楚地看到对应的菜单与工具栏所在的位置,从视觉是认识这七个窗口,下面内容也就好说明一点。
如果有编程经历的朋友,可以不用我说明就可以知道这些窗口的用途,现在就一一说下下用途:
1、代码窗口。从字面上就可以知道,就是在这里写VBA的代码的,也就是在这里查看代码的。
2、对象窗口。可以在这里设定窗体的界面(如果你使用了窗体的话),在这里能很直观地设定窗体各个控件的布局。
3、对象浏览器。在这里可以查看所有对象库、特定对象或你自己的工程,包括所有的对象的列表与每个对象的成员列表。
4、工程资源管理器。在这里,我们可以很方便地管理工程中的模块、类模块与窗体,还可以很容量地在代码与对象间切换。
5、属性窗口。可以说这是一个万能的窗口,在这里可以很简单地设定很多对象相关的属性,简单到只要用鼠标选择即可。
6、工具箱。在插入窗体后,可以从工具箱添加各种控件。
7、立即窗口。这个窗口可能很多朋友都没有使用过,所以多说两句,其三个主要工作是:
(1)在开发过程中,用Debug.Print输出的内容就在此显示。
(2)当代码是Break模式时,查看对象和变量的状态。
(3)用?加上语句,就可以看到运行的结果,在很多情况下比用msgbox报出方便多了。
再进入VBA学习的最后一节里,我要强调一下,要利用好录制宏,Excel的帮助与网络的搜索。

在自学VBA的过程中,我常说要多用——多学——多想——多句。而初学者自学VBA还三大利器:录制宏,VBA的帮助和网络的搜索。善用好这三样的话,很多问题不需要别人的帮助就能自己解决了。
1、录制宏。从工具/宏/录制新宏里,可以将停止录制宏之前的操作,转化为VBA的代码。这样不需要任何VBA的知识,都可以录制出一段宏。但录制的宏基本都有局限性,但可以通过修改与加工后,让录制的代码更完善。录制宏还可以通过操作来认识Excel的对象与方法,做为初期学习VBA,不失为一个学习的好方法与好助手。
2、VBA帮助。VBA的帮助中包含Excel所有属性、对象、方法等的说明,自学VBA的过程中,经常查阅VBA帮助,对了解Excel所有属性、对象、方法很有帮助。在查看别人的代码时,对不熟悉的字眼,只要将光标移到其中,再按F1键,就能快速查获到相关的帮助。我们还可以在帮助的应答向导中,键入相关的关键词,来查找我们需要的内容。当你能熟练应用好帮助时,你的VBA水平可以说已上了一个台阶了。
3、网络的搜索。网络的好处就在于信息量之巨大,而要查找到我们需要的信息时,最基本的就是用好搜索。平常常用的搜索如百度,Google等,只要键入正确与合理的关键词,就能找出不少相关的信息。而在某个站点内,也常常有自己本身的站内搜索,这时利用搜索就能让我们更快更方便地找到我们需要的信息。
很多教编程的书籍,都喜欢用一个Hello World的小程序开头,我们的VBA之旅也从这里开始吧。

进入VBA编辑器,由插入菜单中插入一个模块,然后再由插入菜单中插入我们的第一个程序,名称为xcelbaSub1,然后VBA编辑器就自动生成一个子过程,在子过程的中间空白处键入我们的代码:
Public Sub ExcelbaSub1()
Dim TStr As String
TStr = "Hello World!"
Application.ActiveSheet.Range("A1").Value = TStr
Debug.Print TStr
MsgBox TStr, , "www.excelba.com"
End Sub
我们的Hello World的小程序就成完了,下面按运行菜单中的运行子过程/用户窗体,或在点击工具栏的运行按钮,或按F5就可以运行我们的程序了,除此外还可以从Excel主界面的工具/宏/宏中,选择ExcelbaSub1来运行。
下面来解释一下这个程序的每个句的用途:
第一句:Public关键字是声明这个子程序是公用的,Sub是声明子程序的关键字,而ExcelbaSub1就是我们的子程序名称了。
第二句:Dim是声明变量的关键字,这里我们用来声明一个名为TStr的字符串变量。
第三句:将TStr这个变量赋值为Hello World!
第四句:将当前工作表的A1格的值设定为Hello World!
第五句:利用Debug.Print地立即窗口中显示TStr的值。
第六句:用消息对话框显示TStr的值。
最后一句:标记结束子程序。
程序虽小,但最基本的元素都齐了,也许,这就是Hello World的魅力所在吧.
大家通过之前的介绍,已知道怎么将一个空模块插入VBA的工程中。从插入模块中可以看到,模块有有两种——标准模块与类模块。类模块是含有类定义的特殊模块,包括其属性和方法的定义。在后面会有介绍与说明。
随着工程越来越委员复杂,我们就有可能会有多个模块。使用多模块的好处就是,它允许将相关的过程聚合在一起,使代码的可维护性与可重用性大大提高,更使我们能够方便地管理代码。通过不同的模块,我们还可以为不同模块定制不同的行为,定制模块行为的方法有4种:
1、Option Explicit。当使用Option Explicit时,必须在模块中的所有过程声明每一个变量,否则会出现语法错误并不能被编译。这样做的好处是,它能消除程序中因为错拼变量名而导致程序错误,所以见意使用此选项。自动设定的方法:在VBA编辑器工具菜单中选项里的编辑器选卡中的要求声明变量选项选上即可。这个每次插入新模块时会自动插入此声明。
2、Option Private Module。当使用此设定时,模块中的代码将标记为私有,这样在宏对话框中就不能看到这些代码,也就是在Excel主界面的工具/宏/宏的对话框中不会显示私有的子程序名称,这也防止了模块的内容被其它工程引用,不过在同一工程中的其它模块仍然是可用的。
3、Option Compare {Binary | Text | Database}。用于声明字符串比较时所用的缺省比较方法。如果模块中没有 Option Compare 语句,则缺省的文本比较方法是 Binary。Option Compare Binary 是根据字符的内部二进制表示而导出的一种排序顺序来进行字符串比较。在 Microsoft Windows 中,排序顺序由代码页确定。典型的二进制排序顺序如下例所示:A < B < E < Z < a < b < e < z
Option Compare Text 根据由系统区域确定的一种不区分大小写的文本排序级别来进行字符串比较。当使用 Option Compare Text 对相同字符排序时,会产生下述文本排序级别:
(A=a) < (B=b) < (E=e) < (Z=z) < (_=_)
Option Compare Database 只能在 Microsoft Access 中使用。当需要字符串比较时,将根据数据库的区域 ID 确定的排序级别进行比较。
4、Option Base {0 | 1}。用来声明数组下标的缺省下界。
注意 Dim、Private、Public、ReDim 以及 Static 语句中的 To 子句提供了一种更灵活的方式来控制数组的下标。不过,如果没有使用 To 子句显式地指定下界,则可以使用  Option Base 将缺省下界设为 1。使用 Array 函数或 ParamArray 关键字创建的数组的下界为 0; Option Base 对 Array 或 ParamArray 不起作用。
Option Base 语句只影响位于包含该语句的模块中的数组下界。
过程,就是执行一个或多个给定任务的集合。又分为两种类型:子程序与函数。其两者之间的主要区别在于,函数会返回一个值而子程序不会返回值。

1、子程序。子程序是一个程序中可执行的最小部份,其语法为:
[Private | Public | Friend] [Static] Sub name [(arglist)] 
[statements]
[Exit Sub]
[statements]
End Sub
Sub 语句的语法包含下面部分:

部分描述
Public可选的。表示所有模块的所有其它过程都可访问这个 Sub 过程。 如果在包含 Option Private 的模块中使用,则这个过程在该工程外是不可使用的。
Private可选的。表示只有在包含其声明的模块中的其它过程可以访问该 Sub 过程。
Friend可选的。只能在类模块中使用。表示该 Sub 过程在整个工程中都是可见的,但对对象实例的控制者是不可见的。
Static可选的。表示在调用之间保留 Sub 过程的局部变量的值。Static 属性对在 Sub 外声明的变量不会产生影响,即使过程中也使用了这些变量。
name必需的。Sub 的名称;遵循标准的变量命名约定。
arglist可选的。代表在调用时要传递给 Sub 过程的参数的变量列表。多个变量则用逗号隔开。
statements可选的。Sub 过程中所执行的任何语句组。
其中的 arglist 参数的语法以及语法各个部分如下:

[Optional] [ByVal | ByRef] [ParamArray] varname[( )] [As type] [= defaultvalue]

部分描述
Optional可选的。表示参数不是必需的关键字。如果使用了该选项,则 arglist 中的后续参数都必须是可选的,而且必须都使用 Optional 关键字声明。如果使用了 ParamArray,则任何参数都不能使用 Optional。
ByVal可选的。表示该参数按值传递。
ByRef可选的。表示该参数按地址传递。ByRef 是 Visual Basic 的缺省选项。
ParamArray可选的。只用于 arglist 的最后一个参数,指明最后这个参数是一个 Variant 元素的 Optional 数组。使用 ParamArray 关键字可以提供任意数目的参数。ParamArray 关键字不能与 ByVal,ByRef,或 Optional 一起使用。
varname必需的。代表参数的变量的名称;遵循标准的变量命名约定。
type可选的。传递给该过程的参数的数据类型,可以是 Byte、Boolean、Integer、Long、Currency、Single、Double、Decimal(目前尚不支持)、Date、 String(只支持变长)、Object 或 Variant。如果没有选择参数 Optional,则可以指定用户定义类型,或对象类型。
defaultvalue可选的。任何常数或常数表达式。只对 Optional 参数合法。如果类型为 Object,则显式的缺省值只能是 Nothing。

2、函数:函数与子程序最大的区别就在于其可以返回值,而其它地方与子程序相似,语法如下:

[Public | Private | Friend] [Static] Function name [(arglist)] [As type]
[statements]
[name = expression]
[Exit Function] 
[statements]
[name = expression]
End Function
可以看出除了声明的关键词外,其它基本类同,说明与用法也相近,这里就不再重复了。

VBA代码中包含变量、运算符和语句。变量在代码中起到互交与连接的作用。变量从创建为合适的对象与数据类型,到初始化,再通过运算符计算或执行语句修改,来完成整个互交的过程。

声明变量的关键词是Dim,其语法是

Dim [WithEvents] varname[([subscripts])] [As [New] type] [, [WithEvents] varname[([subscripts])] [As [New] type]] . . .

Dim 语句的语法包含下面部分:

部分描述
WithEvents可选的。关键字,说明 varname 是一个用来响应由 ActiveX 对象触发的事件的对象变量。只有在类模块中才是合法的。使用 WithEvents,可以声明任意个所需的单变量,但不能使用 WithEvents 创建数组。New 和 WithEvents 不能一起使用。
varname必需的。变量的名称;遵循标准的变量命名约定。
subscripts可选的。数组变量的维数;最多可以定义 60 维的多维数组。subscripts 参数使用下面的语法:
 [lower To] upper [, [lower To] upper] . . .
如果不显式指定 lower,则数组的下界由 Option Base 语句控制。如果没有使用 Option Base 语句,则下界为 0。
New可选的。可隐式地创建对象的关键字。如果使用 New 来声明对象变量,则在第一次引用该变量时将新建该对象的实例,因此不必使用 Set 语句来给该对象引用赋值。New 关键字不能声明任何内部数据类型的变量,以及从属对象的实例,也不能与 WithEvents 一起使用。
type可选的。变量的数据类型;可以是 Byte、 Boolean 、Integer、Long、Currency、Single、Double、Decimal(目前尚不支持)、Date、String(对变长的字符 串)、String * length (对定长的字符串)、Object、Variant、用户定义类型、或对象类型。所声明的每个变量都要一个单独的 As type 子句。

而在声明变量或创建变量的同时,最好先考虑好变量用什么数据类型,下面 表格显示所支持的数据类型,以及存储空间大小与范围。

数据类型存储空间大小范围
Byte1 个字节0 到 255
Boolean2 个字节True 或 False
Integer2 个字节-32,768 到 32,767
Long
(长整型)
4 个字节-2,147,483,648 到 2,147,483,647
Single
(单精度浮点型)
4 个字节负数时从 -3.402823E38 到 -1.401298E-45;正数时从 1.401298E-45 到 3.402823E38
Double
(双精度浮点型)
8 个字节负数时从 -1.79769313486231E308 到
-4.94065645841247E-324;正数时从4.94065645841247E-324 到 1.79769313486232E308
Currency
(变比整型)
8 个字节从 -922,337,203,685,477.5808 到 922,337,203,685,477.5807
Decimal14 个字节没有小数点时为 /-79,228,162,514,264,337,593,543,950,335,而小数点右边有 28 位数时为 /-7.9228162514264337593543950335;最小的非零值为 /-0.0000000000000000000000000001
Date8 个字节100 年 1 月 1 日 到 9999 年 12 月 31 日
Object4 个字节任何 Object 引用
String
(变长)
10 字节加字符串长度0 到大约 20 亿
String
(定长)
字符串长度1 到大约 65,400
Variant
(数字)
16 个字节任何数字值,最大可达 Double 的范围
Variant
(字符)
22 个字节加字符串长度与变长 String 有相同的范围
用户自定义
(利用 Type)
所有元素所需数目每个元素的范围与它本身的数据类型的范围相同。

其中的Variant是个很特殊的数据类型,它能表示除固定长度之外的所有值,并可以通过VarType来返回其数据子类型。其语法如下:

VarType(varname)
必要的 varname 参数是一个 Variant,包含用户定义类型变量之外的任何变量。
下面这个表表示VarType返回的值的意义:

常数描述
vbEmpty0Empty(未初始化)
vbNull1Null(无有效数据)
vbInteger2整数
vbLong3长整数
vbSingle4单精度浮点数
vbDouble5双精度浮点数
vbCurrency6货币值
vbDate7日期
vbString8字符串
vbObject9对象
vbError10错误值
vbBoolean11Boolean 值
vbVariant12Variant(只与变体中的数组一起使用)
vbDataObject13数据访问对象
vbDecimal14十进制值
vbByte17位值
vbUserDefinedType36包含用户定义类型的变量
vbArray8192数组

变量因声明的位置与使用Public或Private关键字,其有效范围可能为3种:过程范围、模块范围与全局范围。当变量放在模块头部,并使用 Public代替Dim时,这个变量就是全局变量;而如果是用Dim或者用Private代替Dim时,这个变量是模块级变量;而当变量在过程中声明就是 过程变量了。

通常的过程变量在过程结束后值是不保存,但如果子程序使用Static语句,或者用Static声明的过程变量是例外的,下面用两个实例说明一下。

Static Sub ExcelbaSub2()
    Dim X As Integer
    MsgBox "X = " & X
    X = X 1
End Sub

Sub ExcelbaSub3()
    Static Y As Integer
    MsgBox "Y = " & Y
    Y = Y 1
End Sub

运行上面的过程,你就比较容易理解Static的用途了。

除了变量,我们还经常用到一些常量,包含系统定义与自定义的常量。常量与变量有很多相似之处,但常量的值是保持不变的,如果代码中常常出现一个数值,最好的方法就是定义成常量,这样既使代码易于编写,也让代码容易阅读。定义常量的关键词是Const,其语法是:

语法
[Public | Private] Const constname [As type] = expression
其中的expression就是常量的值,其它与变量相同,就不重复了。

运算符对公式中的元素进行特定类型的运算。Microsoft Excel 包含四种类型的运算符:算术运算符、比较运算符、文本运算符和引用运算符。

运算符的类型

算术运算符   若要完成基本的数学运算,如加法、减法和乘法,连接数字和产生数字结果等,请使用以下算术运算符:

算术运算符含义(示例)
(加号)加法运算 (3 3)
–(减号)减法运算 (3–1)
负 (–1)
*(星号)乘法运算 (3*3)
/(正斜线)除法运算 (3/3)
%(百分号)百分比 (20%)
^(插入符号)乘幂运算 (3^2)

比较运算符   可以使用下列运算符比较两个值。当用运算符比较两个值时,结果是一个逻辑值,不是 TRUE 就是 FALSE。

比较运算符含义(示例)
=(等号)等于 (A1=B1)
>(大于号)大于 (A1>B1)
<(小于号)小于 (A1<B1)
>=(大于等于号)大于或等于 (A1>=B1)
<=(小于等于号)小于或等于 (A1<=B1)
<>(不等号)不相等 (A1<>B1)

文本连接运算符   使用和号 (&) 加入或连接一个或更多文本字符串以产生一串文本。

文本运算符含义(示例)
&(和号)将两个文本值连接或串起来产生一个连续的文本值 ("North"&"wind")

引用运算符   使用以下运算符可以将单元格区域合并计算。

引用运算符含义(示例)
:(冒号)区域运算符,产生对包括在两个引用之间的所有单元格的引用 (B5:B15)
,(逗号)联合运算符,将多个引用合并为一个引用 (SUM(B5:B15,D5:D15))
 (空格)交叉运算符产生对两个引用共有的单元格的引用。(B7:D7 C6:C8)

公式中的运算次序

公式按特定次序计算数值。Excel 中的公式通常以等号 (=) 开始,用于表明之后的字符为公式。紧随等号之后的是需要进行计算的元素(操作数),各操作数之间以运算符分隔。Excel 将根据公式中运算符的特定顺序从左到右计算公式。

运算符优先级

如果公式中同时用到多个运算符,Excel 将按下表所示的顺序进行运算。如果公式中包含相同优先级的运算符,例如,公式中同时包含乘法和除法运算符,则 Excel 将从左到右进行计算。

运算符说明
:(冒号)

  (单个空格)

,(逗号)

引用运算符
负号(例如 –1)
%百分比
^乘幂
* 和 /乘和除
和 – 加和减
&连接两个文本字符串(连接)
= < > <= >= <>比较运算符

使用括号

若要更改求值的顺序,请将公式中要先计算的部分用括号括起来。例如,下面公式的结果是 11,因为 Excel 先进行乘法运算后进行加法运算。将 2 与 3 相乘,然后再加上 5,即得到结果。

=5 2*3

与此相反。如果使用括号改变语法,Excel 先用 5 加上 2,再用结果乘以 3,得到结果 21。

=(5 2)*3

在下例中,公式第一部分中的括号表明 Excel 应首先计算 B4 25,然后再除以单元格 D5、E5 和 F5 中数值的和。

=(B4 25)/SUM(D5:F5)

前面说了代码中三大元素中的变量与运算符,现在说一下语句中的判断语句,判断语句有下面几种形式:

1、If condition Then [statements][Else elsestatements]。

其中各部份的说明:

部分描述
condition必要参数。一个或多个具有下面两种类型的表达式:
 数值表达式或字符串表达式,其运算结果为 True 或 False。如果 condition 为 Null,则 condition 会视为 False。
TypeOf objectname Is objecttype 形式的表达式。其中的 objectname 是任何对象的引用,而 objecttype 则是任何有效的对象类型。如果 objectname 是 objecttype 所指定的一种对象类型,则表达式为 True,否则为False。
statements在没有 Else 子句时,为必要参数,否则为可选。它们在 condition 为 True 时执行。
elsestatements可选参数。它们在前面的 condition 或 condition-n 都不为 True 时执行。

2、 If condition Then
[statements]
[ElseIf condition-n Then
[elseifstatements] ...
[Else]
[elsestatements]]
End If

其中各部份的说明:

部分描述
condition必要参数。一个或多个具有下面两种类型的表达式:
 数值表达式或字符串表达式,其运算结果为 True 或 False。如果 condition 为 Null,则 condition 会视为 False。
TypeOf objectname Is objecttype 形式的表达式。其中的 objectname 是任何对象的引用,而 objecttype 则是任何有效的对象类型。如果 objectname 是 objecttype 所指定的一种对象类型,则表达式为 True,否则为False。
statements可选参数。一条或多条以冒号分开的语句,它们在 condition 为 True 时执行。
condition-n可选参数。与 condition 同。
elseifstatements可选参数。一条或多条语句,它们在相关的 condition-n 为 True 时执行。
elsestatements可选参数。一条或多条语句,它们在前面的 condition 或 condition-n 都不为 True 时执行。

3、IIf 函数。IIf函数不是语句,但由于可以代替If…Then,就将它总结在这里,其语法如下:

IIf(expr, truepart, falsepart)

其中各部份的说明:

部分描述
expr必要参数。用来判断真伪的表达式。
truepart必要参数。如果 expr 为 True,则返回这部分的值或表达式。
falsepart必要参数。如果 expr 为 False,则返回这部分的值或表达式。

4、Select Case testexpression
[Case expressionlist-n
[statements-n]] ...
[Case Else
[elsestatements]]
End Select

其中各部份的说明:

部分 描述
testexpression必要参数。任何数值表达式或字符串表达式。
expressionlist-n如果有 Case 出现,则为必要参数。其形式为 expression,expression To expression,Is comparisonoperator expression的一个或多个组成的分界列表。To 关键字可用来指定一个数值范围。如果使用 To 关键字,则较小的数值要出现在 To 之前。使用 Is 关键字时,则可以配合比较运算符(除 Is 和 Like 之外)来指定一个数值范围。如果没有提供,则 Is 关键字会被自动插入。
statements-n可选参数。一条或多条语句,当 testexpression 匹配expressionlist-n中的任何部分时执行。
elsestatements可选参数。一条或多条语句,当 testexpression 不匹配 Case 子句的任何部分时执行。
语句除了判断语句外,还有循环语句,有以下几种形式:

1、While...Wend 语句。语法 :
While condition
[statements]
Wend
While...Wend 语句的语法具有以下几个部分:

部分描述
condition必要参数。数值表达式或字符串表达式,其计算结果为 True 或 False。如果 condition 为 Null,则 condition 会视为 False。
statements可选参数。一条或多条语句,当条件为 True 时执行。

2、Do...Loop 语句。语法:
Do [{While | Until} condition]
[statements]
[Exit Do]
[statements]
Loop
或者可以使用下面这种语法:
Do
[statements]
[Exit Do]
[statements]
Loop [{While | Until} condition]
Do Loop 语句的语法中的参数说明以上面相同,不重复了。

3、 For...Next 语句。 语法:
For counter = start To end [Step step]
[statements]
[Exit For]
[statements]
Next [counter]
For…Next 语句的语法具有以下几个部分:

部分描述
counter必要参数。用做循环计数器的数值变量。这个变量不能是 Boolean 或数组元素。
start必要参数。counter 的初值。
End必要参数,counter 的终值。
Step可选参数。counter 的步长。如果没有指定,则 step 的缺省值为 1。
Statements可选参数。放在 For 和 Next 之间的一条或多条语句,它们将被执行指定的次数。

4、For Each...Next 语句。语法
For Each element In group
[statements]
[Exit For]
[statements]
Next [element]
For...Each...Next 语句的语法具有以下几个部分:

部分描述
element必要参数。用来遍历集合或数组中所有元素的变量。对于集合来说,element 可能是一个 Variant 变量、一个通用对象变量或任何特殊对象变量。对于数组而言,element只能是一个 Variant 变量。
group必要参数。对象集合或数组的名称(用户定义类型的数组除外)。
statements可选参数,针对 group 中的每一项执行的一条或多条语句。

数组的声明方式和其它的变量是一样的,它可以使用 Dim、Static、Private 或 Public 语句来声明。标量变量(非数组)与数 组变量的不同在于通常必须指定数组的大小。若数组的大小被指定的话,则它是个固定大小数组。若程序运行时数组的大小可以被改变,则它是个动态数组。
数组是否从 0 或 1 索引是根据 Option Base 语句的设置。如果 Option Base 没有指定为 1,则数组索引从零开始。

(1)声明固定大小的数组
下面这行代码声明了一个固定大小的数组,它是个 11 行乘以 11 列的 Integer 数组:
Dim MyArray(10, 10) As Integer

(2)声明动态数组
若声明为动态数组,则可以在执行代码时去改变数组大小。可以利用 Static、Dim、Private 或 Public 语句来声明数组,并使括号内为为空,如下示例所示。
Dim sngArray() As Single
2、获得数组的最大与最小下标。利用LBound 函数与UBound 函数函数可以分别来获得数组的最小与最大下标,其语法是:
LBound(arrayname[, dimension])
UBound(arrayname[, dimension])
语法包含下面部分:

部分描述
arrayname必需的。数组变量的名称,遵循标准的变量命名约定。
dimension可选的;Variant (Long)。指定返回哪一维的下界。1 表示第一维,2 表示第二维,如此类推。如果省略 dimension,就认为是 1。

3、ReDim 语句。ReDim 语句用来定义或重定义原来已经用带空圆括号(没有维数下标)的 Private、Public 或 Dim 语句声明过的动态数组的大小,其语法是:
ReDim [Preserve] varname(subscripts) [As type] [, varname(subscripts) [As type]] . . .
ReDim 语句的语法包括以下几个部分:

部分描述
Preserve可选的。关键字,当改变原有数组最末维的大小时,使用此关键字可以保持数组中原来的数据。
varname必需的。变量的名称;遵循标准的变量命名约定。
subscripts必需的。数组变量的维数;最多可以定义 60 维的多维数组。subscripts 参数使用下面的语法:
 [lower To] upper [,[lower To] upper] . . .
如果不显式指定 lower,则数组的下界由 Option Base 语句控制。如果没有 Option Base 语句则下界为 0。
type可选的。变量的数据类型;可以是 Byte、Boolean、Integer、Long、Currency、Single、Double、Decimal(目前尚不支持)、Date、 String(对变长的字符串)、String * length(对定长的字符串)、Object、Variant、用户定义类型或对象类型。所声明的每个变量都要有一个单独的 As type 子句。对于包含数组的 Variant 而言,type 描述的是该数组的每个元素的类型,不能将此 Variant 改为其它类型。
可 以使用 ReDim 语句反复地改变数组的元素以及维数的数目,但是不能在将一个数组定义为某种数据类型之后,再使用 ReDim 将该数组改为其它数据 类型,除非是 Variant 所包含的数组。如果该数组确实是包含在某个 Variant 中,且没有使用 Preserve 关键字,则可以使用  As type 子句来改变其元素的类型,但在使用了此关键字的情况下,是不允许改变任何数据类型的。
如果使用了 Preserve 关键字,就只能重定义数组最末维的大小,且根本不能改变维数的数目。例如,如果数组就是一维的,则可以重定义该维的大小,因为它是最末维,也是仅有的一维。不过,如果数组是二维或更多维时,则只有改变其最末维才能同时仍保留数组中的内容。
什么是类
在我们身边,相同或相似的物品无处不在,从生活用品,到工业产品,……通常它们都是由同一个可以称为“模具”的东西生产出来。
理 解类,一般要先从对象谈起,但由于从不同的角度,有不同的理解,从而也有不同的关于类的定义,本文不去探讨一个完整并且公认的类概念,在这一部分后,只要 你有一个基本的判断并且在遇到时知道是类就可以了。在上面的这个例子中,一个“模具”就是一个“类”,而由它生产出的每一个产品,就是一个“对象”。看下 面的VBA语句:
Dim tx1 As Textbox
不用解释它的意思吧,这里我们用到一个类Textbox定义了一个对象tx1,再来看:
Dim tx1 As Textbox
Dim tx2 As Textbox
又定义了一个对象tx2,如果你不嫌烦,我还可以继续下去。对象增加了,但As后的Textbox没有变,它可以无限制的使用下去。
类是一个隐者,上面Textbox是VBA已经给我们准备好的一个类,我们无法知道VBA是怎么准备的(它的真身被隐藏了),但我们可以知道对象tx1怎么用。VBA把对对象的使用划分为三种,属性、方法和事件(后面预备知识我们再提)。
然 而,Textbox是一个类,仍然不是本文要说的类,因为它是VBA已经给我们准备好了的,我们要做的,只是知道如何使用它而已。本文要介绍的,是利用 VBA已经给我们提供的资源,来构建我们自己的类,姑且可称之为自定义类。这需要在VBE下,通过插入类模块,然后向类模块中写入代码来完成。这就是你常 常听说的类,本文的主题就是这个包含代码的模块!通过这个模块,可以提供给我们一个和VBA提供给我们的诸如Textbox功能性质完全相同的类,然后, 再由我们自己象使用Textbox一样使用!
类是一个隐者,她把自己藏在所有模块的最后,甚至在多数情况,她从不出场。现在,她挂着神秘的微笑,向你走来,你要拒绝吗?
二、为什么要学习类
类通常被认为是学习VBA的难点之一,之所以如此,因为相对于制造一个标准模块或用户窗体,我们可以找到的类的学习资 源少之又少,甚至很多VB的书籍也只有缪缪字语(在后面的预备知识,我们再提另一个重要的原因)。从技术角度上看,类的构建,不象窗体,VBA的类也不象 有些语言提供了可视的设计界面,感性上那样直接,隐者!她是不可视的设计,所有的构建都是通过在类模块中写代码来实现的。
1.学习是一种兴趣的追求
俗 语云:学的千千万,用的有几何?又曰:书到用时方恨少。林语堂先生将做学问划分为三重境界,第二重说“为伊消得人憔悴,衣带渐宽终不悔”,大多数朋友,包 括本人,都不是专业程序员,学习程序只是一种兴趣和爱好,就好象有人喜欢**网游一样,对喜欢程序的人,不断地学习和提高,也是一种追求优秀的态度,并且 乐意享受这个追求的过程。模块、控件、链接库和类构成软件工程开发的四大技术,而类技术是控件和链接库技术的基础,我们不得不学。
2.类有什么用
如你前面看到的我们使用Textbox类,类可以创建大量性质相近的对象,减轻我们的程序量,简洁代码并提高效率。记得清风兄做过一个扫雷的程序,100颗雷,如果没有类,光是单击的代码你就要写100个!
类定义后,在其它模块中使用时,我们就可以暂时忘记或不必考虑它内部复杂的细节,让我们变得轻松,VBA虽然不能真正封装类的形式,但在这里,我们可以封装它的概念。
这并不是类的全部好处,其它的,留着朋友们用的时候慢慢体会吧。
类挂着神秘的微笑,已经走到你的大门口,开门迎接她吧,你还等什么?
三、类的预备知识
广义上讲,所有VBA的知识,包括语句、函数以及为我们提供的标准类甚至第三方的资源都可以在类中被使用。本文无法也不 准备逐一探讨,这里只说一些最密切最基本的,但即使这样,笔者仍然不能把这些点的知识都写到,甚至因为对问题解释清晰或符合逻辑的需要,采用非规范的表 述,对专门问题的全面理解,请读者注意参考有关标准帮助文档并加以甄别。
1.从构建者的角度理解对象
上面我们提到,类被 认为是VBA难点还有一个原因,这就是我们的思想!VBA提供了大量的现成的类,我们几乎不再需要去构建自己的类,这种结果,我们熟练地习惯了从使用者的 角度去理解类—的实例:对象,包括它的属性、方法和事件。但是,现在你还要尝试做一个提供者,这和你作为使用者时的思考方法是完全不同的,甚至是革命性 的。这种角色的转位是痛苦的,它需要你放弃你原本可以自豪地解释出对象以及它的属性、方法、事件的定义,它们原本是如此逻辑地被划分,如此清晰,但现在, 类模块中的一切,彼此交织,你会发现它们都模糊了!是需要你忘掉所有固执的“招势”的时候了,当你心中无剑时,转位也就完成了,隐者变得清晰了,她是如此 美丽。且慢,在你完全忘掉前,让我们最后再看一眼它们的样子,呵呵,如果你实在忘不掉,你就提醒一下自己构建者的身份吧。下面是通常情况下关于对象、属 性、方法、事件的基本表述,如果你以前没了解过,则应当找些资料先认真地理解它们,然后再按照上面的提示去做。
对象是由类创建的一个实例,它是类的实体化。
对象的引用和操作被逻辑上划分为不重叠的三个部分:
属性是指对象的特性。以前面的Textbox为例,有长度,高度,框中显示的文字等等。
方法是指对象的某个操作。如让Textbox成为当前的焦点(即光标移动到它上面)。
事件是指对象对外部动作的响应。如我们用鼠标点击Textbox时,会产生一个Click事件,改变它的值,则产生一个Change事件。
2.变量的作用域
变量因为声明的位置和方式不同,从而有不同的作用域。作用域是指变量在多大范围内能被代码识别。可以划分为过程级、模块级和全局变量。
过 程级变量在过程中声明,这里过程指的是一个Sub或Function,也包括后面提到到属性过程。通常用Dim或Static进行声明。Dim声明的变 量,只在该过程执行时存在,过程结束,变量的值也就消失了。Static声明的变量称为静态变量,这个值在整个程序运行期间都存在。
模块级变量对整个模块的所有过程都有效,但对其它模块不可用。可以在模块顶部声明。声明模块级变量用Private关键字和直接使用Dim没有区别。但推荐使用Private进行声明,因为这样可以方便地与后面的全局变量区分开来。
全局变量是对整个VBA工程的所有过程都有效的变量,使用Public关键字在标准模块的顶部来声明。
在类模块中,对变量作用域的理解要注意下面两点:
(a)由于类是生成对象的模具,每生成一个对象,相当于产生了一个副本,这个副本就是对象的“真身”,副本间是相互独立的,从而,模块级的变量只作用于副本自身。
(b)类模块中使用Public关键字,只有当对象变量是这个类的实例时,才能被访问。
3.过程和函数
变 量、过程(Sub)、函数(Function)是我们在标准模块中使用的最基本的构件,在类摸块中,它们仍然是最基本和重要的角色。对于它们,你已经再熟 悉不过,之所以前面还要花这么多文字,是为了突出它的重要,也是想让你放松一下,哦,我花了很短的时间已经看了这么多(我也写了这么多!)。
过程和函数并无实质的区别,当需要返回值时,就使用Function,如果不需要返回任何结果,随你的爱好,但这时推荐你使用Sub,因为这样更符合微软的本意。
过 程(Sub)、函数(Function)也有作用域,在标准模块中通过使用Private和Public关键字(可以省略Public关键字,因为它是默 认的),可以划分为模块级和全局级,以决定它是在当前的模块有效还是整个工程有效。同变量一样,在类模块中使用Public关键字,只有当引用对象变量是 这个类的实例时,才能被访问。
4.通用内部控件Control(s)
VBA提供Control类作为一般内部控件类型,当使用
Dim Ct As Control
声明了一个变量后,就可以将任何控件赋给该变量,而不管具体的类型,因为在类的使用通常是处理大量相近的对象,所以这种特性非常有用。在实际使用时,我们多是通过容器控件的Controls属性来返回一个Control的集合对象。
Dim Ct As Control
For Each Ct In Me.Controls
If TypeName(Ct) = "CommandButton" Then MsgBox Ct.Caption
Next
上面这段代码可以遍历窗体的所有控件并报告找到的命令按钮。
5.集合Collection
Collection是我们在使用类时最常用到的对象。一个Collection对象代表一组相关的项目,虽然它的成员并不被强制要求是同一类型的的,但请记住,这通常并不能给我们带来额外的方便,相反,我们通常是用来收集同一类型的数据。
建立集合的方法和建立其它对象一样,如:
Dim col As New Collection
集合建立后,可以使用Add方法添加成员,用Remove方法删除成员,用Item方法从集合中返回特定成员。
Private Sub CommandButton1_Click()
Dim col As New Collection
Dim i%
Dim ct As Control
For Each ct In Me.Controls
If Left(ct.Name, 7) = "TextBox" Then col.Add ct
Next ct
For i = col.Count To 1 Step -1
MsgBox "下面删除成员" & col.Item(i).Name
col.Remove i
Next i
End Sub
上 面的代码先将窗体上所有的TextBox加入到集合中,然后再删除掉。Count属性返回集合的成员数量,Remove方法后面的参数是集合成员的索引 号。成员的索引号通常是按照加入的顺序自然编号,从1开始,但可以在加入时使用Add方法的参数进行改变。Add方法的完整语法是:
object.Add item[, key][, before][, after]
item 必需的。任意类型的表达式,指定要添加到集合中的成员。
key 可选的。唯一字符串表达式,指定可以使用的键字符串,代替位置索引来访问集合中的成员。
before/after 可选的。表达式,指定集合中的相对位置。
下面语句向集合增加一个对象TextBox1,并定义该成员的关键字为tx1。
col.Add TextBox1, "tx1"
然后,下面两句都可以向集合中增加一个TextBox2,并把它放在成员TextBox1的前面。
col.Add TextBox2, , col.Count
col.Add TextBox2, , "tx1"
第一句中,因为只有一个成员,所以col.Count也是索引号
6.使用事件的WithEvents变量
WithEvents不是一个单独的语句,为了使用对象的事件,需要在声明该对象时使用WithEvents关键字。例如:
Dim WithEvents app As Application
将上面的语句写入ThisWorkBook的模块,可以看到在通用框中出现了一个变量app:
在通用框选择app后,左边的声明框便会显示app的事件。
需 要注意的是,使用WithEvents只是声明了对象变量,而并不实际生成对象,为了生成真实的对象,你仍然需要在明后向生成其它对象一样,使用Set 语句进行指定。此外,WithEvents变量不能是通用类变量如Object,而必须指定类名,也不能把WithEvents变量声明为As New。不能在标准模块中使用WithEvents。
7.初识类
现在,请打开你的VBE,主菜单-插入-类模块。
插 入了一个类模块,也就建立了一个类。类模块的名字就是类的名字。你现在看到的,她的名字叫“类1”,这是VBA按她姐妹排行给她取的的,是的,VBA一贯 如此,你早就熟悉了这种规则,现在,在标准模块或其它模块中输入Dim …As的时候,提示框中她已经出现了。但我知道,有件事你正耿耿于怀,“类1”,太没个性了,想改成自己要的名字吧。很容易,和你改标准模块的名字一样, 打开属性窗口,看到了吧,第一行就是她的名字,随你的意愿修改吧。
你或许已经注意到,在名字下面,只有一个属性:Instancing, 其值也只有两个选项:Private和PublicNotCreatable。事实上,你完全可以忽略这个Instancing,就象你完全忽略条件编译 指令一样,因为在VBA中我们几乎用不到它们,而只需维持她的默认值即可。至少我是这样认为的,但我给不了您充足的理由,而只是个人的一种狭隘经历。既然 提到了,就简单说明一下:
Instancing属性决定该“类”在其它工程中是否可以被使用。我们知道,标准模块中的Public过程, 可以保存在宏工作簿甚至直接被另一工作簿的工程调用,但类中的代码是不可分割的整体,所以必须整体决定是否允许外用。当Instancing属性设为 Private(默认)时,不允许其它工程访问。当设置为PublicNotCreatable时,只有在自己的工程创建了该类的对象时,其它工程才允许 使用这个对象,注意,仅仅是在本工程中创建的对象,而不能用她在其它工程中创建对象。
隐者已经来到你的身边,透过薄薄的面纱,你似乎已看到她神秘的微笑。站起身来,走过去吧!
四、创建类属性
让我们想一下作为类的使用者时,我们是如何操作对象的属性的,对象属性的操作不外乎读和写两种。当我们要给对象的某个属性赋值时,我们会:
TextBox1.Text=”abc”
当我们要读取对象的属性时,
S= TextBox1.Text
现在,看看作为类的提供者需要怎样做。
我们将“类1”改名为“MyClass”并为它创建一个名称为x的字符型属性。
1.使用Public变量创建类属性
在类模块中写下行代码:
Public x$
是的,就这么简单,通常情况下,只需要这么简单。
2.使用Property过程创建类属性
Private s$
Public Property Get x() As String
x = s
End Property
Public Property Let x(ByVal c As String)
s = c
End Property
我 们可以省去上面默认的Public。但看上去还是有点麻烦哦,不仅需要两个公共过程,而且还要一个辅助的私有变量s和一个参数c。在类模块中, Property过程把对属性的读写分开了,说一下Property过程的工作机制,当标准模块中的代码读取对象的属性时,便会触发存在的 Property Get过程,或者说Property Get过程提供了属性的读功能,同样,Property Let过程提供了写属性。这样,上面的两个过程(当然在模块中没有先后的要求),可以只有一个,或者虽然两个都有,但却不全是Public,从而提供出去 的属性是只读或只写(呵呵,没见过只写哈)。仅仅是为了提供只读或只写的属性,代码就从一行变成了七行?!这样的理由,你不会信服, VBA中的类通常是提供给我们自己使用的!如果它确实是只读的,我们自觉地去只读就是了!我们使用Property过程还有其它理由,最基本的一条,我们 可以利用这个“过程”来做我们想做的事。看一看:
Public Property Let x(ByVal c As String)
s = Format(c, "0000")
End Property
这里我们只是简单的利用了一下,更多的在后面你会看到。此外,谁会保证有一天你不使用VB给别人提供类呢,这个技术可是通用的。提供一段标准模块的测试代码,来看看我们上面构建的类属性,你自己试试吧。
Sub aTest()
Dim mc As New MyClass
mc.x = "123"
Debug.Print mc.x
End Sub
就象我们给普通变量和对象变量赋值的方式不同一样,对象变量是使用Set赋值的。对“对象”属性,VBA提供了Property Set来代替构建“普通”属性使用的Property Let。来看一段代码:
Private tx As Object
Property Get x() As Object
Set x = tx
End Property
Property Set x(ByVal o As Object)
Set tx = o
End Property
和前面的比较一下,出来多一个Set,实在没有什么不同。
告诉你一个小秘诀,你可以按照Function去记住Property Get的用法,按照Sub去记住Property Let /Set。
3.属性的初始值
我们常常希望,当一个对象建立的时候,它的某些属性会被自动赋予一个初始值,这样,对具有最常见的属性值的对象可以减少重复性的赋值工作。这需要借助于类的构建函数来完成。
在 类模块代码窗口的“通用”框中点击向下的小三角箭头,选择“Class”,右面声明框中可以看到两个选项,“Initialize”和 “Terminate”,我们对它们应该不陌生,很多对象都有这两个事件,Initialize事件当对象建立时发生,Terminate事件在对象对释 放时发生。由于类是静态存在的,它并不是真正的对象,所以在类模块中,它们通常被称为构建函数和析构函数,或构建过程和析构过程。对它们的理解和你在对象 中的用法并没有什么不同。当一个对象被建立时,构建函数将被首先执行,同样,当对象释放后,将执行析构函数。
下面建立MyClass,属性x初始值为”0001”的全部测试代码:
[类模块MyClass的代码]
Option Explicit
Private s$
Public Property Get x() As String
x = s
End Property
Public Property Let x(ByVal c As String)
s = c
End Property
Private Sub Class_Initialize()
s = "0001"
End Sub
[标准模块1的代码]
Option Explicit
Sub aTest()
Dim mc As New MyClass
Debug.Print mc.x
End Sub
隐者为你揭开了第一层面纱,你隐约已看到她美丽的面厐,虽然还不是很清晰,但你知道,早晚会的。
五、创建类方法
放松一下,请拿出你家的紫砂壶,泡上一壶好茶,听我给你将类的方法的故事,你的茶品完了,我的故事也差不多讲完了。
1.构建类的方法其实就是在类模块中写公共的Sub和Function
现在我们给前面提到的MyClass创建一个方法PutIntoActiveCell,功能是将x属性值写入活动单元格。
Public x$
Sub PutIntoActiveCell()
ActiveCell = x
End Sub
在标准模块中用下面的代码测试一下:
Sub aTest()
Dim mc As New MyClass
mc.x = "abc"
mc.PutIntoActiveCell
End Sub
这是本回要告诉你的全部吗?你还没有开始品茶吧?就这样了结束?这是最重要和基本的,但却不是全部。
你是否有一种感觉,但你不能清楚地说出来? 端起你可爱的茶杯,品一口茶,我们继续。
2.类的方法环境
借用广为众知的一个名词“数据环境”,虽然不准确,但我实在想不出更好的称谓来代替,姑且这么叫吧。稍后你就知道它的含义。
类 可以象VBA提供给我们的很多标准类一样风光无限,所有的程序设计者都在工程中使用它,但更多时候,我们所构建的类只在特定的环境下被使用,类的方法环境 是指包括类所在工程的其它成员在内的,可以调用的资源的集合。工作簿、工作表、窗体或其它,在类模块中,你可以象在标准模块中一样操作它们,千万不要因为 换成了类模块而产生任何疑虑,作为类的创建者,你要让类模块中的代码象你在标准模块中一样亲近它们,只要你认为必要。脱离了方法环境的、谨小慎微的、封闭 的类实在没有什么意义。如果你预期方法环境在运行时可能会有变化,你要事先预知它们并象在标准模块中一样使用恰当的措施,比如你不能确定运行时活动工作表 的名称(但你确定届时会是一个工作表),你可以使用ActiveSheet。
我反复说“和标准模块一样”,就是想告诉你在类模块中创建方法时,对工程中其它成员的操作,和你已经熟悉的标准模块中的方式的实在没有什么不同,这一原则适用于类模块中所有代码(也许叫代码环境更准确些),而不仅仅是构建方法的代码。
现在,你知道了,你刚才的感觉到的是开放的方法环境。是的,以后你会更深地体会到,作为好的提供者,开放的思维有多重要。
3.方法的兄弟—成员事件
类 方法的执行需要在代码中以显性的方式指定,象上面的mc.PutIntoActiveCell,有时候,当最终操作者触发类对象成员(属性)的某个事件, 需要在事件发生时产生一系列的操作,这时,我们要运用成员事件。成员事件和方法都是类提供的一系列代码的操作,倆兄弟的区别在于,成员事件无法也不必再由 代码显性调用。
我们来看一个具有普遍意义的事例。
[重要例]
窗体UserForm1上有5个 CommandButton控件(名称分别为默认CommandButton 1- CommandButton 5)和1个TextBox控件(名称为TextBox1)。要求当各个CommandButton控件被点击时,它的按钮文字(Caption)会写入 TextBox1。
如果不用类,我们需要为5个CommandButton控件分别写5个相同的Click事件代码。如:
Private Sub CommandButton 1_Click()
TextBox1 = CommandButton 1.Caption
End Sub
下面是用类的成员事件方法的代码:
‘类模块Cmds的代码
Option Explicit
Public WithEvents cmd As CommandButton
Private Sub cmd_Click()
UserForm1.TextBox1 = cmd.Caption
End Sub
‘窗体UserForm1的代码
Option Explicit
Dim co As New Collection
Private Sub UserForm_Initialize()
Dim i%
Dim myc As Cmds
For i = 1 To 5
Set myc = New Cmds
Set myc.cmd = Me.Controls("CommandButton" & i)
co.Add myc
Next i
Set myc = Nothing
End Sub
仔 细玩味上例的每一行代码,直至品完你壶中的茶。呵呵,因为它实在很有用。最后提一下Friend关键字,虽然在VBA中几乎没有什么用,但如果有一天你要 制作ActiveX部件,可能会用到它。之所以要有Friend关键字,是因为类的私有部分在类模块外是不可见的,但有时却需要从外面访问这些私有部分, 这时,可以使用Friend关键字使属性和方法成为“友元成员”。友元成员在本工程中相当于Public,但在工程外,它仍是Private 。
隐者为你揭去了第二层面纱,你几乎已看清她美丽的面庞,她带着甜蜜的微笑,似乎在问:什么才是最美的期待?
六、创建类事件
在VBA中,因为我们既是提供者,也是使用者,所以通过良好地构建类的属性和方法,已可以满足我们需要全部的要求。我不再 去解释这个观点,在本回后你自然会明白。从这个意义上讲,创建类事件实在没有必要。唯一的遗憾是,我们没有体会到作为创建者的全部乐趣,标准类给我们提供 了各种事件,当然希望自己也可以做到,想象中这应当是一件激动人心的事,所以,追求快乐是创建类事件的重要理由,另一个理由,前面已经提到。
回到前面我们的MyClass类,我们将x属性改名为Value属性,虽然对属性、方法以及事件的命名,VBA没有特别的限制,但建议您不要象我前面那样,随便取一个x,可能的话,要尽量和标准类的成员(属性、方法以及事件)名称相一致。
现在我们为“使用”者提供一个“Change”事件,不错,我们给它取名为“Change”,而不再是随意的“y”或其它(虽然也可以),这样,我也不用解释这个事件的用意了,呵呵。为了做到这一点,看看我们应该做什么。
1.第一步:使用Event语句声明事件
看一下类模块中现在的代码:
Option Explicit
Public Event Change(ByRef Cancel As Boolean)
Private s$
Public Property Get Value() As String
Value = s
End Property
Public Property Let Value(ByVal c As String)
s = c
End Property
Private Sub Class_Initialize()
s = "abc" ‘初始值
End Sub
和前面的代码比较,多出了一句:
Public Event Change(ByRef Cancel As Boolean)
这就是Event语句,只此一句,我们已经为我们的类声明(我想使用“注册”一词是不是更妥切)了一个事Change。在看Event语句产生的效果前,先来看它的特性:
(1)为了声明事件,Event总是Public的,这好理解吧。
(2) 事件可以不带参数,如Public Event Change(),也可以带参数,如我们上面给出的,但参数不能是命名参数,可选参数或数组参数。这里我只解释一下命名参数的含义。我们知道,事件可以因 特定的用户事件触发,也可以在代码中象方法一样指定执行,如下面的CommandButton1_Click:
Private Sub CommandButton2_Click()
CommandButton1_Click
End Sub
但在调用对象的方法时我们通常喜欢这样的方式:
Selection.Sort Key1:=Range("A2"), Order1:=xlAscending
这里Key1、Order1就是命名参数,命名参数的好处是我们不必记住它们的次序,调用时直接以名称和冒号后加等于号指定它的值,但对事件的调用却不允许这样。
(3)事件没有返回值。
现在我们看一下,Event为我们做了什么。
建 立一窗体UserForm1,添加一个TextBox控件(名称为TextBox1),两个CommandButton控件(名称为 CommandButton1和CommandButton2),CommandButton1的Caption设置为“赋值”, CommandButton2的Caption设置为“读值”,窗体的代码如下:
Option Explicit
Dim WithEvents mc As MyClass
Private Sub CommandButton1_Click()
mc.Value = TextBox1 ’赋值
End Sub
Private Sub CommandButton2_Click()
MsgBox "mc当前的值为" & mc.Value ’读值
End Sub
Private Sub UserForm_Initialize()
Set mc = New MyClass
End Sub
上面这段代码实现的是,当点击CommandButton1时便会将TextBox1的值赋给mc的Value,当点击ommandButton2时便会显示mc当前的Value值。
来运行一下这个窗体,先点击CommandButton2,此时显示“abc”,是mc的初始值,然后在TextBox1输入“123”,点击CommandButton1,再点击CommandButton2,显示“123”,说明赋值成功了。
呵呵,忘了,我们要做什么了!现在,请从UserForm1代码窗口的“通用”框中选择mc,哇!我们声明的事件在右边“声明”框中已经出现了!
我们定义这个事件是希望当mc的值改变时响应的,现在就迫不及待地给它写一句代码吧:
Private Sub mc_Change(ByRef Cancel As Boolean)
If MsgBox("要改变mc的值吗?", vbYesNo) = vbNo Then Cancel = True
End Sub
上面这句代码你不会陌生吧,希望当用户选择了在改变时给用户一个确认的机会。但是,现在点击CommandButton1,却不会给你选择的机会,我们还有一步没有做。
2.第二步:使用RaiseEvent语句引发事件
声明了事件后,我们要做的,便是找到所有与事件发生关联的地方,使用RaiseEvents语句引发事件,这里引发的含义相当于Call,就是调用用户在事件中写的代码。在本例中,只有一个地方,就是Property Let Value过程中:
Dim chyn As Boolean
RaiseEvent Change(chyn)
If chyn Then Exit Property
通过传递回的chyn,决定是否执行后面的赋值语句。下面就是添加了RaiseEvents语句后的类模块的代码:
Option Explicit
Public Event Change(ByRef Cancel As Boolean)
Private s$
Public Property Get Value() As String
Value = s
End Property
Public Property Let Value(ByVal c As String)
Dim chyn As Boolean
RaiseEvent Change(chyn)
If chyn Then Exit Property
s = c
End Property
Private Sub Class_Initialize()
s = "abc"
End Sub
现在你可以去运行你的窗体了,我们要的效果应该是达到了吧。为了便于你调试,下面给出窗体的全部代码:
Option Explicit
Dim WithEvents mc As MyClass
Private Sub CommandButton1_Click()
mc.Value = TextBox1 ’赋值
End Sub
Private Sub CommandButton2_Click()
MsgBox "mc当前的值为" & mc.Value ’读值
End Sub
Private Sub UserForm_Initialize()
Set mc = New MyClass
End Sub
Private Sub mc_Change(ByRef Cancel As Boolean)
If MsgBox("要改变mc的值吗?", vbYesNo) = vbNo Then Cancel = True
End Sub
当然,我们可以把上面mc_Change的代码要做的直接在Property Let Value过程的代码中,从而不使用事件。这就是在本回的开头说的。
事件的构建已经完成,说了这么多,其实你只要记住两步的标题就可以了。到这里,关于VBA类最基本最重要的部分已经给朋友们介绍完了。余下的,留着您在未来的探索路上慢慢体会吧,也请您不要忘了和大家分享您的喜悦。
隐者已向你展示了她所有的秘密,铅华去尽,只有美丽!
七、一个完整的类实例
现在,提供一道简单的测试,帮大家回顾一下前面的知识。建议您做一下,因为VBA是实践性的。
题目要求:
(一)构建两个类:
1.Student类
具有2个属性:
(1)Name:可读写。
(2)Id:可读写,但只能写一次。格式为字母S加两位整数,如S01,S02…等。
2.Students类
具有1个属性,3个方法,2个事件:
(1)Count属性:只读,返回Student成员数量。
(2)Item方法:使用下标如Stus.Item(i)的方式调用,返回相应的Student成员,i可以是Student成员的自然顺序,也可以是Student成员的Id。
(3)Add方法:增加Student成员。当增加成员时,按顺序递增生成成员的Id,每个Id号只用一次,不因删除成员受影响。
(4)Remove方法:删除Student成员。
(5)BeforeAdd事件:在增加成员前作出响应,允许用户取消增加成员。
(6)AfterRemove事件:在删除成员后响应。
(二)构建一个用户窗体测试前面的类:
1.窗体上包含四个CommandButton,分别完成如下功能:
(1)增加成员
使用InputBox输入Student成员的Name,完成增加。
(2)删除成员
使用InputBox输入Student成员的Id或自然序号,完成删除。
(3)显示学员总数。
使用MsgBox显示Student成员总数。
(4)查询学员
使用InputBox输入Student成员的Id或自然序号,然后使用MsgBox显示相应Student成员的Name。
2.一个ListView,即时显示现有的所有Student成员。
3.事件处理
(1)BeforeAdd事件,查找现有成员的Name是否有和要增加的成员的Name相同的,如有,则给出提示,让用户选择是否增加。
(2)AfterRemove事件,刷新ListView显示
(三)未尽之处自由发挥,假定用户操作规范,可不考虑错误处理。
八、未完的结尾
当初构思这篇文章时想说的话已经想不起来了,现在最想说的是感谢,感谢所有关注这个贴子的朋友,感谢朋友们在我写作过程中给予的鼓励,感谢taller兄的特别鼓励。成文仓促水平所限,错漏之处请朋友们予以补充指正吧。
关于Excel的隐藏名称空间
这个被隐藏的名称空间是一个属于当前Excel实例的内存区域,该区域加载项DLLs(“XLLS”)能存储临时的名称。使用这个区域,即使没有可利用的宏工作表可用,XLLs也能定义名称。
隐藏着的名称操作基于XLM函数SET.NAME、GET.NAME和EVALUATE,而且当在XLM宏工作表中使用时,SET.NAME定义一个标准的工作表级名称,同时,当它在XLL中被调用时,创建一个应用程序级名称并且存储在一个被隐藏的区域中。
定义在被隐西藏域的名称有一些特殊的功能,这使得它们与标准工作簿名称有很大的不同。这些功能将在后面描述。
与隐藏着的名称相关的可用C API命令如下:
Excel4(xlfSetName,&xResult,2,&xName,&xValue),定义包含xValue的名称xlName。
Excel4(xlfGetName,&xResult,1,&xName),获取xlName的定义(例如”=1”)并将它存储在xResult中。
Excel4(xlfEvaluate,&xResult,1,&xName),获取xlName的内容(例如:1)并将它存储在xResult中。
Excel4(xlfSetName,&xResult,1,&xName),删除xName(忽略第二个参数)。
在VBA中访问被隐藏的名称空间
(1) 创建一个隐藏的名称
下面的语句创建一个包含字符串“OK”的名为Test的隐藏名称:
Application.ExecuteExcel4Macro "SET.NAME(""Test"",""OK"")"
(2) 获取一个隐藏名称所代表的内容
为了获取名称Test所代表的内容,使用下面的代码:
TestVal = Application.ExecuteExcel4Macro("Test")
注意,只使用名称本身作为ExecuteExcel4Macro的参数。
(3) 删除一个隐藏的名称
为了删除名称Test,使用下面的语句:
Application.ExecuteExcel4Macro "SET.NAME(""Test"")"
注意,忽略了SET.NAME中的第二个参数。
被隐藏的名称空间的特征
在被隐藏的名称空间中所定义的名称的主要特征是:它们不属于任何工作簿,而属于应用程序本身,这意味着:
(1)在Excel中的任何地方可以直接访问这些名称。无论在哪个工作簿中创建了这种名称,在任何工作簿中的任何VBA模块、工作表或宏工作表(和任何DLL加载项)中直接都能直接读取和修改它们。
(2) 它们的“生存时间”与当前Excel会话一致
如 果在工作簿Wbk1.xls中的某个VBA模块中创建了一个名称,然后关闭了这个工作簿,那么该名称仍然存储在被隐藏的命名空间中。如果接着打开另一个工 作簿Wbk2.xls,那么这个工作簿的VBA过程仍能获取和修改刚才在Wbk1.xls中所创建的名称。在没有被任何VBA加载项所限制的情况下,在被 隐藏的名称空间中所定义的名称能作为永久的“公共变量”访问。
对于这些属于应用程序的隐藏的名称,关闭所有工作簿和加载项不会销毁它们。通过对SET.NAME(没有第二个参数)明确的调用或者退出并重启Excel,才能销毁它们。在这种情况下,这些名称能作为一种Excel的环境变量来使用。
(3) 它们是“完全隐藏着的”
当一个受保护的加载项使用这个隐藏的名称空间时,新的名称不能被任何其它的VBA模块读取,也不能被用户读取,除非他们知道名称空间的身份证书。没有方法去“列出”被定义在隐藏命名空间中的名称。
这些名称无须与标准的隐藏名称(即将工作簿或工作表名称的.Visible属性设置为False)相混淆。标准的工作簿级的名称决不会真正的被隐藏,因为它们能通过使用Application.Names集合的任何VBA过程来获取和修改,如下面的代码所示:
Dim CName As Name
  For Each CName In Workbooks("Wbks1.xls").Names
    If CName.Hidden Then
        MsgBox CName.Name & " deleted"
        CName.Delete
    End If
  Next CName
上面的代码在工作簿Wbks1.xls所有隐藏的名称中循环并删除它们,但是这些代码不能发现存储在被隐藏的命名空间中的名称,因为这些名称不属于Application.Names集合,因此,它们被保护以反对任何恶意的访问或修改。
示例
下面的代码演示了在受保护的VBA加载项中隐藏着的命名空间的可能的用途。
它限制用户在相同的Excel会话中执行加载项主过程超过3次。允许剩余执行次数的计数器没有存储在模块级的变量中,也没有存储在依赖加载项的名称中,而是存储在隐藏的命名空间里。通过排除了传统方法的下列缺点,隐藏的命名空间阻止了用户能够中止保护。
(1) 像所有变量一样,存储在VBA中的计数器变量能在VBE中手动清除。
(2) 同样的方式,任何外部过程通过在加载项的Names集合中循环,都可以读取、修改和可能删除加载项中所有隐藏的或未隐藏的工作簿名称。
但是,隐藏的命名空间避免了这些危险。它也比使用基于环境字符串的实例、临时文件或注册进入等方法更简单,而且隐藏的命名空间是永久的,用户能关闭和重新打开该工作簿而无须重新设置这个计数器。
在这个代码中,函数SetHName、GetHName和DelHName可以创建、获取和删除隐藏的名称,而不需要直接使用冗长的Application.ExecuteExcel4Macro方法。
代码清单如下:
Sub Main()
  Application.EnableCancelKey = xlDisabled
  Dim Count
  Count = GetHName("TswbkCount")
  If IsError(Count) Then
    SetHName "TswbkCount", 3
  ElseIf Count = 1 Then
    MsgBox "Macro disabled. You must restart Excel.", vbInformation
  Else
    SetHName "TswbkCount", Count - 1
  End If
End Sub
Sub SetHName(Name As String, Value)
  Application.ExecuteExcel4Macro _
    "SET.NAME(""" & Name & """," & Value & ")"
End Sub
Function GetHName(Name As String)
  GetHName = Application.ExecuteExcel4Macro(Name)
End Function
Sub DelHName(Name As String)
  Application.ExecuteExcel4Macro "SET.NAME(""" & Name & """)"
End Sub
注:本文翻译整理自Chip Pearson的文章《Hidden Name Space In Excel》,略作修改。
VBA类虽然留给了我们一些遗憾,比如不能构建隐藏成员、默认成员,也不能创建列举属性,但并不影响它作为VBA的一项非常有用技术。本文期望的,是把您带入这个大门,不知做到了没有,对它的进一步探讨,以后有机会再和朋友们一起学习吧。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
vba基础 个人学习详细笔记 知识点梳理
Excel VBA编程教程(基础一)
VBA操作要点
完全手册Excel VBA典型实例大全:通过368个例子掌握
VBA语法概述
Access教程 第八章 使用VBA编程
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服