【数据分析】Excel中使用VBA进行宏编程

1 篇文章 0 订阅

目录

Microsoft Excel是广泛用于数据处理和分析的工具,而VBA(Visual Basic for Applications)是一种基于Microsoft Visual Basic的编程语言,可用于在Excel中创建自定义宏和自动化任务,控制Excel工作簿、工作表和数据,执行各种操作。这篇博客将介绍如何使用VBA进行Excel宏编程,以提高工作效率和自动化重复任务。

0 准备工作

  1. Excel中主选项卡默认是没有开发工具选项的,需要选择主选项卡文件→选项→自定义功能区,打开Excel中的开发工具面板:
    在这里插入图片描述
    这样,在Excel的主选项卡中就有开发工具选项了:
    在这里插入图片描述

  2. 在开发工具选项卡中选择Visual Basic,进入VBA编辑器(或使用快捷键Alt+F11):
    在这里插入图片描述

  3. 创建新的模块(Module)并开始编写第一个宏:
    在这里插入图片描述

  4. 如,在Excel中显示消息框:

    Sub 显示消息框()
        MsgBox "欢迎使用VBA宏编程!"
    End Sub
    

    在这里插入图片描述

  5. 编写完成后,保存,使用F5运行或者在开发工具选项卡中选择“运行”来执行宏:
    在这里插入图片描述

  6. 运行结果:
    在这里插入图片描述

1 VBA简介

1.1 Excel VBA应用程序的构成

从开发者角度看,Excel VBA应用程序由工作表、用户窗体、模块和类模块等部分构成。

  • 工作表:用于保存和显示程序的数据,是程序的主体部分。一般先在工作表中制作出特定表格的格式,并设置好样式,再通过VBA代码获取表格中的数据,经过加工处理后将其填写入相应的单元格,供用户进行查看、打印输出等操作。
  • 用户窗体:在Excel VBA应用程序中,除了可使用工作表与用户进行交互外,还可向程序添加用户窗体,用来与用户进行交互操作。使用用户窗体可将用户与工作表中的数据进行隔离,防止数据被意外修改并隐藏工作表中的敏感数据,使限制权限的用户只看到应该操作的数据。
  • 模块:在模块中可保存程序的通用过程,供其他过程调用。例如录制宏的代码就保存在模块中。
  • 类模块:在Excel VBA中,除了可使用系统提供的对象外,还可通过自定义类来创建自定义的对象,自定义的类必须保存在“类模块”中。大多数应用程序都不使用“类模块”。

1.2 事件驱动

VBA 是运行在 Microsoft Office 软件之上,包括 Excel、Word、PPT、Outlook 等,可以用来编写非软件自带的功能的编程语言,不同的是每一个软件具有自己独有的对象,例如 Excel 有单元格对象,Word 有段落对象,PPT 有幻灯片对象。

Office 软件提供丰富的功能接口,VBA 可以调用它们,实现自定义的需求。基本上,能用鼠标和键盘能做的事情,VBA 都能做,它采用了与Windows相似的事件驱动编程方式。

在这种模式下,Windows监视窗口活动或事件信号,这些事件可以是用户鼠标点击或按键操作,也可以是程序控制或其他窗口的操作引发的。通过VBA的事件驱动机制,开发人员可以编写事件过程来处理系统产生的事件,实现特定的功能,如在鼠标点击时打开一个窗口。

与传统的过程化应用程序不同,事件驱动的应用程序中,代码执行路径不是按照预定顺序进行的。相反,代码在响应不同事件时执行不同的片段。这些事件可以由用户、操作系统、其他应用程序的消息触发,甚至是应用程序自身的消息触发。

由于事件顺序无法预测,代码必须对各种执行状态做出假设。为确保这些假设在执行时有效,应用程序的结构需要组织良好。在Excel中使用VBA开发应用程序实质上是编写处理各对象不同事件的代码。

1.3 宏

简单的说,宏就是一段可以运行的 VBA 代码片段。Excel宏使用VBA语言进行编写,通过VBA编写的宏可控制Excel,对Excel的功能进行扩充。

1.3.1 创建宏

Excel提供了两种创建宏的方法:一种方法是利用Excel操作环境中的宏录制器录制用户的操作;另一种方法是使用Visual Basic编辑器编写自己的宏代码。

利用宏录制器可记录用户在Excel中的操作动作,以便自动创建需要的宏,这在不太了解宏命令时是非常方便的。

使用Visual Basic编辑器可以打开已录制的宏,修改其中的命令,也可以在Visual Basic编辑器中直接输入命令创建宏。对于很多无法录制的命令(如创建新的窗体等),使用Visual Basic编辑器创建宏是唯一的方法。

启用录制宏的两种方式:

在这里插入图片描述

在这里插入图片描述

注:宏名不但可以包含字母、数字和下画线,还可以使用中文,但不能包含空格。但名称开头需为字母或下划线,且不能与已有宏重名。

在这里插入图片描述
注:在宏中定义的快捷键将覆盖任何对等的默认的Excel快捷键。

在创建宏之后,可以将宏分配给对象(如按钮、图形、控件和快捷键等),这样执行宏就像单击按钮或按快捷键一样简单。正是由于这种操作方便的特性,使用宏可以方便地扩展Excel的功能。如果不再需要使用宏,可以将其删除。

如果将录制的宏名称设置为“Auto_Open”,则每次打开包含此宏的工作簿时,该宏都会运行。另一种在打开工作簿时自动运行宏的方法是使用Visual Basic编辑器(VBE)在工作簿的Open事件中编写VBA过程。Open事件是一个内置的工作簿事件,它会在每次打开该工作簿时都运行自己的宏代码。如在用户打开工作簿时显示一个“欢迎”对话框,可新建一个模块,在其中输入以下代码:

        Sub Auto_Open()
          MsgBox ("欢迎使用Excel!")
        End Sub

保存并关闭该文件,若提示不能保存,则选择“否”后,将文件保存为.xlsm类型即可。
在这里插入图片描述
再次打开该文件时,将会显示“欢迎使用Excel!”的对话框。

1.3.2 宏安全

从Office软件支持宏开始,宏病毒也随之出现。许多病毒经过专门设计,可以利用VBA宏对系统和数据文件进行恶意操作。因此,宏的安全性越来越受到用户的重视。

按照如下步骤设置宏安全性:

  1. Excel选项卡选择文件→选项→信任中心或者选项卡选择开发工具→宏安全
    在这里插入图片描述
  2. 设置宏的安全性为禁用所有宏,并发出通知
    在这里插入图片描述
    四种类型的宏设置:
  • “禁用所有宏,并且不通知”:如果不信任宏,则使用此设置。文档中的所有宏及有关宏的安全警报都被禁用。如果文档具有信任的未签名的宏,则可以将这些文档放在受信任的位置上。受信任的位置中的文档可直接运行,不会由信任中心安全系统进行检查。
  • “禁用所有宏,并发出通知”:这是默认设置。如果想禁用宏,但又希望在存在宏时收到安全警报,则应使用此选项。这样,可以根据具体情况选择何时启用这些宏。
  • “禁用无数字签署的所有宏”:此设置与“禁用所有宏,并发出通知”选项相同。但下面这种情况除外:在宏已由受信任的发行者进行了数字签名时,如果信任发行者,则可以运行宏;如果不信任发行者,将发出通知。这样,可以选择启用那些签名的宏或信任发行者的宏。所有未签名的宏都被禁用,且不发出通知。
  • “启用所有宏(不推荐,可能会运行有潜在危险的代码)”:可以暂时使用此设置,以便允许运行所有宏。因为此设置容易使计算机受到恶意代码的攻击,所以不建议永久使用此设置。

2 VBA基础

2.1 注释

VBA 中的注释以英文单引号 ' 开头,后面接注释的内容。从单引号开始的部分不会被执行。

'我是一行注释

2.2 数据类型

Excel单元格中可以保存处理多种类型的数据,包括数值、日期/时间、文本、货币等。VBA中除了提供这些数据类型之外,还提供字节、布尔和变体数据等类型。

2.2.1 基本数据类型

常见的VBA基本数据类型如下表:

数据类型含义精度范围
Byte字节型0 ~ 255
Integer整型-32,768 ~ 32,767
Long长整型-2,147,483,648 ~ 2,147,483,647
String字符串变长字符串:0 ~ 20亿(231)个字符
定长字符串:1~约64K(216)个字符
Single单精度浮点型在表示负数时: -3.402823E38 ~ -1.401298E-45
在表示正数时: 1.401298E-45 ~ 3.402823E38
Double双精度浮点型在表示负数时: -1.79769313486231E308 ~ -4.94065645841247E-324
在表示正数时: 4.94065645841247E-324 ~ 1.79769313486231E308
Decimal定点数未放置定点数: +/- 79,228,162,514,264,337,593,543,950,335
放置定点数: +/- 7.9228162514264337593543950335
Currency货币型-922,337,203,685,477.5808 ~ 922,337,203,685,477.5807
Date日期型时间:00:00:00 至 23:59:59
日期: 100-1-1 至 9999-12-31
Boolean逻辑值True 或 False
Variant变体型不限
Object对象型VBA 和 Excel 对象

2.2.2 枚举类型

枚举就是将变量的值逐一列举出来,属于该枚举型的变量只能取列举的某一个值。当一个变量只有几种可能的值时,可以定义为枚举类型(Enum)。

枚举型举例:

Public Enum WorkDays
    星期日
    星期一
    星期二
    星期三
    星期四
    星期五
    星期六
End Enum

2.2.3 用户自定义数据类型

在VBA中,还可以使用Type语句定义自己的数据类型,其格式如下:

Type 数据类型名
    数据类型元素名 As 数据类型
    数据类型元素名 As 数据类型
    ...
End Type

注,自定义数据类型的定义必须放在模块(模块和类模块)的声明部分中。在使用记录类型之前,必须用Type语句进行定义。一般情况下,记录类型在模块中定义,其变量可以出现在工程的任何地方。

2.2 变量

变量是存储数据的一种表达方式。在程序开始,可以声明一个变量,指定变量的类型(数字、文本、逻辑值等),并给变量赋值。在程序的其他地方,就可以使用该变量,用其存储的值参与运算。

2.2.1 声明变量

使用变量前需要先声明变量,即通过以下格式指定变量名和变量的数据类型:

Dim [变量名] As [数据类型]

2.2.2 命名变量

在VBA中,变量名称为标识符,定义标识符名称时应遵循以下规则:

  • 第一个字符必须使用英文字母(中文版Excel支持中文字符作标识符名称)。
  • 不能在标识符中使用空格、句点(.)、感叹号(!),或者@、&、$、#等字符。
  • 标识符的长度不能超过255个字符。
  • 通常,标识符不能与VBA本身的Function过程、语句及方法的名称相同。必须谨慎使用与程序语言的关键字相同的名称。若所使用的内在语言函数、语句或方法与所指定的名称相冲突,则必须显式地识别它。一般会在内置函数、语句或方法的名称之前加上关联的类型库的名称。例如,如果有一个名为Left的变量,则只能用VBA.Left来调用Left函数。
  • 不能在同一范围的相同层次中使用重复的名称。例如,不能在同一过程中声明两个命名为age的变量。但可以在同一模块中声明一个私有的命名为age的变量和过程级别的命名为age的变量。

在实际开发过程,建议按驼峰法命名。即第一个单词以小写字母开始;从第二个单词开始以后的每个单词的首字母都采用大写字母。如:myFirstName、myLastName。

2.3 常量

在程序运行过程中值不发生变化的量称为常数(又称常量),常数的值在程序执行之前就已经确定,执行过程中不能改变。由于其值的数据类型不同,常量也具有数据类型。VBA中常量的类型有三种,分别是直接常数、符号常数和系统常数。

2.3.1 直接常数

在VBA程序代码中直接书写的量称为直接常数,如:

area = r * r * 3.14

上述代码中的数值3.14就是直接常数。

直接常数也有数据类型的区别,其数据类型由它本身所表示的数据形式决定。根据数据类型的不同,直接常数分为数值常数、字符串常数、日期/时间常数和布尔常数。

  • 数值常数:数值常数是由数字、小数点和正负符号所构成的量。一个数值常数有时可能存在多种数据类型的解释。例如6.18可解释为单精度型,也可为双精度型。VBA将使用占用内存少的那种类型,即单精度型。
  • 字符串常数:字符串常数是由数字、英文字母、特殊符号和汉字等可见字符构成的,在书写时必须使用双引号作定界符,如:"VBA"。如果字符串常数中包含双引号,则需要在有双引号的地方输入两次双引号,如:"Milko说:""VBA程序很简单!"""注意最后用了3个双引号,前2个双引号将输出为一个引号,最后1个双引号为字符串的定界符。
  • 日期/时间常数:日期/时间常数用来表示某一天或某一具体时间,使用#作为定界符,日期时间的意义要正确,例如#8/8/2008#是正确的,而#2/30/2008#是错误的,因为二月份没有30日。
  • 布尔常数:布尔常数也称为逻辑常数,只有两个值:True(真),False(假)。

2.3.2 符号常数

如果在程序中需反复地使用某一个常数,可为该常数命名,在需要使用该常数的地方引用其常数名则可。

使用符号常数有如下优点:

  • 提高程序可读性。符号常数是有意义的名字,可提高程序的可读性。
  • 快速修改程序。如果需要在程序中修改常数的具体值,只需要在定义符号常数处修改即可。
  • 减少出错率。如果反复在程序中输入同一数据,有可能在某处输入错误,导致计算结果不同,不好查错。使用符号常数则只需要定义一次就可引用。

在程序运行时,不能对符号常数进行赋值和修改,因此符号常数在程序运行前必须有确定的值。

声明符号常数与声明变量类似,需要指明符号常数名和数据类型。不同在于,符号常数的值在声明时就需要指定。

声明符号常数的格式如下:

Const [常量名] As [数据类型] = [值]

2.3.3 系统常数

系统常数就是VBA系统内部提供的一系列各种不同用途的符号常数。例如,色彩常数用“vbBlack”表示黑色,比用数值“0x0”更直观易用。这些常数可与应用程序的对象、方法和属性一起使用。

在VBA中,系统常数名采用大小写混合的格式,其前缀表示定义常数的对象库名。在Excel中的系统常数名通常都是以小写的xl(如xlWindowType的成员包括xlWorkbook等几个)作为前缀,而VB中的系统常数名通常都是以小写的vb作为前缀。要查询某个系统常数的具体名称及其确切值,可通过视图→对象浏览器查询。

在这里插入图片描述

2.4 运算符

运算符是介于操作数间的运算符号,VBA 中运算符可以分为赋值运算符、算术运算符、比较运算符、逻辑运算符、连接运算符和其他运算符这 6 类。

2.4.1 赋值运算符

给变量赋值会使用到赋值运算符,语法如下:

[变量名] = [数据]

赋值语法核心是=(赋值运算符),它是右结合的,阅读时从右至左阅读时从右至左,即将数据的值赋值给变量。

其中,[数据]的形式可以是直接书写的内容,例如 4、“好”、True等;也可以是另外一个变量。

变量赋值完成后,在后续的程序中,就可以使用变量参与各类的计算了。

2.4.2 算术运算符

算数运算符即常用的数学运算符,包括加减乘除等。

VBA 中的算术运算符如下表所示。

算术运算符含义
+两数相加
-两数相减 或 表示数值表达式的负值
*两数相乘
/两数相除
\两数相除,取整
Mod两数相除,取余
^幂运算

2.4.3 比较运算符

比较运算符用来表示两个或多个值或表达式之间的关系,比较结果只能为True或False。

VBA 中的比较运算符如下表所示。

比较运算符含义
=比较两个值是否相等
>大于
>=大于等于
<小于
<=小于等于
<>不等于

2.4.4 逻辑运算符

逻辑运算符是指连接表达式进行逻辑运算的运算符,逻辑运算结果只有True和False两种可能。

逻辑运算符含义
And逻辑与,若两个表达式都为真,则返回 True
Or逻辑或,若两个表达式至少有一个为真,则返回 True
Not逻辑非,对逻辑表达式取否
Xor逻辑异或,若两个表达式不相同,则返回 True
Eqv逻辑等价,若两个表达式两在逻辑上等效,则返回 True
Imp逻辑蕴含,若第一个表达式蕴含第二个表达式,则返回 True

2.4.5 连接运算符

VBA 中的连接运算符用于连接 2 个或多个文本。

连接运算符含义
&强制两个表达式做字符串连接。如果表达式的结果不是字符串,则将其转换成字符串
+用于两个字符串的连接,只有运算符两边的表达式都是字符串时才进行连接运算

注:在使用“+”运算符时有可能无法确定是做加法还是做字符串连接。因此,为避免混淆,一般使用“&”运算符进行连接。

2.4.6 其他运算符

其他运算符含义
_将一行代码分解成两行
:将两行代码放置在一行

2.5 常用语句

2.5.1 语句

一行代码就是一条语句。在默认情况下,在VBA编辑器中输入语句后,VBA编辑器将自动进行语法检查。

2.5.1.1 自动格式化

输入VBA语句后,VBA编辑器将按一定的规则进行简单的格式化处理。例如将关键字的首字母大写,在运算符前后加入空格,删除各部分多余的空格等。

在输入VBA关键字时,可以不区分大小写。例如,输入MsgBox时,无论输入的是Msgbox、msgbox还是MSGBOX,当输入完该函数的参数并按回车键后,VBA编辑器会自动将其变为MsgBox。

为了提高程序的可读性,VBA代码中应加上适当的空格。当按回车键完成语句的输入后,各关键字之间无论插入多少空格,VBA编辑器都将其自动调整为一个空格。

2.5.1.2 复合语句

一般情况下,要求程序中每个语句占一行。但在VBA中,也可以把几个语句放在一行中构成复合语句。复合语句中各语句之间用冒号(:)分隔,例如:

	Selection.Font.Bold = True: Selection.Font.Size = 20

与以下两个语句的功能相同:

   Selection.Font.Bold = True
   Selection.Font.Size = 20
2.5.1.3 语句断行

在VBA编辑器的代码窗口中,每行VBA代码可包含1023个字符。但是,为了使程序便于阅读,建议读者将一条长的语句打断为若干行。VBA中使用空格后接着一个下画线——续行符,可将一行代码延伸成两行以上。例如以下语句:

	ActiveWorkbook.Protect Password:=" abc ", Structure:=True, Windows:=False

等价于

    ActiveWorkbook.Protect _
                  Password:="abc", _
                  Structure:=True, _
                  Windows:=False

通过续行符_可创建长的逻辑行。一个逻辑行,最多可包含24个连续的续行字符,也就是最多可以包含25个物理行。这样,逻辑行的字符总量可达10230字符。如果超过了字符总量,必须将该行分为若干语句或指定一些表达式为中间变量。

2.5.2 输入/输出语句

计算机程序一般分为三部分:首先接收用户输入的数据;再按一定的算法对数据进行加工处理;最后输出程序处理的结果。在程序中,输入/输出语句占有很大的比例。在Excel中,可从工作表、用户窗体等多处获取数据,并可将数据输出到这些对象中。数据输入/输出的相关内容将在后续章节中进行介绍,本节主要介绍VBA中标准的输入/输出方法。

2.5.2.1 输入语句

为了实现数据输入,VBA提供了InputBox函数。该函数将打开一个对话框作为输入数据的界面,等待用户输入数据,并返回所输入的内容。语法格式如下:

	InputBox(prompt[,title][,default][,xpos][,ypos][,helpfile,context])

其中,InputBox函数的7个参数意义分别如下:

  • Prompt:对话框消息出现的字符串表达式,最大长度为1 024个字符。如果需要在对话框中显示多行数据,则可在各行之间用回车换行符来分隔,一般使用VBA的常数vbCrLf代表回车换行符。
  • Title:对话框标题栏中的字符串。如果省略该参数,则把应用程序名放入标题栏中。
  • Default:显示在文本框中的字符串。如果省略该参数,则文本框为空。
  • Xpos(和Ypos成对出现):指定对话框的左边与屏幕左边的水平距离。如果省略该参数,则对话框会在水平方向居中。
  • Ypos(和Xpos成对出现):指定对话框的上边与屏幕上边的距离。如果省略该参数,则对话框被放置在屏幕垂直方向距下边界大约三分之一的位置。
  • Helpfile:设置对话框的帮助文件,可省略。
  • Context:设置对话框的帮助主题编号,可省略。

例如,接收用户的输入示例如下:

	Sub 使用InputBox函数()
	  Dim strPrompt As String
	  Dim strTitle As String
	  Dim strDefault As String
	  Dim strReturn As String
	  strPrompt = "请输入用户姓名:"
	  strTitle = "输入对话框"
	  strDefault = "Milko"
	  strReturn = InputBox(strPrompt, strTitle, strDefault)
	  Debug.Print strReturn
	End Sub

运行上述代码,会弹出如下的对话框:
在这里插入图片描述
在文本框中输入新的姓名,单击“确定”按钮,程序将把用户输入的内容输出到“立即窗口”中。

使用InputBox函数时,需注意:

  • 在默认情况下,InputBox函数的返回值是一个字符串类型,而不是变体类型。如果需要使用该函数输入数值,则需要使用Val函数(或其他的转换函数)将返回值转换为相应类型的数值。
  • 如果用户单击“取消”按钮(或按“Esc”键),则表示不使用当前输入的值,函数将返回一个空字符串。根据这一特性,可以判断用户是否把数据输入到对话框中。
  • 执行一次InputBox函数,只能返回一个值,如果需要输入多个值,则必须多次调用该函数。
2.5.2.2 输出语句

1.Print方法

上述接收用户输入的代码中用到了Print方法用于输出信息,信息将被输出到“立即窗口”(视图→立即窗口或使用快捷键Ctrl+G)中。

在这里插入图片描述
Print方法的语法如下:

	object.Print [outputlist]

在VBA中,object只能为Debug对象,表示向“立即窗口”输出内容。

参数outputlist是要打印的表达式或表达式的列表。如果省略,则打印一个空白行。

Print首先计算表达式的值,然后输出计算的结果。在outputlist参数中还可以使用分隔符以格式化输出的数据。格式化分隔符有以下4种。

  • Spc(n):插入n个空格到输出数据之间。
  • Tab(n):移动光标到适当位置,n为移动的列数。
  • 分号:表示前后两个数据项连在一起输出。
  • 逗号:以14个字符为一个输出区,每个数据输出到对应的输出区。

2.MsgBox函数

使用MsgBox函数可打开一个对话框,在对话框中显示一个提示信息,并让用户单击对话框中的按钮,使程序继续执行。MsgBox有语句和函数两种格式,语句格式如下:

	MsgBox prompt[,buttons][,title][,helpfile,context]

函数格式如下:

	Value=MsgBox(prompt[,buttons][,title][ ,helpfile,context])

通过函数返回值获得用户单击的按钮,并可根据按钮的不同而选择不同的程序段来执行。

该函数(或语句)共有5个参数,除第一个参数是对话框展示的提示外,其余参数都可省略。各参数的意义与Inputbox函数参数的意义相同,不同的是多了一个buttons参数。buttons参数用来指定显示按钮的数目及形式、使用提示图标样式、默认按钮,以及消息框的强制响应等。其常数值如下表所示。

常量说明
vbOkOnly0只显示“确定”(Ok)按钮
vbOkCancel1显示“确定”(Ok)及“取消”(Cancel)按钮
vbAbortRetrylgnore2显示“异常终止”(Abort)、“重试”(Retry)及“忽略”(Ignore)按钮
vbYesNoCancel3显示“是”(Yes)“否”(No)及“取消”(Cancel)按钮
vbYesNo4显示“是”(Yes)及“否”(No)按钮
vbRetryCancel5显示“重试”(Retry)及“取消”(Cancel)按钮
vbCritical16显示CriticalMessage图标
vbQuestion32显示WarningQuery图标
vbExclamation48显示WarningMessage图标
vbInformation64显示InformationMessage图标
vbDefaultButton10以第一个按钮为默认按钮
vbDefaultButton2256以第二个按钮为默认按钮
vbDefaultButton3512以第三个按钮为默认按钮
vbDefaultButton4768以第四个按钮为默认按钮
vbApplicationModal0进入该消息框,当前应用程序暂停
vbSystemModal4096进入该消息框,所有应用程序暂停

表中的数值(或常数)可分为4组:

  • 第一组值(0~5):决定对话框中按钮的类型与数量。
  • 第二组值(16,32,48,64):决定对话框中显示的图标。
  • 第三组值(0,256,512,768):设置对话框的默认活动按钮。活动按钮中文字的周围有虚线,按“Enter”键可执行该按钮的单击事件代码。
  • 第四组值(0,4096):决定消息框的强制响应性。

buttons参数可由上面4组数值组成,其组成原则是:从每一类中选择一个值,把这几个值累加在一起就是buttons参数的值(大部分时间里都只使用前三组数值的组合),不同的组合可得到不同的结果。

如询问用户是否退出系统:

    Sub 使用msgbox函数退出系统()
      Dim intReturn As Integer
      intReturn = MsgBox("真的退出系统吗?", vbYesNo + vbQuestion, "提示")
      If intReturn = vbYes Then Application.Quit
    End Sub

运行以上代码,会弹出如下对话框:
在这里插入图片描述
如果用户单击“是”按钮,将执行Quit方法退出Excel;如果用户单击“否”按钮,将返回应用程序。

2.5.3 暂停/退出语句

程序在运行过程中,如果需要查看变量的运算中间值,可在代码中加入暂停语句,使程序暂停运行,然后在立即窗口中查看各变量的值。当满足一定条件时,可使用退出语句结束程序的运行。

2.5.3.1 暂停语句

在需要暂停程序执行的地方放置Stop语句,即可让程序暂停运行。用Stop语句,就相当于在程序代码中设置断点。

Stop语句会暂停程序的执行,但是它不像End语句,因为Stop不会关闭任何文件或清除变量。暂停的程序又可以接着执行后续的代码。

Stop语句主要用在调试程序阶段,在应用程序发布之前需要将程序中的Stop语句删除或注释掉。

下面的代码使用Stop语句来暂停“For…Next”循环里的每一次完成。

    Sub 暂停程序的执行()
      Dim i As Integer
      For i = 1 To 10           '开始For...Next循环
          Debug.Print i          '将变量i的值输出到“立即”窗口
          Stop                   '每一次的完成都会在此暂停
      Next
    End Sub

运行以上代码,每点击一次“运行”按钮,立即窗口都会显示一个递增的值:

在这里插入图片描述

2.5.3.2 退出语句

所谓的“退出”有两种意义,一是退出正在执行的VBA代码,返回到VBA的编辑环境中;另一种是退出Excel系统。

1.End语句

使用End语句可结束程序的运行,返回到VBA的编辑环境中。执行End语句会重置所有模块级别变量和所有模块的静态局部变量。若要保留这些变量的值,需改为使用Stop语句,可以在保留这些变量值的基础上恢复执行。

End语句提供了一种强迫终止程序的方法。VBA程序正常结束应该卸载所有的窗体。只要没有其他程序引用该程序公共类模块创建的对象并且无代码执行,程序将立即关闭。

注:End语句只是强制性地终止代码执行,窗体和类模块中的Unload、QueryUnload和Terminate事件代码并未被执行。同时,类模块创建的对象被破坏,由Open语句打开的文件被关闭,并且释放程序所占用的内存。其他程序的对象引用无效。

2.Quit方法

使用Application对象的Quit方法,将退出Excel:

Application.Quit

使用此方法时,如果未保存的工作簿处于打开状态,则Excel将显示一个对话框,询问是否要保存所作更改。

2.6 程序结构

程序结构表示程序的运行方式。VBA 正是因为存在多种代码结构,才能实现各类复杂的数据计算。常用的代码结构有顺序结构、选择结构和循环结构。

2.6.1 顺序结构

顺序结构即按照语句的书写顺序从上到下、逐条语句地执行程序。执行时,排在前面的代码先执行,排在后面的代码后执行,执行过程中没有任何分支。顺序结构是最普遍的结构形式,也是选择结构和循环结构的基础。

2.6.2 选择结构

选择结构又称为分支结构,是根据“条件”来选择执行哪一分支中的语句,包括二分支和多分支,以及分支的嵌套。
在这里插入图片描述

2.6.2.1 If … Then语句

选择结构中,If Then 结构是最基础的一个。它只有条件表达式真时,执行的代码。

If...Then结构有条件地执行一个或多个语句有两种语法形式。

1.单行结构条件语句

单行结构条件语句是最基本的条件语句,其语法为:

	If 条件表达式 Then 语句

条件表达式即可以任何计算数值的表达式,VBA将这个值解释为True或False:为零的数值为False;而任何非零数值都被看作True。

该语句的功能为:若逻辑表达式的值是True,则执行Then后的语句;若逻辑表达式的值是False,则不执行Then后的语句,而执行下一条语句。

2.块结构条件语句

If…Then语句中,如果条件成立时需要执行多个操作,可将多个语句写在Then后面,并用冒号:分隔。If…Then语句还提供另外一种块结构的方法,可使执行多行代码的过程更清晰,其语法如下:

	If 条件表达式 Then
	    语句1
	    语句2
	    ……
	End If
2.6.2.2 If … Then … Else语句

If … Then语句中,当条件为False时,不执行任何语句。若要求在条件为False时要执行另一段代码,可用If...Then…Else语句。If...Then...Else语句也有两种格式:单行和多行。单行的格式为:

	If 条件表达式 Then 语句1 Else 语句2

当“逻辑表达式”的值为True时,执行关键字Then后面的“语句1”;当“逻辑表达式”的值为False时,执行关键字Else后面的“语句2”。

多行条件语句将根据条件表达式的值来判断并执行其中一个语句块。语法格式如下:

	If 逻辑表达式 Then
	   语句序列1
	Else
	   语句序列2
	End If
2.6.2.3 IIf函数

IIf函数可用来执行简单的条件判断操作,它是If…Then…Else结构的简写版本,其语法格式如下:

	result=IIf(条件表达式, true部分, false部分)

变量result保存函数的返回值。“条件表达式”为判断的条件,当条件为True时,IIf函数返回“true部分”;当条件为False时,IIf函数返回“false部分”。

2.6.2.4 If … Then … ElseIf语句

在很多情况下,可能需要判断几个不同的条件,并根据不同条件执行不同的语句。这时可使用If…Then…ElseIf语句来对多个不同条件进行判断,并在多个语句块中选择执行其中的一个,其语法格式如下:

        If 条件表达式1 Then
            语句序列1
        ElseIf 条件表达式2 Then
            语句序列2
        ElseIf 条件表达式3 Then
            语句序列3
            ……
        Else
            语句序列n
        End If

在以上结构中,可以包括任意数量的ElseIf子句和条件,ElseIf子句总是出现在Else子句之前。

VBA首先判断“条件表达式1”的值。如果它为False,再判断“条件表达式2”的值,以此类推,当找到一个为True的条件,就会执行相应的语句块,然后执行End If后面的代码。如要所有“逻辑表达式”都为False,且包含Else语句块,则执行Else语句块。

2.6.2.5 Select Case语句

If...Then分支语句中,总是可以添加更多的ElseIf块,但是,当每个ElseIf块都将相同的表达式比作不同的数值时,这个结构编写起来很乏味,也不易阅读。在这种情况下可以用多分支选择结构Select Case语句。

Select Case语句的功能与If...Then...Else语句类似,但对多重选择的情况,Select Case语句可使代码更易读。

Select Case在结构的开始处理一个测试表达式并只计算一次,然后,VBA将表达式的值与结构中的每个Case的值进行比较,如果相等,就执行与该Case相关联的语句块,执行完毕再跳转到End Select语句后执行。其语法格式如下:

	Select Case 测试表达式
	  Case 表达式列表1
	        语句序列1
	  Case 表达式列表2
	        语句序列2
	          ……
	  Case Else
	        语句序列n
	End Select

其中“测试表达式”可以是数值型或字符型的表达式,通常是一个数值型或字符型的变量。表达式列表可以是一个或几个值的列表。如果在一个列表中有多个值,就用逗号将各个值分隔开。每一个语句序列中含有零个或多个语句。如果不止有一个Case与测试表达式相匹配,则只对第一个匹配的Case执行与之相关联的语句块;如果表达式列表中没有一个值与测试表达式相匹配,则VBA执行Case Else子句(此项可选)中的语句。

2.6.2.6 分支结构的嵌套

在一个分支结构语句中还可以包含另一个分支结构语句,称为分支结构的嵌套。

2.6.3 循环结构

在VBA中,循环结构用于多次重复执行同一段代码。重复次数通过特定数字或特定条件控制。

通过控制循环过程中特定变量,循环结构可执行复杂的重复任务。
在这里插入图片描述

2.6.3.1 Do … Loop循环

用Do循环重复执行一语句块,且重复次数不定。Do…Loop语句有4种演变形式,但每种都需要计算条件表达式的值,以决定是否继续执行。在Do循环中可以使用Exit Do语句中途退出该循环。

1.Do While…Loop循环
Do While语句属于先测试循环条件Do…Loop语句,其语法格式如下:

	Do While 条件表达式
	    语句序列1
	  [Exit Do]
	  [语句序列2]
	Loop

在Do While和Loop之间的语句称为循环体。

当VBA执行这个Do循环时,首先判断条件表达式:如果为False(或零),则跳过所有语句,执行Loop的下一条语句;如果为True(或非零),则执行循环体,当执行到Loop语句后,又跳回到Do While语句再次判断条件。在循环体中如果包含有Exit Do语句,则当执行到Exit Do语句时,马上跳出循环,执行Loop的下一条语句。

这种形式的循环体可能执行零次或多次。只要条件表达式为True或非零,循环就会重复执行。如果逻辑表达式最初就为False,则不会执行循环语句。

2.Do…Loop While循环
Do...Loop While语句属于后测试循环条件Do…Loop语句,该结构先执行循环体中的语句,然后再进行条件判断。这种形式的循环体至少执行一次,其语法格式如下:

	Do
	    语句序列1
	  [Exit Do]
	  [语句序列2]
	Loop While 条件表达式

3.Do Until…Loop循环

Do Until…Loop语句为先测试结束条件Do…Loop语句,其语法形式如下:

	Do Until 条件表达式
	    语句序列1
	    [Exit Do]
	    [语句序列2]
	Loop

这种形式与Do While…Loop类似,不同的是当条件表达式的值为False时才执行循环体,否则退出循环。这种形式的循环体可能执行零次或多次。

4.Do…Loop Until循环

Do…Loop Until语句是后测试结束条件Do…Loop语句,其语法形式如下:

	Do
	    语句序列1
	    [Exit Do]
	  [语句序列2]
	Loop Until 条件表达式

这种形式与“Do…Loop While”类似,不同的是当条件表达式的值为False时才执行循环体,否则退出循环。这种形式的循环体至少能被执行一次。

2.6.3.2 While … Wend循环

While…Wend循环语句的功能与Do…While循环相同,是从Basic的早期版本中保留下来的语句,VBA保留它是为了向后兼容,其语法格式如下:

	While 条件表达式
	    循环体
	  ……
	Wend

如果“条件表达式”为True,则所有的“循环体”语句都会执行,一直执行到Wend语句,然后再回到While语句,并再一次检查“逻辑表达式”的值,如果还是为True,则重复执行;如果不为True,则程序会从Wend语句之后的语句继续执行。

2.6.3.3 For … Next循环

For…Next语句以指定次数来重复执行循环体。与Do循环不同,For循环使用一个叫做计数器的变量,每重复一次循环之后,计数器变量的值就会增加或者减少。在For循环中可以使用Exit For语句随时退出该循环。For循环的语法格式如下:

	For 循环变量=初始值 To 终值 [Step 步长值]
	    语句序列1
	    [Exit For]
	  [语句序列2]
	Next [循环变量]

其中,步长值可正可负。如果步长值为正,则初始值必须小于或等于终值才能执行循环体,否则退出循环;如果步长值为负,则初始值必须大于或等于终值,这样才能执行循环体。如果没有设置Step,则步长值默认为1。

VBA执行For循环时的过程如下所述。
1)将初始值赋值给循环变量。

2)判断循环变量是否超过终值,若为真(True),退出循环,执行Next的下一语句。这里的“超过终值”有两种意思:若步长值为负数时,超过就是循环变量的值小于终值;而当步长值为正数时,超过就是循环变量的值大于终值。

3)执行循环体。

4)循环体执行完后到达Next语句时,循环变量累加上步长值。

5)重复步骤2)到步骤4)。

For循环一般都可计算出循环体的执行次数,计算公式如下:

循环次数=[(终值-初值)/步长值]+1

中括号[ ]表示取整。

2.6.3.4 循环的嵌套

与分支结构类似,循环结构也可进行嵌套,即将一个循环放置在另一个循环中。VBA允许在同一过程里嵌套多种类型的循环。

2.6.3.5 Goto语句

使用Goto语句,可无条件地将程序代码跳转到指定的行去执行。VBA中保留Goto语句是为了保持与早期的Basic版本兼容。Goto语句的语法格式如下:

	GoTo 行号/标号

注:Go To只能跳到它所在过程中的行。

要使用Goto语句,首先需用了解VBA中语句的行号和标号。在早期的Basic语言中,每一行程序都有一个行号,行号按从小到大的顺序排列,例如:

	Sub 使用Do循环删除为0的行()
	10   Dim i As Integer
	20   i = 1
	30   With Sheets("sheet2")
	40   Do While i <= 100
	50       If .Cells(i, 2) = 0 Then
	60          .Cells(i, 2).EntireRow.Delete
	70       End If
	80       i = i + 1
	90   Loop
	100 End With
	End Sub

在VBA中,执行这样的代码也不会出错。为程序添加行号的目的就是方便使用Goto语句跳转到相应的语句去执行。

随着结构化程序设计方法的使用,使用Goto语句跳转的方式已经不常用了,也就不再需要为每行代码添加行号了。

为了使Goto跳转到需要的地方,可在程序中添加标号。标号是以英文字母开头的一个标识符后加上一个冒号构成的。在程序代码中输入的标号始终是靠左对齐的。

例如,以下代码使用Goto语句来进行循环操作,删除单元格为0的行:

	Sub 使用GOTO语句删除为0的行()
	  Dim i As Integer
	  i = 1
	line1:
	  With Sheets("sheet2")
	      If .Cells(i, 2) = 0 Then
	          .Cells(i, 2).EntireRow.Delete
	      End If
	      i = i + 1
	      If i <= 100 Then GoTo line1
	  End With
	End Sub

不建议在程序中使用太多的Goto语句,因为使用Goto语句会使程序代码不容易阅读及调试。应尽可能使用结构化控制语句(Do…Loop,For…Next,If…Then…Else和Select Case)。

2.7 数组

对于大量有序的数据,可以使用数组对其进行存储和处理。

在VBA中,数组中各元素可以是相同的数据类型,也可以是不同的数据类型。

2.7.1 声明数组

计算机中数组占用一片连续的内存空间。在使用之前,必须先对数组进行声明(定义),让计算机系统为其分配一片连续的内存空间。

声明数组变量必须为数组指定大小。若数组的大小被指定,则它是一个大小固定的数组;若程序运行时数组的大小可以被改变,则它是一个动态数组。

2.7.1.1 声明一维数组

有两种方法声明大小固定的一维数组。

方法1:

Dim 数组名(上界) As 数据类型

方法1与其他程序设计语言定义数组的格式相同,只给出数组下标的上界而省略下标的下界(默认为0),即数组的下标从0开始至定义的上界。
例如,定义一个名为MyArray 的数组,共有11 个元素,分别为MyArray(0),MyArray(1)…MyArray(10):

Dim MyArray(10) As String

如果希望数组下标从1开始,可以通过Option Base语句来设置:

Option Base 1

注:使用该语句指定的数组下标的默认下界只能为0或1。该语句只能出现在用户窗体或模块的声明部分,不能出现在过程中,且必须放在数组定义之前。

方法2:

Dim 数组名(下界 To 上界) As 数据类型

方法2可定义数组的下标下界为任意值。
例如,定义一个数组MyArray,共有12 个元素,分别为MyArray(-1),MyArray(0),MyArray(1)…MyArray(10):

Dim MyArray(-1 To 10) As String
2.7.1.2 声明二维数组

二维数组的声明方式与一维数组类似,不同的是需要设置两个上下标变量。

方法1(只定义数组上界):

Dim 数组名(1维上界,2维上界) As 数据类型

方法2(同时定义上下界):

Dim 数组名(1维下界 To1维上界,2维下界 To2维上界) As 数据类型

声明多维数组的格式与二维数组类似,每一维都使用逗号隔开即可。但需要注意的是,定义大数据量的数组将占用很大的内存空间。

2.7.2 默认数组

默认数组,即数据类型为Variant(默认类型)的数组。使用默认数组的好处是可以让每个数组元素保存一个不同类型的值,得到一种混合状态的数组。

VBA中定义默认数组语法如下:

Dim 数组名(上界)

等同于:

Dim 数组名(上界) As Variant

2.7.3 动态数组

动态数组是指在程序运行时大小可以改变的数组。在声明动态数组时,可使用变量来设置下标。

声明动态数组一般分两个步骤:

  1. 在用户窗体、模块或过程中使用Dim或Public声明一个没有下标的数组(不能省略括号);
  2. 在过程中用ReDim语句重新定义该数组的大小。
    ReDim语句在过程级别中使用,用于为动态数组变量重新分配存储空间。其语法格式如下:
ReDim [Preserve] 数组名(下标) [As 数据类型]

可使用ReDim语句反复改变数组的元素及维数的数目,但是不能在将一个数组定义为某种数据类型之后,再使用ReDim将该数组改为其他数据类型,除非是Variant所包含的数组。

在默认情况下,使用ReDim语句重定义数组的维数和大小时,数组中原来保存的值将全部消失。

如果使用Preserve关键字,当改变原有数组最后一维的大小时,可以保持数组中原来的数据。同时,只能重定义数组最后一维的大小,并且不能改变维数的数目。

2.7.4 数组的基本操作

声明一个数组后,就可以对数组或数组中的元素进行操作了。操作通常包括数组的初始化、数组元素的复制及数组的删除等。

2.7.4.1 数组的初始化

在VBA中,创建数组后将自动初始化数组中的每个元素:对于数值型的数组,每个元素都将清零;如果是字符串型的数组,每个元素将初始化为空字符串。

而如果是重定义动态数组,并且使用了Preserve关键字,则不会对已有元素进行初始化,而只对新增加的元素进行初始化。

对数组元素的初始化一般使用循环语句来进行操作,可让用户逐个输入数组的初始值;也可按一定的规则读取工作表单元格中的值来对数组赋初始值;还可使用Array函数来为数组元素赋值。

1.用户逐个输入数组的初始值

例如,以下代码将反复显示输入框要求用户依次输入10个数组元素的初始值,并保存到对应的数组元素中:

	For i = 1 To 10
	  MyArray(i) = InputBox("请输入数组的第" & i & "个元素的值")
	Next

注:InputBox函数返回的是字符串类型。如果数组定义为数值型,还需使用相关的函数将用户输入的内容进行转换,如用Val函数将字符串转换为数值,也可以使用如Ccur、Cdate、CDbl、Cint等相关函数进行转换。

2.读取工作表单元格中的值来对数组赋初始值

由于工作表就相当于一个二维数组,每个单元格就是二维数组中的一个元素。使用工作表中某部分单元格为数组赋初值时,可通过两个循环的嵌套完成。

例如,以下代码使用工作表单元格中的数据来为数组元素赋初始值:

	Sub ArrayInit()           '使用工作表单元格中的数据来为数组元素赋初始值
	  Dim MyArray(5, 5)
	  Dim i As Integer, j As Integer
	  For i = 1 To 5
	      For j = 1 To 5
	          MyArray(i, j) = Worksheets("sheet2").Cells(i, j)
	      Next j
	  Next i
	End Sub

3.使用Array函数来为数组元素赋值

Array函数的语法如下:

数组变量名 = Array(数据集)

其中,“数组变量名”是预先定义的数组名,后面不跟括号;Array函数中的“数据集”是由一些常数构成的数据集合,各值之间用逗号分开。例如:

	Sub ArrayInit1()   '使用Array函数初始化数组
	  Dim MyArray, i As Integer
	  MyArray = Array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五",
	"星期六")
	End Sub

执行上述代码,最终得到MyArray(0)=“星期日”、MyArray(2)=“星期一”…MyArray(6)=“星期六”。

注:注因为Array函数的返回值为Variant类型,所以在使用Array函数给数组变量赋初值之前,定义数组时只能将其定义为Variant类型,且不能设置其下标,否则将显示“不能给数组赋值”的错误提示。

2.7.4.2 数组元素的复制

单个的数组元素可像普通变量一样进行访问和赋值。因此,要将一个数组中的值复制到另一个数组时,使用For循环语句将数组元素逐个元素复制即可:

	Sub 数组元素的复制()
	  Dim MyArray1(5) As Integer, MyArray2(5) As Integer
	  Dim i As Integer
	  For i = 1 To 5
	      MyArray1(i) = i   '将MyArray1初始化为1~5
	  Next
	  For i = 1 To 5
	      MyArray2(i) = MyArray1(i)   '将MyArray1中的元素对应复制到MyArray2中
	  Next
	End Sub

在以上代码中,如果将数组MyArray2声明为一个Variant变量,那么通过一条语句就能将MyArray1中的所有元素复制到MyArray2中:

	Sub 数组元素的复制()
	  Dim MyArray1(5) As Integer, MyArray2 As Variant
	  Dim i As Integer
	  For i = 1 To 5
	      MyArray1(i) = i
	  Next
	  MyArray2 = MyArray1    '将数组元素保存到Variant变量中
	End Sub

通过MyArray2=MyArray1语句赋值后,变量MyArray2将保存一个数组变量。可使用MyArray2(1)、MyArray2(2)的形式访问数组中每个元素的值。

2.7.4.3 数组的清除

对于静态数组,当创建完成后,其内存空间也分配完成,数组的大小将不能再改变。

有时可能需要清除数组的内容或对数组重新定义,可使用Erase语句重新初始化大小固定的数组的元素,并释放动态数组的存储空间。其语法格式如下:

	Erase 数组名1, 数组名2, … …

Erase语句根据静态数组还是动态数组来采取完全不同的行为:

  • 静态数组
    • 固定数值数组:Erase语句将每个元素设为0
    • 固定字符串数组(长度可变):Erase语句将每个元素设为零长度字符串(“”)
    • 固定字符串数组(长度固定):Erase语句将每个元素设为0
    • 固定 Variant 数组:Erase语句将每个元素设为 Empty
    • 用户定义类型的数组:Erase语句将每个元素作为单独的变量来设置
    • 对象数组:将每个元素设为特定值 Nothing
  • 动态数组:Erase语句将删除整个数组结构并释放动态数组所使用的内存。在下次引用该动态数组之前,程序必须使用ReDim语句来重新定义该数组变量的维数。

简单来说,静态数组在使用Erase语句后仍然存在,只是其内容被清空;而动态数组使用Erase语句后即不存在了。

2.7.4.4 数组相关函数

VBA中操作数组的主要有5个函数:Array、IsArray、Erase、LBound和UBound。

1.检测变量是否为数组

IsArray函数可检查指定的变量是否为一个数组:如果指定变量是一个数组,返回值为True,否则返回False。对于包含数组的Variant表达式来说,IsArray很常用。

	Dim MyArray(1 To 5) As Integer, YourArray, MyCheck    ' 声明数组变量
	YourArray = Array(1, 2, 3)    ' 使用数组函数
	MyCheck = IsArray(MyArray)    ' 返回 True
	MyCheck = IsArray(YourArray)    ' 返回 True

2.获得数组下标的范围

LBound函数和UBound函数可获得数组下标的下界和上界。其语法格式为:

	LBound(数组名[, 维数])
	UBound(数组名[, 维数])

如果省略“维数”参数,表示返回一维的下标下界或上界。

例如,定义数组:

	Dim A(1 To 100, 0 To 3, -3 To 4)

则使用LBound函数的返回值分别为:

	LBound(A, 1)    ' 返回值为1
	LBound(A, 2)    ' 返回值为0
	LBound(A, 3)    ' 返回值为-3

则使用UBound函数的返回值分别为:

	UBound(A, 1)    ' 返回值为100
	UBound(A, 2)    ' 返回值为3
	UBound(A, 3)    ' 返回值为4

2.8 过程

过程是指由一组完成指定任务的VBA语句组成的代码集合。在VBA中,可执行的代码都必须放置在过程中。

2.8.1 过程的分类

VBA中的过程可分为事件过程、属性过程和通用过程。

2.8.1.1 事件过程

事件是某个特定的时刻所发生的事情,它是对象状态转换过程的描述。事件没有持续时间,是瞬间完成的。

事件过程是当发生某个事件(如单击、双击)时,对该事件做出响应的程序段。在VBA中,可以激发事件的用户动作包括切换工作表、选择单元格、单击鼠标等几十种事件。当事件发生时,将执行包含在事件过程中的代码。如果用户没有定义某事件所调用的过程,那么当发生该事件时就不会产生任何响应。

事件既可以由用户动作(如单击鼠标、按键等)产生,也可以由程序代码或系统产生(如计时器定时产生的事件)。编写事件响应代码是在“代码编辑器”中进行的,“代码编辑器”的右边有一个事件列表,这个事件列表从属于左边的控件对象,当左边控件列表中的控件改变后,右边的事件列表也会发生变化。

在这里插入图片描述

2.8.1.1.1 事件分类

Excel提供了非常多的事件,主要分为以下几类。

  1. 工作簿事件:工作簿事件发生在特定的工作簿中如Open(打开工作簿)、BeforeClose(关闭工作簿之前)和SheetActivate(激活任何一张表)等。工作簿事件的代码必须在ThisWork对象代码模块中编写。
  2. 工作表事件:工作表事件发生在特定的工作表中,例如Activate(激活工作表)、Change(更改工作表中的单元格)和SelectionChange(工作表上的选定区域发生改变)等。工作表事件的代码必须在对应工作表的代码模块中编写。
  3. 窗体、控件事件:新建的用户窗体及窗体上的控件也可响应很多事件,例如Click(单击)、Change(控件内容更改)等,这类事件的代码必须编写在相应的用户窗体代码模块中。
  4. 图表事件:图表事件针对某个特殊的图表产生如Select(选中了图表中的某个对象)和SeriesChange (改变了系列中的某个数据点的值)。
  5. 不与对象关联的事件:不与对象关联的事件只有两个,分别是OnTime和OnKey,根据时间和按钮来产生这两个事件。
2.8.1.1.2 编写事件程序

以对工作簿的Open事件编写处理代码为例,步骤如下:

  1. 在Excel环境下按“Alt+F11”组合键打开Visual Basic编辑器。
  2. 在左侧的“工程资源管理器”窗口中列出了当前工程项目中的Excel对象,双击其中的“ThisWorkbook”对象,将在右侧打开代码编辑器。
  3. 在代码窗口左侧对象下拉列表框中选择对象。
  4. 选择好对象“Workbook”后,在代码窗口右侧的事件下拉列表框中可看到出该对象的事件列表。
  5. 选择好对象和事件后,系统自动生成事件过程的外部结构。在事件过程结构中编写响应该事件的代码即可。
2.8.1.2 属性过程

在VBA中,除了模块、用户窗体外,还提供了类模块。类模块向开发人员提供了创建和操作自己的对象类的功能。

在“类模块”中设置类的属性值时,可通过“属性过程”来完成。

2.8.1.2.1 类

在VBA中,有各种各样的对象如Workbook、Worksheet和Range等,这些对象也都是通过相应的类来创建的。例如:

	Dim rngUser As Range '定义了一个对象变量rngUser,该变量存储对象Range的引用

在这里,Range是VBA内部定义好的一个类(标准类)。

2.8.1.2.2 类的作用

VBA提供类模块功能,让开发者也可以根据需要定义自己的类。

使用类模块编写代码,可以使程序更易管理、维护,也更利于大型应用程序的开发。

1.创建自定义对象

类模块可以用于在应用程序中建立和使用自己的对象类型。在应用程序开发过程中可以发现,很多系统都要使用到相似的功能。这时可将已有系统中的代码复制到新系统中,包括复制代码用到的数据,再逐行检查代码并进行修改。如果没有能保证代码与数据之间关系的措施,在操作过程中很可能会出现混乱,从而导致程序出现错误。

其实,将这些数据和程序包装在一个类中,在新的系统中使用该类创建一个对象,即可通过对象的属性、方法等来完成相应的功能。

2.封装复杂过程

使用类还可以将复杂的过程封装在类的内部,类的使用人员不需要知道类的实现过程,只需要熟悉类的属性、方法和事件即可。

2.8.1.2.3 类的预备知识

1.对象

对象是由类创建的一个实例,它是类的实体化。对象的引用和操作被划分为以下三个部分。

  • 属性是指对象的特性。例如Worksheet对象,有包含单元格的行数、列数等属性。
  • 方法是指对象的某个操作。例如Worksheet对象,有增加工作表、删除工作表等方法。
  • 事件是指对象对外部动作的响应。例如单击Worksheet对象的单元格时,会产生一个Change事件;修改单元格内容时,会产生一个SelectionChange事件。

2.变量的作用域

变量可以划分为过程级、模块级和全局变量。在类模块中,对变量作用域的理解要注意以下两点。

  • 由于类是生成对象的模板,每一个对象相当于是类的一个副本,对象之间是相互独立的,因此,模块级的变量只作用于对象自身,对其他通过该类创建的对象不会起作用。
  • 在类模块中使用Public关键字声明的变量,通过该类生成的对象都可访问。

3.过程和函数

变量、过程和函数是标准模块中使用的最基本的构件。在类模块中,它们仍然是最基本和最重要的角色。过程和函数并无实质的区别,当需要返回值时,就使用Function;如果不需要返回任何结果,可使用Function,,也可使用Sub。

过程、函数也有作用域,在标准模块中通过使用Private和Public关键字(可以省略Public关键字,因为它是默认的)可以划分为模块级和全局级,以决定它是在当前的模块有效还是整个工程有效。

同变量一样,在类模块中使用Public关键字的函数或过程才能被声明的对象调用。使用Private关键字的函数或过程只能在类模块中进行调用。

2.8.1.2.4 创建类
  1. 建立对象类
    在VBA编辑器中选择“插入”→“类模块”命令,向工程插入一个类模块:
    在这里插入图片描述
    在“属性”窗口中修改类的“(名称)”为“clsPerson”:
    在这里插入图片描述

  2. 建立属性
    建立属性的方法有两种:
    a. 最简单的方法是在类模块中声明Public变量作为对象的属性。这种方法的缺点是,类不能知道属性值何时被外部过程修改了,也就无法将属性值限定在一个有效的范围了。
    b. 另一种方法是使用Property过程来定义属性值,这时外部过程修改属性值时,将执行Property过程。在该过程中可以编写代码,对设置的值进行检查,控制其在一个规定的范围中。

    Property过程有以下3种形式:

    • Property Let:这类过程用来设置类模块的属性值。

    • Property Get:这类过程用来读取类模块的属性值。

    • Property Set:这类过程用来设置对对象的引用。

      在使用Property过程设置属性值时,类模块中的变量可声明为Private类型,避免过程直接修改类模块中变量的值。

      创建属性时的3个步骤如下:

      ① 创建私有变量。在创建clsPerson类时,需要在类模块的声明部分使用以下代码声明变量:

      Private m_ID As String     '编号
      Private m_Name As String   '姓名
      Private m_Sex As String    '性别
      Private m_Birthday As Date '出生年月
      Private m_Company As String'工作单位
      

      ② 创建Property Get过程来获取对象的属性值。

      ③ 创建Property Let过程来设置上一步定义的私有变量的值。

  3. 创建Property Get过程
    Property Get语句用来获取对象的属性值。其基本形式就是一个声明和一个主体。声明包括属性名和数据类型。每个属性值都需要创建一个Property Get过程。

    对于clsEmp类,其4个属性值的Property Get过程代码如下:

    Property Get ID() As String
      ID = m_ID                                    '返回编号
    End Property
    Property Get PerName() As String
      PerName = m_Name                             '返回姓名
    End Property
    Property Get Sex() As String
      Sex = m_Sex                                  '返回性别
    End Property
    Property Get Birthday() As Date
      Birthday = m_Birthday                        '返回出生日期
    End Property
    Property Get Company() As String
    Company = m_Company                            '返回工作单位
    End Property
    
  4. 创建Property Let过程
    与Property Get过程相对应的就是Property Let过程。通过Property Let过程可让使用者改变对象的属性值。

    一般每个属性值都需要编写一段Property Let过程。如果对象的某个属性为只读,则不能定义Property Let过程。以下代码中没有定义ID属性的Property Let过程,表示该属性为只读。

    Property Let PerName(strName As String)
      m_Name = strName                         '设置姓名属性
    End Property
    Property Let Sex(strSex As String)
      If strSex <> "男" And strSex <> "女" Then
          Exit Property
      Else
          m_Sex = strSex                       '设置性别属性
      End If
    End Property
    Property Let Birthday(datBirthday As Date)
      m_Birthday = datBirthday                '设置出生日期属性
    End Property
    Property Let Company(strCompany As String)
    m_Company = strCompany                    '设置工作单位属性
    End Property
    
  5. 创建对象类的方法
    除了属性之外,一般对象至少有一个方法。方法是对象可以执行的操作,使用方法可以操作对象类中的数据。

    在clsPer类中创建两个方法,一个用于调换工作单位;

    Public Function ExchangeCom(strCompany As String) As Boolean
    If strCompany <> "Super公司" And strCompany <> "End公司" Then
      ExchangeCom = False
      Else
      m_Company = strCompany
         ExchangeCom = True
      End If
    End Function
    
  6. 类模块的事件
    事件是对象可识别的动作,例如按钮对象可识别Click事件。自定义类模块有Initialize事件和Terminate事件,这两个事件分别在类的实例初次创建时和最后一个指针释放或破坏时触发。可以用Initialize事件设置对象类的默认属性值;用Terminate事件进行销毁对象前的整理工作。

    在clsPer类中,员工的编号不能通过Property Let过程设置(为只读属性),但可以在Initialize事件中编写代码对其进行赋值。编写Initialize事件代码的过程如下。

    (1)在类模块代码窗口的对象下拉列表中选择“Class”,在事件下拉列表中选择“Initialize”。
    (2)编写以下代码:

        Private Sub Class_Initialize()
          Randomize
          m_ID = Int(10000 * Rnd)                     '生成一个4位数
          m_ID = Format(m_ID, "0000")                 '进行格式化处理
        End Sub
    

    在上述代码中,通过随机函数Rnd得到一个0~1之间的小数,再将其乘以10 000后取整,可得到一个4位数。如果位数小于4位,则使用Format函数在前面加上0。

2.8.1.2.5 使用类模块创建对象

创建一个用户窗体,在窗体中使用clsPerson类来管理人员资料。

  1. 设计新增人员窗体

    设计一个新增人员窗体,可接收用户输入人员的相关数据,并将其保存到clsPer对象类中。具体的操作步骤如下:

    ① 在VBA编辑器中选择“插入”→“用户窗体”命令,向工程中增加一个用户窗体。

    ② 设置窗体的名称属性为“frmMain”,并向窗体中添加如下图所示的控件:
    在这里插入图片描述
    ③ 在窗体模块的声明部分输入以下代码:

    Dim person As New clsPerson            '声明一个clsPer对象
    Public persons As New Collection       '声明一个集合对象
    

    ④ 在窗体的Initialize事件中调用过程DisableBox,将窗体中的输入控件禁用,禁止用户输入数据:

    Private Sub UserForm_Initialize()
    	Call DisableBox                    '调用过程禁止用户输入
    End Sub
    

    ⑤ DisableBox过程的代码即设置各控件的Enable属性为False即可,EnableBox则是设置为True:

     Sub DisableBox()
         txtName.Enabled = False
         txtBirthday.Enabled = False
         txtCom.Enabled = False
         optMan.Enabled = False
         optWoman.Enabled = False
    End Sub
    
    Sub EnableBox()
        txtName.Enabled = True
        txtBirthday.Enabled = True
        txtCom.Enabled = True
        optMan.Enabled = True
        optWoman.Enabled = True
    End Sub
    

    ⑥ 为“新增”按钮编写如下代码:

    Private Sub AddPerson_Click()
    If AddPerson.Caption = "新 增" Then
        AddPerson.Caption = "保 存"                  '更改按钮提示信息
        Call EnableBox
        txtName.Value = ""
        txtBirthday.Value = ""
        txtCom.Value = ""
        txtName.SetFocus
      Else
        AddPerson.Caption = "新 增"                  '更改按钮提示信息
        With person
          .PerName = txtName.Value               '姓名
          If optMan Then                         '性别
              .Sex = optMan.Value
          Else: .Sex = optWoman.Value
          End If
          If IsDate(txtBirthday.Value) Then
            .Birthday = CDate(txtBirthday.Value))'出生日期
          Else
            .Birthday = DateSerial(1900, 1, 1)   ' 默认值
          End If
          .Company = txtCom.Value                '工作单位
        End With
        persons.Add person, person.PerName       '将对象添加到集合中
        Call DisableBox                          '禁止用户录入
      End If
    End Sub
    

    其中,单击“新增”按钮时,将窗体中的文本框等控件设置为允许输入状态,先清空原有数据,接受用户输入数据,同时将按钮的提示文字改为“保存”。
    单击“保存”按钮时,将窗体中输入的数据保存到emp对象的各个属性中,并将emp对象添加到emps集合对象中,同时修改按钮和文本框等控件的状态。

    ⑦ 当用户点击调整工作单位按钮时,弹出相应的窗体,代码如下:

    Private Sub Adjust_Com_Click()
      frmCom.Show                              '调整人员工作单位
    End Sub
    
  2. 设计调整员工部门窗体

    调整员工部门的窗体将调用emp对象的ExchangeDep方法来调整员工的工作部门。该窗体用于输入员工的姓名,以及调整后的部门。具体步骤如下:

    ① 在VBA编辑器中选择“插入”→“用户窗体”命令,向工程中增加一个用户窗体。

    ② 设置窗体的名称属性为“frmCom”,并向窗体中添加如下图所示的控件:
    在这里插入图片描述
    为“保存”按钮编写代码如下:

    Private Sub Confirm_Click()
        Dim person As clsPerson
        Dim b As Boolean
        b = False
        For Each person In frmMain.persons
            If person.PerName = txtName.Value Then
                person.ExchangeCom txtCom.Value
                MsgBox "人员姓名:“" & txtName.Value & "”的新工作单位为:" & person.Company
                b = True
            End If
        Next
        If Not b Then
            MsgBox "输入的人员姓名有误!"
        End If
    End Sub
    

测试结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.8.1.3 通用过程

有时在不同的事件过程中需要执行一段相同的代码,可以把这段代码独立出来,作为一个过程,这样的过程称为“通用过程”。

在VBA中,通用过程又分为两类:Sub(子程序)过程和Function(函数)过程。

  • Sub过程:这类过程完成指定任务,执行结束后不返回值。
  • Function过程:这类过程完成指定的任务,并返回一个值供调用程序使用。

一般通用过程保存在VBA的“模块”中,一个模块可以包含任意数量的过程,同时,一个VBA工程可以包括任意多个模块。

2.8.2 定义Sub过程

VBA中有两种定义过程的方法:一种方法是使用窗体创建过程的结构,再在过程中编写相应的代码;另一种方法是在模块中直接输入代码来定义过程。

2.8.2.1 使用窗体创建Sub过程
  1. 在VBA编辑器中选择“插入”→“过程”命令,打开“添加过程”对话框:
    在这里插入图片描述

在这个对话框中,除了可以插入一个新的Sub过程、Function过程或属性过程,还可以设置Public或Private有效范围,并使得过程中所有的局部变量成为静态变量。

  1. 在“名称”后的文本框中输入过程的名称,并在“类型”栏中选择“子程序”单选框,接着在“范围”栏中选中“公共的”单选框,设置过程为全局的(即在工程的各模块中都可以调用该过程),将在过程前面添加Public关键字。还可根据需要选中下方的“把所有局部变量声明为静态变量”复选框,如果选中该复选框,将在过程名前面添加Static关键字。
  2. 单击“确定”按钮,VBA将自动生成过程的结构代码:
    在这里插入图片描述
2.8.2.2 使用代码创建Sub过程

Sub过程的结构如下:

	[Private | Public | Friend] [Static] Sub 过程名 [(参数列表)]
	    [语句序列1]
	    [Exit Sub]
	    [语句序列2]
	End Sub

过程由Sub、End Sub及它们之间的VBA代码构成。其中在Sub前面可加上限制过程作用域的关键字,主要有以下几个:

  • Private:表示只有在包含其声明的模块中的其他过程可以访问该Sub过程。
  • Public:表示所有模块的所有其他过程都可访问这个Sub过程。如果在Sub前面省略关键字,则表示其为Public。
  • Friend:只能在类模块中使用,表示该Sub过程在整个工程中都是可见的,但对对象实例的控制者是不可见的。
  • Static:表示在调用时保留Sub过程的局部变量的值。Static属性对在Sub之外声明的变量不会产生影响,即使过程中也使用了这些变量。

End Sub语句标志着Sub过程的结束。为了能正确运行,每个Sub过程必须有一个End Sub语句,当程序执行到该语句时就结束该过程的运行。另外,在过程中可以使用一个或多个Exit Sub语句直接退出过程的执行。

需要注意的是,Sub过程的定义不能嵌套,即不能将过程的定义放在另一个过程中。

2.8.3 调用Sub过程

在VBA中,通过调用定义好的过程来执行程序。Sub过程的调用分两种方式:一种是在VBA代码中调用Sub过程;另一种是在Excel中以调用宏的方式来执行Sub过程。

2.8.3.1 使用VBA代码调用Sub过程

在程序中调用Sub过程有两种方式:一种是把过程名放在一个Call语句中;另一种是把过程名作为一个语句来使用。

  1. 用Call语句调用Sub过程
    用Call语句可将程序执行控制权转移到一个Sub过程或Function过程中,在过程中遇到End Sub或Exit Sub语句后,再将控制权返回到调用程序的下一行。Call语句的语法格式如下:
    Call 过程名(过程参数列表) '如果过程没有参数,可省略过程名后的括号
    
  2. 将过程作为一个语句
    在调用过程时,如果省略Call关键字,过程也可调用。与使用Call关键字不同的是,如果过程有参数,这种调用方式必须要省略“参数列表”外面的括号。
    即:
    过程名 过程参数1, 过程参数2, ...
    
2.8.3.2 以宏方式调用Sub过程

在VBA中,可以将Sub过程作为一个宏来调用。

  1. 切换到Excel工作界面。

  2. 在工作表中任意位置插入并绘制一个按钮:
    在这里插入图片描述

  3. 在打开的“指定宏”界面中选择VBA中写好的Sub过程名称,并点击确定:
    在这里插入图片描述

  4. 点击该创建的按钮即运行相应的Sub过程。

注:以宏方式调用Sub过程无法给过程传递参数,因此需要用参数的过程不会显示在宏列表中。

2.8.4 传递参数

为了使过程更具有通用性,大多过程都需要设置参数。传递不同的参数给过程,能有不同的执行结果。

对于有参数的过程,在调用时必须将实际参数传递给过程,完成形参与实参的结合,过程再使用实参执行代码。

2.8.4.1 形参与实参的结合

形参即形式参数的简称,是在Sub过程的定义中出现的变量名。因其没有具体的值,只是形式上的参数,所以称为形参。

实参即实际参数的简称,是在调用Sub过程时传递给Sub过程的值。在VBA中实参可为常量、变量、数组或对象类的数据。

在VBA中,形参与实参的结合有两种方式。

  1. 按位置结合

    大多数程序语言调用子过程时都按位置结合形参与实参。

    按位置结合即调用Sub过程时使用的实参次序必须与定义Sub过程时设置的参数次序相对应。例如,使用以下代码定义Sub子过程:

    Sub Test(arg1 As Integer, arg2 As Long, arg3 As String)
    ...
    End Sub
    

    子过程中定义了3个参数。可使用以下语句调用该子过程:

    Call Test(1, 2, "abc")
    
  2. 按命名参数方式结合

    形参与实参的另一种结合方式是按形参名称来进行的,即在调用Sub过程时,输入形参的名称,将形参名称与实参用符号:=连接起来。与按位置结合方式不同,使用这种方式时,调用过程的参数位置可随意设置。
    例如,使用命名结合的方式调用上面定义的“Test”子过程:

    Call Test(arg1:=1, arg3:="abc", arg2:=2)
    
2.8.4.2 传地址

在VBA中,实参可通过两种方式将数据传递给形参,即传地址和传值。

传地址是VBA默认的方式,在定义过程时,如果在形参前面有关键字ByRef,则该参数通过传地址的方式传递。传地址是指将实参变量的地址传递给形参,这让形参和实参都代表同一个内存区域。

例如:

    Sub 传地址测试(ByRef a As Integer)
      a = a + 1
      Debug.Print "子过程中的变量A=" & a
    End Sub

由于Sub过程不能返回运算结果,如果需要Sub过程返回值时,可通过使用ByRef方式来定义形参就可将子过程的运算数据返回到调用程序中。

2.8.4.3 传值

传值就是将实参的值作为一个副本,赋值给形参,而不是传送实参的地址给形参。定义过程时,在形参的前面添加ByVal关键字,则该参数就按传值方式传递,否则用传地址方式传递。

使用传值方式传递参数时,传递的只是变量的副本。如果过程改变了形参的值,所做改变也只在过程内部起作用,不会影响到调用程序中变量的值。

例如:

    Sub 传值测试(ByVal a As Integer)
      a = a + 1
      Debug.Print "子过程中的变量A=" & a
    End Sub

在程序中使用传地址方式比传值方式效率高,但是传地址方式中,形参不是一个真正的局部变量,有可能对程序产生不必要的影响。如无特殊需求,应尽量使用传值方式。

2.8.4.4 传递数组参数

数组作为在内存中的一片连续区域,也可作为一个参数传递给Sub子过程进行处理。数组一般是通过传地址方式进行传递的。

例如,编写一个求数组中最大数的过程:

     Sub 求最大数(a() As Integer)
       Dim i As Integer, max As Integer
       max = a(LBound(a))
       For i = LBound(a) To UBound(a)
           If a(i) > max Then max = a(i)
       Next
       Debug.Print "最大数:" & max
     End Sub

在该过程中,将形参定义为一个数组。使用数组作为形参时,必须输入数组名并跟上一对空括号。对传递到过程的数组,应使用LBound函数和UBound函数获取其下标的下界和上界,接着程序才能遍历数组或对数组进行其他相关的操作。

2.8.5 可选参数和可变参数

在创建VBA的过程时,除了可设置参数按地址或按值传递之外,还可以根据需要为过程设置可选参数和可变参数。

2.8.5.1 可选参数

通常情况下,一个VBA过程中的形参数量是固定的,调用时提供的实参数量也是固定的。但在有的过程中,可能有必需收集信息和可选信息,例如收集顾客的信息时必须提供“姓名”、“性别”,而“身份证号码”则是可选的。

VBA的过程可以通过对形参前面加上Optional关键字来设置该形参为可选参数。在过程内部通过使用IsMissing函数可测试调用程序是否传递了该可选参数。例如:

    Sub 可选参数(strName As String, strSex As String, Optional ID)
      With Worksheets("sheet2")
          .Range("A2") = strName
          .Range("B2") = strSex
          If Not IsMissing(ID) Then
              .Range("C2") = ID
          End If
      End With
    End Sub

注:过程中可定义多个可选参数,但可选参数必须放在参数表的最后,而且必须是Variant类型。

2.8.5.2 可变参数

无论是固定参数还是可选参数,在定义过程时都已经指定了参数的个数。在VBA中,还可以定义可变参数,即参数的个数在定义时是未知的。

在定义过程的参数表时,在最后一个参数前面加上ParamArray关键字,过程将接受任意个数的参数。例如,使用可变参数编写求和函数Sum:

    Sub MySum(intTotal As Integer, ParamArray intNum())
      Dim i As Integer, j As Integer
      For i = LBound(intNum) To UBound(intNum)
          intTotal = intTotal + intNum(i)
      Next
    End Sub

该过程中,可变参数为一个数组。程序中使用LBound函数和UBound函数获得数组下标的上下界,然后进行累加,并将累加的结果保存在第一个参数中,用于返回给调用程序。调用以上子过程的代码如下:

    Sub 调用可变参数()
      Dim i As Integer
      MySum i, 1, 2, 3, 4, 5, 6, 7, 8
      Debug.Print i
    End Sub

程序中定义了一个变量i,用来获得子过程运算的结果。参数传递时,将参数i用传地址方式传递给“MySum”子过程的形参intTotal,将后面的“1、2、3、4、5、6、7、8”作为一个数组传递给形参intNum。

注:ParamArray只能用于参数列表的最后一个参数,指明最后这个参数是一个Variant元素的Optional数组。另外,ParamArray关键字不能与ByVal、ByRef或Optional一起使用。

2.9 函数

2.9.1 函数与过程

Function函数和Sub过程都属于VBA的通用过程。

Function函数和Sub过程的相同点有:

  • 都是构成VBA程序的基本单位。
  • 都可用Public、Private等关键字设置过程的作用区域。
  • 都可接收参数,参数的设置相同(如传递方式、可选参数、可变参数等)。

Function函数和Sub过程的不同点有:

  • Sub过程不能返回一个值,而Function函数可以返回一个值,因此Function函数可以像Excel内部函数一样在表达式中使用。
  • Sub过程可作为Excel中的宏来调用,而Function函数不会出现在“选择宏”对话框中,要在工作表中调用Function函数,可像使用Excel内部函数一样。
  • 在VBA中,Sub过程可作为独立的基本语句调用,而Function函数通常作为表达式的一部分。
2.9.2 定义函数

Function函数的创建方法与Sub过程的方法类似。在使用Function函数时,一般需要使用一个变量来接收返回值。

2.9.2.1 创建函数

创建函数有两种方法:

方法1:

通过对话框和手工输入代码。通过对话框创建函数的方法与创建Sub过程相似,在VBA编辑器中选择“插入”→“过程”命令,打开“添加过程”对话框,接着在“添加过程”对话框在“类型”栏中选择“函数”单选框,输入函数名称即可创建函数的结构。
在这里插入图片描述
方法2:

输入代码创建Function函数,Function结构如下:

    [Public | Private | Friend] [Static] Function 函数名 [(参数列表)] [As 返回类型]
          语句序列1
          函数名 = 表达式1
        Exit Function
          语句序列2
          函数名 = 表达式2

    End Function

与Sub过程不同的是,Function函数在声明函数名的第一行使用“As返回类型”定义函数的返回值类型;在函数体内,通过给函数名赋值来返回计算结果。

如果在函数体内没有赋值返回计算记过语句,则该函数返回一个默认值:数值函数返回0;字符串函数返回空字符串。

例如,使用Function函数实现数组求和功能:

    Function MySum(ParamArray intNum()) As Long
      Dim i As Integer, j As Long
      For i = LBound(intNum) To UBound(intNum)
          j = j + intNum(i)
      Next
      MySum = j
    End Function
2.9.2.2 调用函数

调用Function函数也有两种方式:一种是在工作表的公式中使用;另一种是从VBA的另外一个过程里调用。

1.在工作表中调用函数

自定义Function函数和系统内置函数一样,可在Excel工作表的公式中进行引用。如果不知道Function函数的名称或它的参数,可以使用“插入函数”对话框帮助用户向工作表中输入这些函数。例如,要在工作表中引用“MySum”函数的过程如下。

① 在Excel窗口中,单击选择一个单元格。

② 在功能区“公式”选项卡的“函数库”组中,点击“插入函数”按钮,将打开“插入函数”对话框:
在这里插入图片描述
③ 选择“用户定义”类别,并点击“确定”:
在这里插入图片描述
④ 在“函数参数”对话框可输入函数所需要的参数,并点击“确定”即可:
在这里插入图片描述

2.在VBA代码中调用函数

Function函数与内部函数没有什么区别,只不过内部函数VBA由系统提供,而Function函数是由用户自己定义的。因此,可以像使用VBA内部函数一样来调用Function函数。例如,以下代码调用MySum函数,并将计算结果保存到t中:

    Sub test1()
      Dim t As Long
      t = MySum(1, 3, 8, 10, 12)
      Debug.Print t
    End Sub

还可将Function函数作为表达式的一部分,使用其返回值参加表达式的运算,例如:

    t = t + MySum(1, 3, 8, 10, 12) * 3

3 Excel应用程序开发流程

3.1 需求分析

在进行Excel应用程序开发时,首先需要准确了解用户的需求,需求分析的目的是确定需要完成哪些工作。

需求分析阶段的主要任务如下:

  • 功能需求:给出应用程序必须完成的所有功能。
  • 环境需求:用户的计算机硬件环境、软件环境和Excel的版本等。
  • 界面需求:应用程序的用户界面是直接面对用户的,界面设计是用户能否方便、快捷地操作应用程序的关键之一。在需求分析阶段,应提出界面的需求。
  • 安全保密需求:对客户信息的保密要求应在本阶段进行计划。
  • 用户技术层次:在需求分析阶段,了解用户的技术层次,可为应用程序的开发提供一些辅助信息。

3.2 界面设计

在Excel中进行界面设计的方式主要有以下3种:

  1. 在工作表中添加控件
    在较简单的应用程序中(只需要调用少数几个宏过程),可向工作表中添加按钮或其他控件,接着与宏过程进行绑定即可:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 用户窗体
    用户界面是应用程序的一个重要组成部分。在Excel的应用程序中,用户窗体作为应用程序的用户界面部分,将用户的操作和Excel工作表中的数据隔离开。
    在VBA编辑器中插入一个用户窗体:
    在这里插入图片描述
    使用工具箱(视图→工具箱打开)中的控件设计窗体:
    在这里插入图片描述
  3. 自定义功能区
    功能区是从Excel 2007起新增加的组件,取代了以往版本的菜单和工具栏,可使用XML代码自定义功能区。
    在这里插入图片描述

3.3 代码设计

将用户界面设计好以后,接下来就需要编写界面中各部分的事件代码(如用户窗体中的按钮、功能区中的按钮等)。

在Excel中设计VBA应用程序时,界面设计和代码设计一般是交替进行的,即设计好一个界面后就编写相应的代码。有时也可先录制修改好宏代码,再和工作表或用户窗体中的按钮进行绑定。

3.4 帮助系统

在Windows应用程序中提供了在线电子文档的帮助系统,Excel也可以制作这种帮助系统。
对于小型应用系统,一般不提供帮助系统;但对于一个大型应用系统,提供一个好的帮助系统可让用户更快地理解系统,更快地熟悉系统的功能。

3.5 系统测试

在创建了应用程序之后,必须对其进行测试,测试和调试应用程序所花费的时间可能与开发系统的时间同样多。

对于一个完成开发的应用程序,在设计测试数据时,应尽可能多地考虑到各种不同的情况:不但要使用正常的合乎逻辑的数据去测试应用程序的功能性,还应使用一些可能导致应用程序出错的数据去测试应用程序的健壮性。

在设计测试数据的同时应编写出测试数据的结果,并与应用程序进行实测时得到的数据进行对比,如果结果相同,则通过测试;否则,应检查并修改应用程序。

3.6 应用程序发布

通过测试后的应用程序就可以发布给最终用户使用了。在发布时需要注意以下3个问题:

  1. Excel版本:如果是在Excel特定版本环境下开发的应用程序,并使用了该特定版本的一些新功能,就要求用户使用该Excel的特定版本。
  2. 动态链接库:如果应用程序中使用了ActiveX控件,则需要考虑是否要将包含该ActiveX控件的DLL文件(或OCX文件)包含在应用程序中予以发布。
  3. 辅助文件:在一个大型的应用程序中,有时可能还会使用到其他辅助文件(如图片文件、数据库文件和帮助文件等),需要将这些文件包括在发布文件中,并且最好将其发布到其他盘符中进行测试,以检查在VBA代码中是否使用了绝对路径来引用相关的文件。

4 常用功能

  • 待完成
  • 24
    点赞
  • 137
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值