第3章 对象编程基础
本书前两章详细介绍了VBA的语法规则和编程技术,这些内容虽然通用于所有支持VBA的Microsoft Office组件,但是要想在Excel中通过编程来完成实际工作,还需要掌握Excel对象模型及其中常用对象的使用方法。本章将介绍对象编程的基本概念和技术,这些内容是使用Excel VBA编程来处理各种Excel对象、实现各种功能的基础。
3.1 理解类、对象与集合
对象是Excel VBA编程的核心,在Excel中进行编程的整个过程几乎都离不开对象。打开一个工作簿,新建一个工作表,在单元格中输入数据,这些操作中的工作簿、工作表、单元格都是对象。本节将介绍对象编程的基本概念,包括Excel对象模型、类和对象、使用对象浏览器查看类和对象的相关信息、引用集合中的对象、父对象与子对象等内容。
3.1.1 Excel对象模型
虽然在Excel中编程会涉及大量的VBA语言元素和语法规则,但是它们操作的主体都是Excel对象。整个Excel程序由大量的对象组成,Excel程序本身就是一个对象,其内部的工作簿、工作表、单元格也都是不同的对象,所有这些对象按照特定的逻辑结构组成了Excel对象模型。
Application对象位于Excel对象模型的顶层,它表示Excel程序本身。在Excel程序中可以创建很多个工作簿,每个工作簿都是一个Workbook对象。每个工作簿可以包含很多个工作表,每个工作表都是一个Worksheet对象。每个工作表又包含很多个单元格,每个单元格或单元格区域都是一个Range对象。上述几种对象的层次结构可以表示为如下形式。Excel对象模型中的其他对象可以在此基本结构的基础上继续扩展,从而构成复杂的体系结构。
Application→Workbook→Worksheet→Range
Excel VBA帮助系统提供了Excel对象模型中每一个对象的详细说明。由于Excel对象模型中包含上百个对象,所以在短时间内掌握所有对象几乎是不现实的,可以先学习较常用的对象,然后再逐步扩展到其他对象。
3.1.2 类与对象
类是面向对象程序设计中的一个重要概念。可以将类想象成Excel中的工作簿模板,所有新建的工作簿都是基于工作簿模板创建的,这些新建的工作簿继承了工作簿模板中的内容、格式设置和其他特性。可以根据需要,在创建工作簿后对工作簿中的内容和格式进行自定义设置,从而使工作簿中的内容和格式不同于其他工作簿。
对于类而言,所有新建的对象都是基于类创建的,这些对象称为类的实例,它们继承了类的属性、方法和事件。可以根据需要,在创建对象后通过设置对象的属性、方法和事件,使同一类对象具有不同的特征和行为方式。例如,工作簿中的每一个工作表都是一个Wordsheet对象,通过修改工作表的名称和标签颜色,可以使各个工作表具有不同的名称和标签颜色。
3.1.3 使用对象浏览器查看类和对象的相关信息
在Excel中编程遇到的主要困难之一是很难掌握复杂的Excel对象模型中各对象之间的关系,以及每个对象所包含的属性和方法。Excel提供了一个用于查询类、对象、集合、属性、方法、事件、常数的易用工具——对象浏览器。打开VBE窗口,可以使用以下几种方法打开对象浏览器:
□ 单击菜单栏中的“视图”|“对象浏览器”命令。
□ 单击“标准”工具栏中的“对象浏览器”按钮。
□ 按F2键。
打开的对象浏览器如图3-1所示。由于在“工程/库”中默认选择的是“所有库”,因此在“类”列表中会显示当前引用的所有库以及当前工程中包含的所有类。如果想要查看某个库中包含的内容,则可以在“工程/库”列表中选择一个特定的库,“类”列表会自动显示所选库中的类。在“类”列表中选择一个类,右侧会显示该类的成员,即类的属性、方法和事件。如果想要快速查找特定信息,可以在搜索框中输入信息的名称,然后单击右侧的搜索按钮。下方会显示搜索结果,可以单击按钮显示或隐藏搜索结果。
图3-1 对象浏览器
在对象浏览器中使用不同的图标来区分不同内容,表示库,表示类,表示对象的属性、表示对象的方法,表示对象的事件,表示常数。
3.1.4 引用集合中的对象
同一类对象组成了该类对象的集合,对象是集合中的成员。在Excel对象模型中存在着很多对象以及与其对应的集合,例如Worksheet对象和Worksheets集合,Worksheet表示一个工作表,Worksheets表示一个工作簿中的所有工作表。在拼写形式上,集合比其相关的对象在名称末尾多了一个字母s。
有时需要引用集合中的某个对象,可以使用对象的名称或索引号来引用集合中的对象。例如,如果工作簿中包含从左到右依次排列的名为“1月”“2月”和“3月”的3个工作表,那么当需要引用名为“2月”的工作表时,可以使用以下两种方法:
使用名称进行引用:Worksheets("2月") 使用索引号进行引用:Worksheets(2)
第一种方法最安全,只要不改变工作表的名称,即使调整工作表的排列顺序,使用同一个名称始终都引用同一个工作表。而第二种方法引用的是位于第2个位置上的工作表,如果调整工作表的顺序,那么位于第2个位置上的工作表就不一定是名为“2月”的工作表了,因此在引用特定工作表时使用第二种方法并不安全。
需要注意的是,如果引用的工作表所在的工作簿是当前活动的工作簿,那么可以直接使用上面的形式来引用。如果要引用的工作表所在的工作簿不是活动工作簿,那么必须添加工作簿的限定,类似于如下所示:
Workbooks("一季度").Worksheets("2月")
3.1.5 父对象与子对象及其定位方法
Excel对象模型中的各个对象彼此交错关联,形成了相对复杂的层次结构。前面介绍的Application对象位于Excel对象模型的顶层,Workbook对象位于Application对象的下一层,Worksheet对象又位于Workbook对象的下一层。可以将处于上一层的对象称为父对象,将处于下一层的对象称为子对象。那么在上面的例子中,Workbook对象的父对象是Application对象,Workbook对象的子对象是Worksheet对象。
大多数对象都有一个Parent属性,使用该属性可以返回对象的父对象。下面的代码返回名为“2月”的工作表所在的工作簿的名称:
Worksheets("2月").Parent.Name
由于Worksheet对象的父对象是Workbook对象,因此Worksheets("2月").Parent返回的是名为“2月”的工作表所在的工作簿的Workbook对象,之后使用该对象的Name属性返回了工作簿的名称。
如果希望返回名为“2月”的工作表所在的Excel程序的版本号,那么可以使用下面的代码,通过Parent属性从当前对象依次返回上一层的父对象,直到Application对象为止。第一个Parent属性返回的是Worksheet对象的父对象,即Workbook对象。第二个Parent属性是Workbook对象的属性,因此返回Workbook对象的父对象,即Application对象。
Worksheets("2月").Parent.Parent.Name
如果对象的层次级别很低,那么在上面的代码中可能需要使用更多的Parent属性。实际上Excel提供了从任意层级的对象直接跳转到顶层对象的方法,即对象的Application属性,大多数对象都包含该属性。因此,可以将前面的代码修改为以下形式:
Worksheets("2月").Application.Version
3.2 对象的属性
基于同一个类创建的一系列对象在最初阶段具有完全相同的外观和特征。为了让对象区别于其他同类对象,可以通过设置对象的属性来改变对象所具有的特征。例如,在默认情况下,工作表中的单元格具有相同的行高、列宽、填充色。当改变某个单元格的行高、列宽、填充色后,该单元格的外观将不同于其他单元格。行高、列宽、填充色等就是单元格的属性。通过修改对象的属性,可以改变对象的外观或状态。本节将介绍对象属性的引用和赋值的方法,还介绍了可返回对象的属性这一重要概念。
3.2.1 引用对象的属性
如果要引用一个对象的属性,可以使用以下格式:
对象的名称.对象的属性
下面的代码引用的是Application对象的Version属性。由于VBA具有自动弹出成员列表的功能,因此在输入一个正确的对象名称和一个英文句点后,将会自动弹出该对象包含的属性和方法的成员列表,如图3-2所示,使用方向键选择所需的属性,然后按Tab键将其输入到代码窗口中。
Application.Version
图3-2 使用自动弹出的成员列表查看和输入对象的属性
当然,也可以直接手动输入属性,但要确保不要出现拼写错误,否则在运行代码时会出现运行时错误。
在VBA中不能直接运行引用了对象属性的语句,否则会出现编译错误。但是使用类似下面的语句,则可以正常运行该语句,并在对话框中显示对象的属性值。
MsgBox Application.Version
为了便于使用对象的属性值,可以将其赋值给一个变量,然后在后面的代码中使用该变量代替对象属性的引用,这样做不但减少了代码的输入量,还可以加快代码的运行速度。下面的代码将Excel程序的版本号赋值给名为strVer的变量。
strVer = Application.Version
3.2.2 设置属性的值
通过设置对象的属性,可以改变对象的特征。设置对象属性的方法与将对象的属性赋值给一个变量类似,只不过是相反的过程。要设置属性的值,首先使用上一节介绍的方法输入对象属性的引用,然后输入一个等号,在等号右侧输入要为属性设置的值。
案例3-1 设置工作表的名称
下面的代码将InputBox函数的返回值赋值给Worksheet对象的Name属性,从而将用户输入的内容指定为当前活动工作表的名称。代码中的ActiveSheet引用当前活动的工作表,它是Application对象的一个属性,第4章将会详细介绍Application对象及其应用。
Sub 设置工作表的名称() Dim strName As String strName = InputBox("请输入工作表的名称:") If strName <> "" Then ActiveSheet.Name = strName End Sub
下面的代码将数字100输入到当期活动工作表的A1单元格中:
Range("A1").Value = 100
由于Value属性是Range对象的默认属性,因此在设置该属性时不需要输入Value,而使用下面的形式:
Range("A1") = 100
虽然默认属性为代码的输入提供了方便,但是仍然建议输入完整的属性名,这样可使代码易于理解。
3.2.3 可返回对象的属性
很多对象的属性可以返回另一个对象,这句话看起来可能不容易理解。下面的代码用于设置A1单元格中的字体格式。Range是一个对象,Font是该对象的属性,但是在Font之后还有一个Name。Name是Font的属性吗?可是Font不是Range对象的属性吗?所以Font应该不会是对象,那么Name又是什么?对于没有太多Excel VBA编程经验的用户而言,这句代码很容易带来困惑。
Range("A1").Font.Name = "宋体"
这句代码中的Font既是属性,又是对象。首先,Range("A1").Font这部分中的Font是作为Range对象的属性出现的,用于设置Range对象的字体格式。然而Excel会在运行Range("A1").Font之后,通过Font属性返回一个Font对象,之后又为该对象使用了Name属性。因此,上面的代码相当于设置的是Font对象的Name属性,开头的Range("A1")部分只是限定了设置字体格式的是哪个单元格。
Excel VBA中的很多对象的属性都能返回另一个对象。换句话说,某个对象其自身是一个独立的对象,但同时它可能还会作为另一个对象的属性出现。
3.3 对象的方法
方法是对象自身拥有的动作,通过使用对象的方法,可以执行与对象相关的操作,从而改变对象的状态。例如,打开工作簿中的“打开”(Open)就是Workbook对象的一个方法,“打开”这个方法的结果是向工作簿集合中添加了一个新的工作簿成员。对象的方法的使用主要在于设置方法的参数,参数为执行的操作提供了所需的数据或执行方式。此外,与可返回对象的属性类似,某些对象的方法也可以返回另一个对象。除了属性和方法,对象还包含事件,第12章将会详细介绍对象的事件。
3.3.1 方法的参数
为了给对象的方法提供所需操作的数据或操作方式,很多方法都包含一个或多个参数。例如,Workbooks集合有一个Add方法,它包含一个参数,用于指定新建工作簿时以哪个模板为基准。
要输入对象的方法,首先需要输入对象的名称,然后输入一个英文句点。如果对象名称的拼写正确,则会自动弹出该对象包含的属性和方法的成员列表,从中选择所需的方法并按Tab键,将方法输入到代码窗口中。按一下空格键,将会显示如图3-3所示的提示信息,加粗显示的部分是当前准备接收用户输入的方法的第一个参数,这里是Template。该单词用方括号括起,表示这是一个可选参数,也就是说不是必须要提供该参数的值。如果不显示可选参数的值,Excel会自动使用其默认值。
图3-3 包含一个参数的方法
有些方法包含多个参数,例如Worksheets对象的Add方法,它包含4个参数,如图3-4所示。这些参数都用方括号括起,因此它们都是可选参数,在使用Worksheets对象的Add方法时可以不提供任何参数的值,而使用默认设置添加新的工作表。
图3-4 包含多个参数的方法
在输入多个参数的值时,各参数值之间需要以英文逗号分隔,而且必须按照各参数的默认顺序依次输入。当输入第一个参数和一个英文逗号后,第二个参数的名称会自动加粗显示,如图3-5所示,从而提醒用户当前正在设置的是哪个参数。
图3-5 输入不同参数时会以加粗字体提醒用户
如果要省略其中的某个参数值,而跳到下一个参数值的设置上,则必须为省略的参数保留一个英文逗号。如图3-6所示跳过了第二个参数After,而直接设置第三个参数Count的值,因此需要为第二个参数额外保留一个英文逗号以占位。
图3-6 省略某个参数的值时需要为其保留一个英文逗号
当然,也有一些方法不需要参数,例如Workbook对象的Save方法,该方法不包含任何参数,下面的代码用于保存当前包含代码的工作簿。
ThisWorkbook.Save
3.3.2 使用命名参数
在为一个方法设置多个参数值时,如果省略了其中的某个参数值,则必须为其保留一个英文逗号,以便让Excel可以根据逗号来正确识别参数值的对应关系。如果不想为省略的参数额外保留一个英文逗号,则可以使用命名参数的方式来设置参数值。使用命名参数的好处还在于可以按任意顺序指定参数值,以及增加代码的可读性。
在指定参数值时使用命名参数的方法是:先输入参数的名称,然后输入一个冒号和一个等号,最后输入要为参数指定的值。参数的名称不严格区分大小写。下面的代码是对上一节新建工作表的案例修改后的版本,其中使用了命名参数。参数的指定顺序与Add方法默认的参数顺序不同,此处先设置的是第三参数Count的值,然后设置的是第二参数After的值。
Worksheets.Add Count:=5, After:=Worksheets(2)
3.3.3 可返回对象的方法
与某些属性可返回新的对象类似,对象的某些方法也可以返回新的对象。例如,在使用Worksheets集合的Add方法新建工作表后,默认情况下会返回一个Worksheet对象,表示对刚刚新建的工作表的引用。
如果要在后面的代码中使用方法所返回的对象,那么通常可以将该方法的返回值赋值给一个对象变量,然后在代码中使用该变量来代替返回的对象。与使用函数的返回值时设置参数的方式类似,需要将对象的方法的所有参数值放置在一对圆括号中,然后通过使用等号将方法的返回值赋值给一个变量。
案例3-2 在程序中使用由方法返回的对象
下面的代码将Worksheets.Add方法新建的工作表赋值给名为wks的对象变量,然后使用wks变量来设置新建工作表的名称。intIndex变量用于指定新建工作表的索引号,本例中为将新工作表放置在最后一个工作表之后,因此通过Worksheets.Count获取工作簿中包含的工作表总数。
Sub 可返回对象的方法() Dim wks As Worksheet, intIndex As Integer intIndex = Worksheets.Count Set wks = Worksheets.Add(after:=Worksheets(intIndex)) wks.Name = "1月" End Sub
3.4 对象编程技巧
本节将介绍在VBA中进行对象编程时经常用到的3个技巧,实际上它们也属于VBA的语言元素,只不过主要用在对象编程方面。下面列出了这3个技巧的简要描述,本章剩余内容将对它们做详细介绍。
□ 使用对象变量:简化对象引用,减少代码输入量,提高程序运行效率。
□ 使用With结构:简化对象引用,减少代码输入量,提高程序运行效率。
□ 使用For Each结构:遍历集合中的对象。
3.4.1 使用对象变量
在VBA中,对象也是一种数据类型,可以将变量声明为一般对象类型或特定对象类型。一般对象类型表示为Object,特定对象类型由对象所属的类决定,比如工作簿对象类型表示为Workbook,工作表对象类型表示为Worksheet。
无论将变量声明为哪种对象类型,在声明和使用对象变量时都需要遵循以下3个步骤:
声明对象变量→为对象变量赋值→释放对象变量占用的内存空间
声明对象变量的方法与声明普通变量类似。下面的代码声明了一个Worksheet类型的对象变量wks,该变量代表一个工作表:
Dim wks As Worksheet
在使用已经声明好的对象变量之前,需要使用Set关键字将某个具体的对象赋值给对象变量。就上面的代码而言,需要将一个实际的工作表赋值给wks对象变量。假设在当前的活动工作簿中存在一个名为“1月”的工作表,那么下面的代码将该工作表赋值给wks对象变量。
Set wks = Worksheets("1月")
为对象变量赋值后,之后就可以使用对象变量代替实际的对象引用,不但可以减少对象引用的代码输入量,还可以提高程序的运行效率。本例中可以使用wks变量代表名为“1月”的工作表,下面的代码显示名为“1月”的工作表的名称。
MsgBox wks.Name
当不再需要使用对象变量时,应该使用Set关键字将对象变量赋值为Nothing,以释放对象变量占用的内存空间,如下所示:
Set wks = Nothing
案例3-3 使用对象变量引用特定的对象
下面的代码将上面几行分开的语句合并到一起,形成完整的使用对象变量的案例。为了避免由于指定的工作表不存在而导致的运行时错误,因此在代码中加入了防错机制。如果指定的工作表不存在,则会显示一条提示信息并退出程序,否则显示工作表的名称。
3.4.2 使用With结构
正常情况下,当需要对同一个对象进行多种操作时,会在代码中多次引用该对象。下面的代码对活动工作簿中的第一个工作表的A1:C6单元格区域进行了一系列设置:
代码中的Worksheets(1).Range("A1:C6")部分重复出现了7次,VBA会对每次遇到的对象之间的英文句点进行解析,因此上面这段代码的运行效率会受到严重影响。
在VBA中,当需要在代码中反复引用同一个对象时,为了提高程序的运行效率,同时减少代码的输入量,可以使用With结构简化对象的引用。With结构的格式如下:
With 要引用的对象 要为对象执行的操作 End With
在With语句之后输入要引用的对象,按Enter键后VBA会自动添加End With语句。在With语句和End With语句之间放置要为对象执行的操作,通常是为了设置位于With语句之后的对象的属性和方法。这些属性和方法以英文句点开头,然后可从自动弹出的成员列表中选择所需的属性和方法。
案例3-4 使用With结构简化对象的引用
下面的代码使用With结构对上一个案例进行了修改,在With和End With之间省略了Worksheets(1).Range("A1:C6")部分,代码看起来更简洁。
With结构也可以嵌套在另一个With结构中。上面代码中的第3~5行重复出现了3次Font,因此也可以使用With结构对该对象的引用进行简化。下面的代码使用了嵌套的With结构,外层的With结构处理的是Range对象,内层的With结构处理的是Font对象。
3.4.3 使用For Each结构
For Each结构主要用于处理集合中的对象,尤其适用于包含大量对象的集合,而且预先不知道对象的数量的情况。可以使用For Each结构对逐个对象进行处理,也可以只处理集合中符合特定条件的某些对象。
For Each结构的语法格式如下:
For Each element In group [statements] [Exit For] [statements] Next [element]
□ element:必选,用于遍历集合中的每一个对象或数组中的每一个元素的变量。
□ group:必选,要在其内部进行遍历的集合或数组。
□ statements:可选,For Each结构中包含的VBA代码,它们将被重复执行,直到处理完集合中的最后一个对象。
□ Exit For:可选,中途退出For Each循环。
For Each结构与For Next结构有些类似,都用于循环执行特定的代码,但是For Each结构是对集合中的每一个对象重复执行相同的代码,而不是重复执行指定次数的代码,For Each结构对代码进行重复执行的次数取决于集合中的对象总数。当处理完集合中的最后一个对象后,将会自动退出For Each结构,也可以使用If Then结构设置判断条件,只对符合条件的对象执行操作,并使用Exit For语句中途退出循环。
案例3-5 使用For Each结构遍历集合中的对象
下面的代码显示了当前活动工作簿中的每个工作表的名称。由于运行代码前无法确定活动工作簿中包含的工作表数量,因此很适合使用For Each结构来进行处理。
For Each结构也可以嵌套使用,即在一个For Each结构中嵌套另一个For Each结构。下面的代码显示了当前打开的每一个工作簿中的每一个工作表的名称,由于运行代码前无法确定当前一共打开了多少个工作簿,以及每个工作簿中包含多少个工作表,因此使用两个嵌套的For Each结构,外层的For Each结构用于处理打开的每一个工作簿,内层的For Each结构用于处理工作簿中的每一个工作表。