Excel 2007 VBA开发技术大全
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第2章 Excel的对象模型及其主要对象

本章将从Excel 2007对象模型开始,介绍面向对象编程的一些基本概念,然后介绍 Application 顶级应用程序对象的常用的属性和方法、Workbooks 和Worksheets集合对象,以及Range和Name对象。

本章的知识点如下。

(1)Excel 2007对象模型。

(2)Application对象。

(3)Workbooks工作簿集合对象。

(4)Worksheets工作表集合对象。

(5)Range对象的一些常用属性。

(6)Range对象的一些常用方法。

(7)使用Name名称。

2.1 Excel 2007对象模型

VBA程序语言广泛应用于所有Microsoft Office应用程序,一旦掌握了其中任何一个应用程序中的VBA,即可举一反三在其他应用程序中使用。要处理一个应用程序,需要了解其包括的对象,即可在不同的应用程序使用不同的对象。例如,在 Word 中处理的是documents、paragraphs,以及words对象;在Access中处理的是databases、recordsets和fields对象;在Excel中处理的是workbooks,worksheets和ranges对象。

与许多编程语言不同,并一定需要在Office VBA中创建用户自定义对象。每个应用程序都有一套明确定义并按照彼此间关系安排的对象,这个架构就是应用程序的对象模型。

2.1.1 Objects对象及对象层次结构

在VBA代码中引用Range对象的示例为一个简单的引用单元格B2:C4的方法,如下所示:

        Range("B2:C4")

如果已命名要引用的区域,那么可以以类似的方式使用该名称:

        Range("Data")

也可以使用快捷方式引用当前活动单元格和选择区域。

在图2-1 中,ActiveCell(活动单元格)指向 B2 单元格,Selection(选择区域)指向B2:E6区域。

图2-1 Excel中的对象

2.1.2 集合

多个对象都属于集合,一个城区是高楼大厦的集合。集合从本身来说,也是对象,是一个包含其他密切关联对象的对象。集合和对象常常以层次结构和树状结构相关联。

Excel本身是一个对象,其名为“Application”。在Excel Application对象中包含所有当前打开的工作簿对象的Workbooks集合。每个Workbook对象又包含一个Worksheets集合,该集合包括了工作簿中所有的Worksheet(工作表)集合。

提示

需要明确区分复数Worksheets和单数Worksheets对象,前者是一个工作表的集合;而后者仅为一个工作表对象,二者是两个明显不同的对象。

如果打算引用集合对象的一个成员,可以通过其在集合中的位置(从1开始的序列号),也可以通过名称(加双引号的文本)。例如,如果打开且仅打开一个名为“Data.xls”的工作簿,可以通过如下两种方式之一引用:

        Workbooks(1)
        Workbooks("Data.xls")

如果在活动工作簿中依此有名为“North”、“East”和“South”3 个工作表,在该次序下可以如下两种方式引用第2个工作表:

        Worksheets(2)
        Worksheets("East")

如果想要引用名为 Sales.xls 工作簿中名为“DataInput”的工作表,必须要通过对工作簿的引用来限定对工作表的引用。通过一个点号分隔,如下所示:

        Workbooks("Sales.xls").Worksheets("DataInput")

在其他工作簿处于激活状态时,如果引用DataInput工作簿中的B2单元格,使用如下语句:

        Workbooks("Sales.xls").Worksheets("DataInput").Range("B2")

2.1.3 属性

属性是对象的物理特征,可以被度量或是限定。

一个worksheet Range对象有一个RowHeight和ColumnWidth属性,一个Workbook对象有一个Name属性(该属性包括其文件名)。一些属性很容易被改变,如可以通过为Range对象的ColumnWidth属性赋一个新值来改变该属性值。其他如Workbook对象的Name属性是只读的,不能通过简单地为该属性赋一个新值来改变工作簿的名称。

要引用一个对象的属性,首先引用该对象,然后跟一个点号和属性。

例如,要将活动单元格的列宽改为20点,则使用如下语句为活动单元格的ColumnWidth属性新值:

        ActiveCell.ColumnWidth = 20

为了在C10单元格中输入名字“Florence”,可以为Range对象的Value属性分配这个名字:

        Range("C10").Value = "Florence"

如果该Range对象不在活动工作簿的活动工作表中,则需要更加具体的引用:

        Workbooks("Sales.xls").Worksheets("DataInput").Range("C10").Value = 10

提示

VBA可以完成一些无法通过手工操作完成的任务,如在屏幕上不可见的工作表中输入数据。可以无需使工作表成为活动工作表的情况下执行复制和移动数据操作,因此很少需要使用VBA激活一个特定的工作簿、工作表或range对象来操作数据。避免激活对象可使代码运行速度加快,然而宏录制器仅能够录制针对激活对象的操作。

在前面的例子中,已经看到如何将值分配给对象的属性,也可以将对象的属性值分配给变量或其他对象的属性。例如,可以使用如下语句将活动工作表中的一个单元格的列宽分配给另一个单元格:

        Range("C1").ColumnWidth = Range("A1").ColumnWidth

也可以将位于同一个活动工作簿上的活动工作表中 C1 单元格中的值分配给工作表Sales上单元格D10:

        Worksheets("Sales").Range("D10").Value = Range("C1").Value

可以将属性值分配给一个变量,以备以后在代码中使用。如下的示例将单元格 M100的值存储在一个变量中,并赋一个新值给M100。然后打印自动计算的结果,最后将M100单元格的值设为初值:

        OpeningStock = Range("M100").Value
        Range("M100").Value = 100
        ActiveSheet.PrintOut
        Range("M100").Value = OpeningStock

一些属性是只读的,这意味着不可以直接为其分配一个值。某些时候也有间接的方法,一个例子是 Range 对象的 Text 属性。可以将值赋给其 Value 属性,并通过 NumberFormat属性来改变该单元格数字格式。

单元格的Text属性给出了单元格格式化后的外观,如下例在消息框中显示$12,345.60:

        Range("B10").Value = 12345.6
        Range("B10").NumberFormat = "$#,##0.00"
        MsgBox Range("B10").Text

这也是惟一可以设置Text属性值的方法。

2.1.4 方法

属性是对象可以计量的特征,而方法是对象或作用于对象的操作。以语言比喻,对象是名词,属性就是形容词,而方法是动词。

一个Excel方法的简单例子就是Range对象的Select方法,如同处理属性要引用一个方法。首先放好对象,添加一个点号。然后添加方法,如下方例选择单元格G4:

        Range("G4").Select

另一个Excel方法的例子是Range对象的Copy方法,下例将区域A1:B3的内容拷贝到剪贴板中:

        Range("A1:B3").Copy

方法常常都有参数,可以用来修改方法工作的方式。例如,可以使用工作表对象的Paste方法将剪贴板的内容粘贴进工作表中。但是如果没有指定数据粘贴的位置,则将数据插入活动单元格的左上角,这可以通过Destination参数来避免(在本节的后面将讨论参数):ActiveSheet.Paste Destination:=Range("G4")

注意参数值通过“:=”,而非“=”指定。通常Excel方法提供快捷键,前面的Copy和Paste示例完全可以通过Copy方法实现:

        Range("A1:B3").Copy Destination:=Range("G4")

这远比通过宏录制器生成的代码有效率:

2.1.5 事件

VBA中的另外一个重要概念就是对象都可以响应事件,单击命令按钮、双击单元格、工作表的重算,以及工作簿的打开和关闭等均为事件。

所有的ActiveX控件都可以响应事件,这些控件可以内嵌在工作表和窗体中以提高这些对象的功能,工作表和工作簿也可以响应很大范围的事件。如果需要一个对象来响应一个事件,将VBA代码输入该对象的合适的事件过程,事件过程存在于工作簿、工作表或用户窗体对象相关的代码模块中。

例如,如需监视用户是否已选择一个新单元格并且需要高亮显示该单元格所在的整行和整列,可以在Worksheet_SelectionChange()事件过程中输入代码。

(1)激活VBE窗口,并在“工程资源管理器”窗口中双击工作表。

(2)在工作表代码模块顶端的左右下拉列表框中,分别选择 Worksheet 和SelectionChange,并键入如下代码:

        Private Sub Worksheet_SelectionChange(ByVal Target As Range)
        Rows.Interior.ColorIndex = xlColorIndexNone
        Target.EntireColumn.Interior.ColorIndex = 36
        Target.EntireRow.Interior.ColorIndex = 36
        End Sub

每次用户选择一个新单元格,或单元格区域时,该事件过程都会运行。Target参数引用选择的区域作为一个Range对象。第1行代码将工作表上所有单元格的ColorIndex属性都设为无颜色,以移除任何已存的背景色,第 2 行和第3 行将选择单元格所在的整行和整列的背景色设为浅黄色。这个颜色可能有所不同,这主要取决于工作簿的颜色模板设置。

在本例中所使用的属性比之前的例子都要复杂。如果假定 Target 对象是一个指向单元格B10的Range对象,如下代码将使用B10Range对象的EntireColumn属性来指向整个B列,即B1:B1048576或简称为“B:B”:

        Target.EntireColumn.Interior.ColorIndex = 36

同理,如下代码改变第10行(A10:XFD10)或简称为“10:10”的颜色:

        Target.EntireRow.Interior.ColorIndex = 36

Range对象的Interior属性指向一个Interior对象,指一个Range的背景色,最后将Interior对象的ColorIndex属性设置为所需颜色的序列号。

2.1.6 获取帮助

找出用来执行一个操作的代码的最简单方式是执行这个操作,并使用宏录制器。录制的代码也许效率很低,但是将指出所需的对象,以及相应的属性和方法。例如打开录制器来找出如何设置单元格的背景色,将获得如下所示的代码:

        With Selection.Interior
        .Pattern = xlSolid
        .PatternColorIndex = 56
        .Color = 65535
        .TintAndShade = 0
        .PatternTintAndShade = 0
        End With

With...End With结构体将在本章的后面详细讨论,这等同于:

        Selection.Interior.Pattern = xlSolid
        Selection.Interior.PatternColorIndex = 56
        Selection.Interior.Color = 65535
        Selection.Interior.TintAndShade = 0
        Selection.Interior.PatternTintAndShade = 0

上面的代码中指定Pattern、TintAndShade和PatternTintAndShade的代码行都是不需要的,因为其设置的为默认值。宏录制器不能判断用户需要和不需要什么,所以包含所有的操作。不论如何,录制的代码能够提供给用户开始的线索。再次只需要了解如何将 Range 及Selection及改变为整行或整列,如果这些完成,就可以使用Range对象的属性或方法来完成。

2.1.7 对象浏览器

如图2-2所示的对象浏览器是一个只能在VBE窗口中用来发现Excel对象所拥有的属性、方法和事件的有用工具,有如下3种方式可以打开。

图2-2 对象浏览器

(1)单击“视图”|“对象浏览器”命令。

(2)按F2键。

(3)单击“标准”工具栏上的对象浏览器按钮

对象都罗列在标题“类”的窗口中,都是类的实例,可以在该窗口中单击并键入一个“r”来快速到达Range对象。

也可以单击搜索文本框(从顶向下第2个框,其旁边有一个望远镜按钮),并输入range。当按回车键或单击望远镜按钮时,将看到包含搜索项的项目列表。当单击搜索结果窗口中的类标题下的Range,Range在下面的类窗口中高亮显示,该技术在搜索关于某个特定属性、方法或事件的信息时非常便利。

现在选项获取了该对象的所有属性、方法和事件按字母排序的列表,右击该列表,在快捷菜单中选择“组成员”选项则按照属性、方法和事件显示。浏览列表将发现EntireColumn和 EntireRow属性可能是解决需求的可能方法。为了进一步确认,选择 EntireColumn 并单击对象浏览器中顶端的问号图标,打开如图2-3所示的“Excel Help”窗口。

图2-3 “Excel Help”窗口

“请参阅”部分将指向有关相关对象和方法的更多信息,现在剩下的就是将发现的属性应用于合适的对象。

2.1.8 使用“立即”窗口来调试代码

如果需要试验代码,可以使用VBE的“立即”窗口,有3种方式可以打开该窗口。

(1)单击“视图”|“立即”窗口命令。

(2)按Ctrl+G快捷键。

(3)单击“调试”工具栏中的对象浏览器按钮

可以将 Excel 窗口和 VBE 窗口重排放置,然后在“立即”窗口中输入命令并在 Excel窗口中查看结果,如图2-4所示。

图2-4 使用“立即”窗口

当输入一个命令并按回车键,该命令就会被执行。要再次执行同样的命令,将鼠标指针放置在该行命令的任何地方,然后按回车键。

在此,ActiveCell 对象的 Value 属性被分配文本“Sales”,如果要显示一个值,在代码前加上问号(等同于Print方法):

        ?Range("B2").Value

上面的代码将Sales打印到“立即”窗口的下一行,最后一行代码将数值从B2复制到J2单元格。

2.2 Application对象

Application对象位于Excel对象模型的顶端,包括Excel中的所有其他对象,以及所有没有属于其他任何对象并且重要的方法和属性。例如,控制屏幕刷新和切换警告消息显示与否的Application的ScreenUpdating和DisplayAlerts属性,计算打开工作簿公式的Calculate方法等。

提示

本章示例参见光盘中的“Application示例.xlsm”工作簿。

2.2.1 全局对象

Application 对象的一部分方法和属性是<全局>成员,可以在如图2-5所示的对象浏览器中的类树状列表的顶端查看。

图2-5 在对象浏览器中查看<全局>对象的所有成员

如果一个方法或属性属于<全局>,那么引用该方法或属性时无须使用对象识别符。例如,如下的两个引用效果都是等同的:

        Application.ActiveSheet
        ActiveSheet

但是要特别注意,Application 对象的常用的属性并不都是全局成员,例如,如下代码是正确的:

        Application.DisplayAlerts = False

但是如下代码将获得“变量未定义”的错误提示:

        DisplayAlerts = False

如果没有上述代码所处的模块中没有“Option Explicit”,即不要求强制声明变量时,就不会出现“变量未定义”错误提示。代码将新建一个变量DisplayAlerts,并将值False分配给该变量,这样势必无法获得应有的效果。

提示

一定记得选择“工具”|“选项”|“编辑器”|“要求变量声明”复选框,这样 VBE在每次创建一个新模块时就会自动在模块顶端插入“Option Explicit”语句。

2.2.2 Active属性

Application 对象提供了多种引用激活对象的快捷方式,而不需显示声明该对象。这样能够轻易地获知代码运行时当前的激活对象并使得代码更具通用性,可以应用于使用不同名称的同类型的对象。

Application 对象的可以引用激活对象的<全局>属性为 ActiveCell、ActiveChart、ActivePrinter、ActiveSheet、ActiveWindow、ActiveWorkbook和Selection。

如果创建了一个新工作簿,并且将其保存为某个具体的文件名,使用 ActiveWorkbook属性是获取引用新Workbook工作簿对象的一种快捷方式:

        Workbooks.Add
        ActiveWorkbook.SaveAs Filename:=ThisWorkbook.Path & "\Test.xls"

如果需要编写一个使当前选择单元格字体加粗的宏,可以使用Selection属性来返回对包括选择单元格的Range对象的引用:

        Selection.Font.Bold= True

2.2.3 DisplayAlerts属性

在宏运行时,如果响应系统警告,将是让人十分不快的事情。例如,如果删除一个工作表时,会弹出一条警告消息,提示需要单击“确认”按钮以继续操作。并且用户还可能在此单击“取消”按钮,这样将不会删除该工作表;反过来就会可能会影响到后续代码的执行,因为其假定该工作表已经被删除。

可以通过将DisplayAlert属性设为False以屏蔽警告信息,当屏蔽一个警告对话框时与该对话框中默认按钮相关的操作就会被执行,例如:

        Application.DisplayAlerts = False
        ActiveSheet.Delete
        Application.DisplayAlerts = True

提示

不需要在宏的结尾将DisplayAlerts置回True,因为VBA会自动完成。但是应该养成习惯在屏蔽特定的信息之后,就将警告功能置回命令,因此才能防止屏蔽不应该屏蔽的信息。

DisplayAlerts 也常用于来屏蔽在使用“文件”|“另存为”命令覆盖已存文件时出现的警告信息。此时,执行默认的操作,文件会被覆盖而又不会中断宏的执行。

2.2.4 ScreenUpdating属性

在宏运行,尤其在运行使用宏录制器生成的包括大量的Select和Activate objects的代码时,屏幕刷新会使人眼花缭乱。

提示

如果能够避免选择或激活对象,代码的运行速度将大大加快。

如果想要在代码运行时冻结窗口,可以使用如下代码行:

        Application.ScreenUpdating = False

屏幕将一直处于冻结状态,直到分配给这个属性一个True值或代码结束运行,并将控制权交回用户界面。一般不必将 ScreenUpdating 置回 True,除非需要在代码运行过程中向用户展示屏幕改变情况。

如果在代码运行过程中显示一个用户窗体或内置对话框时,则需要将 ScreenUpdating置回 True,在显示对象时确保屏幕更新被打开;否则在屏幕上拖动用户窗体时。如果是其背景屏幕的一个橡皮擦,可以在显示对象后重新关闭屏幕刷新。

提示

关闭屏幕刷新的一个好处就是代码运行更快,甚至也会加快那些已经避免使用选择对象的代码。因为此时只需要很少的屏幕刷新,代码在实现避免选择对象和关闭屏幕刷新后将达到最快速度。

2.2.5 Evaluation方法

本节说明Evaluation方法。

(1)Evaluation方法的语法及其使用范围。

该方法主要用来将一个Microsoft Excel名称转换为一个对象或者一个值,其常用语法如下所示:

        Application 对象表达式.Evaluate("名称表达式")

也可以在“名称表达式”两端省略双引号,并使用方括号的快捷方式,如下所示:

        [名称表达式]

其中,名称表达式可以使用下列Microsoft Excel名称类型。

● 可以通过A1格式表示法引用单个单元格,所有引用均视为绝对引用。

● 在引用中使用区域、交集和联合运算符分别为冒号、空格和逗号。

● 使用在VBA中不能通过WorksheetFunction直接调用的工作表函数。

● 定义的名称。

● 可以使用!运算符引用另一工作簿中的单元格或已定义的名称,例如,Evaluate ("[BOOK1.XLS]Sheet1!A1")。

● 可以指定任何图表对象名称(如“Legend”、“Plot Area”或“Series 1”),以访问该对象的属性和方法。例如,Charts("Chart1").Evaluate("Legend").Font.Name 返回图例中所用字体的名称。

(2)示例。

使用方括号(例如,“[A1:C5]”)与用字符串参数调用Evaluate方法等效,例如,如下两句代码均实现在单元格B2中写入25:

        [B2].Value = 25
        Evaluate("B2").Value = 25

如下代码调用工作表函数COS计算45度的反正弦:

        vResult = [COS(45)]
        vResult = Evaluate("COS(45)")

如下代码实现对外部工作簿Test.xlsm中第1个工作表上A1单元格的引用:

        Set rngfirstCellInSheet = Workbooks("Test.xlsm").Sheets(1).[A1]
        Set rngfirstCellInSheet = Workbooks("Test.xlsm ").Sheets(1).Evaluate("A1")

Evaluate 方法还可以返回工作簿的Names 集合中的Name名称对象的内容,并能够高效生成数组值,例如,如下代码创建一个用来存放密码的隐藏名称。

首先在工作簿的Names集合中添加一个名为“RN_PassWord”的隐藏名称:

        Names.Add Name:="RN_PassWord", RefersTo:="TEST_All", Visible:=False

接下来如下所示使用表达式中的隐藏数据:

        sUserInput = InputBox("输入密码")
        If sUserInput = [RN_PassWord] Then

提示

隐藏名称在“名称管理器”及“新建名称”对话框等中不可见,它不能在Excel中通过手工方式直接创建,需要使用VBA创建。尽管不是一种高的隐藏信息的安全方式,但是隐藏名称也提供了一种在工作簿中存储信息的便利方式,本章最后一节将详细说明Name名称对象。

(3)Evaluation方法的两种使用形式。

使用方括号的优点在于代码较短,使用 Evaluate 的优点在于参数是字符串。这样既可以在代码中直接构造该字符串,也可以使用VBA变量。

例如在如下代码中构造一个公式表达式sFunctionName & "(" & sCellAddress & ")",那么使用Evaluate方式:

        Sub IsActiveCellEmpty()
        '判断当前选中单元格是否为空,并弹出相应的提示信息
            Dim sFunctionName   As String
            Dim sCellAddress   As String
            Dim sMsg          As String
            sFunctionName = "ISBLANK"
            sCellAddress = ActiveCell.Address
            sMsg = IIf(Evaluate(sFunctionName & "(" & sCellAddress & ")"), _
                  "当前激活单元格为空", "当前激活单元格不为空")
            MsgBox sMsg
        End Sub

如果只是对某个单元格执行简单操作,如为单元格A1赋值,那么使用方括号形式即可:

        [A1].Value = "Test"

而没有必要使用:

        Evaluate("A1").Value = "Test"

2.2.6 StatusBar属性

该属性用来返回或设置在屏幕底端Excel状态栏中左端显示的文字,这是一种在一个长时间的宏操作中通知用户处理进程的一个便利的方式。在运行时宏时常常需要关闭屏幕刷新,用户无从得知处理进程,所以在较长时间的处理中有必要给出程序进度的提示信息。即便关闭屏幕刷新,状态栏上仍然可以显示信息。

如下代码示范了如何在一个较长的循环处理中在状态栏上显示相应的进度提示信息:

        Sub ShowMsgInStatusBar()
            Dim lCounter As Long
            For lCounter = 0 To 200000000
              If lCounter Mod 2000000 = 0 Then
                  Application.StatusBar = "正在处理第" & lCounter & _
                                        "条记录"
              End If
            Next 'lCounter
            '交回Excel控制状态栏
            Application.StatusBar = False
        End Sub

本代码的运行效果如图2-6(左)所示,在程序运行结束需要将StatusBar设为False,以交回Excel控制状态栏,如图2-6(右)所示;否则最后一条信息就将停留在状态栏上。

图2-6 在状态栏上显示循环程序的进度和Excel控制状态栏

StatusBar 属性的设置在状态栏处于隐藏状态时也起作用,但是如果状态栏处于隐藏状态,则对其设置也没有意义,所以在操作之前应该确保其处于显示状态,并且一个好的编程习惯是在改变某项设置之前应该首先记下其最初的状态,在操作后恢复原状态。如下面的代码所示:

        Sub ChangeStatus()
            Dim bOldStatusBar As Boolean
            '在操作之前,获取并记录下状态栏是否显示
            bOldStatusBar = Application.DisplayStatusBar
            '显示状态栏
            Application.DisplayStatusBar = True
            '在操作之前在状态栏上显示"请等待..."的提示信息
            Application.StatusBar = "请等待..."
            '打开工作簿
            Workbooks.Open Filename:="Names名称.xlsm"
            '交回Excel控制状态栏
            Application.StatusBar = False
            '置回状态栏的最初显示状态
            Application.DisplayStatusBar = bOldStatusBar
        End Sub

2.2.7 SendKeys方法

SendKeys允许向当前激活窗口发送按键,通常用来控制不支持其他任何交互形式(如动态数据交换DDE或OLE)的应用程序,一般认为是解决问题所使用的最后一个手段。

如下程序打开一个不支持DDE或OLE 技术的记事本应用程序,并在其上面输入一行内容“SendKeys method Test”:

提示

如下程序需要在Excel中调用,不能在VBE中直接运行,并且默认输入法是英文输入法:

        Sub SendKeysTest()
            Dim dReturnValue As Double
            '启用记事簿应用程序
            dReturnValue = Shell("NOTEPAD.EXE", vbNormalFocus)
            '激活刚刚启用的记事簿程序
            AppActivate dReturnValue
            '在记事簿上输入字符串"SendKeys method Test"
            Application.SendKeys "SendKeys method Test", True
            '按Enter回车键结束输入
            Application.SendKeys "~", True
            '按“文件”|“另存为”,并使用SendKeysTest文件名存储
            Application.c "%FASendKeysTest%S", True
        End Sub

SendKeysTest过程首先使用Alt+F+A快捷键来执行“文件”|“另存为”操作,并输入“SendKeysTest”来保存该文本文件。百分号(%)代表Alt,波形符(~)代表Enter,插入符号(^)代表 Ctrl,这些都是没有屏幕显示的字符的按键。有屏幕显示的字符直接发送该字符即可,如本例中的“SendKeysTest”。没有屏幕回显的按钮除了上面介绍的 Alt、Enter及Ctrl等之外,还有一个Shift,使用的加号(+),即SendKeys中发送的按键分3个部分:

(1)第1类:没有屏幕显示的按键,使用花括号包括的英文名称表示,如表2-1所示。

表2-1 SendKeys方法中使用的没有屏幕显示的按键

(2)第2类:使用其他的字符来表示的按键,主要有4个,即百分号代表Alt、插入符号代表Ctrl、加号代表Shift和波形符代表Enter。其中前3个主要用做快捷键。

(3)第 3 类:普通字符,主要是键盘上的字母和数字字符,直接使用各字符即可。该方法并不支持中文字符,在使用此SendKeys方法,尤其在操作不同的应用程序时最好将输入法切换到英文状态,并且发送的内容需要是英文的。

当然该方法也可以操作Excel和VBE,下面的ClearImmediateWindow过程清除“立即”窗口中的所有内容。该过程首先激活“立即”窗口,然后选中其上面的所有内容,最后使用Delelte键删除选中的内容:

        Sub ClearImmediateWindow()
            '激活“立即”窗口
            Application.VBE.Windows.Item("“立即”窗口").SetFocus
            '发送按键Alt+A,选中“立即”窗口上的所有内容
            Application.SendKeys "^a"
            '发送Delete键,删除选中的所有内容
            Application.SendKeys "{Del}"
        End Sub

提示

上面的方法可以在Excel和VBE窗口中调用,但是需要设置信任对VBE工程的访问以使得该过程得以运行。设置步骤为单击功能区中“开发工具”选项卡|“代码”组|“宏安全性”按钮,在“信任中心”对话框中切换到“宏设置”选项卡,选择“信任对VBA工程对象模型的访问”复选框。

2.2.8 OnTime方法

该方法可以安排一个过程在将来的特定时间运行(可以是具体指定的某个时间或指定的一段时间之后)。

如下代码设置SayHello在上午10点开始运行:

        Application.OnTime TimeValue("10:00:00"), "SayHello"

提示

在使用在具体的某个时间运行宏时需要确保该时间不是过去的某个时间,必须为当前时间或将来的某个时间;否则将发生运行时错误。

如下代码设置从现在开始半小时后运行SayHello:

        Application.OnTime Now + TimeValue("00:30:00"), " SayHello"

如果使用Application对象的Wait方法,则所有的Excel活动都将被挂起,包括在Excel中的手工操作也不允许。OnTime的优势就在于在其等待计划宏运行的过程,可以将程序控制权交回Excel。以允许用户执行正常的Excel交互操作,包括运行其他宏。

如图2-7所示的一天工作安排表。如何发出提示?

图2-7 使用OnTime实现约会提示

要实现在各个约会到来前半小时发出提示,首先在图中的D3:D8区域(该区域使用了名称 RN_DateReady)输入图中所示的公式以标识半小时及半小时以内的约会,然后运行RunDateAttention:

        Dim mdteScheduledTime As Date
        Sub RunDateAttention()
            Dim rngAttention   As Range
            '忽略找不到满足条件的单元格而发生的运行时错误
            On Error Resume Next
            '定位满足条件的单元格
            Set rngAttention = wsDate.Range("RN_DateReady"). _
                          SpecialCells(xlCellTypeFormulas, xlNumbers)
            '关闭错误处理
            On Error GoTo 0
            '如果找到需要提醒的约会,则弹出提示消息框
            If Not rngAttention Is Nothing Then
              With rngAttention
                  MsgBox "请准备 " & Format(.Offset(, -2).Value, "hh点mm分") _
                    & .Offset(, -1).Value
              End With
          End If
          '设置15分钟后再次自动运行该程序
          mdteScheduledTime = Now + TimeSerial(0, 15, 0)
          Application.OnTime mdteScheduledTime, "RunDateAttention"
        End Sub
        Sub StopDateAttention()
          Application.OnTime mdteScheduledTime, "RunDateAttention", , False
        End Sub

一旦运行RunDateAttention,它就会每隔15分钟进行自我调用。只要在该Excel会话期间,即便已经关闭RunDateAttention所在的工作簿“Application示例.xlsm”,也会自动打开该工作簿然后运行该程序,停止该程序运行的OnTime方法的语法如下所示:

        表达式.OnTime(EarliestTime, Procedure, LatestTime, Schedule)

其中最后一个参数Schedule的作用是如果为True(默认),则预定一个新的OnTime过程;否则清除先前设置的过程,所以可以使用如上 StopDateAttention 过程来停止调用RunDateAttention。此处还使用了一个模块级变量 mdteScheduledTime 来记录调用RunDateAttention的时间,以便StopDateAttention能够在该时间停止对RunDateAttention的调用。

2.3 Workbooks工作簿集合对象

工作簿集合包括在一个 Excel 应用程序中打开的工作簿,如果需要修改每个打开的工作簿,可以使用For Each…Next循环遍历该集合的各个成员并修改。Workbook集合也包括多个用来处理已存工作簿的方法,下面将介绍其中常用的常用部分,最基本的是创建一个工作簿。

提示

本节的所有示例请参见附随光盘中的工作簿“Workbook工作簿示例.xlsm”。

2.3.1 创建一个新的工作簿

要基于默认的工作簿创建一个立即新的工作簿,可以使用Workbooks的Add方法:

        Workbooks.Add

新工作簿将成为活动工作簿,所以可以在接下来的代码中使用ActiveWorkbook来引用该工作簿。如果在创建后立即使用SaveAs方法保存该工作簿,那么在以后的代码中可以使用保存后的文件名来引用该工作簿,而不用担心该工作簿不再是活动工作簿:

        Workbooks.Add
        ActiveWorkbook.SaveAs Filename:="D:\Test1.xlsx"
        Workbooks.Add
        ActiveWorkbook.SaveAs Filename:="D:\Test2.xlsx"
        Workbooks("Test1.xlsx").Activate

但是另一个更好的方式就是将指向新建工作簿的Add方法的返回值分配给一个对象变量,从而提供了一种引用新建工作簿的快捷方式。为此可以用于跟踪新建的一个临时工作簿,而无需保存该工作簿:

        Sub CreateNewWorkbook()
        '新建一个工作簿,并激活代码所在的工作簿
          Dim wbNew As Workbook

          Set wbNew = Workbooks.Add
          ThisWorkbook.Activate
      End Sub

Add方法提供了Template参数,允许用户指定创建新工作簿所使用的模板。模板可以是后缀名为.xltx的模板文件,为.xlsx的普通文件或者为.xlsm的启用宏的工作簿。如下代码将基于与本工作簿同目录下的“Application 示例.xlsm”创建一个新的工作簿,新建的未保存的工作簿将自动命名为“Application示例1”及“Application示例2”,即命名为“Application示例n”。其中n将随着新建的工作簿逐一递增,和在Excel用户界面单击“新建”按钮,将按照Book1、Book2、Book3……创建工作簿是同样道理:

        Set wkb1 = Workbooks.Add(Template:=thisworkbook.Path & _
                          "\Application示例.xlsm")

使用这种方法创建的工作簿将和Template 指定的工作簿具有完全一样的工作表用户界面及VBA代码模块,所不同的是名字。

2.3.2 打开工作簿

可以使用Workbooks集合的Open方法来打开已有的工作簿,例如:

        Workbooks.Open (FileName:="Application示例.xlsm")

Open方法的语法如下所示,其中提供了大量的参数:

        表达式.Open(FileName, UpdateLinks, ReadOnly, Format, _
            Password, WriteResPassword, IgnoreReadOnlyRecommended, Origin, _
            Delimiter, Editable, Notify, Converter, AddToMru, Local, CorruptLoad)

各个参数的用法可以参考VBA帮助信息。

2.3.3 保存和关闭所有工作簿

可以使用Workbook对象的Save和Close方法来保存和关闭工作簿,下面的第1个示例保存除新建工作簿外的所有工作簿:

        Sub SaveAll()
            Dim wb As Workbook
            For Each wb In Workbooks
              If wb.Path <> "" Then wb.Save
            Next 'wb
        End Sub

如果一个工作簿刚刚创建,并且从来没有保存过,则其wb.Path将为空。故而上面的例子借此删除了所有新建的工作簿,仅保存已经保存过的工作簿。

下面的程序将关闭本工作簿外的所有工作簿,并退出Excel应用程序:

        Sub CloseAll()
            Dim wb As Workbook
            '关闭非本工作簿的所有工作簿,并保留修改
            For Each wb In Workbooks
              If wb.Name <> ThisWorkbook.Name Then
                  wb.Close SaveChanges:=True
              End If
            Next 'wb

            '保存本工作簿
            ThisWorkbook.Save
            '退出Excel应用程序

          Application.Quit
      End Sub

注意在上面的代码中没有 ThisWorkbook.Close 的代码,如果关闭了本代码所在的工作簿,那么代码将无法执行,得不到预期的结果。

2.3.4 与工作簿相关的3个自定义函数

本小节将介绍如下3个与工作簿相关的自定义函数。

(1)获取文件全称中的文件名部分。

在VBA中处理工作簿时常常需要指定目录路径和文件名,一些任务仅需要知道路径,例如设置一个默认目录;一些任务仅需要知道文件名,例如激活一个工作簿;而其他的任务则同时需要路径和文件名,例如需要打开一个不在当前目录下的一个存在的工作簿文件。

一旦打开一个工作簿,即可容易地获取包括路径和文件名的全称或仅仅是文件名。例如,如下代码打开同目录下的Application示例.xlsm工作簿,通过其FullName、Name和Path属性分别获取其全称、名称和路径:

        Sub ShowInfo()
            Dim wbTemp  As Workbook
            Set wbTemp = Workbooks.Open(Filename:=ThisWorkbook.Path & _
                                        "\Application示例.xlsm")
            With wbTemp
              MsgBox "该文件的全称为:" & .FullName & vbCr & _
                      "名称为:" & .Name & vbCr & _
                      "路径为:" & .Path
            End With
        End Sub

结果如图2-8所示。

图2-8 打开工作簿的全名、名称和路径

上面的代码通过打开给定全称的工作簿和其Name属性来获取工作簿名称,如下代码能够从“E:\EXCEL 2007 VBA开发(电子工业出版社)\Application示例.xlsm”中获取工作簿的名称部分“Application示例.xlsm”:

        Function sGetFileName(sFullName As String) As String
        '----------------------------------------------------
        '目的:从由路径和文件名组成的文件全称中获取文件名部分
        '参数:sFullName为由路径和文件名组成的文件的全称
        '-----------------------------------------------------
            Dim sPathSeparator  As String
            Dim iPlace         As Integer
            '获取系统路径分隔符
            sPathSeparator = Application.PathSeparator

            '获取在文件全称中最后一个系统路径分隔符出现的位置
            iPlace = VBA.InStrRev(sFullName, sPathSeparator)
          If iPlace = 0 Then
          '如果没有找到系统分隔符,则返回文件全称
              sGetFileName = sFullName
          Else
          '截取文件名部分
              sGetFileName = Right(sFullName, Len(sFullName) - iPlace)
          End If

      End Function

(2)判断工作簿是否打开。

在用户试图打开某个工作簿时,可以使用如下的函数判断其是否已经打开:

        Function bIsWorkbookOpen(sWBName As String) As Boolean
        ''----------------------------------------------------
        '目的:测试给定工作簿是否已经打开,如果已打开,返回True;
        '      否则返回False
        '参数:sWBName为需要判断的仅包括文件名部分的工作簿的名字
        '-----------------------------------------------------
        Dim wbDummy    As Workbook
        '忽略所有错误
        On Error Resume Next
        '试图获取对给定名称的工作簿的引用
        Set wbDummy = Workbooks(sWBName)
        '如果引用成功,则该工作簿已打开,返回True
        If Not wbDummy Is Nothing Then
            bIsWorkbookOpen = True
        Else
            bIsWorkbookOpen = False
        End If
        End Function

(3)判断文件是否存在。

在保存工作簿时,可能指定的工作簿名称已被使用,如下函数提供了判断给定名称是否已经被使用:

        Function bFileExists(sFile As String) As Boolean
        '使用Dir函数判断给定文件是否存在
            If Dir(sFile) <> "" Then bFileExists = True
        End Function

2.3.5 使用自定义函数完成的一些工作簿操作

前面介绍了3个自定义函数,本节将介绍3个实际操作例子。

1.激活给定的工作簿

本示例通过给定工作簿的全称来激活该工作簿,实现步骤如下。

将给定的工作簿全称赋给变量sFullName。

使用自定义函数sGetFileName从工作簿的全称sFullName中获取文件名,并将其赋给变量sFileName。

使用自定义函数bIsWorkbookOpen判断名为“sFullName”的工作簿是否已经打开,如果已经打开,激活该工作簿;否则使用Workbooks的Open方法打开。

代码如下:

        Sub ActivateWorkbookByFullName()
            Dim sFullName As String
          Dim sFileName As String
          '指定需要打开的工作簿的全称
        sFullName = " E:\EXCEL 2007 VBA开发(电子工业出版社)\Application示例.xlsm "
          '从工作簿的全称中获取文件名
          sFileName = sGetFileName(sFullName)
          '判断工作簿是否已经打开,如果已经打开,则激活该工作簿
          '否则打开该工作簿
          If bIsWorkbookOpen(sFileName) Then
              Workbooks(sFileName).Activate
          Else
              Workbooks.Open Filename:=sFullName
          End If
      End Sub

下面的示例仅提供工作簿的名称,而激活与代码所在工作簿(即 ThisWorkbook)同目录下的名称为给定名称的工作簿,该代码和上面的ActivateWorkbookByFullName的思路和调用的自定义函数均相同。

● 引用工作簿时仅需要提供工作簿的名称,如Workbooks(sFileName)。

● 打开工作簿时需要提供工作簿的全称,如Set wbTemp = Workbooks.Open (Filename:=sFullName)

● 在获取工作簿路径时可以使用sPath = ThisWorkbook.Path来动态获取路径,这样在每次移动文件时不用修改文件,只要保证代码所在的文件和所需操作的文件位于同一个目录下即可。

示例代码如下:

        Sub ActivateWorkbookByName()
            Dim sPath      As String
            Dim sFileName   As String
            Dim sFullName   As String
            Dim wbTemp As Workbook
            '指定文件名
            sFileName = "Application示例.xlsm"
            '判断工作簿是否已经打开,如果已经打开,则激活该工作簿
            '否则则打开与代码所在工作簿同目录下的指定文件名的工作簿
            If bIsWorkbookOpen(sFileName) Then
              Set wbTemp = Workbooks(sFileName)
              wbTemp.Activate
            Else
              sPath = ThisWorkbook.Path
              sFullName = sPath & "\" & sFileName
              Set wbTemp = Workbooks.Open(Filename:=sFullName)
            End If
        End Sub

提示

在本例中使用wbTemp返回对激活对象的引用,可以使用该引用继续操作该对象。

2.判断文件是否存在。

自定义函数bFileExists,能够判断给定全称的工作簿是否存在,如下所示:

        Sub TestFileExist ()
            Dim sFileName As String
          '组织文件的全称
          sFileName = ThisWorkbook.Path & "\Application示例.xlsm"
          If bFileExists(sFileName) Then
              MsgBox sFileName & " 存在"
          Else
              MsgBox sFileName & " 不存在"
          End If
      End Sub

3.获取下一个可用的文件名。

下面的示例更巧妙地使用了自定义函数 bFileExists,在 GetNextFileName 中通过bFileExists并结合Do…Loop循环获取了下一个可使用的文件名,而避免了直接覆盖已有文件的可能性:

        Sub GetNextFileName()
            Dim wbNew      As Workbook
            Dim iIndex     As Integer
            Dim sFullName   As String
            '以同目录下Application示例.xlsm为模板新建工作簿
            Set wbNew = Workbooks.Add(Template:=ThisWorkbook.Name & _
                                    "\Application示例.xlsm")
            '初始化系列号
            iIndex = 0
            '直到找到没有使用的文件名
            Do
              iIndex = iIndex + 1
              sFullName = ThisWorkbook.Name & _
                          "\Application示例" & iIndex & ".xlsm"
            Loop While bFileExists(sFullName)
            '然后保存工作簿
            wbNew.SaveAs Filename:=sFullName
        End Sub

2.4 Worksheets工作表集合对象

在一个Workbook对象中有一个Sheets集合,其组员可以是普通工作表Worksheet对象或图表Chart对象。为了与以前版本的Excel兼容,还可能是MS Excel 4.0宏表及MS Excel 5.0对话框。

Worksheet和Chart也属于自己的集合,分别为Worksheets和Charts。Charts图表集合仅包括图表工作表,而不包括嵌入式图表,即嵌入式图表并不属于 Charts 集合。嵌入式图表包括在ChartObject对象中,为Worksheet的ChartObjects集合的成员。

在Sheets集合或Worksheets集合中引用一个工作表时既可以使用名称,也可以使用索引号。如果知道工作表的名称,最好使用名称来引用,使用名称来指定Worksheet集合中的所需的成员通常来说更为安全。如果需要处理 Worksheets 集合中的所有成员,需要通过索引号引用各个工作表,例如在For…Next循环中。

一个Worksheet在Worksheets集合和Sheets集合中的索引号可能会不同,例如在图2-9所示的工作簿中,可以通过如下4种方式引用Sheet1。

图2-9 同时拥有Worksheet和Chart的工作簿

        Workbooks("Worksheet工作表示例.xlsm").Sheets ("Sheet1")
        Workbooks("Worksheet工作表示例.xlsm").Worksheets ("Sheet1")
        Workbooks("Worksheet工作表示例.xlsm").Sheets (3)
        Workbooks("Worksheet工作表示例.xlsm").Worksheets (1)

虽然Worksheets是所有普通工作表的集合,而Sheets是所有工作表的集合(包括图表工作表和普通工作表),但是Worksheet普通工作表在两个集合中的索引号却是一样的。在下面的程序中列出了图2-9所示工作簿中所有普通工作表分别在两个集合中的索引号,结果如图2-10所示:

图2-10 工作簿中所有普通工作表在Worksheets和Sheets集合中的索引号

        Sub ShowWorksheetIndex()
        '显示本工作簿中所有普通工作表在Worksheets和
        'Sheets中的索引号。
            Dim iIndex  As Integer
            Dim sMsg   As String
            With ThisWorkbook
              For iIndex = 1 To .Worksheets.Count
                  sMsg = sMsg & .Worksheets(iIndex).Name & _
                  "在Worksheets集合中的索引号为 " & _
                  .Worksheets(iIndex).Index & ";" & _
                  "在Sheets集合中的索引号为 " & _
                  .Sheets(.Worksheets(iIndex).Name).Index _
                  & vbCr
              Next 'iIndex
            End With
            '显示结果
            MsgBox sMsg
        End Sub

普通工作表Worksheet在Worksheets集合中的索引号不合逻辑,例如在上例中,该工作簿中仅有3个Worksheet。索引号应该分别为1、2和3,但是结果却是与其在Sheets中的索引号一致,所以应该尽量避免在Worksheets集合中使用工作表Worksheet对象的Index属性。如下示例显示了如何在Sheets集合(而不是Worksheets集合中)使用Worksheet对象的索引号实现在激活工作簿的每个工作表前添加一个图表工作表:

        Sub AddChartsBeforeWorksheets()
            Dim wsTemp As Worksheet
            For Each wsTemp In Worksheets
              Charts.Add Before:=wsTemp
            Next 'wsTemp
        End Sub

2.5 Range对象概述

Range对象可能是用户在VBA代码中使用最多的对象,它可以是一个单元格、一个单元格区域或许多个区域的联合(非连续区域)。该对象包括在一个Worksheet中,即其父对象为Worksheet对象。

Excel对象模型不支持跨越多个工作表的三维Range对象,即在一个Range对象中的每一个单元格都必须位于同一个工作表中。如果需要处理三维区域,则必须单独处理各张工作表中的Range对象。

一个Range对象代表包含工作表对象中的一个Range区域,可以小到一个单元格,如A1单元格。也可以大到包括整个工作表,如A1:XFD1048576。

Range对象的属性如表2-2所示,方法如表2-3所示。

表2-2 Range对象的属性

续表

续表

表2-3 Range对象的方法

续表

续表

2.6 Range对象的常用属性

在表2-2中列出了Range对象的91个属性,事实上其属性还不只这些,本节将介绍常用属性。

提示

本节的示例代码请见随附光盘中的“Range对象属性示例.xlsm”。

2.6.1 Value属性

Value属性代表包括在Range区域的值,是一个读写属性。

(1)读取并设置一个单元格的值。

可以直接使用该属性读取一个单元格的值,如下语句在消息框中显示Sheet1中单元格B1中的值:

        MsgBox Worksheets("Sheet1").Range("B1").Value

也可以直接设置一个单元格的值,如下语句在Sheet1上单元格B1中写入11:

        Worksheets("Sheet1").Range("B1").Value = 11

因为Value是Range对象的默认值,故可以省略Value属性的键入,如以下代码:

        '在消息框中显示Sheet1上单元格B1中的值。
        MsgBox Worksheets("Sheet1").Range("B1")
        '在Sheet1上单元格B1中写入11。
        Worksheets("Sheet1").Range("B1")= 11

(2)读取并设置多个单元格组成的Range区域的值。

也可以直接使用该属性读取多个单元格的值,但是只能将该Range的值赋给一个Variant变量。其实质就是一个二维数组,下标均为1,行数和列数均等于Range区域的行数和列数,如下语句在消息框中显示了Sheet1单元格区域B1:D3中C3的值:

        vArr = Worksheets("Sheet1").Range("B1:D3").Value : _
        Msgbox vArr(3,2)

提示

可在“立即”窗口中指向上面的代码,注意第1行末尾的冒号加空格加下画线(: _),在同一行两句代码间需要使用冒号(:),加换行符( _)。

同样可以将一个数组分配给Range区域:

        '在Sheet1上B4到D4区域填入1、2、3。
        Worksheets("Sheet1").Range("B4:D4").Value = Vba.Array(1,2,3)

如果将一个值赋给一个Range,那么这个区域中的所有单元格都将填入这个值:

        '在Sheet1上B4到D4区域都填入1。
        Worksheets("Sheet1").Range("B4:D4").Value = 1

提示

关于数组和区域之间的传值将在后面详细介绍。

2.6.2 Text属性

Text属性返回一个在单元格中显示的文本字符串,即格式化后的值;而Value属性返回的是单元格中的实际值,即返回和格式设置无关的值,如下示例显示了包含格式数字的单元格Text和Value属性返回值的区别:

        Sub ShowTextValueDiff()
            With Worksheets("Sheet1").Range("B14")
              '为单元格赋值
              .Value = 12345.6
              '设置单元格格式
              .NumberFormat = "¥#,##0.00"
              '显示单元格的Value及Text属性
              MsgBox "Value为:" & .Value & vbCr & _
                      "Text为:" & .Text
            End With
        End Sub

结果如图2-11所示。

图2-11 Value和Text属性返回值的差异

此外,Text 属性是只读的,不能直接改变,但是可以根据需要执行如下操作。

通过Value属性改变单元格的值。

使用NumberFormat设置单元格的格式。

提示

Text 属性在数组和区域之间传值时,还能发挥重要的作用,因为只能把 Range 区域的值赋给一个Variant变体类型的变量。如果该区域内有过长的字符串数据,则将该Value传给Variant类型变量时会发生错误,而此时将Range的Text属性传给Variant类型变量则没有问题。所以在一个 Range 区域中有过长字符串数据并与变体类型变量传值时,不要使用Value属性,而使用Text属性。

2.6.3 Count及Areas属性

Count属性常用来返回集合中对象的数量,在此用来返回Range中所有单元格(包括空单元格)的数目,这是一个只读属性。

也可以用其的返回非连续区域的单元格数目:

        '返回B10,B4:B6区域的单元格数目为10。
        Msgbox Worksheets("Sheet1").Range("B10,B4:D6").Count

但是要留意的是在计数非连续区域的行和列时,不能简单地使用 Rows 或Columns.Count:

        Msgbox Worksheets("Sheet1").Range("B10,B4:D6").Rows.Count '返回值1

在Range("B10,B4:D6")中应该有4行,但是上面的代码仅返回了第1个区域B10所拥有的1行。在处理非连续区域时最好使用区域的Areas属性来返回各个区域,分别处理各个区域。

计数Range("B10,B4:D6")中行数的代码如下:

        Sub DisplayRowsCount()
            Dim iAreaCount  As Integer
            Dim iIndex     As Integer
            Dim rngTest    As Range
            Dim lRowsCnt   As Long
            '将需要测试单元格数目的Range对象分配给range对象变量
            Set rngTest = Worksheets("Sheet1").Range("B10,B4:D6")
            '确定指定的区域内是否包括多个区域
            iAreaCount = rngTest.Areas.Count
            '获取指定区域内单元格的数目
            If iAreaCount <= 1 Then
              '如果仅包括一个区域直接使用其Rows的count属性
              lRowsCnt = rngTest.Rows.Count
            Else
              '如果包括多个区域,则循环遍历每个区域获取其行数并汇总
              For iIndex = 1 To iAreaCount
                  lRowsCnt = lRowsCnt + rngTest.Areas(iIndex).Rows.Count
              Next 'iIndex
            End If
            '显示结果
            MsgBox rngTest.Address & " 区域的行数为:" & lRowsCnt
        End Sub

结果如图2-12所示。

图2-12 非连续区域的总行数

2.6.4 Address属性

Address属性是一个只读属性,返回一个以U.S.美国制格式表示的String值,代表Range的地址。其语法为:

        对象表达式.Address(RowAbsolute, ColumnAbsolute, ReferenceStyle, External,
    RelativeTo)

说明如下。

(1)对象表达式:一个代表Range对象的变量。

(2)RowAbsolute:指定是否以绝对引用形式返回 Range 地址中的行部分,为 True 默认,则以绝对引用形式返回行部分。数据类型为Variant,为可选参数。

(3)ColumnAbsolute:指定是否以绝对引用形式返回 Range 地址中的列部分,为 True (默认),则以绝对引用形式返回列部分。数据类型为Variant,为可选参数。

(4)ReferenceStyle:指定Range地址返回的引用样式,是以A1的形式还是以R1C1的形式。数据类型为XlReferenceStyle,有两个取值,即xlR1C1和xlA1。为可选参数,默认值为xlA1。

(5)External:指定Range地址是否返回尾部引用,数据类型为Variant。如果为True,则返回外部引用;否则返回本地引用。为可选参数,默认值为False,。

(6)RelativeTo:用来在ReferenceStyle为xlR1C1,RowAbsolute和ColumnAbsolute为False,ReferenceStyle为xlR1C1时,定义相对引用的起始点Range对象。为可选对象,仅在上述条件情况下出现。

如下示例对当前活动工作表中的A1单元格的地址使用了8种不同的表达方式:

        Sub ShowAddress()
            Dim rngShow As Range
            Set rngShow = ActiveSheet.Cells(1, 1)
            With rngShow
            '使用A1样式引用部分
              '行列皆绝对引用
              Debug.Print .Address                              '$A$1
              '行相对,列绝对
              Debug.Print .Address(RowAbsolute:=False)          '$A1
              '行绝对,列相对
              Debug.Print .Address(columnabsolute:=False)        'A$1
              '行列皆相对
              Debug.Print .Address(RowAbsolute:=False, _
                          columnabsolute:=False)                   'A1
            '使用R1C1样式引用部分
              '行列皆绝对引用
              Debug.Print .Address(ReferenceStyle:=xlR1C1)       'R1C1
              '行相对,列绝对
              Debug.Print .Address(columnabsolute:=False, _
                          ReferenceStyle:=xlR1C1)                   'R1C
              '行绝对,列相对
              Debug.Print .Address(RowAbsolute:=False, _
                          ReferenceStyle:=xlR1C1)                   'RC1
              '行列皆相对
              Debug.Print .Address(RowAbsolute:=False, _
                          columnabsolute:=False, _
                          ReferenceStyle:=xlR1C1, _
                          RelativeTo:=Worksheets(1).Cells(2, 2))   'R[-1]C[-1]
            End With
        End Sub

2.6.5 HasFormula属性

HasFormula 属性是一个只读属性,用于判断指定区域内的所有单元格是否包括公式,语法为:

        对象表达式. HasFormula

如果对象表达式中仅有一个单元格,那么在该单元格包括公式时返回 True;否则返回False。

如果对象表达式中包括多个单元格,则仅在所有单元格都包括公式时返回True;所有单元格都不包括公式时返回False;而在部分包括公式时返回Null。

在处理多个单元格组成的Range对象的HasFormula属性返回值一定要小心,当处理返回一个Null值的任何属性时,很容易因使用错误的数据类型而产生运行时错误。

例如,假定需要得出B1:D1区域内是否包括公式情况,其中在单元格B1中包括公式,而在其他单元格都不包括公式。如下语句将产生一个运行时错误:

        Dim bFormulaTest As Boolean
        bFormulaTest = Range("B1:D1").HasFormula

因为在所有单元格区域都不包括公式时,HasFormula 属性返回值为 Null。所以将bFormulaTest声明为布尔型时,将产生一个“无效使用Null”的运行时错误。要解决这个错误,将bFormulaTest声明为Variant类型,而不是Boolean类型。

如下示例提示用户在当前工作表上选择需要判断的区域,并根据该选定区域内所有单元格包含公式的情况显示具有不同提示信息的消息框:

        Sub TestHasFormula()
            Dim vHasFormula    As Variant
            Dim sMsg          As String
            Dim rngTest        As Range
            '提示用户在当前工作表上选择需要判断的区域
            Set rngTest = Application.InputBox( _
              prompt:="在当前工作表上选择需要判断是否有公式的区域", _
              Type:=8)
            '通过HasFormula属性获取指定区域的是否包括公式情况
            vHasFormula = rngTest.HasFormula
            '根据不同的情况,组织相应的提示信息字符串
            Select Case vHasFormula
            Case True  '包括公式
              sMsg = rngTest.Address & "包括公式"
            Case False  '不包括公式
              sMsg = rngTest.Address & "不包括公式"
            Case Else  '部分包括公式,部分不包括
              sMsg = rngTest.Address & "中部分单元格包括公式,部分不包括."
            End Select
            '显示结果
            MsgBox sMsg
        End Sub

2.6.6 Font属性

一个对象的属性可以返回另一个对象,对象可能有相应的属性和方法。Font 属性返回一个Font对象,该对象有相应的属性。因此要改变一个Range对象的字体特征,首先需要访问其Font属性,然后通过改变Font对象的相应属性设置而改变Range对象的字体。

例如,可通过如下语句返回一个Range对象的Font对象:

        Range("A1:C1").Font

如下语句将包括在Range对象中的字体设为斜体:

        Range("A1:C1").Font.Italic = True

提示

可以通过使用宏录制器了解更多如何操作一个Range对象的Font属性的信息。

2.6.7 Interior属性

Interior属性返回一个Interior(内部)对象,它使对象操作该Range对象的底纹颜色或填充色,即背景色。

下面的语句将当前激活单元格的填充色变为黄色:

        Activecell.Interior.Color = 65535

Color属性的取值为0~16 777 215,录制宏产生的代码也使用这种赋值方式。但是这种赋值方式并不直接,而且也不够安全。在Excel 2003中Color属性的取值为0~55,如果要向后兼容,就不能使用这种方式。

1.使用RGB函数

另一种指定颜色的方式是使用VBA的RGB函数,该函数使用3个分别对应于红色、绿色和蓝色的三原色成分。每个颜色参数均为0~255。例如可以使用如下语句将当前激活单元格的填充色变为黄色:

        Activecell.Interior.Color = RGB(255,255,0)

表2-4所示为常见标准色的RGB表示方法。

表2-4 常见标准颜的RGB表示方法

2.使用颜色常数

除了上述的两种方式,标准色还可以使用颜色常数,例如可以使用如下语句将当前激活单元格的填充色变为黄色:

        Activecell.Interior.Color = vbYellow

3.获取所需颜色的RGB值

颜色的RGB表示法是一种常用的方式,表2-4仅列出了常见标准色的表示方法。要获取他颜色的RGB值的简单方法是使用“颜色”面板,操作步骤:

单击功能区上“开始”选项卡|“字体”组|“填充颜色”按钮,如图2-13所示。

图2-13 “填充颜色”按钮

单击“其他颜色”按钮弹出如图2-14所示的“颜色”对话框。

图2-14 选择所需标准色和获取或设置RGB值

可按照如下的两种方式分别获取标准色和自定义颜色的RGB值。

● 如果要获取标准色的RGB值,可以在“标准”选项卡中选择所需的标识色。然后切换到“自定义”选项卡(如图2-14(右)所示),读取“红色(R)”、“绿色(G)”及“蓝色(B)”微调框中对应的颜色值。

● 如果是要设置自定义颜色,则在“自定义”选项卡中,通过改变“红色(R)”、“绿色(G)”及“蓝色(B)”微调框中的取值,并记录该颜色。

2.6.8 Formula属性

Range的Formula属性可以返回或设置该区域中的公式,这是一个读写属性,因此可以通过该属性在单元格插入公式。例如,如下语句设置工作表Sheet1上单元格A1的公式:

        Worksheets("Sheet1").Range("A1").Formula = "=$A$2+$A$3"

注意,公式是一个文本字符串,并包括在一对双引号中。在使用该属性返回区域中的公式值时,如果某单元格没有公式,则会返回其值,即其Value属性的值。如果要判断该单元格中的是否有公式,可以使用HasFormula属性。

该属性可以读取或设置一个或多个单元格的公式,但是只能将该 Range 的公式赋给一个Variant变量。其实质是一个二维数组,下标均为1,行数和列数均等于Range区域的行数和列数。设置多个单元格的公式时,可以使用数组赋值。也可以使用某一个公式字符串,此时所有的单元格都将填入该公式字符串。

2.6.9 NumberFormat属性

NumberFormat属性返回或设置一个代表指定Range对象的格式代码的Variant值,如果指定区域中的所有单元格包含不同的数字格式,则返回Null。

以下示例分别设置当前活动工作表中的A1单元格、第2行和D列的数字格式:

        Range("A1").NumberFormat = "General"     '设为常规型
        Rows(2).NumberFormat = "hh:mm:ss"        '设为小时/分钟/秒型
        Columns("D"). NumberFormat = " 0.00%"    '设为带两个小数位的百分比型

可以根据如下步骤来查看其他的数字格式列表,最好同时打开宏录制器。

激活工作表。

按快捷键Ctrl+1打开“设置单元格格式”对话框。

切换到“数字”选项卡。

在“分类”下拉列表框中选择“自定义”选项,然后设置并查看自定义格式,如图2-15所示。

图2-15 设置并查看自定义格式

2.7 Range对象的常用方法

表2-3中列出Range对象的68种方法,本节将介绍其常用方法。

提示

本节的示例代码请见随附光盘中的“Range对象属性示例.xlsm”。

2.7.1 Activate和Select方法

ActiveCell和Selection的区别如图2-16所示,当前激活单元格ActiveCell为E6单元格。而选择的单元格区域Selection为C3:G12,选择区域可以是多个不连续的单元格,但是激活区域仅能为一个单元格。

图2-16 激活单元格和选择的单元格区域

当前激活单元格一般为选择区域的首单元格,但是可以通过 Tab、Enter、Shift+Tab、Shift+Enter快捷键改变激活单元格的位置。实现图2-16所示效果的步骤如下。

选择单元格C3:G12区域。

此时激活单元格为C3,连续按3次回车键向下移动3行,使激活单元格变为C6。连续按3次Tab键向右移动3列,激活单元格就变为E6。

也可以使用如下代码实现:

        Range("C3:G12").Select
        Range("E6").Activate

如果要试图激活选择区域范围以外的单元格,则改变选择区域 Selection,激活的区域将成为新的选择区域。

Activate 方法只能激活一个单元格,如果用其来激活多个单元格,Excel 通过指定激活区域的首单元格决定采取的操作。如果首单元格仍在原选择区域,那么Excel仅仅激活指定该区域的首单元格,而不改变原选择区域,如下代码也将产生图2-16所示的结果:

        '选择C3:G12区域,当前激活单元格为其首单元格C3
        Range("C3:G12").Select
        '因E6仍在C3:G12区域中,故仅将激活单元格改为E6,而选择区域仍然为C3:G12区域
        Range("E6:AB300").Activate

如果首单元格不在原选择区域中,那么Excel不仅是要激活指定区域的首单元格,同时指定的激活区域将变成新的选择区域:

        '选择C3:G12区域,当前激活单元格为其首单元格C3
        Range("C3:G12").Select
        '因E16不在C3:G12区域中,故激活单元格改为E16,而选择区域也将变为E16:AB300区域
        Range("E16:AB300").Activate

在上面的代码中,E16:AB300取代C3:G12成为新的选择区域。

在需要激活某个单元格时使用Activate方法,如果需要选择某个区域,而不只是激活某个单元格,则最好使用Select方法。使用Activate方法来选择某个区域时,指定区域的首单元格是否在原选择区域范围内,将导致两种不同结果。尤其指定区域的首单元格在原选择区域内时指定区域的单元格将不会替换原选择区域,得不到期待的结果。

2.7.2 Copy和Paste方法

可以在 VBA 中使用 Copy 和 Paste 方法执行复制和粘贴操作,Copy 方法可以应用于Range对象,而Paste方法仅适用于Worksheet对象。如下代码复制B2:D4并将其粘贴到以B5开始的单元格区域中:

        Sub CopyPasteRange()
            '复制B2:D4区域到剪贴板
            Range("B2:D4").Copy
            '选择单元格B5
            Range("B5").Select
            '将剪贴板中内容粘贴到当前工作表中
            ActiveSheet.Paste
        End Sub

Copy方法的Destination参数指定复制到的目标区域,如果省略此参数,Excel的将复制到剪贴板中,如上面的过程 CopyPasteRange所示。因此可以通过指定 Copy 方法的Destination参数来一次完成复制粘贴过程:

        Sub CopyPasteRange2()
            '复制B2:D4区域,并粘贴到B5开始的单元格区域
            Range("B2:D4").Copy Range("B5")
        End Sub

2.7.3 Clear和Delete方法

Range对象的Clear方法清除Range对象的内容及所有单元格格式,例如,可以通过如下语句清除当前活动工作表中A列的所有内容:

        Columns("A:A").Clear

与Clear相关的其他常用方法如下。

(1)ClearComments:清除指定区域的所有单元格批注。

(2)ClearContents:清除指定区域的公式。

(3)ClearFormats:清除指定区域的单元格格式设置。

清除一个Range区域只是删除其内容和格式等,该区域仍然存在;而当删除一个区域后,该区域不复存在,Excel将会移动其周围的区域以填充删除的Range。在删除定义了名称的单元格区域要特别小心,删除该单元格区域会导致名称引用无效,即产生#Ref错误。

Range方法的语法为:

        对象表达式.Delete(Shift)

说明如下。

(1)对象表达式为需要删除的Range对象。

(2)Shift参数的取值为xlShiftToLeft或xlShiftUp,如果省略此参数,Excel将根据区域的形状确定调整方式。

例如,如下语句删除B2:D2区域,并使下面的单元格向上移动以填补空缺:

        Range("B2:D2").Delete xlShiftUp

2.8 返回Range对象的方法

提示

本节的范例请见随附光盘中工作簿“返回Range对象的多种方法示例.xlsm”。

2.8.1 Range属性

1.使用一个参数的Range属性

当引用一个Range对象时,地址被双引号引起,如:

        Range("A1")         '引用当前激活工作表上A1单元格
        Range("A1:D4")      '引用当前激活工作表上A1到D4单元格区域

也可以引用定义名称的单元格区域(可单击“公式”选项卡|“定义的名称”组|“自定义名称”命令打开“新建名称”对话框创建),可以通过如下所示的表达式引用:

        Range("SalesData")  '引用当前激活工作簿中名称为SalesData的单元格区域

需要强调的是,如果在引用 Range 时未指明其工作表父对象,则默认为当前激活工作表ActiveSheet对象。如果此时的普通工作表Worksheet对象不处于激活状态,则会产生一个运行时错误。

也可以通过在 Range 对象前加上对非激活工作表的引用来引用位于非激活工作表中的Range对象:

        '引用当前激活工作簿上工作表Sheet3上的A1:D4单元格区域
        Worksheets("Sheet3").Range("A1:D4")

如果需要引用位于一个不同工作簿中(非当前激活工作簿之外的工作簿)的 Range 对象,可以使用如下所示的代码:

        '引用工作簿Test.xlsx中的Sheet3上的A1:D4单元格区域
        Workbooks("Test.xlsx")Worksheets("Sheet3").Range("A1:D4")

一个Range对象可以包括多行或多列,如可以使用如下语句来引用一整行:

        Range("2:2") '引用当前激活工作表中的第2行

也可以引用整列:

        Range("A:A") '引用当前激活工作表中的第1列

甚至可以引用非连续区域(在Excel中可以通过按Ctrl键而选中非连续的区域)。如下代码引用了3个不连续的区域,注意其中使用了一个逗号(,)分隔3个区域:

        '引用当前激活工作表上$C$8:$C$10、$E$4:$E$6、$F$9:$F$10,个区域
        Range("$C$8:$C$10,$E$4:$E$6,$F$9:$F$10")

提示

如上使用A1引用方式,其中美元符号($)代表绝对引用;不使用美元符号,则为相对引用。

在处理包括非连续区域的Range对象时一定要额外小心,Range对象的某些属性和方法不能很好处理这种包括非连续区域的Range对象,此时一般需要使用其Areas属性。

2.使用两个参数的Range属性

Range对象的Range属性的语法如下:

        表达式.Range(区域1, 区域2)

其中区域1为必须,区域2可选。在使用区域2时,区域1及区域2为区域开始的首尾单元格,即区域1、区域2中左上角和右下角的单元格,并返回由这两个单元格组成的区域。例如,如下所有语句都返回A1:G1区域:

        Range("A1","G1")
        Range("G1","A1")
        Range("D1:G1","A1")
        Range("D1:G1","A1:B1")

使用两个参数这种形式在区域1 和区域2 两个参数的表示上有很大的灵活性,例如假定要引用当前活动工作表中的 A1:G1 区域,且 A1 和 G1 单元格分别名为“StartCell”和“EndCell”,那么在使用两个参数时可以使用如下几种形式:

        Range("A1","G1")
        Range("A1",Range("EndCell"))
        Range(Range("StartCell"),Range("EndCell"))
        Range(Range("StartCell"), "G1")
        Range(Cells(1,1),Cells(1,7))
        Range("A1", Cells(1,7))

此外在使用两个参数时特别需要注意的是前面的例子都是引用当前活动工作表中的单元格区域,如果引用不同工作表或不同工作簿中的单元格区域,除了使用美国制A1样式外,需要为每一个参数添加对象标识符,即使用如下的形式:

        对象表达式.Range(对象表达式.区域1, 对象表达式.区域2)

例如假定当前激活工作表为Sheet1,需要引用Sheet2上的单元格区域A1:G1,那么可以使用如下形式:

        Worksheets("Sheet2").Range("A1","G1")
        Worksheets("Sheet2").Range(Worksheets("Sheet2")。Range("A1"), _
        Worksheets("Sheet2")。Range("G1"))
        Worksheets("Sheet2").Range(Worksheets("Sheet2")。Cells(1,1), _
        Worksheets("Sheet2")。Cells(1,7))

如下形式都将导致运行时错误:

        Worksheets("Sheet2").Range(Worksheets("Sheet2")。Range("A1"), Range("G1"))
        Worksheets("Sheet2").Range(Cells(1,1),Worksheets("Sheet2")。Cells(1,7))

3.3种区域操作符

Range属性使用美国制A1样式引用的参数字符串中可以使用如下3种类型的操作符。

(1)区域操作符(冒号)。

其形式为用一个冒号隔开两个A1样式的单元格组成的字符串,冒号两边表示首尾单元格。该字符串被双引号包括,即:

        对象表达式.Range("单元格1:单元格2")

其中的单元格1和单元格2都是以A1样式引用的字符串,无相对引用和绝对引用之分。如果使用了绝对引用的美元符号,也将被忽略,其作用等同如下使用两个参数的Range属性:

        对象表达式.Range(单元格1,单元格2)

使用两个参数的Range属性中的“单元格1”和“单元格2”的形式具有更大的灵活性,可以使用多种形式。

例如,假定需要引用当前活动工作表中A1:D1区域,且D1单元格名为EndCell,那么可以使用如下几种形式:

        Range("A1:D1")
        Range("A1","D1")
        Range("A1",Range("EndCell"))
        Range(Cells(1,1),Cells(1,4))

从上面也可以看出,使用该单参数的区域操作符的表示形式最为简单。但是使用两个参数的Range属性具有更大的灵活性,同一个区域引用有多种形式。

(2)相交区域操作符(空格)。

其形式为用空格隔开的两个区域,空格表示求其交集,等同于 Intersect 的作用。如果两个区域无交集,则会发生运行时错误。

示例如下:

        Range("A1:D1 A1")  'A1:D1和A1的交集为A1,该Range最终引用单元格A1
        Range("A1:D1 A1:B1") 'A1:D1和A1:B1的交集为A1:B1,该Range最终引用单元格A1:B1

(3)合并区域操作符(逗号)。

其形式为用空格隔开的两个区域,逗号表示求其并集,等同于Union方法的作用。

示例如下:

        Range("A1:D1,A1")  'A1:D1和A1的并集为A1:D1,该Range最终引用单元格A1:D1
        Range("A1,D1")  'A1和D1的并集为A1和D1,该Range最终引用单元格A1和D1

4.Range对象的Range属性

Range对象的Range属性表现为对Range对象本身的相对引用。例如,如下代码引用单元格C2:

        Range("B1").Range("B2")

可以想象一个首单元格为 B1 的工作表,B2 即是引用该工作表中从首单元格起向下两行且向右一列后的新的单元格,即C2单元格。

在使用宏录制器并选择相对录制时,产生的代码即是使用基于Range对象的Range属性来引用单元格。例如录制一个从当前激活单元格开始选中3 行3 列的区域,将会得到如下代码:

        ActiveCell.Range("A1:C3").Select

这种引用方式很容易让人迷惑,所以最好是不要使用。

5.快捷Range引用方式

可以通过将一个 A1 样式的区域引用字符串或该区域的名称放在方括号中来引用该区域,这是Appliation对象的Evaluate方法的快捷方式。它等同于使用带字符串参数的Range属性,但是要简短很多,如下是在如图2-17所示的“立即”窗口中的输入测试结果:

图2-17 测试结果

        ?[A1].ADDRESS
        $A$1
        ?[A1,D1].ADDRESS
        $A$1,$D$1
        ?[A1:D1].ADDRESS
        $A$1:$D$1
        ?[A1:D1,A2:A3,G1].ADDRESS
        $A$1:$D$1,$A$2:$A$3,$G$1

2.8.2 Cells属性

Excel表格中的底层表格对象就是Range对象,没有Cell对象。不仅若干个单元格区域组成的一个连续的或不连续的单元格区域为 Range 对象,而且其中的每一个单元格也称之为一个Range对象。

Cells属性也可以返回一个Range对象,即指定对象的所有单元格或某一个单元格。

一个Cells属性使用两个参数,即:

        对象.Cells(行号,列号)

如果没有加上对象识别符,则此处的对象为 ActiveSheet,表示引用当前工作表中的Range对象。

        Cells(1,1)      '引用当前工作表的A1单元格

可以通过加上对象识别符引用非激活工作表或非激活工作簿上某个工作表中的单元格:

        '引用当前激活工作簿上工作表Sheet3上的A1单元格
        Worksheets("Sheet3").Cells(1,1)
        '引用工作簿Test.xlsx中的Sheet3上的A1单元格
        Workbooks("Test.xlsx")Worksheets("Sheet3").Cells(1,1)

可以同时省略行号和列号,而返回指定对象的所有单元格:

        '省略对象,默认为当前活动工作表,返回当前活动工作表上整个Range区域A1:XFD1048576
        '即由1048576行和16384 列组成的共计17,179,869,184个单元格
        'Cells
        '引用当前激活工作簿上工作表Sheet3上的所有单元格
        Worksheets("Sheet3").Cells
        '引用工作簿Test.xlsx中的Sheet3上的所有单元格
        Workbooks("Test.xlsx")Worksheets("Sheet3").Cells

Cells的父对象仅可以为工作表对象或Range对象,此时省略行号和列号则返回该Range对象的所有单元格:

        '引用当前激活工作表上A1到D4单元格区域中的所有单元格
        Range("A1:D4").Cells
        '引用当前激活工作簿上工作表Sheet3上的A1:D4单元格区域的所有单元格
        Worksheets("Sheet3").Range("A1:D4").Cells
        '引用工作簿Test.xlsx中的Sheet3上的A1:D4单元格区域的所有单元格
        Workbooks("Test.xlsx")Worksheets("Sheet3").Range("A1:D4").Cells

上面的3行代码和下面的3行代码返回同样对象:

        '引用当前激活工作表上A1到D4单元格区域
        Range("A1:D4")
        '引用当前激活工作簿上工作表Sheet3上的A1:D4单元格区域
        Worksheets("Sheet3").Range("A1:D4")
        '引用工作簿Test.xlsx中的Sheet3上的A1:D4单元格区域
        Workbooks("Test.xlsx")Worksheets("Sheet3").Range("A1:D4")

之所以使用指定区域的Cells属性来返回包括的单元格,原因在于可以使用如下形式:

        对象.Cells(序列号)

说明如下。

● 对象可以为任何的Range对象,即整个工作表,或一个单元格,或由连续或不连续单元格组成的区域。

● 序列号为1到指定对象所包括的单元格数目。

该形式可以返回指定区域中某一个单元格,编号按行计,即编完一行的号后继续下一行:

        Range("A1:D4").Cells(5)      '返回单元格A2
        Range("E3:J10").Cells(5)     '返回单元格I3

除了可以通过 Range(“A1:D4”)这种引号中加上一个单元格区域地址来引用某块单元格区域,也可以使用Range(cell1,cell2)这种形式,即:

        '引用当前激活工作表上A1到D4单元格区域
        Range("A1:D4")
        Range(Cells(1,1),Cells(4,4))

上面这两种方式是等效的,Cells方式的好处就在于在Cells(行号,列号)这种形式中行号和列号可以换成循环变量。

2.8.3 Offset属性

Offset属性提供了另一种便利的引用Range区域的方式,该属性基于一个Range对象返回另一个Range对象,即从一个单元格偏移指定的行数和列数来引用另一个单元格对象:

        对象.Offset(偏移的行数, 偏移的列数)

说明如下。

(1)对象为一个Range对象。

(2)偏移的行数和列数可以是正数、负数或零,正数表示向下偏移;负数表示向上偏移。默认值是0,即不发生任何偏移。如果不发生偏移,可以省略相应的参数。

例如:

        Range("C2").Offset(1, 2)     '返回单元格E3
        Range("C2").Offset(-1, -2)   '返回单元格A1
        Range("C2").Offset(0, 0)     '返回单元格C2
        Range("C2").Offset(1,    -2) '返回单元格A3
        Range("C2").Offset(-1, 2)    '返回单元格B1
        Range("C2").Offset(0, 2)     '返回单元格E2
        Range("C2").Offset(, 2)      '返回单元格E2
        Range("C2").Offset(2)        '返回单元格C4

2.8.4 Columns和Rows属性

Columns和Rows属性的使用形式为:

        对象表达式.Columns
        对象表达式.Rows

其中,对象表达式可以是Application、Worksheet和Range对象,返回对工作表或Range 83上所有行或列的引用。无论哪种情况下都返回一个Range对象,该对象代表指定区域中的所有行或列。在需要处理一个Range上的所有行/列或计算行/列数目时,这两个属性非常有用。

(1)返回工作表中的所有行/列或某行/列。

可用 Rows 或 Columns 属性来处理整行或整列,如下示例说明了如何使用 Rows 和Columns属性的引用行和列:

        Rows(2)         '引用工作表中的第2行
        Rows            '返回工作表上所有行
        Columns         '返回工作表上所有列
        Columns(2)      '引用工作表中的第2列
        Columns("B")    '引用工作表中的第2列

(2)获取最后一列或最后一行的行号。

Excel 97将工作表的行数从之前版本中的16 384行增加到65 536行,而Excel 2007增加到1 047 576行。Excel工作表的行数在不同的版本不是一成不变的,在以后的版本有可能还要增加。所以为了写出兼容所有版本的代码,就需要使用Rows的Count属性来获取实际的行数:

        Rows.Count

例如,如果需要编写一个选中第 2 列最后一个有内容的单元格的代码,并且能够兼容所有的Excel版本,则代码如下:

        Cells(Rows.Count,"B").End(xlUp).Select

同样,Excel的列数也在不断变化,Excel 2007中将列数从256列增加到16 384。如果要引用第1行最后一个有内容的单元格,并且兼容所有的Excel版本,则代码如下:

        Cells(1,Columns.Count).End(xlToLeft).Select

(3)处理Range上的所有行/列。

Rows和Columns属性的另一个优势就在于可以返回区域中的所有行列,借此可以循环处理区域中的各行/列。例如假设有如图2-18所示的位于Sheet1上的名为“Sales”的数据,范围为$B$2:$BI$27,其中第 2 行为汇总列“累计销售额”。现在需要突出显示累计销售额大于15 000的厂房,即将Sales区域中第2行单元格中大于15 000的各列挑出以格式化,可以使用如下代码:

图2-18 循环处理整列示例

        Sub ColumnSample()
            Dim rngCol As Range
            '循环处理区域中的各列,将累计销售额大于15000的厂房设置格式
            For Each rngCol In Range("Sales").Columns
              With rngCol
                  If .Cells(1).Value > 15000 Then
                      '将该列的标题设为红色
                      .Cells(1).Offset(-1).Font.Color = vbRed
                      '加粗本列的数据
                      .Font.Bold = True
                      '将本列的背景色变为黄色
                      .Interior.Color = vbYellow
                  End If
              End With
            Next 'rngCol
        End Sub

注意在处理普通Range对象,如Range(“A1:D3”)时可以使用Range(“A1:D3”)(1)格式,但是不能将“rngCol.Cells(1)”换为“rngCol(1)”;否则导致一个运行时错误。原因在于这返回是对整列的引用,在处理整行整列时可以将其看作一个Row或Column对象(当然这并不存在)。

(4)处理包括非连续区域的Range对象。

在处理包括非连续区域的Range对象时,Range对象的Rows或Columns属性返回的只是第1个区域的总行数或总列数,而不是其实际包括的总行数或列数。此时需要使用Areas属性来返回Range中的各个区域,然后分别处理。

2.8.5 Resize属性

可以使用一个Range对象的Resize属性来返回一个与原区域有相同顶端单元格,但不同行列数的区域。如下代码返回对A1:B2的引用:

        Range("A1:D10").Resize(2,2)

Resize属性的语法为:

        对象表达式.Resize(行数,列数)

其中行数和列数为改变后的区域所包括的行数和列数。

Resize 属性在需要在行数或列数上扩展或压缩一个区域时十分有用。例如,有一个名为“Sales”的数据列表。在其中增加了一条记录,需要重命名该数据列表以包括新一行,代码如下:

        With Range("Sales")
            .Resize(.Rows.Count + 1).Name = "Sales"
        End With

在上面的Resize语句中仅使用了一个参数,而省略了第2个列数的参数。如果省略了行数和列数中的某个参数,则表示保留原区域相应的行数或列数不变。例如,如下返回对A1:B10的引用:

        Range("A1:D10").Resize(,2)

2.8.6 用CurrentRegion属性

CurrentRegion属性返回指定单元格所在的当前区域,该区域指以空行与空列组合为边界的区域,等同于 Ctrl+*快捷键的选中范围。以图2-19 为例,则 Range(“B2”).CurrentRegion ‘返回区域为 B2,Range(“C2”). CurrentRegion‘返回区域为 B2:C2,Range(“B3”).CurrentRegion ‘返回区域为B2:C4,Range(“C3”).CurrentRegion ‘返回区域为 B2:C4, Range(“B4”).CurrentRegion ‘ 返 回 区 域 为 B4:C4, Range(“C4”).CurrentRegion ‘返回区域为C4。

图2-19 当前区域

2.8.7 End属性

End 属性返回一个 Range 对象,该对象代表包含源区域的区域尾端的单元格。等同于按Ctrl+↑、Ctrl+↓、Ctrl+←或Ctrl+→快捷键。其语法为:

        表达式.End(方向)

其中,方向参数为XlDirection枚举值,取值如表2-5所示。

表2-5 XlDirection枚举值

Ctrl+→快捷键将到达同一行中位于空单元格前的下一个数据项目,如果在本行的数据区域中没有空单元格,将到达本行中最后一个数据项所在的单元格;如果在给定单元格后的一个单元格为空,那么将跳至下一个有数据的单元格或工作表的最右边;如果第 1 行的A1单元格和数据区域的第1行最后一个单元格之间没有空格,那么如下代码将引用了第1行最后一个单元格:

        Range("A1").End(xlToRight)

另外3个方向对应的中文举例为xlDown、xlToLeft和xlUp。

如果数据区域有空格并要达到第1行最后一个数据项单元格,那么可以从第1 行最后一列的开始向左。只要XFD1没有数据即可,可以使用如下语句:

        Range("XFD1").End(xlToLeft)

如果要兼容Excel 2007以前的版本,则使用如下语句:

        Cells(1,Columns.Count).End(xlToLeft)

同样,可以将End属性用于返回区域。假定有一个起于单元格B1的数据表格,并且被一个空行和一个空列分隔。只要在首行有连续的标题,在最后一列也有连续的数据,就可以使用如下代码行返回对该数据表格的引用:

        Range("B1",Range("B1".End(xlToRight).End(xlDown)))

在上面的示例中,其作用等同于使用CurrentRegion属性,即等价于如下代码行:

        Range("B1").CurrentRegion

但是End属性具有更灵活的使用方式,如下代码行将第1个工作表的标题行复制到第2个工作表:

        With Worksheets(1).Range("A1")
            .Range(.Cells(1), .End(xlToRight)).Copy Destination:= _
                                        Worksheets(2).Range("A1")
        End With

2.8.8 Union和Interset方法

Union和Interset是Application对象的方法,因为它们为<globals>的成员,因此可以直接使用。而无需引用Application对象,二者是非常有用的工具。

在需要从两个或更多的单元格区域中生成一个区域时,使用Union方法;在需要从两个或更多的区域中找到共同的单元格区域,即求其重叠的部分,则使用Intersect方法。例如:

        '求多个区域的并集,并设置并集区域的填充色为黄色
        Union(Range("A1:D3"), Range("B2:C3"), _
                  Range("B1:E3")).Interior.Color = vbYellow
        '求多个区域的交集,并设置交集区域的外边框为红色且加粗
        Intersect(Range("A1:D3"), Range("B2:C3"), _
                  Range("B1:E3")).BorderAround ColorIndex:=3, Weight:=xlThick

上面两行代码的运行结果如图2-20所示。

图2-20 Union和Intersect方法的运行结果

在组合连续区域时,可以选择使用CurrentRegion及Resize等属性根据现有区域返回需要处理的区域;而在组合非连续区域时,Union方法则是一个更好的选择,可以用其联合多个非连续区域,并一次完成处理。

2.8.9 SpecialCells方法

SpecialCells方法的作用等同于如图2-21所示的Excel中“定位条件”对话框的作用,可以按F5键打开该对话框。

图2-21 “定位条件”对话框

该对话框允许用户执行多种操作,例如选择工作表中最后一个单元格或所有拥有数据,而非计算公式的单元格。某些操作有自己的方法,但是多数操作都可以通过 Range 对象的SpecialCells方法实现。

SpecialCells方法返回一个Range对象,该对象代表与指定类型和值匹配的所有单元格。

其语法为:

        表达式.SpecialCells(类型, 值)

其中“类型”参数的取值为表2-6所示的XlCellType枚举值,当类型为xlCellTypeConstants或xlCellTypeFormulas时可以用“值”参数确定结果中应包含的单元格类型,如表2-7所示。将这些值相加可使此方法返回多种类型的单元格,默认情况下将选择所有常量或公式。

表2-6 XlCellType枚举值列表及其描述

表2-7 XlSpecialCellsValue枚举值列表及其描述

如下语句返回当前工作表中最后一个单元格的地址:

        ActiveSheet.Cells.SpecialCells(xlCellTypeLastCell).Address

假定有一个数据表“Data”,基于上面的源数据执行了多种计算。现在需要删除所有的计算,而仅保留最初的源数据,那么可以使用如下语句:

        Worksheets("Data").UsedRange.SpecialCells(xlFormulas).Delete

2.9 操作Range对象的示例

2.9.1 快速定位并填充区域中满足条件的单元格

VBA中的Range操作在处理连续区域时非常方便,可以使用Range对象的CurrentRegion、Resize、EntireRow及EntireColumn等属性一次性获取所需处理的区域。而在处理非连续区域时被迫使用For Each…Next或For…Next等语句循环访问每一个单元格,以获取满足条件的区域并处理。频繁地访问Excel对象将造成VBA代码运行效率大大降低。

本节将以图2-22所示的工作表为例来说明如何使用SpecialCells方法。该工作表为某超市收集的各供货商报价单,其中报价都有有效日期。在K列“价格过期日期”一列中给出了各个报价的过期截止日期,超过了该日期,则该项报价作废并在“状态”一列中以颜色标识报价日期。

图2-22 供货商报价单

● 红色:报价过期日期已在当前日期以前,表示已过期。

● 黄色:尚未过期,当前日期距离报价过期日期还有100天。

● 绿色:当前日期距离报价过期日期大于100天。

可以通过如下两个步骤实现。

在单元格L3中输入公式:

        =IF(K3-NOW()<0,"RED",IF(K3-NOW()<100,NA(),1))

将如上公式填充至L列的最后一个数据单元格。

运行如下代码,其中Range("Status")为该L状态列对应的数据区域:

        Sub GetStatus()
            With Range("Status")
              '使用SpecialCells方法时,如果找不到满足条件的单元格
              '区域,会产生错误。故使用此语句忽略错误
              On Error Resume Next
              '定位到公式结果为数字的单元格区域,并将字体和背景色
              '都设为黄色
              With .SpecialCells(xlCellTypeFormulas, xlNumbers)
                  .Font.Color = vbYellow
                  .Interior.Color = vbYellow
              End With
              '定位到公式结果为数字的单元格区域,并将字体和背景色
              '都设为黄色
              With .SpecialCells(xlCellTypeFormulas, xlTextValues)
                  .Font.Color = vbRed
                  .Interior.Color = vbRed
              End With
              '定位到公式结果为错误值的单元格区域,并将字体和背景色
              '都设为绿色
              With .SpecialCells(xlCellTypeFormulas, xlErrors)
                  .Font.Color = vbGreen
                  .Interior.Color = vbGreen
              End With
              On Error GoTo 0
            End With
        End Sub

提示

本节示例请见随附光盘中的“Range_SpecialCells应用示例.xlsm”。

2.9.2 快速定位并删除满足条件的所有数据行

仍以图2-22所示的数据表为例,假定超市不卖黑芝麻口味的任何食品,那么就需要移除D列“口味”为“黑芝麻”的所有行。

(1)使用普通的循环遍历删除法。

一种方式是遍历所有的行,并检查D列的每一个单元格。采取这种方式时首先检查最后一行,然后逐行的向上查看,这样Excel不用向上移动即可删除的行。如果从上往下查看,那么不能使用一个简单的For…Next循环记数来捕获正在处理的行号。因为一旦删除行,那么循环计算器和行数将不再对应:

        Sub DeleteRows()
            Dim lRow As Long
            '删除操作容易引起屏幕抖动,因此关闭屏幕刷新
            Application.ScreenUpdating = False
            '从最后一行向前处理,直到到达第3行
            For lRow = Cells(Rows.Count, "D").End(xlUp).Row To 3 Step -1
              '删除D列中有"黑芝麻"的所有行
              If Cells(lRow, "D").Value = "黑芝麻" Then
                  Cells(lRow, "D").EntireRow.Delete
              End If
            Next 'lRow
            '打开屏幕刷新
            Application.ScreenUpdating = True
        End Sub

如果一定要从上往下查找并删除,则可以使用Do…Loop循环:

        Sub DeleteRows_Down()
            Dim lRow As Long
            Dim rngTest As Range
            '删除操作容易引起屏幕抖动,因此关闭屏幕刷新
            Application.ScreenUpdating = False
            Set rngTest = Cells(2, 4)
            Do
              If rngTest.Value = "黑芝麻" Then
                  Set rngTest = rngTest.Offset(1)
                  rngTest.Offset(-1).EntireRow.Delete
              Else
                  Set rngTest = rngTest.Offset(1)
              End If
            Loop Until VBA.IsEmpty(rngTest)
            '打开屏幕刷新
            Application.ScreenUpdating = True
        End Sub

在上面的代码中使用一个Set rngTest= rngTest.Offset(1)来获取下一个需要处理的代码,并使用IsEmpty()来判断是否已经处理完所有的单元格,这样避免了For…Next中将循环计数器与所需处理的行数绑定时删除行而调整计数器的代码。

此外,在上面的代码中判断单元格是否为空,使用的是VBA.IsEmpty(rngTest),而不是rngTest=“”。如果单元格有值,但是为空字符串,使用是否为“”来判断就会漏掉需要处理的行,所以在判断单元格是否为空最好使用IsEmpty()。

(2)使用查找方法。

Find方法等同于Excel中的“查找”功能,也能返回满足条件的单元格,故可用其查找并删除满足条件的数据行:

        Sub DeleteRows_Find()
            Dim rngFoundCell As Range
          '删除操作容易引起屏幕抖动,因此关闭屏幕刷新
          Application.ScreenUpdating = False
          '寻找包括黑芝麻的单元格区域
          Set rngFoundCell = Range("D:D").Find(What:="黑芝麻")
          '循环到找不到单元格区域为止
          Do Until rngFoundCell Is Nothing
              '删除找到的单元格数据行
              rngFoundCell.EntireRow.Delete
              '查找下一个单元格
              Set rngFoundCell = Range("D:D").FindNext
          Loop
          '打开屏幕刷新
          Application.ScreenUpdating = True
      End Sub

(3)使用筛选和定位结合的方法。

最后一种方法是综合使用Excel的筛选和定位功能,即AutoFilter和SpecialCells方法:

        Sub DeleteRows_Autofilter()
            Dim lLastRow As Long 'Last row
            Dim rng As Range
            Dim rngDelete As Range
            '删除操作容易引起屏幕抖动,因此关闭屏幕刷新
            Application.ScreenUpdating = False
            With ActiveSheet
              '重置最后一行
              .UsedRange
              '定位到最后一行
              lLastRow = .Cells.SpecialCells(xlCellTypeLastCell).Row
              '将rng设为包括整个数据表的区域
              Set rng = Union(Range("Test"), Range("Status"))
              '设置D列的筛选仅显示需要删除的黑芝麻
              rng.AutoFilter field:=3, Criteria1:="黑芝麻"
              '获得对可见行的引用
              Set rngDelete = rng.SpecialCells(xlCellTypeVisible)
              '删除行
              rngDelete.EntireRow.Delete
              '显示所有的数据
              .ShowAllData
              '重置最后一行
              .UsedRange
            End With
            '打开屏幕刷新
            Application.ScreenUpdating = True
        End Sub

在使用SpecialCells方法时一定要注意如果没有满足条件的单元格,则会返回“未找到单元格”的运行时错误,除非确定能够有满足条件的单元格;否则建议使用On Error Resume Next的语句来屏蔽可能发生的找不到单元格的错误。在本例中可以在SpecicalCells方法前后添加如下语句:

        '屏蔽可能因找不到满足条件的区域而引起的运行时错误
        On Error Resume Next
        '获得对可见行的引用
        Set rngDelete = rng.SpecialCells(xlCellTypeVisible)
        '删除行
        rngDelete.EntireRow.Delete
        On Error GoTo 0

提示

使用On Error Resume Next语句时尽量同时使用On Error GoTo 0来关闭Resume Next的错误处理,以避免屏蔽掉不应该漏掉的错误。

一个好的Excel VBA编程习惯是尽可能在VBA中调用Excel内置的功能和函数,在一般情况下内置的功能函数都比用户自定义的VBA方法运行效率更高。在本例中,如上3种方法的运行效率逐步提高,尤其在数据量增大的情况下这种差异会根据明显。

查找方法相对于普通的循环遍历删除法大大地减少了访问Excel对象的次数,但由于访问次数取决于满足条件的数据量。在满足条件的数据量较多的情况下也不尽如人意,所以考虑使用第 3 种方法。该方法只需使用 AutoFilter 筛选出满足条件的单元格,然后使用SpecialCells 定位这些单元格并一次删除其整行。这种方法几乎不受总数据量和满足条件的数据量的限制,完成速度很快。

以上3种删除满足条件的数据行的结果均如图2-23所示,相对于如图2-22所示的原始数据,位于口味为“黑芝麻”的行已经被删除。可通过“重置数据”按钮恢复原数据,并单击“设置状态颜色”按钮重新设置颜色,然后重新运行并比较不同的删除方法。

图2-23 删除满足条件的数据行的结果

提示

本节示例请见随附光盘中的“Range_SpecialCells应用示例.xlsm”。

2.9.3 根据选择区域执行相应的操作

为了减少访问Excel对象的次数,常常使用Union函数在返回多个需要处理的连续或非连续区域的并集,然后一次处理。Intersect返回多个区域之间的交集,在判断用户是否已经操作了特定的区域是不可或缺的。

下面例子根据用户的选择区域来判断是否发生跳转:

        Private Sub Worksheet_SelectionChange(ByVal Target As Range)
            Dim rngJump           As Range
            Dim rngIntersect      As Range
            Dim vInput            As Variant
            '设置需要发生跳转的区域
            Set rngJump = Union(Range("B3"), Range("O2:O8"))
            '获取所选区域和跳转区域的交叠区域
            Set rngIntersect = Intersect(Target, rngJump)
            '如果所选区域没有与跳转区域重叠,则退出
            If rngIntersect Is Nothing Then Exit Sub
            '根据选中的单元格不同,跳转到不同的页
            Select Case rngIntersect.Cells(1).Value
            Case "总览"
              '选中A1的"总览",则跳转到Sheets("总览").
              Sheets("总览").Select
            Case "明细"
              '选择位于N2:N6中的某个"明细"按钮,则跳转到"明细"页
              '且将"明细"页的图表源数据区域Range("ChtSoc")的数据
              '替换成Range("InputData")部分选择的数据
              vInput= Range("InputData").Rows(rngIntersect.Cells(1).Row - 3).Value
              Sheets("明细").Select
              Sheets("明细").Range("ChtSoc").Value = vInput
            End Select
        End Sub

其中Worksheet_SelectionChange放在图2-24所示的主页工作表的模块中,这是一个在用户选择发生改变时自动引发的工作表事件过程(工作表事件过程请参阅第4章)。

图2-24 主页工作表

该事件过程的实现步骤如下。

使用Union方法联合需要跳转的B3和O2:O8区域,并将其赋给对象变量rngJump。

使用 Intersect 方法判断用户是否选择了需要跳转的区域,并将选择区域 Target 和rngJump区域的交叉区域赋给变量rngIntersect。

获取rngIntersect的第1个单元格的值,并据此判断需要跳转到总览或明细工作表。注意此处判断 rngIntersect.Cells(1).Value,即交集区域第 1 个单元格的值,因为交集区域可能不只一个单元格。例如用户选择了整个工作表,那么交集区域就是rngJump。这样据此无法判断跳转的工作表,因此仅使用交集区域的第1个单元格判断。

如果选择B3总览单元格,则直接跳转到总览工作表,如图2-25所示,不需要其他处理;如果选择了 O2:O8“明细”区域的单元格,则跳转到明细工作表,并将其中的明细数据替换为当前选中的车间明细数据,如图2-26所示。

图2-25 总览数据表

图2-26 明细数据表

提示

本节示例请见随附光盘中的“Range_Union&Intersect应用示例.xlsm”。

2.10 使用名称

Excel中最有用的功能特性之一就是具有为各种对象提供有意义的名称的能力,不仅可以命名工作簿、工作表及图表等对象,同时也可以命名单元格和单元格区域,甚至可以直接命名常数或公式。处理使用有意义名称的单元格区域很容易,本节将介绍什么是名称,以及如何在VBA中操作名称。

提示

本节的所有示例请见随附光盘中的“Names名称.xlsm”。

2.10.1 名称及“名称管理器”对话框

1.什么是名称

Excel帮助中给出的名称定义为“代表单元格、单元格区域、公式或常量值的单词或字符串”。事实上,在为Excel的单元格或单元格区域创建名称时,实际上是在创建一个命名的公式。这些命名的公式不存在于单元格中,而存在于Excel的内存中。

2.名称类型

可以创建和使用的名称类型有以下两类。

(1)定义的名称:代表单元格、单元格区域、公式或常量值的名称,可以创建自己的定义的名称。Excel 也会自动创建定义的名称,例如在设置打印区域之后,Excel 会自动创建Print_Area。

(2)表名称:每次插入Excel表时,Excel都会创建表1和表2等默认的Excel表名称,可以更改此名称以使其更有意义。

3.名称的作用域

每个名称都有其作用域,按作用域可分为工作表级别的局部名称和整个工作簿的全局名称,指在没有对象识别符的情况下能够识别名称的位置。

如果用户定义了一个名称(如Test),并且其作用域为Sheet2,则该名称在没有使用对象识别符时只能在Sheet2中,而不能在Sheet1或Sheet3中被识别。

要在另一个工作表中使用局部工作表名称,可以通过在其前面加上该的名称来访问,如下面的示例所示:

        Sheet2!Test

如果定义了一个名称(如AllTest),并且其作用域为工作簿,则该名称对于该工作簿中的所有工作表而言都是可识别的,对于其他任何工作簿都是不可识别的。

名称在其使用范围内必须始终惟一,Excel不允许在同一范围内出现两个相同的名称,可以在不同的范围中使用同一个名称。例如,可以在同一个工作簿中定义一个作用域分别为Sheet1、Sheet2和Sheet3的局部名称(如Test)。尽管每个名称都相同,但在其作用域内都是惟一的。

甚至也可以定义与局部名称相同的工作簿级别的全局名称(如Test),在这种情况下,存在局部名称与全局名称之间的冲突。为解决此冲突,默认情况下Excel使用工作表级别的名称,因其优先于全局工作簿级别。如果要不受此优先级影响而在工作表中使用工作簿名称,可以通过在名称前加上工作簿名称前缀来消除名称冲突:

        Workbooks("Book1")!Test

4.“名称管理器”对话框

使用“名称管理器”对话框可以管理工作簿中所有已定义的名称和表名称(除VBA中创建的隐藏名称外),例如可以用其查找有错误的名称、查看名称的值和引用、查看或编辑说明性批注、排序和筛选名称列表,以及添加、更改或删除名称。

5.打开“名称管理器”对话框

单击功能区上的“公式”选项卡|“定义的名称”组|“名称管理器”按钮 或按快捷键Ctrl+F3来打开如图2-27所示的“名称管理器”对话框。

图2-27 “名称管理器”对话框

表2-8所示为“名称管理器”对话框中“名称“列表中的选项及其内容。

表2-8 “名称”列表中的选项目及其内容

在“名称管理器”对话框中可以执行的操作如下。

(1)调整列宽。

拖动列宽到合适处或双击,以使其根据列中的最大值自动调整该宽,如图2-28所示。

图2-28 调整列宽

(2)排序列表项。

如要排序某列,则单击列标题,则会按照按升序或降序排序该列并扩展到整个列表。

(3)使用“筛选”下拉列表框按钮指定列表显示内容。

使用如图2-29的所示“筛选”下拉列表框中的命令可以指定显示名称子集。

图2-29 “筛选”下拉列表框中的命令

其中使用4个分隔符将7个筛选项分成4组,除第1个筛选项“清除筛选”清除所有的筛选外,其他各组之中的两个选项是互斥的,同一时间只能选择其中一个命令。但是同一时间可以在3个组中各选择一个命令,最后将筛选出同时满足这些条件的名称。

各筛选命令及其筛选结果如表2-9所示。

表2-9 各筛选命令及其筛选结果

(4)更改名称。

单击要更改的名称,然后单击“编辑”按钮。也可以双击需要更改的名称,以打开“编辑名称”对话框。

在“名称”文本框中为引用键入新名称。

在“引用位置”文本框中更改引用,然后单击“确定”按钮。

若要取消不需要或意外的更改,单击取消按钮 ,或者按Esc键。

若要保存更改,单击提交按钮 ,或者按回车键。

(5)删除名称。

以如下方式之一选择所需的名称。

● 选择单个名称:单击该名称即可。

● 选择连续的多个名称:拖动选择所需名称,或者按住Shift键单击所需名称。

● 选择不连续的多个名称,按住Ctrl键单击所需名称。

单击“删除”按钮或按Delete键。

单击“确定”按钮。

2.10.2 在Excel中创建并使用名称

1.名称的命名规则及内置名称

命名如下。

(1)名称的第1个字符必须是一个除符号和数字外的字符(如汉字字符或字母),下画线(_)和反斜杠(\)可以用,其余字符可以是字母、汉字字符、句点(.)、下画线(_)、反斜杠(\)及数字等。

(2)名称不能包括空格。可以使用下画线、小圆点及单词的大小写等来替代空格以区分不同的单词(如RN_Chart_XAxes、LRN_DataCount)。

(3)可以使用任何字母和数字的组合,但是不能以数字开始(如 2RowStart),也不能使用和单元格引用类似的名称(如A3或R1C1)。

(4)不能超过255个字符。

(5)不区分大小写字符。

以上规则是必需遵守的,同时能够直接从名称中判断出该名称的作用域,建议使用如下命名规则。

(1)全局名称:使用RN+下画线+有意义的名称。

(2)局部名称:使用LRN+下画线+有意义的名称。

例如:

        '用做存放Sheet1中数据行行数的局部名称
        Worksheets("Sheet1").Names("LRN_RowCount")
        '用作存放工作簿Book1打开的次数的全局名称
        Workbooks("Book1").Names("RN_OpenTimes")

除了上述的命名规则外,另外还尽量不要使用 Excel 的一些内置名称,如 Criteria、Database、Extract、Print_Area、Print_Titles、表1或表2等,以及Consolidate_Area和Sheet_Title。

这些名称都有其相应的作用,也可以通常改变这些内置名称来执行某些设置。

2.创建名称

Excel提供了如下创建名称的方式。

(1)使用“新建名称”对话框。

选择需要命名的单元格或区域。

单击功能区上“公式”选项卡|“定义的名称”组|“定义名称”按钮 或右击所需命名的区域,在快捷菜单中选择“命名单元格区域”命令打开如图2-30所示的“新建名称”对话框。

图2-30 “新建名称”对话框

在“名称”文本框中输入一个合法的满足命名规则的名称,或使用Excel推荐的名称。

在“范围”下拉列表框中选择创建属于某个工作表的局部名称或属于整个工作簿的全局名称。

如果需要,在“备注”文本框中键入一个不超过255个字符的注释。例如,名称描述或其相关的其他信息。

检查“引用位置”处的地址是否正确,如果需要改变引用并删除现有引用,输入带等号的新地址。或单击 按钮,选择新的区域,然后单击 按钮。

提示

除了单元格地址引用外,一个名称可以包括数字、字符串或一个公式。要创建这样的名称,在“引用位置”文本框中输入等号=,后跟常量值或公式。这样的名称在工作表中没有可见的位置,只能通过这种方式创建,并且只能在“名称管理”对话框中查看。可以使用这种名称在工作簿中存储信息,而不用将数据放在工作表单元格中。

单击“确定”按钮。

提示

如果需要创建多个名称,则在“名称管理器”对话框中单击“新建”按钮添加名称,然后返回“名称管理器”对话框继续创建。

(2)使用名称框创建名称。

名称框为公式栏左边的下拉列表框,如果名称指向一个range区域,可以选中该区域。然后在名称框中键入名称,然按回车键。

如果创建局部名称,可以在工作簿中多个工作表中使用同一个名称的要使得一个名称的对象属于特定工作表,如果是从名称框中输入,则需要在名称属性前面加上激活工作表的名称以及一个感叹号。例如,如图2-31所示,可以键入ResizeName!LRN_Rank1th来定义一个属于ResizeName的局部名称LRN_Rank1th。

图2-31 使用名称框创建局部名称

还可以创建跨越多个工作表的名称,步骤如下。

单击功能区上“公式”选项卡|“定义的名称”组|“定义名称”按钮

在“新建名称”对话框的“名称”文本框中键入要用于引用的名称。

在“引用位置”文本框中删除等号=后的引用。

单击要引用的第1个工作表的选项卡,按住Shift键单击要引用的最后一个工作表的选项卡。

选择要引用的单元格或单元格区域。

(3)使用“以选定区域创建名称”对话框创建名称。

工作表中可能包括用来命名相邻单元格或区域的行或列标签,图2-32所示为一个在左边一列有地区标签并在顶端一行有年份标签的数据区域B2:F6。可以为区域中的除第1行外的各行创建一个使用其左边一列(即 B 列)中单元格值标签的名称,同时也可为区域中的除第1列外的各列使用其顶端一行(即第2行)单元格中值的名称。例如,C3:F3区域应该被命名为“甲”,C2:C6应该被命名为“第一季度”。

创建使用基于相邻行或列标签名称,步骤如下。

选择需要命名的单元格及名称标签,可以是单元格或单元格区域,标签必须紧邻需要命名的单元格。不允许选择不连续的区域,在本例中选择B2:F6区域。

单击功能区上“公式”选项卡|“定义的名称”组|“根据所选内容创建”按钮 ,打开图2-32所示的“以选定区域创建名称”对话框,选择“首行”和“最左列”复选框,因为名称标签位于第2列(选中区域的首列)和第2行(选中区域的首行)中。

图2-32 “以选定区域创建名称”对话框

单击“确定”按钮。

提示

如果同时为行列都创建了名称,那么可以使用交叉引用。如在工作表单元格中输入“=第一季度 甲”将获得对C3单元格的引用,返回13.3。

3.查看名称

如图2-33所示,使用名称框来选择一个名称可以查看全局名称及其属于激活工作表的局部名称。

图2-33 使用“名称框”速查看名称

提示

可以拖动名称框的分隔符来改变名称框的大小。

也可以使用图2-34所示的“定位”对话框来查看名称。

图2-34 “定位”对话框

4.使用名称的优势

为单元格和区域使用名称有如下的优势。

(1)在Excel中。

● 一个有意义的区域名称RN_InputData比该区域的地址A1:D3更好记忆和理解。

● 选择一个命名单元格或区域,其名称就会出现在名称框中。

● 可以通过在名称框中选择相应的名称而快速跳转到该命名区域。

● 便于在公式中使用,并可为动态图表的数据系列及坐标轴标签赋值,还可以在设置数据有效性使用。

提示

在任何地方使用名称时,不用重新输入名称。而只需要按F3键,在“粘贴名称”对话框中双击该名称,则该名称即会出现在需要输入名称处,如图2-35所示。例如需要在“数据有效性”对话框中的“来源”文本框中输入LRN_SEX,按F3键打开“粘贴名称”对话框。选择LRN_SEX后单击“确定”按钮,或双击LRN_SEX选项,该名称自动出现在“数据有效性”对话框中的“来源”文本框中。

图2-35 通过“粘贴名称”对话框选择并输入名称

(2)在VBA中。

● 名称使得代码更好维护并更加利用使用,如Range("RN_InputData")比Range("A1:D3")更为直观。

● 在 Excel 中,如果公式引用的单元格发生变化,如移动了引用的单元格或单元格区域,则Excel会自动更新其引用;如果在VBA中也引用了该公式,则不会自动更新其引用。例如,假如VBA要在单元格区域A1中写入一个数字,如果在A1中插入了一行,则代码将写入错误的单元格。

● 名称可用于跟踪单元格区域,在大小可变的数据表上尤其有用。如果知道一个具体的名称,即用来定义包括需要处理的数据的区域,则VBA代码将简单得多。使用一些基本技术,如Range的Resize属性来改变一个名称的区域引用以获得新的需要处理的区域。

2.10.3 Names集合和Name对象

Excel对象模型包括可以在VBA代码中使用的一个Names集合和一个Name对象。

需要区分一个Name名称对象及其Name属性,如下代码返回对Names集合中一个Name对象的引用:

        Names("Data")

如果需要改变一个Name对象的Name属性,则可以使用如下代码:

        Names("Data").Name = "NewData"

已经改变了Name属性,可以如下引用该Name名称对象:

        Names("NewData")

全局名称和局部名称都属于与工作簿对象相关联的 Names 集合,如果使用一个如同Application.Names或Names的引用,则是引用该活动工作簿的Names集合;如果使用一个如同Workbooks(“Data.xls”).Names的引用,则是引用该特定工作簿的Names集合。

除去全局名称的局部名称也属于其所在的工作表的 Names 集合,如果使用一个如Worksheets(“Sheet1”).Names的引用或ActiveSheet.Names,则是引用该工作表的局部名称集合。

可以使用Range区域对象的Name属性,在后面有更多的介绍。

名称也可以声明为隐藏,以使其不在“名称管理器”对话框中出现,这是一个阻止用户查看存储信息的有用方式。

2.10.4 添加指向区域的名称

添加指向区域的名称有如下两种方式。

(1)使用Names集合的Add方法。

可以使用Workbook对象Names集合的Add方法创建一个指向区域的全局名称:

        Names.Add Name:="RN_Data", RefersTo:="=Sheet1!$B$10:$B$12"

重要的是在定义前面添加等于符号,并使用美元符($)以绝对引用方式引用单元格;否则名称将引用一个相对于名称定义时激活单元格地址的相对地址。如果需要名称指向激活工作表,那么可以省略工作表引用:

        Names.Add Name:="RN_Data", RefersTo:="=$B$10:$B$12"

如果名称已经存在,则其被当前定义替换。

如果要创建一个本地名称,可以使用如下代码:

        Names.Add Name:="Sheet1!LRN_Sales", RefersTo:="=Sheet1!$D$10:$D$12"

或在与工作表相关联的Names集合(其中仅包括位于工作表的名称)中添加名称:

        Worksheets("Sheet1").Names.Add Name:="LRN_Sales", _
                                RefersTo:="=Sheet1!$F$10:$F$12"

(2)使用Range对象的Name属性。

可以直接定义Range对象的Name属性:

        Range("A1:A10").Name = "RN_Regions"

如果需要名称为本地的,则包括一个工作表名称:

        Range("D1:D10").Name = "Sheet1!LRN_Years"

在代码中以这种方式处理Range对象要简单得多,如果使用Names集合的Add方法,则需要为其RefersTo参数准备一个带等号的range地址。例如,如果创建了一个rng对象变量,并将名称RN_Sales分配给该对象变量,需要获得rng对象的Address属性并添加一个等号:

        Names.Add Name:="RN_Sales", RefersTo:="=" & rng.Address

另外一种方法如下:

        rng.Name = "RN_Sales"

不能完全不考虑Add方法,因为这是创建一个指向数字、公式和字符串名称的惟一方法。

2.10.5 在名称中保存数据

在名称中保存数据的数据保护数字、字符串及数组。

(1)保存数字或字符串。

当使用一个名称来保存数字或字符串时,可以不在 RefersTo 参数前面添加一个等号。如果添加等号,则作为一个公式处理。如下代码分别在HoldNumber和RN_HoldString中保存一个数字和一个字符串:

        Dim v As Variant
        v = 0.6667
        Names.Add Name:="RN_HoldNumber", RefersTo:=v
        v = "TestString"
        Names.Add Name:="RN_HoldString", RefersTo:=v

这样提供了一种在两个Excel会话之间保存VBA中所需数据的方式,在关闭Excel后不会消失。如果保存字符串,则多至255个字符。

可以使用Evaluate方法等价体来获取名称中数据,如下所示:

        v = [HoldNumber]

也可以在名称中保存公式,公式必须以等号开始。如下所示的代码将一个COUNTA函数保存在一个名称中:

        Names.Add Name:="RN_AvgOfData", RefersTo:="=AVERAGE(Sheet1!$C$3:$F$6) "

该名称可以用在工作表单元格中,以返回一个C3:F6区域的平均值,如图2-36所示。

图2-36 在单元格中使用名称

同样,可以使用Evaluate方法等价体来计算VBA中的名称:

        MsgBox [RN_AvgOfData]

(2)保存数组。

可以在一个名称中存储数组变量,如下代码在 asArray 中创建一个数字数组,并在RN_ArrayName中保存设数组:

        Sub ArrayToName()
            Dim asArray(1 To 10, 1 To 80)
            Dim iRowIndex   As Integer
            Dim iColIndex   As Integer
            Dim lIndex     As Long
            '创建一个数组并将其保存在名称中
            '创建数组
            For iRowIndex = 1 To 10
              For iColIndex = 1 To 80
                  lIndex = (iRowIndex - 1) * 80 + iColIndex
                  asArray(iRowIndex, iColIndex) = "NO." & Str(lIndex)
              Next 'iColIndex
            Next 'iRowIndex
            '保存在名称中
            Names.Add Name:="RN_ArrayName", RefersTo:=asArray
        End Sub

提示

在Excel 97/2000中,在名称中保存的数组的最大列数为256。数组中的总元素数目不能超过5 461个。在Excel 2002/2003/2007中仅受内存的限制。

Evaluate方法可以用来将持有一个数组的名称分配给一个Variant变体类型变量,如下代码将在ArrayToName过程中创建的RN_ArrayName中的内容分配给vArray并显示数组中的最后一个元素的值:

        Sub NameToArray()
            Dim vArray As Variant
            '将名称的内容分配给一个变量
            vArray = [RN_ArrayName]
            '显示数组元素
            MsgBox vArray(10, 3)
        End Sub

提示

通过分配包括一个数组的名称给一个变体类型变量,创建的数组总是以 1 为下标,即使在模块声明部分使用了Option Base 0。

2.10.6 隐藏名称

隐藏的名称在“名称管理器”和“新建名称”对话框等处不可见,并且不能在 Excel中手工方式直接创建,而需要使用VBA创建。这不是一种高的隐藏信息的安全方式,因为任何掌握VBA技术的人都可以发现名称。这是一种避免用户因出现的奇怪名称而困惑的一种有效方式,尽管有一些限制,但是隐藏名称也提供了一种在工作簿中保存信息的方式。(1)创建隐藏名称。

可以通过将名称的Visible属性设置为False以隐藏名称,可以在创建名称时完成:

        Names.Add Name:="RN_ColorCount", RefersTo:=3, Visible:=False

也可以在创建名称以后隐藏名称:

        Names("RN_ColorCount").Visible = False

(2)删除隐藏名称。

一些Excel加载宏会生成隐藏名称,通常可以忽略这些隐藏名称,但是有时这些隐藏的名称会引起问题。例如,将一个工作表复制到另一个工作簿中,隐藏的名称也同样被复制并且可能产生难以跟踪的链接。可以使用如下代码删除指定工作簿中的所有隐藏名称:

        Sub DeleteHiddenNames(Optional ByRef wbDeal As Workbook)
        '----------------------------------------------------------
        '目的:删除指定工作簿中的所有隐藏名称
        '参数:wbDeal 可选,需要删除隐藏名称的工作簿,如果省略,则
        '      删除当前工作簿。
        '----------------------------------------------------------
            Dim objName As Name
            '如果未指定工作簿,则处理当前工作簿
            If wbDeal Is Nothing Then Set wbDeal = ActiveWorkbook
            '循环遍历工作簿的所有名称
            For Each objName In wbDeal.Names
              With objName
                  '如果该名称为隐藏名称,则执行如下操作
                  If Not .Visible Then
                      '在“立即”窗口中记录下该名称的名称及其引用
                      Debug.Print .Name, .RefersTo
                      '删除该名称
                      .Delete
                  End If
              End With
            Next 'objName
        End Sub

2.10.7 处理Excel内置名称

Excel使用一些固有的名称来捕获特定的属性。当需要在一个工作表中应用一个打印区域时,Excel将为该range指定一个Print_Area的本地名称。如果设置打印标题,Excel创建一个本地名称 Print_Titles。如果在功能区上选择“数据”选项卡,单击“排序和筛选”组中的“高级”按钮来在一个列表中从一个新的区域提取数据,Excel创建新的局部名称Criteria和Extract。

提示

如果创建一个使用 ActiveSheet.ShowDataForm 方法来编辑数据列表,将发现宏无法在不是以A1开始的数据列表中运行,可以通过在数据列表应用Database名称来解决这个问题。

需要知道Excel使用的这些名称,通常应尽量避免使用这些名称;除非需要使用这些名称产生的作用。例如,可以通过删除名称Print_Area来移除打印区域。如下两行代码实现该功能:

        ActiveSheet.PageSetup.PrintArea = ""
        ActiveSheet.Names("Print_Area").Delete

如果使用表格属性来控制数据列表,Excel 2007也产生特别的名称。默认情况下,Excel将这些表格命名为“Table1”和“Table2”等。这些名称将出现在“名称管理器”对话框中,但是不包括在Names集合中。不能通过“名称管理器”对话框或引用Names集合的代码进行删除。

综上所述,要特别小心使用Criteria、Database、Extract、Print_Area、Print_Titles、表1和表2、Consolidate_Area,以及Sheet_Title。

2.10.8 改变名称指向的区域

如图2-37所示的工作表包括位于B8:G12的使用LRN_Database名称的数据列表,还有一个位于B4:G4中的使用LRN_Input名称的数据输入区域。

图2-37 命名区域

如果要将输入数据复制到数据列表的底部,并通过名称LRN_Database来增加引用区域以包括新行,可以使用如下代码:

        Sub AppendData()
            Dim lRows As Long
            With Range("LRN_Database")
              '获取可以写入数据最后一行的行号
              lRows = .Rows.Count + 1
              '在LRN_Database最后一行写入新输入数据
              .Rows(lRows).Value = Range("LRN_Input").Value
              '扩展LRN_Database区域以增加一行
              .Resize(lRows).Name = "LRN_Database"
            End With
        End Sub

该代码的一个特点是不受活动工作簿中的 LRN_Database 的大小和位置及 LRN_Input位置的影响。LRN_Database有4行或4 000行,在LRN_Input和LRN_Database添加更多的列,代码也将正常运行,而不需要修改。LRN_Input和LRN_Database如果在不同的工作表中,代码也将继续正常运行,不过需要添加工作表对象识别符。

2.10.9 查找名称

如果需要查找一个名称是否存在一个工作簿中,可以使用如下函数。这样既可以作为工作表函数使用,也可以作为一个VBA调用的函数使用,当然比仅适应于一种情况略微复杂一点:

        Function IsNameExist(sName As String) As Boolean
        '------------------------------------------------------
        '目的:检查名称是否存在于工作簿中
        '参数:sName 需要判断存在性的名称
        '------------------------------------------------------
            Dim sResult        As String
            Dim rngCaller      As Range
            Application.Volatile
            '忽略错误
            On Error Resume Next
            '试图获取调用此函数的单元格
            Set rngCaller = Application.Caller
            '清除已发生的所有错误
            Err.Clear
            If rngCaller Is Nothing Then
                '通过VBA代码调用该函数
                sResult = ActiveWorkbook.Names(sName).Name
            Else
                '在单元格中调用该函数
                sResult = rngCaller.Parent.Parent.Names(sName).Name
            End If
            '如果没有错误,则名称存在
            If Err.Number = 0 Then IsNameExist = True
        End Function

IsNameExist有一个输入参数sName,要求是一个字符串的名称。函数被定义为volatile (易失性函数),因此每次在工作表函数使用或引用的名称被添加或删除时都会重算。

函数首先通过将Application.Caller属性分配给rngCaller来判断是否被工作表单元格调用,如果被一个单元格调用,Application.Caller返回一个指向包括函数的单元格的Range对象;否则Set语句将导致一个错误。可通过On Error Resume Next语句抑制,该错误将被清除。在接下来的运行中,如果导致了更多的错误,则不应该被第1个错误屏蔽。

接下来,函数使用一个If来判断rngCaller是否未被分配值,如果没有,则调用又来自于另一个VBA过程。在这种情况下,函数试图将活动工作簿上的Name对象的Name属性分配给一个哑变量;如果该工作表的父对象是包括rng的工作簿,并且该名称不存在,则再次产生一个错误。

可以在一个工作表单元格中如下所示使用IsNameExist:

        ="在本工作簿中" & IF(IsNameExist("第一季度"),"存在第一季度","不存在第一季度")

可以使用如下的过程来循环用户输入一个名称,并判断其是否存在:

        Sub TestName()
            '测试IsNameExist函数
            If IsNameExist(InputBox("请输入名称")) Then
              MsgBox "名称已存在"
            Else
              MsgBox "名称不存在"
            End If
        End Sub

如果查找一个局部名称,则必须包括其工作表名,即“工作表名!名称”,如前面章节所述。

以一个工作表函数的形式调用IsNameExist,并判断名称“第一季度”是否存在,将得到如图2-38所示的输出。

图2-38 在工作表中测试该函数

(1)查看一个Range区域的名称。

Range对象的Name属性返回该Range的名称,如果Range有一个名称,则Name对象的RefersTo属性指向相应的Range。

可以使用如下代码来显示一个区域rngTest的名称:

        MsgBox rngTest.Name

该代码将无法得到预期的结果,因为一个Range对象的Name属性返回的是一个Name对象。代码将显示Name对象的默认属性值,这是其RefersTo属性。而此处实际所需的是Name对象的Name属性,因此可以使用如下代码:

        MsgBox rngTest.Name.Name

上面的代码仅在rng有一个名称时才能正常运行。如果rng没有名称,则会产生一个运行时错误。可以使用如下代码来显示活动工作表中的选择的单元格中名称:

        Sub TestNameOfRange()
            Dim nmName As Name
            '检查一个range区域是否有一个名称
            '忽略错误
            On Error Resume Next
            '试图获取名称
            Set nmName = Selection.Name
            '显示结果
            If nmName Is Nothing Then
              MsgBox " 所选的区域无名称"
            Else
              MsgBox nmName.Name
            End If
        End Sub

如果这个range有多个的名称,则会显示以字母顺序排在最前面的名称,结果如图2-39所示。

图2-39 判断所选的区域是否有名称

(2)判断与一个Range区域重叠的名称。

如果需要检查名称是否应用在工作表的区域中,将十分方便获取与当前选择单元格相关联的所有名称。可以仅需要完全或是部分与所选单元格重叠的名称,如下代码列出了与活动工作表完全重叠的所有名称:

        Sub ListNamesContainWholeSelection()
            Dim sMsg          As String
            Dim nmName         As Name
            Dim rngNameRange   As Range
            Dim rngIntersect   As Range
            '列出所选单元格区域上的所有名称
            '忽略错误
            On Error Resume Next
            '遍历工作簿上的所有名称
            For Each nmName In Names
              '将rngNameRange设为Nothing,然后试图获取
              '对名称指向区域的引用
              Set rngNameRange = Nothing
              Set rngNameRange = nmName.RefersToRange
              '如果成功,则有一个区域引用
              If Not rngNameRange Is Nothing Then
                  '查看名称是否在当前活动工作表中
                  If rngNameRange.Parent.Name = ActiveSheet.Name Then
                      '查看所选的区域和名称所指向的是否在一个区域中
                      Set rngIntersect = Intersect(Selection, rngNameRange)
                      If Not rngIntersect Is Nothing Then
                          '检查所选的整个区域是否都在名称区域中
                          If Selection.Address = rngIntersect.Address Then
                            sMsg = sMsg & nmName.Name & vbCr
                          End If
                      End If
                  End If
              End If
            Next nmName
            'Displaymessage
            If sMsg = "" Then
              MsgBox "没有包括整个所选区域的名称"
            Else
              MsgBox "完全包括所选区域的名称有:" & vbCr & sMsg
            End If
        End Sub

ListNamesContainWholeSelection以抑制错误的On Error Resume Next开始,然后进入一个处理工作簿上所有名称的For Each…Next循环,将rngNameRange设为Nothing来去除上一个循环留下的任何区域引用。其他试图使用当前Name对象的Name属性作为一个Range对象的名称,并分配一个对Range的引用给rngNameRange。如果该名称没有引用一个range,则会失败,因此剩下的循环仅在一个合法的range对象被分配给rngNameRange时被执行。

下一个If判断rngNameRange是否指向激活工作表及其父对象是否包括rngNameRange的工作表,内层代码仅在父工作表的名称和激活工作表的名称一致时执行。rngIntersect 被分配给选中单元格和rngNameRange的交集。如果此处有一个重叠,并且rngIntersect不是Nothing,则执行最内层的If。

最后一个If检查rngIntersect中的重叠区域是否与选中的区域一样,如果满足条件,则选中的单元格完全包括在rngNameRange中,当前Name对象的Name属性被添到sMessage中已经存在的任何名称之中。此外,使用VBA的内置常数vbCr来添加一个回车符,因此每个名称都会在sMessage中另起一行。

当For Each…Next终止时,If判断是否在sMessage中有任何内容。如果sMessage是一个零长度字符串,则消息框将显示一个合适的消息来通知没有找到任何名称;否则以sMessage显示找到的名称的列表。

在包括 3 个命名区域(LRN_Gray、LRN_Black 和 LRN_White)的工作表中运行ListNamesContainWholeSelection,将得到图2-40所示的结果。

图2-40 判断完全包括所选区域的名称

如果要找出哪些名称与选中的单元格重叠,则忽略是否完全包括所选的单元格,结果图2-41所示。

图2-41 与所选区域交叠的名称

代码如下所示:

        Sub ListNamesOverlapSelection()
            Dim sMsg              As String
            Dim nmName            As Name
            Dim rngNameRange      As Range
            Dim rngIntersect      As Range
            '列出所选单元格区域交叠的所有名称
            '忽略错误
            On Error Resume Next
            '遍历工作簿上的所有名称
            For Each nmName In Names
              '将rngNameRange设为Nothing,然后试图获取
              '对名称指向区域的引用
              Set rngNameRange = Nothing
              Set rngNameRange = Range(nmName.Name)
              '如果成功,则有一个区域引用
              If Not rngNameRange Is Nothing Then
                  '查看名称是否在当前活动工作表中
                  If rngNameRange.Parent.Name = ActiveSheet.Name Then
                      '检查名称指向区域是否与所选区域有交叠部分
                      Set rngIntersect = Intersect(Selection, rngNameRange)
                      If Not rngIntersect Is Nothing Then
                        sMsg = sMsg & nmName.Name & vbCr
                    End If
                  End If
              End If
          Next 'nmName
          '显示信息
          If sMsg = "" Then
              MsgBox "所选的区域不与任何区域交叠"
          Else
              MsgBox "与所选区域交叠的名称有:" & vbCr & sMsg
          End If
      End Sub

注意ListNamesContainWholeSelection和ListNamesOverlapSelection使用不同的技术将名称引用的range分配对象变量rngNameRange,与如下语句效果一样:

        Set rngNameRange = nmName.RefersToRange
        Set rngNameRange = Range(nmName.Name)

2.11 小结

本章介绍Excel2007对象模型,其中详细介绍了Range区域对象相关联的属性和方法、引用Range对象的多种形式并提供了多个操作Range对象的实例。

Name名称对象是一个非常有用的对象,本章从在Excel中操作该对象入手,介绍了如何在VBA中使用该对象。

通过本章的学习,读者应了解Excel 2007的基础和核心对象。