学习excel-vba与xml、asp协同应用


翻译:hxhgxy、fanjy 排版:konggs、 c81 Excel Home 出品 2007-5-4 学习 Excel VBA 与 XML、ASP 的 协同应用 内容介绍与版权说明 本书带领你从始至终创建 VBA 过程,VBScripts,ASP 页面,XML 文件和 XSL 工作表。沿着这条路,有许多详细的,适用的“如何做”例子和插图。本书的 方法是“由做而学”。本书的前面几章介绍了一些基本的 VBA 概念,循序渐进,复 杂的主题在后面的章节。除了 Excel VBA 之外,本书还介绍了两种可以和 Excel 并用的热门英特网技术。一种是 ASP (Active Server Pages),另一种是 XML (Extensible Markup Language)。 书号:1556227612 出版社:Wordware Publishing, Inc. (November 2002) 原书作者:Julitta Korol 原书名:MS Excel 2002 VBA/XML Programming and ASP 本译本仅供 Excel VBA 爱好 者学习与交流之用,严禁用 于商业用途,版权归原作者 及原出版公司所有。 支持正版, 侵权必究! 目 录 第 1 章 电子表格自动化介绍 ......................................................................................... 1 1.1 理解宏.................................................................................................................................2 1.1.1 宏的一般应用 ..............................................................................................................2 1.1.2 计划宏 .........................................................................................................................2 1.1.3 录制宏 .........................................................................................................................4 1.1.4 运行宏 .........................................................................................................................6 1.1.5 修改宏代码 ..................................................................................................................7 1.1.6 添加注释....................................................................................................................10 1.1.7 分析宏代码 ................................................................................................................ 11 1.1.8 清理宏代码 ................................................................................................................ 11 1.1.9 测试已修改的宏.........................................................................................................13 1.1.10 执行宏的两层界面 .....................................................................................................13 1.1.11 改进宏 .......................................................................................................................14 1.1.12 重命名宏....................................................................................................................16 1.1.13 运行宏的其它方法 .....................................................................................................16 1.1.14 使用键盘快捷键运行宏..............................................................................................17 1.1.15 通过菜单运行宏.........................................................................................................17 1.1.16 通过工具栏按钮运行宏..............................................................................................20 1.1.17 通过工作表中的按钮运行宏.......................................................................................20 1.1.18 保存宏 .......................................................................................................................22 1.1.19 打印宏 .......................................................................................................................22 1.1.20 保存宏在个人宏工作簿中 ..........................................................................................22 1.1.21 打开含有宏的工作簿..................................................................................................24 1.2 VBE窗口...........................................................................................................................25 1.2.1 理解工程窗口 ............................................................................................................26 1.2.2 理解属性窗口 ............................................................................................................26 1.2.3 理解代码窗口 ............................................................................................................27 1.2.4 VBE窗口中的其它窗口..............................................................................................29 第 2 章 VBA初步......................................................................................................... 31 2.1 理解指令、模块和过程 .....................................................................................................32 2.1.1 命名VBA工程 ............................................................................................................32 2.1.2 重命名模块 ................................................................................................................33 2.1.3 调用另一个工程中的过程 ..........................................................................................34 2.2 理解对象、属性和方法 .....................................................................................................36 2.2.1 学习对象、属性和方法..............................................................................................37 i 2.3 句法与文法....................................................................................................................... 39 2.3.1 打断很长的VBA语句 ................................................................................................. 41 2.3.2 理解VBA错误............................................................................................................ 42 2.3.3 搜索帮助 ................................................................................................................... 44 2.3.4 语法和编程快捷助手 ................................................................................................. 46 2.3.5 属性/方法列表........................................................................................................... 46 2.3.6 常数列表 ................................................................................................................... 47 2.3.7 参数信息 ................................................................................................................... 47 2.3.8 快速信息 ................................................................................................................... 48 2.3.9 自动完成关键字输入 ................................................................................................. 48 2.3.10 缩进/凸出 .................................................................................................................. 49 2.3.11 设置注释块/解除注释块 ............................................................................................ 49 2.4 使用对象浏览器................................................................................................................ 50 2.5 使用VBA对象库 ............................................................................................................... 55 2.5.1 用对象浏览器来查找过程.......................................................................................... 57 2.6 使用立即窗口 ................................................................................................................... 57 2.6.1 获取立即窗口里的信息 ............................................................................................. 59 2.7 学习对象 .......................................................................................................................... 60 2.7.1 处理电子表格单元格 ................................................................................................. 60 2.7.2 使用Range属性......................................................................................................... 61 2.7.3 使用Cells属性 ........................................................................................................... 62 2.7.4 使用Offset属性.......................................................................................................... 62 2.7.5 选取单元格的其它方法 ............................................................................................. 64 2.7.6 选取行和列................................................................................................................ 64 2.7.7 获取工作表信息 ........................................................................................................ 65 2.7.8 在工作表中输入数据 ................................................................................................. 65 2.7.9 获取工作表中的信息 ................................................................................................. 66 2.7.10 单元格格式................................................................................................................ 66 2.7.11 移动、复制和删除单元格.......................................................................................... 67 2.7.12 处理工作簿和工作表 ................................................................................................. 67 2.7.13 处理窗口 ................................................................................................................... 68 2.7.14 管理Excel应用程序 ................................................................................................... 69 第 3 章 理解变量、数据类型和常量 .............................................................................71 3.1 保存VBA语句的结果........................................................................................................ 72 3.2 变量是什么....................................................................................................................... 72 3.3 数据类型 .......................................................................................................................... 72 3.4 如何创建变量 ................................................................................................................... 74 3.5 如何声明变量 ................................................................................................................... 74 3.6 指定变量的数据类型 ........................................................................................................ 76 ii 3.7 变量赋值...........................................................................................................................77 3.8 强制声明变量....................................................................................................................80 3.9 理解变量的作用域 ............................................................................................................81 3.9.1 过程级(本地)变量 ......................................................................................................81 3.9.2 模块级变量 ................................................................................................................82 3.9.3 工程级变量 ................................................................................................................83 3.10 变量的存活期 ................................................................................................................83 3.11 理解和使用静态变量 .....................................................................................................83 3.12 声明和使用对象变量 .....................................................................................................84 3.12.1 使用明确的对象变量..................................................................................................86 3.13 查找变量定义 ................................................................................................................86 3.14 在VBA过程中使用常量 .................................................................................................86 3.14.1 内置常量....................................................................................................................87 第 4 章 VBA过程:Sub子过程和Function过程.......................................................... 91 4.1 关于Function过程............................................................................................................92 4.1.1 创建Function过程......................................................................................................92 4.1.2 执行Function过程......................................................................................................94 4.2 传递参数...........................................................................................................................96 4.3 找出内置函数..................................................................................................................101 4.3.1 使用MsgBox函数.....................................................................................................103 4.3.2 使用InputBox函数 ...................................................................................................109 4.3.3 使用InputBox方法 ................................................................................................... 111 4.4 使用主过程和子过程....................................................................................................... 115 第 5 章 使用VBA作判断 .............................................................................................119 5.1 关系运算符和逻辑运算符................................................................................................120 5.2 If…Then语句..................................................................................................................120 5.3 基于多个条件的判断.......................................................................................................123 5.4 The If…Then…Else语句 ...............................................................................................125 5.5 If…Then…ElseIf语句 ....................................................................................................128 5.6 嵌套的If…Then语句.......................................................................................................129 5.7 Select Case语句............................................................................................................130 5.7.1 使用带有Is的Case子句............................................................................................132 5.7.2 指定Case子句里数值的范围....................................................................................133 5.7.3 在Case子句里指定多个表达式................................................................................134 第 6 章 在VBA中重复操作......................................................................................... 137 6.1 Do循环: Do…While和Do…Until...................................................................................138 6.2 观察过程执行..................................................................................................................142 iii 6.3 While…Wend循环......................................................................................................... 142 6.4 For…Next 循环............................................................................................................. 143 6.5 For Each…Next循环..................................................................................................... 145 6.6 提前退出循环 ................................................................................................................. 146 6.7 嵌套的循环..................................................................................................................... 147 第 7 章 利用VBA管理列表和数据表 ...........................................................................149 7.1 理解数组 ........................................................................................................................ 150 7.2 声明数组 ........................................................................................................................ 151 7.3 数组的上界和下界.......................................................................................................... 152 7.4 在VBA过程里使用数组 .................................................................................................. 152 7.5 数组和循环语句.............................................................................................................. 153 7.6 使用二维数组 ................................................................................................................. 155 7.7 静态和动态数组.............................................................................................................. 156 7.8 数组函数 ........................................................................................................................ 158 7.8.1 Array函数................................................................................................................ 158 7.8.2 IsArray函数............................................................................................................. 159 7.8.3 Erase函数............................................................................................................... 159 7.8.4 LBound函数和UBound函数.................................................................................... 160 7.9 数组中的错误 ................................................................................................................. 161 7.10 数组作为参数.............................................................................................................. 163 第 8 章 利用VBA操作文件和文件夹 ...........................................................................165 8.1 操作文件和文件夹.......................................................................................................... 166 8.1.1 获取当前文件夹的名称(CurDir函数) ....................................................................... 166 8.1.2 更改文件或文件夹的名称(Name函数)..................................................................... 167 8.1.3 检查文件或文件夹是否存在(Dir函数)...................................................................... 167 8.1.4 获取文件修改的日期和时间(FileDateTime函数) ..................................................... 169 8.1.5 获取文件的大小(FileLen函数)................................................................................. 170 8.1.6 返回和设置文件的属性(GetAttr函数和SetAttr函数) ................................................ 170 8.1.7 更改缺省的文件夹或硬盘驱动器(ChDir语句和ChDrive语句) .................................. 172 8.1.8 创建和删除文件夹(MkDir语句和RmDir语句) .......................................................... 172 8.1.9 复制文件(FileCopy语句) ......................................................................................... 173 8.1.10 删除文件(Kill语句)................................................................................................... 175 8.1.11 从文件写入和读取数据(Input/Output) ..................................................................... 175 8.2 文件访问类型 ................................................................................................................. 175 8.2.1 处理顺序文件.......................................................................................................... 176 8.2.2 处理随机访问文件................................................................................................... 182 8.2.3 处理二进制文件 ...................................................................................................... 187 8.3 处理文件和文件夹的新式方法........................................................................................ 188 iv 8.3.1 使用WSH获取文件信息...........................................................................................191 8.3.2 FileSystemObject的方法和属性..............................................................................192 8.3.3 File对象的属性 ........................................................................................................195 8.3.4 Folder对象的属性....................................................................................................196 8.3.5 Drive对象的属性......................................................................................................197 8.3.6 使用WSH创建文本文件...........................................................................................198 8.3.7 使用WSH进行其它操作...........................................................................................200 第 9 章 利用VBA控制其它应用程序 .......................................................................... 203 9.1 启动应用程序..................................................................................................................204 9.2 在应用程序之间切换.......................................................................................................207 9.3 控制另一个应用程序.......................................................................................................208 9.4 控制应用程序的其它方法................................................................................................210 9.4.1 理解自动控制(Automation)......................................................................................210 9.4.2 理解链接(Linking)和嵌入(Embedding) ....................................................................210 9.4.3 使用VBA进行链接和嵌入 ........................................................................................212 9.4.4 COM和自动控制......................................................................................................213 9.5 理解绑定.........................................................................................................................213 9.5.1 后期绑定..................................................................................................................213 9.5.2 早期绑定..................................................................................................................214 9.5.3 建立对对象库的引用................................................................................................214 9.6 创建自动控制对象 ..........................................................................................................216 9.6.1 使用CreateObject函数 ............................................................................................216 9.6.2 使用自动控制创建一个新的Word文档.....................................................................216 9.6.3 使用GetObject函数 .................................................................................................217 9.6.4 打开已存在的Word文档...........................................................................................218 9.6.5 使用关键字New.......................................................................................................219 9.6.6 使用自动控制访问Microsoft Outlook .......................................................................220 第 10 章 对话框和自定义窗体 ..................................................................................... 223 10.1 Excel对话框 ...................................................................................................................224 10.2 打开文件对话框和保存文件对话框 .............................................................................227 10.3 GetOpenFilename和GetSaveAsFilename方法...........................................................231 10.4 创建窗体 .....................................................................................................................232 10.4.1 创建用户窗体的工具................................................................................................234 10.4.2 在窗体上放置控件 ...................................................................................................238 10.5 应用程序示例 1:信息调查 .........................................................................................238 10.5.1 在窗体上添加按钮、复选框和其它控件...................................................................239 10.5.2 更改控件名称 ..........................................................................................................242 10.5.3 设置其它的控件属性................................................................................................243 v 10.5.4 准备工作表以存储自定义窗体数据 ......................................................................... 244 10.5.5 显示自定义窗体 ...................................................................................................... 245 10.5.6 设置Tab键顺序........................................................................................................ 246 10.5.7 理解窗体和控件事件 ............................................................................................... 246 10.5.8 编写VBA过程响应窗体和控件事件 ......................................................................... 248 10.5.9 编写过程来初始化窗体 ........................................................................................... 249 10.5.10 编写过程填充列表框控件 .................................................................................... 250 10.5.11 编写过程控制选项按钮........................................................................................ 251 10.5.12 编写过程同步文字框和旋转按钮 ......................................................................... 251 10.5.13 编写过程关闭用户窗体........................................................................................ 252 10.5.14 转移窗体数据到工作表........................................................................................ 252 10.5.15 使用Info Survey应用程序.................................................................................... 253 10.6 应用程序示例 2:学生和考试..................................................................................... 254 10.6.1 使用多页控件和TabStrip控件.................................................................................. 254 10.6.2 为“学生和考试”自定义窗体编写VBA过程........................................................... 257 10.6.3 使用“学生和考试”自定义窗体............................................................................. 260 第 11 章 自定义集合和类模块 ......................................................................................265 11.1 使用集合..................................................................................................................... 266 11.1.1 声明自定义集合 ...................................................................................................... 267 11.1.2 给自定义集合添加对象 ........................................................................................... 267 11.1.3 从自定义集合中移除对象........................................................................................ 269 11.2 插入:模块还是类模块? ........................................................................................... 270 11.2.1 创建自定义对象 ...................................................................................................... 270 11.3 创建类 ........................................................................................................................ 270 11.3.1 变量声明 ................................................................................................................. 271 11.3.2 定义类的属性.......................................................................................................... 271 11.3.3 创建Property Get过程............................................................................................. 272 11.3.4 创建Property Let过程 ............................................................................................. 272 11.3.5 创建类的方法.......................................................................................................... 273 11.3.6 创建一个类的实例................................................................................................... 274 11.3.7 类模块里的事件过程 ............................................................................................... 274 11.3.8 创建用户界面.......................................................................................................... 275 11.3.9 观察VBA过程的执行............................................................................................... 285 第 12 章 使用VBA创建自定义菜单和工具栏 ................................................................289 12.1 工具栏 ........................................................................................................................ 290 12.2 使用CommandBar对象............................................................................................. 291 12.2.1 创建自定义工具栏................................................................................................... 292 12.2.2 删除自定义工具栏................................................................................................... 294 vi 12.2.3 使用CommandBar的属性........................................................................................294 12.2.4 使用CommandBar控件 ...........................................................................................294 12.2.5 添加控件到CommandBar........................................................................................296 12.2.6 理解和使用控件属性................................................................................................296 12.2.7 控件方法..................................................................................................................299 12.3 使用菜单 .....................................................................................................................300 12.3.1 菜单编程..................................................................................................................302 12.3.2 创建子菜单 ..............................................................................................................305 12.3.3 修改内置快捷菜单 ...................................................................................................306 12.3.4 创建快捷菜单 ..........................................................................................................308 第 13 章 调试VBA过程和处理错误.............................................................................. 313 13.1 测试VBA过程..............................................................................................................314 13.2 终止过程 .....................................................................................................................314 13.2.1 使用断点..................................................................................................................315 13.2.2 在中断模式下使用立即窗口.....................................................................................318 13.2.3 使用Stop语句 ..........................................................................................................320 13.2.4 添加监视表达式.......................................................................................................321 13.2.5 清除监视表达式.......................................................................................................324 13.2.6 使用快速监视 ..........................................................................................................324 13.2.7 使用本地窗口和调用堆栈对话框 .............................................................................325 13.3 逐句运行VBA过程 ......................................................................................................327 13.3.1 逐句运行过程 ..........................................................................................................328 13.3.2 逐过程执行 ..............................................................................................................328 13.3.3 设置下一条语句.......................................................................................................329 13.3.4 显示下一条语句.......................................................................................................329 13.3.5 终止和重新设置VBA过程 ........................................................................................329 13.4 理解和使用条件编译 ...................................................................................................330 13.5 使用书签导航 ..............................................................................................................332 13.6 捕捉错误 .....................................................................................................................332 第 14 章 Excel2002 事件编程 ..................................................................................... 339 14.1 事件过程介绍 ..............................................................................................................340 14.2 激活和禁用事件 ..........................................................................................................341 14.3 事件顺序 .....................................................................................................................342 14.4 工作表事件..................................................................................................................343 14.5 工作簿事件..................................................................................................................347 14.6 图表事件 .....................................................................................................................357 14.6.1 内嵌图表事件 ..........................................................................................................361 14.7 可为Application对象识别的事件 ...............................................................................362 vii 14.8 查询表事件 ................................................................................................................. 364 第 15 章 在Excel里使用Access ..................................................................................367 15.1 对象库 ........................................................................................................................ 368 15.1.1 建立对对象库的引用 ............................................................................................... 373 15.2 连接到Access............................................................................................................ 374 15.2.1 使用自动控制连接到Microsoft Access数据库 ......................................................... 374 15.2.2 使用DAO连接到Microsoft Access数据库................................................................ 377 15.2.3 使用ADO连接到Microsoft Access数据库................................................................ 377 15.3 从Excel执行Access任务........................................................................................... 379 15.3.1 创建新的Access数据库........................................................................................... 379 15.3.2 打开Microsoft Access窗体...................................................................................... 380 15.3.3 打开Microsoft Access报表...................................................................................... 383 15.3.4 运行Microsoft Access查询...................................................................................... 385 15.3.5 运行选择查询.......................................................................................................... 385 15.3.6 运行参数查询.......................................................................................................... 387 15.3.7 调用Microsoft Access函数...................................................................................... 388 15.4 获取Microsoft Access数据到Excel工作表 ............................................................... 388 15.4.1 使用GetRows方法获取数据.................................................................................... 388 15.4.2 使用CopyFromRecordset方法获取数据 ................................................................. 390 15.4.3 使用TransferSpreadsheet方法获取数据................................................................. 391 15.4.4 使用OpenDatabase方法......................................................................................... 393 15.4.5 从Microsoft Access数据创建文本文件.................................................................... 395 15.4.6 从Microsoft Access数据创建查询表........................................................................ 397 15.5 在Excel里使用Microsoft Access数据 ...................................................................... 399 15.5.1 用Access数据创建内嵌图表 ................................................................................... 399 15.6 传输Excel电子表格到Access数据库......................................................................... 400 15.7 将Excel电子表格连接到Microsoft Access数据库 .................................................... 400 15.8 将Excel电子表格导入Access数据库......................................................................... 402 15.9 在Access表中放置Excel数据.................................................................................... 402 第 16 章 Excel和Internet ............................................................................................405 16.1 使用VBA创建超链接 .................................................................................................. 406 16.2 使用VBA创建和发布HTML文件................................................................................. 410 16.2.1 网络服务器——存储和打开工作簿......................................................................... 416 16.3 Web查询 ........................................................................................................................ 416 16.3.1 使用VBA创建和运行Web查询 ................................................................................ 419 16.3.2 带参数的Web查询................................................................................................... 421 16.3.3 动态Web查询.......................................................................................................... 425 16.3.4 刷新数据 ................................................................................................................. 426 viii 16.4 Excel和ASP(Active Server Pages,动态服务器主页).................................................427 16.4.1 创建一个ASP脚本 ...................................................................................................428 16.4.2 安装Internet Information Services (IIS)或者Personal Web Server .........................431 16.4.3 创建虚拟目录 ..........................................................................................................432 16.4.4 运行第一个ASP脚本................................................................................................437 16.4.5 在网络服务器上创建Tab分隔的文件........................................................................438 16.4.6 从用户的输入创建一个Excel文件............................................................................442 16.4.7 使用GetString方法打印Excel数据到Internet浏览器 ................................................449 16.4.8 在ASP里创建图表 ...................................................................................................451 第 17 章 XML和Excel 2002 ........................................................................................ 457 17.1 XML是什么? .................................................................................................................458 17.2 Excel 2002 对XML的支持..............................................................................................458 17.3 使用VBA创建XML电子表格文件 ................................................................................460 17.4 在记事本里查看XML源文件........................................................................................461 17.5 规范格式(Well-Formed)的XML文档...........................................................................464 17.6 在IE里查看XML源文件 ...............................................................................................465 17.7 在Microsoft Excel 2002 外面创建XML文件 ..............................................................467 17.8 XML Flattener................................................................................................................469 17.9 使用Stylesheets(样式表)设置XML数据格式..............................................................471 17.10 链接XML文档与样式表 ...............................................................................................475 17.11 查看使用样式表格式化后的XML文档 .........................................................................476 17.12 使用XSLT模板 ............................................................................................................478 17.13 XML数据岛(Data Islands)..........................................................................................480 17.13.1 使用VBScript来转换XML数据岛的内容 ...............................................................483 17.14 将单元格区域保存为XML文档 ....................................................................................484 17.15 XML文档对象模型 ......................................................................................................486 17.15.1 使用XSL样式表编程将XML转换为HTML.............................................................488 17.15.2 使用VBScript和XML DOM转换XML文档.............................................................490 17.15.3 操作XML文档节点 ...............................................................................................491 17.15.4 从元素节点获取信息............................................................................................492 17.16 通过ADO创建的XML..................................................................................................495 17.16.1 以XML保存ADO记录集到硬盘.............................................................................496 17.16.2 XML文件的两种类型............................................................................................496 17.16.3 更改XML文件的类型............................................................................................498 17.16.4 应用XSL样式表....................................................................................................499 17.16.5 转换基于属性的XML数据为HTML表格................................................................500 17.16.6 装载ADO记录集...................................................................................................503 17.16.7 在内存里保存ADO记录集为XML.........................................................................504 17.16.8 保存ADO记录集为XML DOMDocument对象 ......................................................506 ix 17.17 XML和ASP................................................................................................................. 507 17.18 发布Excel XML 数据到网络服务器 ............................................................................511 第 18 章 附录A:数据透视表和数据透视图编程.............................................................533 18.1 创建数据透视表报表................................................................................................... 534 18.2 编程创建一个数据透视表报表 .................................................................................... 537 18.3 从外部数据源创建数据透视表报表............................................................................. 538 18.3.1 使用PivotTableWizard方法创建数据透视表报表..................................................... 538 18.3.2 使用PivotCache对象的CreatePivotTable方法创建数据透视表报表........................ 540 18.4 格式、分组和排序数据透视表报表............................................................................. 542 18.5 在数据透视表中隐藏项目 ........................................................................................... 543 18.6 添加计算字段和项目到数据透视表中 ......................................................................... 544 18.7 使用VBA创建一份数据透视图报表............................................................................. 549 18.7.1 将数据透视表报表和数据透视图报表保存为网页.................................................... 550 第 19 章 附录B:一些特殊功能编程...............................................................................553 19.1 Excel 2002 对象模型中的一些对象 ............................................................................... 554 19.1.1 Tab对象................................................................................................................... 554 19.1.2 Speech对象 ............................................................................................................ 554 19.1.3 SpellingOptions对象 ............................................................................................... 555 19.1.4 CellFormat对象....................................................................................................... 555 19.1.5 Characters对象....................................................................................................... 556 19.1.6 AutoCorrect对象 ..................................................................................................... 557 19.1.7 PageSetup对象....................................................................................................... 558 19.1.8 FormatCondition对象.............................................................................................. 558 19.1.9 Graphic对象............................................................................................................ 559 19.1.10 Diagram对象 ....................................................................................................... 560 19.1.11 CustomProperty对象 .............................................................................................. 561 19.2 OfficeXP对象模型中的一些对象.................................................................................... 564 19.2.1 FileSearch对象 ....................................................................................................... 564 19.2.2 SearchScope对象和ScopeFolder对象 ................................................................... 565 19.2.3 NewFile对象 ........................................................................................................... 565 第 20 章 附录C:智能标记(Smart Tags)的使用和编程介绍...........................................569 20.1 检查Excel工作簿中的智能标记选项........................................................................... 570 20.2 创建您的第一个简单的智能标记................................................................................. 571 20.3 存储智能标记列表定义文件........................................................................................ 573 20.4 使用自定义智能标记................................................................................................... 574 20.5 通过ASP页面查找信息............................................................................................... 574 20.6 使用VBA操作智能标记............................................................................................... 577 x 20.6.1 SmartTag对象 .........................................................................................................578 20.6.2 SmartTagAction对象 ...............................................................................................578 20.6.3 SmartTagOptions对象.............................................................................................578 20.6.4 SmartTagRecognizer对象 .......................................................................................579 第 21 章 附录D:Microsoft Office XP网络组件....................................................... 581 21.1 电子表格网络组件.......................................................................................................583 21.2 图表网络组件 ..............................................................................................................588 21.3 数据源网络组件 ..........................................................................................................595 21.4 数据透视表网络组件 ...................................................................................................597 xi 第1章 电子表格自动化介绍 您准备将智能加入到您的 Microsoft Excel 2002 电子表格 中吗?通过将日常工作自动化,您可以使电子表格更快且更有效 率。在此处键入文档的摘要。摘要通常是对文档内容的简短总结。 在此处键入文档的摘要…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 2 共 612 页 您准备将智能加入到您的Microsoft Excel 2002 电子表格中吗?通过将日常工作自动化,您可 以使电子表格更快且更有效率。本章介绍使用宏命令来加速处理电子表格的过程,您将学到宏是什 么,如何使用宏,何时使用宏,如何编写宏,以及如何修改宏代码。开始学习宏很简单,创建宏也 不需要什么,只要具备基本的Microsoft Excel 2002 菜单知识和电子表格概念就可以了。准备好了 吗?打开您计算机中的Excel 2002,开始学习吧。 1.1 理解宏 宏是一些存储了一系列命令的程序。当创建一个宏时,您只是将键盘操作按顺序组合成一个简 单的命令,您以后可以“回演”这个命令。宏可以减少完成复杂任务的步骤,因此使用宏可以显著 地减少创建、设置格式、修改和打印工作表的时间。 可以使用Excel内置的录制工具来创建宏,也可以使用VB编辑器编写宏——使用强大的编程语 言Visual Basic for Applications(通常称作VBA)来创建Microsoft Excel 2002 宏。 技巧1-1:普通语言 Excel 5 是市场上第一个使用VBA的软件。接着,VBA被嵌入到所有的Microsoft Office应用软件中。这意 味着您从本书中学习的VBA知识同样可以应用到其它Microsoft Office软件中,例如,Word,、PowerPoint、Outlook 和Access。 1.1.1 宏的一般应用 Excel 2002 提供了很多内置且省时的功能使工作变得更快更灵活。在决定使用宏来自动化工作 表任务前,确保没有已有的内置功能来执行这项任务。当需要反复地完成一些操作,或者Excel没有 提供内置的工具完成这项工作时,就创建宏。 宏可以自动化工作表中的任何部分。例如,创建宏在工作表中输入标题或者用新标签替换列标 题实现自动化数据录入。宏也可以检查工作表所选区域里的重复值。使用宏,不仅可以快速地将格 式应用到多个工作表,也可以合并不同的格式,例如字体、颜色、边框和阴影等。即使Excel有优秀 的图表功能,但如果想要将图表创建和图表格式设置自动化,那么宏是一个好方法。当开始设置打 印区域、页边距、页眉和页脚、以及选择特殊的打印选项时,宏可以节省键盘操作的时间。 1.1.2 计划宏 在创建宏之前,花几分钟来考虑究竟想做什么。因为宏是大量键盘操作的集合,事先计划好操 作是非常重要的。计划宏最好的方式是手动将宏需要处理的操作执行一遍。在键盘操作的同时,在 一张纸上记录实际发生的情况,不要漏掉任何东西。像录音机一样,Excel宏录制器记录了所执行的 每个操作(译者注:事实上并非如此,有些操作是无法录制的)。如果在录制宏之前没有计划宏,那将 会得到很多不必要的操作,而这些都会影响代码运行的速度。尽管编辑宏代码比去除录制宏里面不 必要的步骤容易,但是,仅仅录制必要的步骤会节省你修改代码的时间和以后的麻烦。 假设一下子就想看出工作表中哪些区域是文本、哪些区域是数字以及哪些区域是公式。图1-1第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 3 显示了一个简单的电子表格,它使用不同的字体颜色和样式来帮助区分这些单元格里潜在的内容。 图1-1:使用Excel宏应用格式找出电子表格中相应的内容是容易的 为了生成如图1-1所示的格式,打开任何现有的包含计算公式的工作表,或者创建一个如图所 示的工作表。如果使用上图所示的工作表,确保使用SUM函数计算每月和季度的总和。 在录制宏之前,在工作表中手动执行下列操作步骤: 1. 选取一个单元格。 2. 选择菜单“编辑”—“定位”命令。 3. 在“定位”对话框中,单击“定位条件”按钮。 4. 在“定位条件”对话框中,选中“常量”选项按钮,然后去掉 “数字”、“逻辑值”和“错误” 复选框的选择,仅选中“文本”复选框。 5. 单击“确定”按钮返回到工作表中。此时工作表中含有文本的单元格已经被选中了。注意,在 下一步应用必要的格式设置前,不要改变所选中的区域。 6. 对于选中的文本单元格区域,选择菜单“格式”—“单元格”命令。 7. 在“单元格格式”对话框中,选择“字体”选项卡。设置字形为“粗体”,颜色为“紫色”。然 后单击“确定”按钮关闭该对话框。注意,现在工作表中含有文本的单元格显示了不同的颜色。 步骤1到7定位并格式所有文本单元格。要选取并格式数字单元格,执行下列操作步骤: 8. 选取一个单元格。 9. 选择菜单“编辑”—“定位”命令。 10. 在“定位”对话框中,单击“定位条件”按钮。 11. 在“定位条件”对话框中,选取“常量”选项按钮,然后去掉 “文本”、“逻辑值”、“错误” 复选框的选择,仅选中“数字”复选框。 12. 单击“确定”按钮返回到工作表中。此时工作表中含有数字的单元格已经被选中了。注意,在学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 4 共 612 页 下一步应用必要的格式设置前,不要改变所选中的区域。 13. 对于选中的数字单元格区域,选择菜单“格式”—“单元格”命令。 14. 在“单元格格式”对话框的“字体”选项卡中,设置字体颜色为“深蓝色”,然后单击“确定” 按钮关闭该对话框。 步骤8到14定位并格式数字单元格。要选取并格式公式单元格,执行下列操作步骤: 15. 选取一个单元格。 16. 选择菜单“编辑”—“定位”命令。 17. 在“定位”对话框中,单击“定位条件”按钮。 18. 在“定位条件”对话框中,选取“公式”选项按钮。 19. 单击“确定”按钮返回到工作表中。此时工作表中含有公式计算结果的单元格已经被选中了。 注意,在下一步应用必要的格式设置前,不要改变选中区域。 20. 对于选中的公式单元格区域,选择菜单“格式”—“单元格”命令。 21. 在“单元格格式”对话框的“字体”选项卡中,设置字形为“粗体”,颜色为“红色”。然后单 击“确定”按钮关闭该对话框。 步骤15到21定位并格式公式单元格。 可以在工作表中添加颜色图例,以便容易理解应用到工作表中单元格里的所有格式。 22. 选取区域A1:A3,选择菜单“插入”—“行”命令。 23. 选取单元格A1。 24. 选择菜单“格式”—“单元格”命令,并在“图案”选项卡中“单元格底纹”框中点击“紫色”, 单击“确定”按钮返回到工作表中。 25. 选取单元格B1并输入“文本”。 26. 选取单元格A2。 27. 选择菜单“格式”—“单元格”命令,并在“单元格底纹”框中点击“深蓝色”,单击“确定” 按钮返回到工作表中。 28. 选取单元格B2并输入“数字”。 29. 选取单元格A3。 30. 选择菜单“格式”—“单元格”命令,并在“单元格底纹”框中点击“红色”,单击“确定” 按钮返回到工作表中。 31. 选取单元格B3并输入“公式”。 32. 选取单元格A1。 完成步骤22到32后,单元格区域A1:A3中将显示一个简单的颜色图例,如图1-1所示。 正如您所看到的,不管工作表显示的任务多么简单,您都可能需要很多步骤来达到预期的效果。 创建一个可以重复刚才操作的宏,真的很省时间,特别是当你必须在大量工作表中重复同样的过程 时。 1.1.3 录制宏 现在您知道需要执行哪些操作后,打开宏录制器,开始创建您的第一个宏。 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 5 按照下面的步骤录制宏之前,确保已经清除了刚才示例工作表中的的格式。否则,按下Ctrl+A 组合键选中整个工作表,然后选择菜单“编辑”—“清除”—“格式”命令,选取单元格区域A1:A3 并选择菜单“编辑”—“删除”命令,在“删除”对话框中,选中“整行”选项按钮后,单击“确 定”按钮。 按照下面的步骤来创建您的第一个宏: 1. 选取一个单元格。 录制宏之前,应该决定是否要录制当前单元格的位置。如果想让宏总是从一个特定的位置开始, 那么在打开宏录制器后选择想让宏开始的单元格。如果当前单元格的位置无关紧要,那么先选取一 个单元格,然后再打开宏录制器。 2. 选择菜单“工具”—“宏”—“录制新宏”命令,出现“录制新宏”对话框。 图1-2:录制新宏时,必须命名宏在该对话框中,也可以给宏设置一个快捷键、保存的位置和描述宏 3. 输入宏名“WhatsInACell”。 技巧1-2:宏命名 如果忘记给宏命名,Excel 会给出一个默认的宏名,例如:Macro1,Macro2,等等。宏名可以包含字母、 数字和下划线,但是必须以字母开头(译者注:中文亦可,建议用英文)。例如:Report1是一个有效的宏名,然 而1Report则是无效的名称。宏名里不能含有空格。如果想隔开宏名中的每个单词,可以使用下划线。例如:输 入改为Whats_In_A_Cell 代替WhatsInACell。 4. 在“保存在”列表框中,选择“当前工作簿”。 技巧1-3:保存宏 Excel 允许将宏保存在三个位置: „ 个人宏工作簿——每次使用Excel 的时候都可以使用保存在这里的宏。个人宏工作簿在XLStart文件夹中。 如果这个工作簿不存在,那么在第一次使用这个选项的时候,Excel会自动生成该工作簿。 „ 新工作簿——Excel将宏放在一个新工作簿中。 „ 当前工作簿——宏将被保存在当前使用的工作簿中。 5. 在“说明”框中输入下面的文本:显示单元格里潜在的内容:文本,数字,公式。 6. 单击“确定”按钮关闭宏录新制对话框并开始录制。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 6 共 612 页 这时,出现“停止录制”工具栏。Excel应用程序底部的状态栏显示“准备录制”。 图1-3:“停止录制”工具栏允许停止宏录制器,或者指出在录制宏时Excel如何处理单元格地址 技巧1-4:在宏中的单元格地址:相对地址或绝对地址? „ 绝对地址——如果在执行宏的过程中,不管选中的单元格,宏都在特定的单元格执行所录制的操作,那么 使用绝对单元格地址。绝对单元格引用具有如下形式:$A$1,$C$5等,Excel宏录制器默认使用绝对引用。 在开始录制前,确保没有按下停止录制工具栏上的第二个按钮。当鼠标指向这个按钮时,提示“相对引用”。 „ 相对地址——如果想让宏可以在任何单元格执行操作,就打开相对引用。相对引用具有如下形式:A1, C5等。在开始录制前,确保按下停止录制工具栏上的第二个按钮。记住,Excel 将会继续使用相对引用, 直到退出Excel或者再次点击相对引用按钮。 „ 在录制宏的过程中,可以使用这两种单元格引用方法。例如:可以选取一个特定单元格(如$A$4),执行一 个操作,然后选取相对于当前单元格的另一个单元格(例如C9,在当前单元格$A$4往下5行和向右2列的位 置)。 在复制单元格时,相对引用会自动调整,而绝对引用则不会。 7. 执行本章前一部分手动试验的那些操作(参见“计划宏”) 录制宏时,只有按下了回车键或者点击“确定”按钮之后,所执行的操作才会被录制。如果按 下了Esc键或者点击“取消”按钮,宏录制器不会录制该顶操作。 8. 完成所有操作后,单击“停止录制”工具栏上的“停止录制”按钮,或者选择菜单“工具” —“宏”—“停止录制”命令。 1.1.4 运行宏 在创建了一个宏之后,至少要运行一次以确保它正确工作。在本章稍后将介绍运行宏的不同方 法,现在使用菜单命令运行宏。 要看到运行宏后的结果,确保清除示例工作表中的格式。按下Ctrl+A组合键选中整个工作表,然 后选择菜单“编辑”—“清除”—“格式”命令。选择单元格区域A1:A3并选择菜单“编辑”—“删 除”命令,在“删除”对话框中,选择“整行”选项按钮,然后单击“确定”按钮。稍后,你将在 另外一个宏里面录制清除工作表格式的步骤。 1. 打开任何包含文本、数字和公式的工作表。 2. 选择菜单“工具”—“宏”—“运行宏”命令打开“宏”对话框。 3. 点击要运行的宏的名称(参见图1-4)。 4. 单击“执行”按钮,执行宏 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 7 图1-4:在“宏”对话框中,可以选择一个宏去运行、编辑或者删除 第一次运行宏时,经常会发现宏不会按照预期的操作执行。这可能是由于在录制宏时,选择了 错误的字体,或者忘记改变单元格的颜色,或者临时发现最好再加上一个操作步骤。不必惊慌!Excel 允许修改代码,而不会强迫您重新录制那些单调的操作。 1.1.5 修改宏代码 在修改宏之前,必须找到宏录制器放置代码的位置。回想一下,在打开宏录制器时,选择了“当 前工作簿”作为保存地点。但最容易找到宏的方法是打开“宏”对话框,如图1-4所示。 1. 选择菜单“工具”—“宏”—“宏”命令。 2. 选择宏名(本例中为“WhatsInACell”) 3. 单击“编辑”按钮。 Excel 打开一个专门的窗口,该窗口称为Visual Basic编辑器(也称作VBE),如图1-5所示。您 可以试一试,使用用快捷键Alt+F11可以快速地在Excel表格应用程序窗口和VBE窗口之间切换。在 VBE中选择“文件”菜单上的关闭选项可以关闭VBA代码窗口并返回到电子表格界面。 代码窗口暂时看上去有些令人迷惑,不必担心。只要您开始录制宏,以及尝试编写一些代码, 您将会熟悉这个屏幕中所有的组件。现在,看一下VBE窗口中的菜单和工具栏,它们与Excel 窗口 中的菜单和工具栏不同。VBE窗口的菜单和工具栏包含一些编程和测试代码所需要的工具。只要彻 底地学习本书的每一章,您将会成为使用这些工具的专家。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 8 共 612 页 图1-5:VBE窗口是编辑宏命令和编写新的VBA代码的地方 VBE窗口的主要部分是多个窗口的集合界面,这些窗口在创建和测试VBA程序时是极其有用的。 图1-5显示了三个集合在一起的窗口:工程窗口、属性窗口和代码窗口。工程窗口显示了一个打开 的模块文件夹,该文件夹中模块1被选中了。Excel将您在特定工作簿中所录制的操作依次称作模块1、 模块2,等等。在本书接下来的章节里,您将利用模块来编写您自己的过程代码。模块类似于Word 中的空白文档,每个单独的模块被存储在称作“模块”的文件夹中。 技巧1-5:宏还是过程? 宏是通过内置的宏录制器录制或在VBA模块中手工输入的一系列命令或函数。从Excel 5.0开始,“宏”经常 被“过程”这个更广的概念所代替。尽管这两个词可以交替互换使用,但是许多编程者更喜欢“过程”。虽然宏 可以模仿键盘操作,但真正的过程则还可以执行一些不能通过鼠标、键盘或菜单来进行的操作。换句话说,过 程是一个更复杂的宏,它吸收了传统编程语言的语言结构。 代码窗口(参见图1-5)显示了下列由宏录制器录制的代码: Sub WhatsInACell() ' ' WhatsInACell Macro ' 显示单元格里潜在的内容:文本,数字,公式。 ' ' Selection.SpecialCells(xlCellTypeConstants, 2).Select With Selection.Font .Name = "宋体" .FontStyle = "加粗" .Size = 12 .Strikethrough = False .Superscript = False .Subscript = False .OutlineFont = False 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 9 .Shadow = False .Underline = xlUnderlineStyleNone .ColorIndex = 13 End With Range("E6").Select Selection.SpecialCells(xlCellTypeConstants, 1).Select With Selection.Font .Name = "宋体" .FontStyle = "常规" .Size = 12 .Strikethrough = False .Superscript = False .Subscript = False .OutlineFont = False .Shadow = False .Underline = xlUnderlineStyleNone .ColorIndex = 11 End With Range("D7").Select Selection.SpecialCells(xlCellTypeFormulas, 23).Select With Selection.Font .Name = "宋体" .FontStyle = "加粗" .Size = 12 .Strikethrough = False .Superscript = False .Subscript = False .OutlineFont = False .Shadow = False .Underline = xlUnderlineStyleNone .ColorIndex = 3 End With Range("A1:A3").Select Selection.EntireRow.Insert Range("A1").Select With Selection.Interior .ColorIndex = 13 .Pattern = xlSolid .PatternColorIndex = xlAutomatic End With Range("B1").Select ActiveCell.FormulaR1C1 = "文本" Range("A2").Select With Selection.Interior .ColorIndex = 11 .Pattern = xlSolid .PatternColorIndex = xlAutomatic End With Range("B2").Select ActiveCell.FormulaR1C1 = "数字" Range("A3").Select With Selection.Interior .ColorIndex = 3 .Pattern = xlSolid .PatternColorIndex = xlAutomatic End With Range("B3").Select 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 10 共 612 页 ActiveCell.FormulaR1C1 = "公式" Range("A1").Select End Sub 从现在开始,让我们集中精力寻找下面两个问题的答案:如何阅读宏代码?如何编辑宏代码? 1.1.6 添加注释 看看所录制的宏代码,注意那些开头带有单引号的行,这些行就是注释。注释默认显示为绿色。 执行宏代码时,VB会忽略这些注释行。注释经常和宏代码放在一起,为那些意义不甚明显的语句提 供说明。现在,让我们来给宏WhatsInACell 添加注释。 1. 激活VBE窗口。 2. 将光标移至Selection.SpecialCells(xlCellTypeConstants, 2).Select语句的开头,按回车键。 3. 将光标往上移一行到空白处,并且添加如下注释,注意前面应以单引号开头(译者注:英文状 态下的单引号)。 ‘ 查找并格式文本单元格 4. 将光标移至Selection.SpecialCells(xlCellTypeConstants, 1).Select语句的开头,按回车键。 5. 将光标往上移一行到空白处,并且添加如下注释,注意前面应以单引号开头(译者注:英文状 态下的单引号)。 ‘ 查找并格式数字单元格 6. 将光标移至Selection.SpecialCells(xlCellTypeFormulas, 23).Select语句的开头,按回车键。 7. 将光标往上移一行到空白处,并且添加如下注释,注意前面应以单引号开头(译者注:英文状 态下的单引号)。 ‘ 查找并格式公式单元格 8. 将光标移至Range("A1:A3").Select语句的开头,按回车键。 技巧1-6:关于注释 „ 在VBE代码窗口里,以单引号开头的都是注释。注释的默认颜色是绿色,可以通过“选项”对话框(选择 菜单“工具”—“选项”命令后,在“编辑器格式”选项卡中)更改注释颜色。 „ 注释也可以写在代码的后面。例如,在语句.ColorIndex = 11之后添加注释,将光标移至该语句句末,按下 Tab键,输入单引号,然后输入注释。显示如下: .ColorIndex = 11 ' 设置字体颜色为紫色 „ 注释只是给用户提供宏或宏代码的用途。在编写VBA代码时,应该给代码添加必要的注释。如果几个月后 需要这该宏代码时,那么注释将使您更容易理解它。同样,注释可以使别人很快理解您的程序的不同部分。 9. 将光标往上移一行到空白处,并且添加如下注释,注意前面应以单引号开头(译者注:英文状 态下的单引号)。 ‘创建图例 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 11 1.1.7 分析宏代码 所有的宏过程都以关键词“Sub”开始,以关键词“End Sub”结束。在关键词“Sub”之后是 宏的真正的名字,然后紧跟着一对括号。在关键词Sub 和End Sub之间是每次运行宏时所执行的VBA 语句。VBA自上而下读取代码行,忽略那些句前带单引号的语句(参见上节关于注释的内容),读取到 End Sub语句时停止。 注意,在录制的宏代码中包含许多句点。几乎每行代码中都有句点,用来连接VBA语言中不同 的要素。如何阅读这种语言的所写的指令呢?要从最后一个句点的右边向左阅读。看看宏 WhatsInACell里的一些语句: Range("A1:A3").Select 该代码将选择单元格区域A1:A3。 Selection.EntireRow.Insert 该代码在所选区域中插入行。因为前一行代码选中了不同行中的三个单元格,VBA将插入三行。 ActiveCell.FormulaR1C1 = "文本" 该代码在当前单元格中输入“Text”。因为前一行代码Range("B1").Select选择了单元格B1,B1 是当前单元格,所以VB在B1单元格里面输入“文本”。 With Selection.Interior .ColorIndex = 3 .Pattern = xlSolid .PatternColorIndex = xlAutomatic End With 这是一段特别的代码块,解释如下:给当前选中的单元格设置红色底纹色(ColorIndex = 3),设 置内部图案为实心(xlSolid),并且为当前单元格指定默认的图案颜色(xlAutomatic)。 这个代码块以关键词With开始,以关键词End With结束,这将加快宏代码的执行速度。宏代码 知道走捷径,而不会每次都重复下面的指令: Selection.Interior.ColorIndex = 3 Selection.Interior.Pattern = xlSolid Selection.Interior.PatternColorIndex = xlAutomatic 将重复的文本Selection.Interior放在关键词With右边,并以关键词End With结束这块代码。 1.1.8 清理宏代码 现在已经逐行解析了所录制的宏代码,可以发现Excel录制了许多并不想要包含进去的一些信 息。例如,在选中了文本单元格后,除了设置字体为粗体和颜色为紫色之外,Excel 还录制了其它 在“字体”选项卡中的选项的当前状态——字体名称、字体大小、删除线、上标、下标、阴影和下 划线。请看下列的代码片断: With Selection.Font .Name = "Arial" .FontStyle = "Bold" .Size = 10 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 12 共 612 页 .Strikethrough = False .Superscript = False .Subscript = False .OutlineFont = False .Shadow = False .Underline = xlUnderlineStyleNone .ColorIndex = 13 End With 在使用对话框时,Excel总会录制对话框中所有的设定。这些多余的指令使得宏代码冗长且难以 理解,因此,在完成录制宏后,最好检查一遍所录制的代码并删除不必要的代码行。 1. 在下面的代码块中,删除所划掉的代码行: With Selection.Font .Name = "Arial" .FontStyle = "Bold" .Size = 10 .Strikethrough = False .Superscript = False .Subscript = False .OutlineFont = False .Shadow = False .Underline = xlUnderlineStyleNone .ColorIndex = 13 End With 清理后,在关键词With和End With之间只剩下了两句代码,这些才是录制宏时在“单元格 格式”对话框中实际进行的设置: With Selection.Font .FontStyle = "Bold" .ColorIndex = 13 End With 2. 找到设置数字单元格格式的代码,按照下面的示例修改代码: ' 查找并格式数字单元格 With Selection .SpecialCells(xlCellTypeConstants, 1).Select .Font.ColorIndex = 11 ' 设置字体颜色为紫色 End With Range("C6").Select 3. 找到设置公式单元格格式的代码,按照下面的示例修改代码: ' 查找并格式公式单元格 Selection.SpecialCells(xlCellTypeFormulas, 23).Select With Selection.Font .FontStyle = "Bold" .ColorIndex = 3 End With 4. 找到下面的两行代码: Range("A1:A3").Select Selection.EntireRow.Insert 5. 用下面的一句代码取代上面的两句代码: 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 13 Range("A1:A3").EntireRow.Insert 注意,宏录制器使用R1C1样式来设置所选中的单元格的公式: ActiveCell.FormulaR1C1 = "文本" ActiveCell.FormulaR1C1 = "数字" ActiveCell.FormulaR1C1 = "公式" 宏录制器使用了一次“ActiveCell”和一次“Selection”来选择当前单元格,这两个词都称为属 性。你将在第二章里学习有关属性的内容。当仅仅选取一个单元格,可以随意使用“ActiveCell”或 者“Selection”。 1.1.9 测试已修改的宏 在修改所录制的宏时,很可能会引入一些错误。例如,可能会删除一行重要的代码,或者可能 不小心清除或忽略了一个必需的句点。为了确保宏在修改之后还能正确地工作,必须运行它。 在VBE代码窗口中,将光标放在宏代码WhatsInACell中的任意行,然后选择菜单“运行”—“运 行子过程/用户窗体”命令。 如果在修改代码时没有引入任何问题,那么宏将顺利运行而不会报告错误。需要切换到Excel窗 口查看宏运行的结果。可以在任务栏中点击相应的按钮或者按Alt+F11组合键返回Excel窗口。 如果宏在运行的过程中遇到错误,将会看到一个显示发现的错误类型的对话框。在运行宏之前, 必须确保宏可以在当前选择的工作表中运行。例如,当前所选择的工作表为空工作表,如果试图在 该工作表中运行WhatsInACell宏,将会得到一条错误信息“运行时错误‘1004’:未找到单元格”, 单击“结束”按钮,在重新运行这个宏之前,确保选择了正确的工作表。 如果选择的工作表只含有文本,则在运行WhatsInACell宏时,VBA试图选取数字单元格时会遇 到问题,同样显示“未找到单元格”的错误消息。 如果忽略了With Selection.Font中的句点,在运行这行代码时VBA会产生“编译错误:变量未定 义”的错误信息。单击信息框上的“调试”按钮,将进入代码窗口。同时,VBA会进入“中断”模 式,并且用黄色突出显示有问题的代码行。一旦更正错误代码,VBA就会弹出信息“该操作将重新 设置工程,继续吗?”,单击“确定”按钮。尽管你可以在中断模式修改代码,但是有些修改会终止 宏的继续执行。更正错误的代码后,重新运行宏,也许需要修正更多的错误后才能顺利地运行它。 您将在第二章和第十三章里面学到更多的关于如何处理VBA错误的方法。 1.1.10 执行宏的两层界面 既可以在Excel窗口界面中运行宏,也可以在VBE窗口界面中运行宏。在VBE屏幕中执行 WhatsInACell宏时,VBA在后台执行宏代码,看不到VBA选择文本单元格并设置格式,也看不到插 入三行空行作为颜色图例行。为了观察到VBA的执行情况,必须在Excel窗口界面中运行宏。在Excel 窗口中选择菜单“工具”—“宏”—“宏”命令,或者将Excel窗口和VBE窗口同时显示在电脑屏幕 上(参见图1-6),以便于观察宏代码执行的情况。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 14 共 612 页 图1-6:如果想在VBE中运行宏时观察到宏执行的情况,必须将Excel窗口面和VBE窗口并排地布置在一 起 按照下列步骤排列Excel窗口和VBE窗口如图1—6所示: 1. 在Windows任务栏上的空白处单击右键。任务栏在屏幕的底部,“开始”按钮的位置。 2. 在弹出的Windows快捷菜单中,选择“纵向平铺窗口”。 3. 最小化那些不需要的窗口,重复步骤1。 4. 现在,两个窗口并排显示了,在宏代码中的任意位置单击,然后按下F5键(或者选择菜单“运 行”—“运行子过程/用户窗体”命令),可以观察到VBA正迅速地执行所录制录制的操作,很 激动吧?稍后,将学习如何让VBA代码慢慢运行,以便于一步一步地观察代码的运行情况。 1.1.11 改进宏 录制宏后,可能会发现宏可以完成一些其它的操作任务。如果已经熟悉了VBA语言,那么往宏 代码中添加新的指令并不困难。然而,在绝大多数情况下,可以让Excel宏录制器更有效地完成这些 额外的任务。虽然宏录制器录制了太多不需要的指令,但是宏录制器不会犯错,您完全可以信赖它。 如果想要通过宏录制器在代码里添加额外的指令,那么必须先录制一个新宏,然后复制需要的 部分,将它们粘贴到原来代码的正确位置。 下面来给单元格区域A1:B3添加粗边框。 1. 激活图1-6所示的Excel窗口。 2. 选择菜单“工具”—“宏”—“录制新宏”命令。 3. 在“录制新宏”对话框中单击“确定”按钮,接受默认的宏名并开始录制 4. 选择单元格区域A1:B3。 5. 选择菜单“格式”—“单元格”命令,单击“边框”选项卡。 6. 在“样式”框中选择最粗的线条。 7. 在对话框中的“预置”部分,单击“外边框”标签,然后单击“确定”按钮关闭该对话框。 8. 单击单元格A1。注意到A1:B3单元格区域加上了粗边框。 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 15 9. 在“停止录制”工具栏中单击“停止录制”按钮,或者选择菜单“工具”—“宏”—“停止录 制”命令。 切换到VBE窗口查看所录制的宏。给单元格区域A1:B3添加粗边框的代码如下: Sub Macro5() ' ' Macro5 Macro ' 宏由 fanjy 录制,时间: 2006—11—20 ' ' Range("A1:B3").Select Selection.Borders(xlDiagonalDown).LineStyle = xlNone Selection.Borders(xlDiagonalUp).LineStyle = xlNone With Selection.Borders(xlEdgeLeft) .LineStyle = xlContinuous .Weight = xlThick .ColorIndex = xlAutomatic End With With Selection.Borders(xlEdgeTop) .LineStyle = xlContinuous .Weight = xlThick .ColorIndex = xlAutomatic End With With Selection.Borders(xlEdgeBottom) .LineStyle = xlContinuous .Weight = xlThick .ColorIndex = xlAutomatic End With With Selection.Borders(xlEdgeRight) .LineStyle = xlContinuous .Weight = xlThick .ColorIndex = xlAutomatic End With Selection.Borders(xlInsideVertical).LineStyle = xlNone Selection.Borders(xlInsideHorizontal).LineStyle = xlNone Range("A1").Select End Sub 现在来分析一下所录制的代码。可以去掉其中的一些指令吗?在删除这些不必要的代码之前, 考虑如何使用注释功能。在删除任何代码之前,先将它们设置为注释,然后运行宏。如果VBE没有 出现任何错误信息,就可以安全地删除这些被设置为注释的代码。如果按照这种方式操作,那就不 会再重复录制相同的操作了。如果宏没有正确地运行,就需要去掉刚才的注释设置,表明这些代码 可能是必须的。关于处理注释块的详细信息,参见第二章。 在使用宏录制器创建宏时,可以很快地掌握与Excel菜单选项和对话框设置等同的VBA方式,然 后可以在在线帮助里查找这些VBA命令的意思和用法。很显然,VBA要读取的指令越多,宏运行的 速度就越慢,因此,去掉那些无关紧要的命令会加速宏的运行。然而,为了使宏代码更容易理解, 需要寻求完成指定任务的最佳途径。例如,看一下录制器为选中的单元格加上外边框的宏,表面上 宏录制器分别处理每条边线,并难以相信VBA会有一个简单的一句命令来给选中的区域加外边框。 学会任何语言中正确的词语或表达都是很费时的。但是随着长时间的学习,您会发现VBA有一个 BorderAround方法,该方法允许在单元格区域添加边框和设置颜色、线型、以及新边框的粗细。 运用VBA中给选中的单元格设置粗边框的最佳方法是使用下面的语句: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 16 共 612 页 Range("A1:B3").BorderAround Weight:=xlThick 上面的指令使用Range对象的BorderAround方法,它给单元格区域A1:B3添加了一个粗线外边 框。(下一章介绍VBA的对象、属性和方法)。 现在将上面的指令添加到宏WhatsInACell中。 1. 激活含有宏WhatsInACell的代码窗口。 2. 在语句ActiveCell.FormulaR1C1 = "Formulas"之后插入一行。 3. 在空白行中输入下面的指令: Range("A1:B3").BorderAround Weight:=xlThick 4 4. 将光标放在宏代码的任何位置,按F5键运行修改好的代码。 技巧1-7:附加指令 „ 要在已有的代码中添加其它指令,先在需要添加指令的位置按回车键加入空白行,然后输入必要的VBA语 句。 „ 如果附加的其它指令是键盘操作或菜单命令,可以使用宏录制器生成必要的代码,然后将它们复制并粘贴 到原来的宏里面。 假设想要VBA在执行完最后一行代码时给您提示,而这种操作是不可能被录制下来的,因为 Excel中没有相应的菜单选项。但是,可以使用VBA语言添加这个新指令到宏中。 1. 在代码窗口中,在语句End Sub前按回车键。 2. 将光标移至空白行并输入下面的语句: MsgBox "所有操作都已完成。" 3. 确保光标在宏代码中,按下F5键。 4. 当VBA执行完最后一个所录制的指令后,将弹出这个信息框,单击“确定”按钮。你现在知道 宏已经运行完成。 MsgBox是使用非常频繁的VBA函数之一,您将在第四章中学习它的更多的用法。 1.1.12 重命名宏 在代码里面添加了一些其它代码后,需要将宏重命名以更好地反映这个宏的目的。过程的名称 应该越接近它的功能越好。你不需要按任何键就可以更改宏名。在代码窗口中,将关键词Sub后面原 来的宏名清除,然后输入新的名称即可。 1.1.13 运行宏的其它方法 到现在为止,我们已经学习了运行宏的三种方法。可以在Excel窗口中选择菜单“工具”—“宏” —“宏”命令,单击“执行”按钮运行宏。但是对于需要经常运行的宏,这种方法是不方便的。也 可以在VBE窗口中使用快捷键F5或者选择菜单“运行”—“运行子过程/用户窗体”命令来运行宏。 此外,还可以在VBE窗口中单击标准工具栏上的“运行”按钮或选择菜单“工具”——“宏”命令 来运行宏(见图1-7)。 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 17 图1-7:通过标准工具栏来运行VBA子过程 1.1.14 使用键盘快捷键运行宏 一个普遍使用的方法是通过设置键盘快捷键来运行宏。按Ctrl+Shift+D组合键比从“宏”对话框 中激活宏要容易得多,但是在使用键盘快捷键之前,必须给宏设置该快捷键。 1. 按Alt+F8组合键快速打开“宏”对话框。 2. 在宏列表中,单击WhatsInACell宏名,然后选择“选项”按钮。 3. 在弹出的“宏选项”对话框(如图1-8所示)中,将光标定位在快捷键文本框。 4. 在键盘中按下Shift键的同时按下字母I。Excel录制Ctrl+Shift+I作为键盘组合键。 5. 单击“确定”按钮关闭“宏选项”对话框。 6. 单击“取消”按钮返回到工作表中。 试试用刚设置的快捷键来运行宏。激活Excel窗口,然后按下Ctrl+Shift+I组合键。 图1-8:使用“宏选项”对话框设置键盘快捷键来运行宏 技巧1-8:避免快捷键冲突 如果给宏设置的快捷键和Excel内置的快捷键冲突,而且所打开的正是含有那个宏的工作簿,那么按下该快 捷键后Excel会运行宏。 1.1.15 通过菜单运行宏 如果喜欢使用菜单,那么可以将宏作为一个菜单项。使用“自定义菜单”对话框,可以快速地 添加自已的菜单命令到任何Excel的内置菜单中。 1. 在Excel窗口菜单栏的空白处,单击右键,从弹出的快捷菜单中选择“自定义…”。 2. 在“自定义”对话框中,选择“命令”选项卡。 3. 在“类别”列表框中,选择“宏”。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 18 共 612 页 图1-9:创建自定义菜单(第一步) 4. 将对话框中“命令”列表框中的“自定义菜单项”拖曳至“工具”菜单。展开“工具”菜单, 将按钮放在想放置的位置。图1-10显示了自定义菜单在“工具”菜单中的最终位置。 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 19 图1-10:创建自定义菜单(第二步),可以将自定义菜单放在Excel菜单里,也可以放在子菜单里 5. 在自定义菜单项上单击右键,并且在弹出的快捷菜单中“名称”的文本框里,输入想要的名字 (参见图1-11)。例如,将名称改为“Contents of Ce&lls”,连接符(&)用来指定键盘快捷键。 将连接符放在想显示下划线的字符之前。如果命名自定义菜单项为“Contents of Ce&lls”,则 该自定义菜单项将显示为“Contents of Cells”。注意,菜单项中字与字之间可以有空格。 6. 在快捷菜单中选择最后一个选项——“指定宏”(参见图1-11)。在“宏”对话框中,选择宏 “WhatsInACell”,单击“确定”按钮。单击“关闭”按钮关闭“自定义”对话框。 现在,可以通过自定义菜单项来运行宏了。如果没有给自定义菜单项指定宏就关闭了这个 快捷菜单,Excel在你第一次试图使用这个自定义菜单项时提示指定宏。 7. 选择菜单“工具”—“Contents of Cells”,或者按组合键Alt+T后再按字母键L来运行宏。 如果在执行上述操作时,清除了内置菜单或菜单选项,那么可以打开“自定义”对话框,单击 “工具栏”选项卡,然后选择“重置设置”按钮就可以恢复这些菜单了。但是,该操作会恢复Excel 默认设置,也会将自定义菜单项清除。 图1-11:创建自定义菜单(第三步)可以使用快捷菜单重命名菜单项和为菜单项指定宏使用该快捷菜单前 必须先打开“自定义”对话框 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 20 共 612 页 1.1.16 通过工具栏按钮运行宏 如果喜欢使用工具栏里的按钮,那么可以很容易地在任何工具栏里添加自定义按钮,并且为该 按钮指定宏。下面来添加WhatsInACell宏到工具栏中。 1. 选择菜单“工具”—“自定义”命令。 2. 在“自定义”对话框中,单击“命令”选项卡。 3. 在“类别”列表框中,选择“宏”。 4. 拖曳“命令”列表框中的“自定义按钮”图标到工具栏中想放置的位置。在本例中,这个按钮 放在“标准”工具条中格式刷的右边。 5. 在该按钮上单击右键,然后在弹出的快捷菜单中“名称”的文本框里,输入文本作为按钮工具 提示信息。本例中,将工具提示信息改为“Contents of Ce&lls”。 6. 在该按钮上单击右键,从弹出的快捷菜单中选择“更改按钮图像”,将出现42个Excel预先设计 的图像供选择去修改按钮图像。本例中,用铅笔图像取代了默认的图像。 7. 给按钮指定宏:在该按钮上单击右键,从弹出的快捷菜单中选择“指定宏”命令。 8. 选择“WhatsInACell”宏,单击“确定”按钮。 9. 单击“关闭”按钮,关闭“自定义”对话框。 10. 将光标指向刚创建的自定义按钮上,按钮旁显示工具提示信息“Contents of Cells”(参见图1 -12)。确保活动工作表中包含文本、数字和公式单元格,然后点击该按钮运行宏。 图1-12: 可以在任何工具栏中添加自定义按钮来运行宏 1.1.17 通过工作表中的按钮运行宏 在本书后面,您将学习如何在工作表中放置按钮,帮助Excel初学者进行数据输入。现在,让我 们逐步实现如何将宏WhatsInACell指定在工作表中的按钮上。 1. 激活含有数据的示例工作表。 2. 选择菜单“视图”—“工具栏”,并且选择“窗体”,出现“窗体”工具栏,如图1-13所示。 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 21 图1-13:可以将宏指定给工作表中的按钮 3. 在“窗体”工具栏中单击“按钮”图标。 4. 在工作表任意地方单击。 5. 当“指定宏”对话框出现时,选择宏名(WhatsInACell),然后单击“确定”按钮。 6. 确保选中了该按钮,然后输入名称“Contents of Cells”改变按钮1的名称。按钮被选中时,如 图1-13所示。如果选择符号没有显示,则在该按钮上单击右键,并且在弹出的快捷菜单中选 择“编辑文字”,然后选择默认的文字,输入新的名称。 7. 按钮重命名后,在工作表中按钮之外的任何地方单击,退出按钮编辑模式。 8. 单击刚才创建的按钮,运行宏。 图1-14:控件工具箱的默认工具 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 22 共 612 页 技巧1-9:在工作表中添加控件 „ 可以使用“窗体”工具栏往工作表中添加控件(参见图1-13),也可以使用控件工具箱(参见图1-14)。两 种工具栏都可以通过“视图”菜单,选择“工具栏”选项来获得。 „ 窗体里的控件和Excel的早期版本(5.0,7.0和97)兼容,并且可以用在图表、老的XLM宏表和所有想通 过点击控件来运行宏的工作表里。 „ “控件工具箱”里的控件就是人们熟知的ActiveX控件。可以将ActiveX控件放在工作表或者使用VBE创建 的用户窗体上。然而,窗体工具栏上的控件只响应点击(Click)事件,ActiveX控件则有不同的动作、或 事件。 „ 当使用“窗体工具栏”中的控件时,给它指定的宏存储在当前工作簿、新工作簿或者个人宏工作簿中的模 块里。当使用ActiveX控件时,编写的宏代码存储在控件自身中。 1.1.18 保存宏 本章中,创建的宏WhatsInACell位于一个Excel工作簿中,需要在磁盘中保存这个打开的工作簿 并保存这个宏,建议将该宏保存在名为Chap01.xls的工作簿文件中。保存后,关闭它,接着打开一 个新工作簿。注意,在新工作簿中的工具栏上的自定义按钮和“工具”菜单中的“Contents of Cells” 菜单项仍然是可用的。在使用这些工具运行宏之前,先在单元格A1中输入“Addition”,单元格A2中 输入数字2,单元格A3中输入数字4,在单元格A4中输入公式“=SUM(A2:A3)”。当运行宏时,Excel 会打开相应的工作簿文件并且执行指定给自定义按钮或菜单项的过程。 1.1.19 打印宏 如果要在文档中阅读宏代码,或者不在计算机中研究宏代码,就需要打印宏。可以打印存储宏 的整个模块,也可以打印选择的行。 打印含有宏的整个模块: 1. 将光标放在模块的任意地方。 2. 选择菜单“文件”—“打印”命令。 3. 在“打印-VBAProject”对话框中,选中“当前模块”选项按钮。 4. 单击“确定”按钮打印模块。 打印选中的文本: 1. 在模块里,选择要打印的文本。 2. 选择菜单“文件”—“打印”命令。 3. 在“打印-VBAProject”对话框中,选中“选定”选项按钮。 4. 单击“确定”按钮打印选中的文本。 1.1.20 保存宏在个人宏工作簿中 录制宏时,可以将它保存在个人宏工作簿里面。当储存宏在个人宏工作簿里时,Excel创建一个 名为“Personal.xls”的文件并且将它放在“Program Files\Microsoft Office\Office”的子文件夹XLStart 文件夹中。每次Excel启动时都会自动装载保存在XLStart文件夹中的文件。个人宏工作簿是一个保存第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 23 通用宏代码(就像下面这个宏)的合适的地方。 现在来录制一个通用的宏“FormulasOnOff”,这个宏的目的是切换显示或隐藏工作簿中的公式。 1. 选择菜单“工具”—“宏”—“录制新宏”命令。 2. 在“录制新宏”对话框中,在“宏名”文本框中输入宏名“FormulasOnOff”。 3. 在“保存在”的下拉框中选择“个人宏工作簿”。 4. 单击快捷键文本框,并且在按下Shift键的同时输入字母F。 5. 选择“确定”按钮退出“录制新宏”对话框。 6. 按下“Ctrl+~”组合键打开公式显示,或者选择菜单“工具”—“选项”命令并且选中“视图” 选项卡中的“窗口选项”中“公式”前的复选框。当打开公式显示时,工作簿单元格中显示公 式而非公式计算结果。如果在一个空白工作表中录制这个宏,那么将注意到的唯一的变化是工 作表的列加宽了。 7. 单击“停止录制”工具栏中的“停止录制”按钮,或者选择菜单“工具”—“宏”—“停止录 制”命令。 8. 按下Alt+F11组合键,或者选择“工具”—“宏”—“VB编辑器”查看代码。 这时,VBE屏幕上的工程窗口里显示了一个多出来的VBA工程(Personal.xls),单击该工程名称 左边的加号(+)打开这个工程,包含两个文件夹:Microsoft Excel对象和模块。单击模块文件夹前面 的加号来打开它,然后双击模块1,此时代码窗口显示了宏FormulasOnOff的内容(参见图1-15)。每 个Excel工作簿只有一个工程。第一次录制宏时,Excel创建一个模块文件夹,并且将代码存放在模 块1里面。如果在同一工作簿中录制另一个宏,那么Excel将其放在与前一个录制的宏相同的模块即 模块1中,并将代码放置在前一个宏代码的下面。在相同工作时间所录制的所有的宏都存放在相同的 模块里面。但是,如果关闭Excel,然后再重新打开这个工作簿,Excel就会将此时所录制的宏存储 在一个新的模块中。 图1-15:在工程浏览器窗口,可以选择想要处理的工程 录制宏时,打开了公式显示,而这个宏的名称表明可以切换公式显示和隐藏,因此,必须修改 该代码才能确保它按照这种方式运行。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 24 共 612 页 所录制的宏设置当前窗口公式显示为真: ActiveWindow.DisplayFormulas = True 设置为“False”将关闭公式显示: ActiveWindow.DisplayFormulas = False 为了在VBA里设置切换操作,需要按照下面的方法来连接这两个语句: ActiveWindow.DisplayFormulas = Not ActiveWindow.DisplayFormulas 用上面的语句代替录制的代码,并且运行这个宏。无论你运行多少次,这个宏总是知道做什么。 可以使用相同的思路创建代码用来切换网格线或其它Excel特征的显示和隐藏。 在关闭Excel时,会提示保存个人宏工作簿的变化,单击“确定”按钮保存变化。当重启Excel 时,个人宏工作簿会在后台自动装载。 如果想要在个人宏工作簿里保存其它的宏,那么可以选择下列方法之一: ƒ 录制一个新宏,并且选择个人宏工作簿来存储。 ƒ 切换到VBE窗口,打开想要移动到个人宏工作簿里去的宏,剪切这个宏,然后打开个人宏工作簿, 将宏粘贴到已经存在的模块中,或者创建一个新模块粘贴宏。 ƒ 选择菜单“文件”—“导入文件……”命令,从一个文本文件或另一个VB工程(*.frm,*.bas,*.cls) 导入宏代码。 1.1.21 打开含有宏的工作簿 无论何时打开一个含有宏的工作簿,Excel都会显示一个警告信息,如图1-16所示。为了避免 显示这个警告信息,可以通过设置“安全性”对话框关闭病毒保护(参见图1-17)。 当安全警告出现时,可以选择: ƒ 禁用宏——如果打开一个来源不熟悉的含有宏的工作簿,例如网站、电子邮件,为了保护计算 机不被宏病毒破坏,那么选择“禁用宏”。此时,打开工作簿时不会运行它里面的任何宏。如果 宏没有密码保护,就可以切换到VBE窗口查看代码。查看代码后(译者注:如果代码安全),就可 以关闭该工作簿,然后重新打开它并且启用宏。 图1-16: 如果打开了病毒保护,当工作簿含有宏时,Excel 会弹出警告信息 ƒ 启用宏——如果知道该工作簿来自于一个可靠的来源并且含有有用的宏,单击“启用宏”按钮。 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 25 ƒ 详细信息——在你决定是否取消或者启用宏打开工作簿前,如果需要了解更多的信息,那么单 击这个按钮。 Excel 2002 有一个有用的功能,能让你自动取消所有没有签署并且来源不明的宏。可以选择菜 单“工具”—“宏”—“安全性”命令,使用这个功能。 当创建宏分发给其他人使用时,可以使用VBE中“工具”菜单里的“数字签名”功能。 宏的数字签名与在纸上的签名类似,可以在Excel在线帮助里搜索如何安装和创建自己的数字签 名的信息。在帮助向导中,输入“数字签名”就可以获得相关主题。 图1-17: 选择“中”选项按钮,可选择在工作簿中是否取消或者启用宏 1.2 VBE窗口 现在,您已经知道了如何录制、运行和修改宏,让我们花些时间来熟悉VBE窗口和它的功能。 使用VBE中的工具能够: ƒ 编写自己的VBA过程 ƒ 创建自定义窗体 ƒ 查看和修改对象属性 ƒ 测试VBA过程和找出错误 一般有两种方法访问VBE窗口: ƒ 从Excel应用程序窗口中的“工具”菜单,即选择菜单“工具”—“宏”—“Visual Basic编辑器”。 ƒ 从键盘,即按下Alt+F11组合键。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 26 共 612 页 1.2.1 理解工程窗口 工程窗口显示了当前打开的工程和它的组成部分的清单。VBA工程包括下列部分: ƒ 工作表 ƒ 图表 ƒ 当前工作簿——存储工程的工作簿 ƒ 模块 ƒ 类模块——可以创建自己的对象的特殊模块 ƒ 窗体 ƒ 引用到其它工程 通过工程浏览器,可以管理工程,并且很容易地在当前打开的工程之间移动。 可以通过三种途径激活工程窗口: ƒ 选择“视图”菜单中的“工程资源管理器” ƒ 在键盘中按下Ctrl+R组合键 ƒ 在工具栏中,点击“工程资源管理器”按钮(参见图1-18) 工程窗口中有三个按钮。左边第一个按钮(查看代码)显示当前选中的模块的代码窗口。中间的按 钮(查看对象)显示Excel对象文件夹中所选择的工作表,或者窗体文件夹里面的窗体。右边的按钮(切 换文件夹)隐藏或者显示工程窗口里的文件夹。 图1-18:标准工具栏上的按钮提供了快速访问许多VBA功能的方式 1.2.2 理解属性窗口 属性窗口让你查看和设置工程中不同对象的属性。当前所选对象的名称就显示在属性窗口的标 题栏下面的对象框中。通过单击相应的选项卡,可以按照字母顺序或者分类顺序查看对象的属性(参 见图1-19)。 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 27 图1-19:属性窗口显示当前被选中的对象的属性设置 ƒ 字母顺序——按字母顺序地列出被选择的对象的所有属性,通过选择属性名并输入或者选择新 的设置来更改属性设置。 ƒ 分类顺序——按类别列出被选中的对象的所有属性,可以将折叠列表以查看类别,也可以展开 类别查看属性。类别名称左边的加号(+)表明这个类别可以展开,减号(-)表明这个类别已经展 开。 有三种方式可以访问属性窗口: ƒ 选择“视图”菜单,然后选择“属性窗口” ƒ 在键盘中按下F4键 ƒ 在工具栏中,点击“属性窗口”按钮(参见图1-18) 1.2.3 理解代码窗口 代码窗口用来编写VBA程序,也可以用来查看和修改录制的宏代码以及已有的VBA过程。每个 模块能在独立的代码窗口中打开。 有几种方法可以激活代码窗口: ƒ 在工程窗口中,选择相应的用户窗体或者模块,然后点击“查看代码”按钮。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 28 共 612 页 ƒ 在菜单中,选择“视图”—“查看代码”命令。 ƒ 在键盘中,按下F7键。 在代码窗口的顶部,有两个下拉列表框(参见图1-20),可以快速地移动到任意代码处。在代码 窗口左上角的对象列表框中,可以选择想要查看代码的对象。在代码窗口右上角的列表框中可以快 速地选择一个特定过程或者事件过程去查看,打开这个列表框后,该模块里的所有过程名按字母顺 序排列在这里。如果在过程/事件列表框中选择了一个过程,光标就会跳到那个过程的第一行处。 将分离工具条(参见图1-20)拖曳到所选择的位置,可以将代码窗口分为两个窗格(参见图1- 21)。 图1-20:代码窗口有几个部分,可以轻松定位过程和查看代码 这样可以查看长过程的不同部分,或者在每个窗格中查看不同的过程,也可以方便地在同一个 模块内的过程间复制或剪切并粘贴代码片断。要返回到一个窗格显示,只需简单地将分离工具条拖 曳至代码窗口顶部就可以了。 代码窗口底部有两个图标。“过程查看”图标在代码窗口里一次只显示一个过程,可以通过过程 /事件下拉列表框选择另一个过程。“全模块视图”图标则可以显示所选模块里的所有过程。使用垂直 滚动条可以在代码中滚动。 页边指示工具条是在修改代码和调试时显示一些有帮助的指示。如果想快速查看关于这些指示 的信息,浏览第十三章。 第 1 章 0B 电子表格自动化介绍 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 29 图1-21:可以将代码窗口分离为两个窗格来查看长过程 1.2.4 VBE窗口中的其它窗口 除了代码窗口外,在VBA编程环境中还频繁地使用很多其它窗口: 窗体窗口用来创建自定义对话框和用户窗体,您将在第十章学到这方面的知识。 图1-22显示了可以出现在VBE窗口中的窗口列表。您将在第二章中学习一些窗口(对象浏览器, 立即窗口)的使用,并在第十三章里学习另外一些(本地窗口,监视窗口)的使用。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 30 共 612 页 图1-22:在“选项”对话框中,可以选择显示哪些窗口 本章小结和下一章内容简介 本章通过一些电子表格自动化的例子,使您不仅已经学会了如何录制宏,而且也学会了如何查 看、阅读和修改VBA代码。此外,您也尝试了用不同的方法运行宏。最后,您还快速浏览并了解了 VBE窗口。 下一章介绍VBA基础。您将学习许多新术语,更重要的是,您将获得有用的VBA词汇,让您将 更多的任务交给Excel帮您完成。 第2章 VBA初步 语言学习是一项长期的活动,精通一门语言会经历不同的阶 段,学习 VBA 编程也一样,没有捷径。要想精通 VBA,您必须 从初级阶段起步…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 32 共 612 页 语言学习是一项长期的活动,精通一门语言会经历不同的阶段,学习VBA编程也一样,没有捷 径。要想精通VBA,您必须从初级阶段起步(第二至四章)。只有当您对VBA中一些基本的东西有了很 好的理解之后,才能逐步进入到中级阶段(第五至七章)和高级阶段(第八至十七章)。但是,首要的事 情是要开始学习。在能够使用VBA定制Excel之前,您需要学到新词汇和语法。在VBA中,如何表达 “在工作簿里添加一个新工作表”、“删除单元格A5的内容”、“将单元格A1中的公式复制到单元格B1 中”?也许您知道单个的词语,然而,您知道如何将它们正确地组合在一起,让Excel完成这些任务? 本章中,您将学习VBA的术语和规则。 2.1 理解指令、模块和过程 在第一章中,您学习了Excel宏录制器创建的一系列与实际执行的操作完全等同的指令,这些指 令自动地放在工作簿的“模块”中。Excel将模块存储在模块文件夹里,这个文件夹在当前工作簿、 新工作簿或者个人宏工作簿中。必须激活VBE窗口,然后双击工程窗口里的模块文件夹才能查看所 录制的宏代码。当模块在代码窗口中打开之后,才能分析过程代码。 “过程”包含所有录制的指令,过程里面的每一行都是一个“指令”。指令的类型有很多种,例 如关键词、运算符或者调用其它过程。“关键词”在VBA中代表专门的意思,在第一章学习了最常见 的VBA关键词——Sub 和End Sub,它们表示一个过程的开始和结束。关键词默认显示为蓝色。因 为关键词是VBA的保留字,所以不要将这些词用于其它的目的。 除了关键词,VBA指令里还可以包含运算符。运算符有四种类型:算术运算符,字符串连接运 算符,逻辑运算符和比较运算符。“运算符”可用来合并、连接和操作某些值。例如,除法运算符(/) 可以用来计算占总数的百分比。本书中,您有很多机会看到如何在VBA过程中使用这些运算符。 另一种VBA指令是调用过程。调用过程可以快速地跳到其它过程并且执行其它一组指令。是不 是很难想象?让我们看看在第一章中录制的宏WhatsInACell,假设需要在该过程中包含宏 FormulasOnOff中的语句。怎么做呢?可以从一个过程复制需要的代码行到另一个过程。然而,有一 种更简捷的方法,可以通过指定过程名调用这个过程,而不需要在两个过程间复制指令。例如,如 果想让VBA在遇到指令“MsgBox "所有操作都已完成"”之前执行宏FormulasOnOff里面的指令,那 么添加下面这行代码: FormulasOnOff 当VBA运行到这一行时,就会立即跳到FormulasOnOff过程中并且执行它的代码。接着,它会返 回到宏WhatsInACell中执行剩下的代码,遇到关键词End Sub时停止。 在尝试这个例子之前,必须学会如何给VBA过程和模块命名,以及如何从不同工程中调用过程。 2.1.1 命名VBA工程 工程是一组Excel对象、模块、窗体和引用。取代工程窗口中处于工作簿名前面的缺省名称 VBAProject,每个工程需要一个独立的名称。 下面来给VBAProject(Chap01.xls)和VBAProject(Personal.xls)命名: 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 33 1. 启动Excel并打开存储了WhatsInACell宏代码的工作簿Chap01.xls,包含有录制宏 FormulasOnOff的个人宏工作簿会自动装载。 2. 切换到VBE窗口。 3. 在工程窗口中,选择VBAProject(Chap01.xls)。 4. 在属性窗口中,双击(名称)属性,选中了默认的工程名称VBAProject。 5. 输入“FirstSteps”作为该VBA工程的名称,按回车键。注意,工程窗口现在显示的是名称是 FirstSteps(Chap01.xls)。 6. 在工程窗口中,选择VBAProject(Personal.xls)。 7. 在窗性窗口中,双击(名称)属性。 8. 输入“Personal”作为该VBA工程的名称,按回车键。 技巧2-1:避免名称冲突 为了避免VBA工程之间的命名冲突,应该命名工程唯一的名称。可以使用下列方法之一来更改工程名称: „ 在工程窗口中,选择工程名称,然后双击属性窗口里的(名称)属性,输入新的名称 „ 在工程窗口中,在工程名称上单击右键,并且在弹出的快捷菜单中选择“工程名称属性”(其中工程名称为 所选工程的名字),出现如图2-1所示的“工程属性”对话框,在“工程名称”文本框里面输入新的名称。 图2-1:工程属性窗口可以用来更改当前被选中的VBA工程名称和描述所选中的VBA工程 2.1.2 重命名模块 录制宏或者创建新的过程时,VBA会创建一个模块文件夹来存储VBA 代码。 第一个文件夹称 作“模块1”,第二个文件夹称作“模块2”,等等。当打开一个新的工作簿并且创建VBA过程时,新 的VBA工程里的模块文件夹又会被命名为“模块1”、“模块2”,等等。拥有相同名称的模块不仅对您 而且对VBA都会造成很大混淆,因为,它要在一个打开了许多工程的环境中执行宏或VBA过程。 为了避免模块混淆,给FirstSteps(Chap01.xls)工程和Personal(Personal.xls)工程里的“模块1”学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 34 共 612 页 重新命名为唯一的名字: 1. 在工程窗口中,选择FirstSteps (Chap01.xls)工程,并且选择“模块1”。 2. 在属性窗口中,双击(名称)属性,选中模块的默认名称“模块1”。 3. 输入“WorksheetFormatting”作为模块1的名称,按回车键。注意,现在在工程窗口中显示的 模块名称是“WorksheetFormatting”。 4. 在工程窗口中,选择Personal (Personal.xls)。 5. 在属性窗口中,双击(名称)属性。 6. 输入“Switches”作为模块1的名称,按回车键。 图2-2:工程窗口显示使用属性窗口的(名称)属性给VBA工程和模块设置的唯一的名称 2.1.3 调用另一个工程中的过程 可以指定过程名称调用同一个工程里任何模块中的过程。 假设过程FormulasOnOff位于与宏WhatsInACell在同一个工程里的另一个模块中,那么要在 WhatsInACell宏里面调用过程FormulasOnOff,只需在WhatsInACell宏中指定过程名称,示例如下: Sub WhatsInACell() <这里放置所录制的宏指令> FormulasOnOff End Sub 然而,如果两个或者两个以上的模块含有相同名称的过程,那么除了指定过程名称外,还必须 包括模块的名称。 假设 FirstSteps(Chap01.xls)工程有三个模块。模块 FormulaFormatting 包含宏 WhatsInACell, 但是,模块 Switches 和模块 Formulas 都含有一个叫 FormulasOnOff 的宏。要在宏 WhatsInACell 中调用位于 Switches 模块中的 FormulasOnOff,可以在过程名称前加上模块的名称,如下例所示: Sub WhatsInACell() <这里放置所录制的指令> 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 35 Switches.FormulasOnOff End Sub 要调用其它工程里的过程,必须建立对该工程的引用,可以在“引用”对话框进行这项操作。 因为FormulasOnOff宏在Personal(Personal.xls)工程里,在WhatsInACell宏中调用它之前,需要添 加对Personal工程的引用。下面是几种建立引用的方法: 1. 在工程窗口中,单击FirstSteps(Chap01.xls)。 2. 选择菜单“工具”—“引用”命令。 3. 在“引用”对话框里,选中“Personal”旁边的复选框(参见图2-3),然后单击“确定”按钮。 图2-3:引用对话框列出了所有这个工程可以引用的工程如果想要执行其它工程里的过程,就必须建立对 这个工程的引用 建立了对“Personal”工程的引用后,就来从WhatsInACell过程中调用FormulasOnOff宏。 1. 在工程窗口,选择FirstSteps(Chap01.xls)并且定位到含有WhatsInACell过程的模块。 2. 在MsgBox "所有操作都已完成"之前增加一空白行,并且输入代码:FormulasOnOff 3. 返回到Excel窗口,确保当前工作表是这个示例电子表格(参见第一章的图1-1)。 4. 使用在第一章里学到的任何方法来运行宏WhatsInACell。 如果两个不同工程里的两个不同过程命名了相同的名称,那么在调用过程时必须指定工程名称。 假设FirstSteps(Chap01.xls)工程和Personal(Personal.xls)工程里都含有名为FormulasOnOff的 宏,如果要调用Personal(Personal.xls)工程里的FormulasOnOff宏(记住,必须先建立对Personal工 程的引用),那么应该包括该工程的名称: Sub WhatsInACell ( ) <这里放置所录制的指令> Personal.Switches.FormulasOnOff End Sub 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 36 共 612 页 技巧2-2:VBA如何找到被调用的过程 当调用一个过程时,VBA先在主过程(WhatsInACell)所处的模块中查找所调用的过程。如果没有找到被调 用的过程(FormulasOnOff),VBA就会在同一个工程的其它模块里查找。如果仍然找不到该过程,VBA就会检查 对其它工程的引用。 技巧2-3:工程名称不在“引用”对话框中 如果想要调用一个当前关闭的工程里的过程,当打开“引用”对话框试图建立对该工程的引用时,这个工 程的名称不在列表中,那么可以单击“浏览”按钮,打开该工程所在的文件夹。默认状态下,“添加引用”对话 框列出库文件(*.olb, .tlb, .dll)。从文件类型下拉列表中选择Excel文件(*.xls, *.xla),选择含有想要设置对过程引 用的文件,然后单击“打开”按钮。此时,这个工程的名称将会添加在引用对话框的最后一行。 2.2 理解对象、属性和方法 使用VBA,可以创建控制Excel许多功能的过程,也可以控制很多其它的应用程序。VBA的功能 来自于它控制和管理各种对象的能力。但是,“对象”是什么呢? “对象”是使用VBA控制的事物。工作簿、工作表、工作表里的单元格、图表或工具条,这些 只是在使用Excel时想要控制的事物的一些示例。这些事物就是对象。Excel含有上百种可以通过不 同方式操作的对象,所有的VBA对象通过层次组织起来,一些对象可能包含其它对象。例如,Excel 是一个Application对象,该对象包含其它对象,诸如工作簿对象或命令条对象。工作簿对象又包含 其它对象,诸如工作表对象或图表对象。在本章中,您将学习如何控制以下Excel对象:Range对象、 Window对象、Worksheet对象、Workbook对象和Application对象。将“Range对象”列在第一位, 有一个非常重要的原因:如果不知道如何操作单元格区域的话,那么基本上不能用电子表格来做什 么。 某些对象看上去相似。如果打开一个新工作簿并检查它的工作表,会发现没有什么不同。一组 相似的对象称为“集合”。例如,工作表集合包含特定工作簿中的所有工作表;命令条的集合包含所 有的工具条和菜单栏。集合也是对象。Excel中使用得最频繁的集合是代表所有的工作表和图表工作 表的Sheets集合、Workbooks集合、Worksheets集合以及Windows集合。当使用集合时,可以在该 集合中所有的对象上执行相同的操作。 每一种对象都有一些可供描述的特征。在VBA里,对象的特征称为“属性”。例如,Workbook 对象Name属性;Range对象有Column、Font、Formula、Name、Row、Style和Value等属性。可 以设置对象的属性,通过设置对象的属性控制对象的外观和位置。对象属性一次只能设置为一个特 定的值。例如,当前工作簿不可能同时有两个不同的名称。VBA中最难理解的部分是有些属性同时 又可以是对象,譬如Range对象。可以通过设置Font属性改变所选择的单元格区域的外观,但是, 字体(Font)可以有不同的名称(Times New Roman,Arial、…)、不同的大小(10、12、14、…)和不同 的字形(粗体、斜体、下划线、…),这些都是Font属性。如果Font有属性,那么Font也是对象。 属性能改变对象的外观。但是,如何控制操作呢?在使用Excel执行任务之前,需要知道另外一 个术语。对象有方法,想要对象执行的每项操作都称为“方法”。最重要的VBA方法是Add方法,可 以使用该方法添加一个新工作簿或者工作表。对象可以使用不同的方法。例如, Range对象有专门 的方法可以清除单元格内容(ClearContents方法)、清 除 格 式 (ClearFormats方法)、以及同时清除内容第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 37 和格式(Clear方法);还有可以选择、复制或移动对象的方法。 方法有可选择的参数,它们指定方法执行的具体方式。例如, Workbook对象有一个Close方法, 可以使用该方法关闭任何打开的工作簿,如果工作簿有改动,Excel会弹出一个信息来询问是否要保 存变化。可以使用Close方法并将其SaveChanges参数设置为假(False)来关闭这个工作簿,而保存 工作簿中的任何变化,示例如下: Workbooks("Chap01.XLS").Close SaveChanges:=False 2.2.1 学习对象、属性和方法 当学习新事物时,理论会为您提供必须的背景知识,但是如何真正知道那是什么呢?大多数人 习惯形象思维,为了使Excel对象层次易于理解,VBA在线帮助提供了一个对象模型图,如图2-4所 示。 注意,Application对象位于树型图的最顶端,它实际上代表Excel本身,其它的对象位于较低的 层次。 假设想要控制Range对象。在能够控制任何Excel对象之前,必须创建对该对象的引用。为了从 对象层次模型图的顶端获得Range对象,只需按照下面所示的代码。每次看到对象层次模图中的线 指向不同的层时,只要巧妙地将线换成一个句点运算符。这样,最终会以下面的方式到达并引用 Range对象: Application.Workbook.Worksheet.Range 使用Excel对象模型图寻找到达其它对象的路径,例如Window对象、Comment对象、AutoFilter 对象、或者ChartArea对象。分析对象模型是一个学习Excel对象的非常好的方法,花在这里的时间 会对以后编写VBA过程带来加倍的回报。通常,需要明确引用对象的确切的名称。 现在,以实例来说明。假设要清除单元格A4里的内容。手动执行该操作时,只要选择单元格A4 然后按下键盘上的Delete键就可以了。用VBA执行同样的操作,首先需要知道如何使Excel选中了正 确的单元格。单元格A4和其它的工作表单元格一样,是Range对象。VBA没有Delete方法来清除单 元格中的内容,取而代之的是ClearContents方法,例如: Range("A4").ClearContents 注意,在对象名称和方法之间使用句点运算符。这个指令清除了单元格A4里的内容。然而,如 何使Excel清除工作簿Chap02.xls中第一个工作表里单元格A4的内容呢?仍然假设已经打开了几个 Excel工作簿。如果不希望最后在错误的工作簿或工作表里删除了A4里的内容,那么必须写下详细的 指令,以便于VBA知道在哪里找这个单元格: Application.Workbooks("Chap02.xls").Worksheets("Sheet1") .Range("A4").ClearContents 上面的指令应该写成一行,并且应该从右向左阅读:清除单元格A4里的内容,这个单元格在一 个名为“Sheet1”的工作表里,而这个工作表又在一个名为“Chap02.xls”的工作簿里面,工作簿 “Chap02.xls”又应该是Excel应用程序的一部分。注意,集合名称的后面带有一个字母“s”: Workbooks和Worksheets。所有引用的工作簿、工作表或单元格的名称都必须包括在引号里。 为了找到Excel对象模型图,在VBA窗口中选择菜单“帮助”- “Microsoft Visual Basic帮助”,学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 38 共 612 页 在“目录”标签中,单击“Microsoft Excel Visual Basic编程参考”—“Microsoft Excel对象模型”。 图2-4、5、6:Excel对象模型图(译者注:原书图2-5、6和7已经合并在内) 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 39 除了Excel对象外,还可以使用Office、Forms和DAO以及ADO对象模型,属于这些库中的对象 都可以在Excel中使用,也可以用在Office家族中的其它应用软件中。在第十五章,可以看到使用DAO 和ADO对象模型从Excel访问Access数据库的例子。 技巧2-4:VBA 和 Excel 的早期版本 Excel在线帮助列出了Excel对象模型从早期版本以来的变化。许多对象、属性和方法都已经被更新的和改 进的功能所代替了。为了兼容,已经隐藏了被取代的对象(译者注:仍然可以使用它们)。为了获取更多的信息, 打开VBE窗口中的在线帮助。隐藏的对象也可以在对象浏览器里找到。在对象浏览器窗口上单击右键,可以选 择“显示隐藏成员”选项。您将在以后的章节中学习如何使用对象浏览器。 2.3 句法与文法 现在已经知道了VBA的一些基本组成要素(对象、属性和方法),那就开始使用它们吧。但是,如 何将对象、属性和方法组合成正确的语言结构呢?每种语言都有应该遵循以确保能被该语言所理解 的语法规则。无论是讲英语、西班牙语、法语还是其它语言,在写和说的时候都必须遵守一定的规 则。在编程中,我们使用术语“句法”(syntax)来指明语言规则。可以在在线帮助或者在对象浏览器 窗口中查找每个对象、属性或方法的句法。 下面列出了一些必须掌握的VBA常用规则,为了使Excel总是理解您的意思,应严格遵守下面的 规则: „ 规则1:引用对象的属性 如果这个属性没有参数,使用下面的句法: Object.Property Object是一个占位符,是放置想要访问的实际对象名称的地方。Property同样也是一个占位符, 可以在这里放置该对象的特征。例如,指向工作表单元格A4中输入的值,可以编写下面的指令: Range("A4").Value 注意,对象名称和其属性之间的句点。当需要访问包含在多个其它对象里的对象的属性时,必 须按顺序地写上所有对象的名称,并且用句点运算符分开,例如: ActiveSheet.Shapes(2).Line.Weight 这个例子引用当前工作表里图形(Shapes)集合里指向第二个对象中的Line对象的Weight属性。 有些属性需要一个或多个参数。例如,使用常用的Offset属性,可以选择一个与当前单元格相关 的单元格。Offset属性需要两个参数,第一个参数是行号(rowOffset) ,第二个参数是列号 (columnOffset)。 ActiveCell.Offset(3, 2) 对象 属性 对象 属性 参数 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 40 共 612 页 在上面的例子中,假设当前单元格是A1,Offset(3, 2)将代表从单元格A1开始向下3行和向右两 列的单元格,也就是单元格C4。因为放置在括号内的参数总是很难理解,通常在参数值前加上参数 名称,如下例所示: ActiveCell.Offset(rowOffset:=3, columnOffset:=2) 注意,参数名称后面总是跟着一个冒号和一个等于号(:=)。如果使用带名称的参数,那么可以任 意顺序列出它们,上面的指令也可以写成这样: ActiveCell.Offset(columnOffset:=2, rowOffset:=3) 修改后的指令并没有改变意思,仍然代表单元格C4。然而,如果改变ActiveCell.Offset(3, 2)中 参数的次序,结果将代表单元格D3,而不是单元格C4。 „ 规则2:改变对象的属性 Object.Property = Value Value是一个要赋给该对象属性的新值。这个值可以是: — 一个数字 Range("A4").Value = 25 上面的指令在当前工作表的单元格A4里输入数字25。 — 在引号里的文本 ActiveCell.Font.Name = "Times New Roman" 上面的指令将当前单元格的字体改为Times New Roman。 — 逻辑值(True或False) ActiveCell.Font.Bold = True 上面的指令设置当前单元格的字形为粗体。 „ 规则3:返回对象属性的当前值 Variable = Object.Property Variable(变量)是VBA将要存储属性设置的位置的名称。您将在第三章里学习关于变量的知识。 CellValue = Range(“A4”).Value 上面的指令将单元格A4里的当前值保存到名为CellValue的变量中。 „ 规则4:指向对象的方法 如果方法没有参数,那么句法应该是: Object.Method Object是一个占位符,放置想要访问的实际对象的名称。Method也是一个占位符,在这里放置 对象 属性 值 变量 对象 属性 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 41 要对该对象执行的操作的名称。例如,可以使用下述指令来清除单元格A4的内容: Range("A4").ClearContents 如果方法接受参数,那么句法为: Object.Method(参数1,参数2, … ,参数N) 例如,使用GoTo方法可以快速地选择工作表里的任何单元格区域。GoTo方法的句法为: Object.GoTo(Reference, Scroll) ‘对象.GoTo(参照, 窗口滚动) Reference参数是目标单元格或者单元格区域,Scroll参数可以设置为真(True)让Excel窗口滚动 到该目标地址出现在窗口的左上角;或者设置为假(False),不滚动窗口。例如,下面的VBA语句选 择工作表Sheet1里的单元格P100,并且滚动窗口到该单元格: Application.GoTo _ Reference:=Worksheets("Sheet1").Range("P100"), _ Scroll:=True 上面的指令没有书写在一行中,而是使用了一条特殊的线(下划线)将它分为几段,将在下一部分 讲述这方面的内容。 2.3.1 打断很长的VBA语句 尽管一行VBA代码可以包含多达1,024个字母,但是,为了使过程容易阅读,最好将长语句打断 为两行甚至多行。VBA使用一个专门的行连续符置于一行代码的末尾,表明下一行与前一行相连续。 例如: Selection.PasteSpecial _ Paste:=xlValues, _ Operation:=xlMultiply, _ SkipBlanks: =False, _ Transpose:=False 这个行连续符是下划线(_),必须在下划线之前加上一个空格。可以在代码中的下列位置使用行 连续符: „ 运算符之前或者之后,例如:&,+,Like,NOT,AND „ 逗号之前或者之后 „ 冒号和等号(:=)之前或者之后 „ 等号之前或者之后 不可以在冒号和等号之间使用行连续符,例如,VBA不能识别下面的代码中使用的行连续符: Selection.PasteSpecial Paste: _ =xlValues, Operation: _ =xlMultiply, SkipBlanks: _ =False, Transpose: _ =False Selection.PasteSpecial Paste: _ =xlValues, Operation: _ =xlMultiply, SkipBlanks: _ 对象 方法 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 42 共 612 页 =False, Transpose: _ =False 也不可以在引号里的文本中使用行连续符,例如,下面的下划线的使用是无效的: MsgBox "To continue the long instruction, use the _ line continuation character." 应该将上面的指令打断如下: MsgBox "To continue the long instruction, use the " & _ "line continuation character." 2.3.2 理解VBA错误 在编写或修改VBA代码过程期间,无论您多么细心,都可能会出现一些错误。例如,可能语句 拼写错误、放置逗号或引号的位置不正确、或者忘记了输入句点或右括号。这些类型的错误称为句 法错误。幸运的是,VBA比较容易发现这种类型的错误。为了让VBA在输入一行代码后,自动检测 语法的正确性,应该选择VBA窗口的菜单“工具”—“选项”命令,确保在“选项”对话框中设置 了“编辑器”选项卡上的“自动语法检测”前的复选框。 图2-7:“选项”对话框的“编辑器”选项卡上的“自动语法检测”帮助发现VBA过程里的打字错误 当VBA发现语法错误时,会显示一个错误信息框,并且将有错误的代码行颜色变为红色(参见图 2-8)或者其它在“选项”对话框的“编辑器格式”页中设定的颜色。 如果错误信息解释不够清楚,那么总是可以点击“帮助”按钮寻求更多的帮助信息。此外,如 果VBA在线帮助没有提供正确的解决途径,则可以返回到程序中,仔细检查有错误的代码行是否漏 掉了字母、引号、句点、冒号、等于号、以及左括号和右括号等。查找语法错误可能是令人厌烦并 费时的事情,有些语法错误只有在过程运行时才能被发现。在试图运行过程时,VBA可以发现那种 因为使用了无效的参数或者是漏掉了那些需要成对使用的指令,如If语句和循环结构,而造成的错误。 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 43 图2-8:这个错误是由于漏掉了xlCellTypeConstants前面的括号而产生 技巧2-5:程序调试 您可能不只一次听过“计算机程序里充满了漏洞”。在编程设计中,错误就被称为“bug”(漏洞),而“调 试”(debug)则是消除程序中错误的过程。调试的第一步就是改正所有的语法错误。VBA提供了大量的工具,可 以使用它们来追踪和消除错误。在本章中,您将知道如何使用VBA助手在编写程序时尽可能少出错。在第十三 章中,您将学习如何使用专门的调试工具来捕获VBA程序里的错误。 除了语法错误外,还有其它两种错误:运行时错误和逻辑错误。运行时错误发生在过程运行的 时候,图2-9显示了一种典型的运行时错误。运行时错误经常是由于程序员编写代码时没有预料到 的情况而产生。例如,当程序试图访问一个用户电脑上并不存在的驱动器或者文件时,或者没有首 先检查是否用户插入软盘并关闭软驱口而试图复制一个文件到软盘时,就会发生运行时错误。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 44 共 612 页 图2-9:当VBA试图在工作表或单元格区域里选择一个并不存在的单元格时,就会产生运行时错误 第三种错误——逻辑错误,通常不会发出明确的错误信息。过程可能没有语法错误,甚至运行 无误,然而,得到的却是错误的结果。逻辑错误通常非常难以查找,并且很隐蔽,可能需要花几个 小时,甚至几天,来查找错误源。 2.3.3 搜索帮助 当使用宏录制器时,所有的操作都被翻译成VBA指令,并且放置在一个模块里。在研究这些录 制的过程时,不要忘记随时可以使用的帮助。有些代码的意思可能是相当简明易懂的,但有些却不 怎么明白,这时就可以询问帮助了。当独自工作时,只要单击或者使用按键可以请教VBA帮助了。 使用VBA在线帮助比使用词典或参考手册要快捷和容易得多。如果您厌倦了一页一页地在词典里查 找所需要的术语,那么您将惊讶于如何快速地从VBA代码窗口找到需要的帮助页面。 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 45 让我们来测试如何通过内置的VBA帮助系统,理解WhatsInACell过程里的第一句: Selection.SpecialCells(xlCellTypeConstants, 2).Select 上面的指令可以为为三部分。哪些部分?Selection是对象还是属性?SpecialCells是什么? Select是什么?要回答这些问题,请依照下面的操作: 1. 激活要分析的过程所在的代码窗口。 2. 在不理解的词语内单击。 3. 按下F1。 帮助系统就会显示相应的页面。如果光标位于词语“Selection”中间,就会知道Selection可以 Application对象的属性,也可以是一个Window对象。如果光标在下一个不明白的术语(SpecialCells) 里并且按上面的步骤再做一遍后,将看到SpecialCells的帮助屏幕(参见图2-10)。 图2-10:在线帮助里详细解释了VBA的对象、属性和方法 注意,每个帮助屏幕都包含着大量的信息。要查找的指令类型显示在帮助窗口的顶部。指令的 类型允许以词语归类,例如,SpecialCells是方法,可以使用这个方法的对象名称列在“应用于”下 面(译者注:即点击“应用于”,出现对象名称列表)。指令名称下面的“参阅”和“示例”可以快速 地跳到其它相似用法或意思的指令帮助中以及查看使用这个指令的代码示例。 指令的意思显示在“参阅”和“示例”标题的下面,接下来则是带有必需参数和其它可选参数 的语法。“说明”部分给出一些推荐使用该指令的情形。 可以很容易地将示例中的代码复制到过程中去。选中所要复制的代码行,按下Ctrl+C组合键, 或者单击右键,选择快捷菜单上的“复制”命令,然后切换到VBA代码窗口,点击需要粘贴代码的 地方,再按下Ctrl+V组合键或者选择菜单“编辑”—“粘贴”命令。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 46 共 612 页 2.3.4 语法和编程快捷助手 VBE窗口上的“编辑”工具条上有很多按钮,有助于快速容易地输入正确格式的VBA指令。如 果“编辑”工具条没有出现在VBE窗口中,那么可以选择菜单“视图”—“工具条”命令。 图2-11:“编辑”工具条上的按钮使得编写和格式VBA指令变得很容易 在VBA中编写过程需要使用成百上千的内置指令和函数。因为大多数人不可能学习到所有VBA 里可用指令的正确语法,所以智能感应(IntelliSense)技术提供了所需要的语法和编程帮助。在代码窗 口编程时,经常会有特别的窗口弹出来,引导您完成正确的VBA代码。 2.3.5 属性/方法列表 每个对象都包含许多属性和方法。在代码窗口中输入一个对象名称和一个句点以分开这个对象 名称和它的属性或方法时,可能会弹出一个列表菜单。这个菜单列出了在句点前的对象的所有可用 的属性和方法(参见图2-12)。要打开这个自动功能,选择菜单“工具”—“选项”,单击“选项” 对话框上的“编辑器”选项卡,确保选中了“自动列出成员”前的复选框。 图2-12:在输入VBA指令时,VBA会建议可以用于该对象的属性和方法 要从弹出菜单(图2-12)中选择项目,输入需要的属性或方法名称的前几个字母,当Excel突出显 示正确的项目名称时,则按下回车键插入该项目到代码中并且开始新的一行;或者,如果需要在同 一行继续编写代码,那么就按Tab键代替回车键。也可以双击该项目将它插入到代码中。只要按下Esc 键就可以关闭这个弹出菜单而不插入任何项目。 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 47 当按下Esc键取消了弹出菜单后,VBA将不会对同样的对象显示该菜单。此时,可以通过以下方 法来再次显示属性/方法弹出菜单: „ 按Ctrl+J组合键 „ 使用退格键(Backspace)删除句点,然后重新输入句点 „ 在代码窗口上(该对象或句点后)单击右键,从快捷菜单上选择“属性/方法列表” „ 选择菜单“编辑”—“属性/方法列表”命令 „ 单击“编辑”工具条上的“属性/方法列表”按钮 2.3.6 常数列表 在本章前面,您学习了在给属性赋值时需要使用下面的规则: Object.Property = Value 如果“选项”对话框(“编辑器”选项卡中)已经选中了“自动列出成员”前的复选框,Excel就 会显示一个弹出菜单并列出等号前的属性所有有效的常数。常数是表明确切的状态或者结果的值, Excel和其它Office应用程序中都有很多预先定义的内置常数。您将在第三章中学习有关常数、常数 的类型和使用的知识。 假设需要程序能够打开Excel工作表上的分页预览。在“视图”菜单上有两个选择:普通视图和 分页预览,普通视图是绝大多数Excel任务的默认视图模式;分页预览则是编辑视图,显示的工作表 就像打印后的该工作表一样。这两种选项都有相应的内置常数来表示。Excel常数名总是以“xl”开 头。一旦在代码窗口里输入指令: ActiveWindow.View = 就会弹出一个菜单,列出这个属性的有效常数名称。 图2-13:常数列表弹出菜单显示了对输入的属性有效的常数 使用在上节中“属性/方法列表”弹出菜单同样的技术,也可以处理“常数列表”弹出菜单。按 下Ctrl+Shift+J组合键或者单击“编辑”工具条上的“常数列表”按钮,可以激活常数列表菜单。 2.3.7 参数信息 如果您有过使用Excel函数的经历,就会知道许多函数需要一个或者多个参数。如果VBA函数需 要参数,那么可以在输入左括号后在光标下面看到一个提示框,显示必需的或可选的参数名称(参见 图2-14)。自动显示参数信息功能帮助您很容易地给VBA函数设置正确的参数。此外,它提醒您其学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 48 共 612 页 它两件对函数正确运行至关重要的事情:参数的顺序和每个参数的数据类型。您将在下一章里学习 有关数据类型的知识。 在代码窗口里面输入下述代码,看看参数信息是如何工作的: ActiveWorkbook.SaveAs( 一旦输入了左括号,光标的下面就会出现一个提示窗口,当前参数会显示为粗体。当输入完第 一个参数并且输入逗号后,VBA会将下一个参数显示为粗体。[ ]中为可选参数。 图2-14:提示窗口显示了VBA函数或指令可用的参数列表 按下Esc键就可以关闭参数信息窗口。如果要使用键盘来打开提示窗口,先输入指令或函数,然 后输入左括号,再按下Ctrl+Shift+I组合键。也可以单击“编辑”工具条中的“参数信息”按钮或者 选择菜单“编辑”—“参数信息”命令。 2.3.8 快速信息 当在代码窗口中选择了指令、函数、方法、过程名称或者常数,然后单击“编辑”工具条上的 “快速信息”按钮 (或者按下Ctrl+I),VBA将会显示所选项目的语法和常数的值。可以通过“选 项”对话框来打开或者关闭“快速信息”。在“编辑器”选项卡中,选中“自动显示快速信息”前的 复选框。 图2-15:快速信息提供了函数参数列表,也可以是常数值和VBA语句语法 2.3.9 自动完成关键字输入 加速在代码窗口编写VBA程序的另一种方法是使用“自动完成关键字”功能。当输入一个关键第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 49 字的前几个字母,然后按下Ctrl+空格键,或者单击“编辑”工具条上的“自动完成关键字”按钮 , VBA会自动输入这个关键字的剩余字母以节省时间。例如,在代码窗口里输入关键字“Application” 的前四个字母,并且按下Ctrl+空格键: Appl VBA将会自动完成剩余的字母,在Appl地方将看到整个关键字Application。 如果有好几个关键字的开头字母相同,当按下Ctrl+空格键后,VBA会显示一个列出了所有关键 字弹出菜单。试一试,输入关键字Application的前三个字母,按工具条上的“自动完成关键字”按 钮,然后在弹出菜单上选取合适的关键字。 2.3.10 缩进/凸出 也许您已经看到在“选项”对话框的“编辑器”选项卡上,有许多设置可以打开代码窗口中很 多可用的自动功能。如果选中了“自动缩进”复选框,就可以自动为所选的代码行缩进在“Tab宽度” 文本框里指定数字的字符,默认的自动缩进量是4个字母,也可以在文本框里输入一个新的数字来改 变Tab宽度。 为什么需要在代码里使用缩进呢?缩进可以使你的代码更容易阅读和理解,特别是输入一些条 件或循环操作的代码行时,建议使用缩进。您将在第五和第六章中学习如何创建这种类型的VBA指 令。现在,我们使用第一章里所录制的宏WhatsInACell作为例子,来练习缩进和凸出代码行。 1. 在工程窗口中,选择FirstSteps(Chap01.xls)工程,并且激活含有WhatsInACell代码的 WorksheetFormatting模块。 2. 选择开始为关键字With和结束为End With的任何一段代码。 3. 单击“编辑”工具条上的缩进按钮 ,或者按键盘上的Tab键。 4. 如果使用了“选项”对话框(“编辑器”选项卡)中“Tab宽度”文本框中的默认设置,那么选中 的指令块会向右移动4个空格的位置。 5. 单击“编辑”工具条上的“凸出”按钮 ,或者按Shift+Tab组合键将使代码窗口里选中的代 码行恢复到原来的位置。 缩进和凸出同样可以在编辑菜单里找到。 2.3.11 设置注释块/解除注释块 在第一章中,您学习了在一行代码前面加一个引号表示注释。注释不但使代码更容易理解,而 且它在VBA过程的测试和处理问题中都是很有用的。例如,执行一个过程时,可能并不像所期望的 那样运行。对于那些出现问题的代码行,可能想暂时忽略它们,以后再修改它们,这时,就可以在 它们前面加一个引号将它们设置为注释,而不必删除它们。在想要暂时避免运行的代码行开头加上 引号,可以继续检查过程的其它部分。需要设置一行代码为注释,只需在它前面输入一个引号就可 以了,如果要将整块代码设置为注释,则可以使用“编辑”工具条上的“设置注释块”和“解除注 释块”按钮。要将几行代码设置为注释,选中这些代码行并且单击“设置注释块”按钮 。要将设 置为注释的代码块恢复为VBA指令,单击“解除注释块”按钮 。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 50 共 612 页 如果没有选中文本而单击“设置注释块”按钮,则只在光标所在代码行的前面加入引号。 2.4 使用对象浏览器 如果想要在VBA众多的组件和功能中自由切换,那么使用对象浏览器。这个专门的内置工具在 VBE窗口中是可用的。 可以使用下面任何一种方法访问对象浏览器。 „ 按F2键 „ 选择菜单“视图”—“对象浏览器”命令 „ 单击工具条上的 (对象浏览器)按钮 图2-16:对象浏览器可以浏览当前VBA工程里可用的所有对象,属性和方法 对象浏览器可以浏览VBA过程可用的对象,也可以查看它们的属性、方法和事件。在对象浏览 器的帮助下,可以在VBA工程的过程之间快速移动,也可以通过类库搜索对象和方法。 对象浏览器分为三个部分(参见图2-16)。窗口的顶部显示的“工程/库”下拉列表框中列出了所 有可用于当前VBA工程的所有库和工程的名称。库是包含应用程序里相关对象信息的专门文件。新 的库可以通过“引用”对话框(选择菜单“工具”—“引用”命令)来添加。<所有库>列出了已安装 在计算机中所有库的对象。当选择“Excel”库时,仅仅能看到在Excel里执行的对象名称。和Excel 库相反,VBA库列出了所有能在VBA里执行的对象名称。 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 51 在“工程/库”下拉列表框的下方,有一个“搜索”文本框,可以快速地在指定库里查找信息。 这个地方会记住最近搜索的四个项目(译者注:在Excel2003中可以记住多于4个项目)。在对象浏览 器的任何地方单击右键,从快捷菜单中选择“全字匹配”,就可以只查找与所搜索文本完全匹配的内 容。对象浏览器上的“搜索结果”(参见图2-16和2-17)显示符合搜索文本框中所输入的搜索条件 的库、类和成员。当输入搜索文本并且单击搜索按钮 ,VBA展开对象浏览器对话框以显示搜索结 果。可以点击“望远镜”按钮右边的“显示/隐藏搜索结果”按钮来显示或者隐藏搜索结果。 图2-17:在对象浏览器里搜索结果 类列表框显示所选中的库里面所有可用的对象类,如果选择VBA工程,列表显示该工程里的对 象。在图2-16里选中了CommandBarComboBox 对象类。当选中一个类后,右边的(成员)列表显 示该类可用的属性、方法和事件。图2-16上显示了CommandBarComboBox类的一些成员。默认状 态下,成员按字母顺序列出。然而,也可以从对象浏览器的快捷菜单中使用“组成员”命令(属性、 方法或事件)来组织这些成员。 如果选择“工程/库”列表框里面的VBA工程,那么成员列表框将列出该工程里的所有可用的过 程。要检查某过程的代码,只需双击该过程的名称。如果选择VBA库,将看到VBA内置的函数和常 数列表。如果想查看所选的类或成员的更多的信息,可以单击对象浏览器窗口顶部的问号标记按钮。 对象浏览器窗口底部显示所选成员定义的代码模板。如果单击代码模板里绿色链接文本,就可 以在对象浏览器窗口中快速跳到所选成员的类或库。代码模板里的文本可以复制到Windows剪切板 并且粘贴到代码窗口里去。如果当对象浏览器打开时代码窗口是可见的,那么只需选中代码模板里学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 52 共 612 页 的文本,直接拖曳到代码窗口就行了。 可以通过拖曳对象浏览器窗口上的水平和垂直分割线来容易地调整窗口中不同部分的大小。 您已经发现了对象浏览器,也许您在想如何在VBA编程中使用它。假设在工作表中央放置了一 个文本框,如何让Excel将这个文本框移动到工作表的左上方? 1. 打开一个新工作簿。 2. 选择菜单“视图”—“工具栏”,然后单击“绘图”命令。 3. 单击“绘图”工具栏上的“文本框”,在工作表中央画一个文本框,并且随便输入什么文字。 4. 选取文本框之外的任意单元格。 5. 按下Alt+F11组合键激活VBE窗口,并且选择工程窗口的Personal (Personal.xls)。 6. 选择菜单“插入”—“模块”,添加一个新的模块到个人宏工作簿中。 7. 在属性窗口中,给该模块重命名为:Manipulations。 8. 选择菜单“视图”—“对象浏览器”,或按F2键。 9. 在“工程/库”下拉列表框里选择“Excel”类型库。 10. 在“搜索”框里输入“textbox”并单击搜索按钮 。确保没有在搜索的字符串中输入了空格。 图2-18:Excel 在工作表上面的名称框里显示所插入对象的名称 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 53 图2-19:使用对象浏览器窗口,可以找到合适的VBA指令来编写过程 VBA搜索Excel库并显示搜索结果,显示的结果表明Shapes对象控制着文本框的操作(参见图2 -19)。从成员列表上看,可以很快就知道使用AddTextbox方法在工作表里添加新的文本框。 对象浏览器底部的代码模板窗口显示了使用该方法的正确语法。如果选择AddTextbox方法并且 按F1键,将看到更详细的如何使用该方法的帮助窗口(参见图2-20)。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 54 共 612 页 图2-20:要获取在对象浏览器中找到的任何项目的详细信息,只要选择该项目并且按F1键 当在帮助窗口中察看AddTextbox方法的参数和它们的解释时,就可以很快地知道Left属性和Top 属性决定文本框在工作表中的位置。现在所需要做的只是返回代码窗口,编写程序来移动文本框到 工作表的左上方。 11. 关闭对象浏览器窗口和帮助窗口(如果它们还是打开的话)。 12. 双击Manipulations模块,并输入过程MoveTextBox: Sub MoveTextBox() With ActiveSheet.Shapes("文本框 1") .Select .Left = 0 .Top = 0 End With End Sub 13. 选择菜单“运行”—“运行子过程/用户窗体”命令来测试这个过程。 当返回到放置该文本框的工作表中时,该文本框已经被移动到了工作表的左上方。 注意,MoveTextBox过程在Shapes集合里选择了“文本框 1”对象。“文本框 1”是工作表里 所放置的第一个文本框对象的默认名称。每次增加新的对象后,Excel将给它指定新的号码(索引)。 除了使用对象名称外,还可以使用索引号引用集合中的成员。例如,也可以输入: 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 55 With ActiveSheet.Shapes(1) 来代替: With ActiveSheet.Shapes("文本框 1") 试试用VBA来操作另一个对象。在放置文本框的工作表里再放置一个小圆圈,使用“绘图”工 具上的椭圆工具画这个圆。在Manipulations模块里插入一个新的过程,并且编写VBA代码将该圆放 置在文本框中。记住,Excel将为对象连续地编号,即第一个对象的编号为1,第二个则为2,依次类 推,不管这个对象的类型是文本框、圆或者是矩形,都没有关系。 下面的过程MoveCircle演示如何将当前工作表里的椭圆移动到工作表的左上方去: Sub MoveCircle() With ActiveSheet.Shapes(2) .Select .Left = 0 .Top = 0 End With End Sub 移动圆与移动文本框或者放在工作表里的其它对象的方法相似。 注意,过程代码中通过对象的索引号而不是它的名字椭圆 2引用该圆。运行MoveCircle过程后, Excel放置该圆到文本框中。 2.5 使用VBA对象库 在前面的例子里,使用了Excel对象库里的Shapes集合成员对象的属性。Excel库包含专门使用 Excel的对象,而VBA库则提供对许多内置VBA函数的访问,这些函数按类别分组。它们是通用的, 能够管理文件、设置日期和时间、与用户交互、转换数据类型、处理文本字符串或者进行数学计算。 在下面的练习中,您将学习如何在Excel中使用内置的VBA函数来创建一个新的子文件夹。 1. 返回到含有MoveTextBox过程和MoveCircle过程的模块Manipulations。 2. 输入一个新的过程:Sub NewFolder()。 3. 单击回车键,VBA会自动输入结束关键词End Sub。 4. 按F2键激活对象浏览器窗口。 5. 在“工程/库”列表框里单击下拉箭头并选择VBA。 6. 在“搜索”文本框里输入file并按回车键。 7. 滚动成员列表框,并且选中MkDir方法(参见图2-21)。 8. 单击对象浏览器窗口上的“复制”按钮,将被选择的方法名称复制到剪贴板。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 56 共 612 页 图2-21:编写过程时,在对象浏览器中输入名称来查找内置的VBA函数 9. 返回Manipulations窗口,并且将复制的指令粘贴到NewFolder过程。 10. 输入一个空格,接着输入”C:\Study”。确保在引号里输入了完整的路径名。NewFolder过程代码 如下: Sub NewFolder() MkDir "C:\Study" End Sub 11. 运行过程NewFolder。 当运行NewFolder过程时,VBA在C盘上创建了一个新的文件夹。可以激活Windows资源管理器 来查看该新文件夹。创建一个新的文件夹后,可能会发现根本就不需要它。虽然可以轻易地从 Windows资源管理器里删除该文件夹,但是,如何编程删除它呢?对象浏览器上列出了许多对文件 夹和文件操作很有帮助的一些其它方法。使用RmDir方法就像使用MkDir 方法一样简单。想要删除 硬盘上的“Study”文件夹,只需将代码中的MkDir方法换成RmDir方法,然后重新运行NewFolder 过程就可以了。或者,也可以创建一个新的过程RemoveFolder来删除文件夹,该过程代码如下: Sub RemoveFolder() RmDir "C:\Study" End Sub 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 57 RmDir方法可以从硬盘上删除不需要的文件夹。 2.5.1 用对象浏览器来查找过程 除了查找对象、属性和方法外,对象浏览器还是一个用来查找不同工程里的过程的非常方便的 工具。下面的例子演示如何查看存储在个人宏工作簿里面的过程: 1. 激活对象浏览器,并且从“工程/库”下拉列表里选择Personal。 图2-22:对象浏览器列出所有在某个指定VBA工程里可用的过程 对象浏览器的左边显示所选工程里面对象名称,而右边的列表框则显示所有可用的过程名。 2. 双击NewFolder过程名称,VBA将光标定位到该过程的第一行。 3. 关闭对象浏览器窗口。 2.6 使用立即窗口 在开始创建一个完整的VBA过程前(见下一章),先来做一些热身练习增加VBA词汇。怎样才能很 快掌握而且愉快地学习?如何试验一些新学的VBA语句?这里有一些简短的、互动的语言练习:输 入一个简单的VBA指令,Excel会检查并且将结果显示在下一行。我们开始建立练习屏幕。 1. 在VBE窗口,选择菜单“视图”—“立即窗口”命令。 在决定将语句使用在VBA过程中之前,立即窗口可以用来试验VBA语言中不同的指令、函数和 运算符。这是一个非常好的调试新语句的工具,输入在这个窗口里面的指令将会立即显示结果。 立即窗口可以在VBE窗口中任意移动,或者将它停靠以便每次都出现在屏幕上相同的位置。可 以通过“选项”对话框上的“可连接的”选项卡来打开或关闭可连接设置。 在VBE窗口上按下Ctrl+G组合键就可以快速访问立即窗口。单击立即窗口右上角的“关闭”按 钮可关闭该窗口。 立即窗口允许输入VBA语句并立即测试语句的结果,而不需要编写一个过程。立即窗口就像一 个草稿板,可以用它测试语句。如果该语句输出了所期望的结果,就可以将立即窗口中的语句复制学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 58 共 612 页 到过程中(或者,如果代码窗口是可见的,也可以将语句拖曳到代码窗口中合适的位置)。 2. 将Excel窗口和VBE窗口并排排列。 3. 在立即窗口里输入下述指令,并按回车键: Worksheets("Sheet2").Activate 当按下回车键时,VBA开始工作,如果上面输入的语句是正确的话,VBA激活当前工作簿里的 第二个工作表。这时,工作簿底部的“Sheet2”标签应该是突出显示的。 4. 在立即窗口中,输入另一句VBA语句并按回车键: Range("A1:A4").Select 一旦按下回车键后,VBE将选中当前工作表中的单元格A1、A2、A3和A4。 5. 在立即窗口里输入下面的指令: [A1:A4].Value = 55 按下回车键后,VBE在指定单元格区域A1:A4中的每个单元格里放置数字55。上面的语句是引用 Range对象的一种简写方式,完整的语法可读性更强: Range("A1:A4").Value = 55 图2-23:将Excel窗口和VBE窗口并排排列可以观察到在立即窗口中输入的指令的运行情况 6. 在立即窗口中输入下面的指令: Selection.ClearContents 按回车键后,VBA清除前一语句所选单元格区域的内容,现在区域A1:A4是空的。 7. 在立即窗口中输入下面的指令: ActiveCell.Select 按回车键后,VBA激活单元格A1。 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 59 图2-24:在立即窗口里输入指令后,一旦按下回车键,指令就会被执行 图2-24显示了上面练习中在立即窗口里输入的所有指令。每次按下回车键后,Excel总是执行 光标所在行的语句。如果想要再次执行同一指令,那么单击该指令所在行后面空白处的任意位置, 按回车键。 为了更多的练习,重新运行图2-24里的语句。从立即窗口中的第二行指令开始,单击合适的地 方并按回车键,一句一句地执行这些指令。 2.6.1 获取立即窗口里的信息 到目前为止,您已经使用过立即窗口执行操作了,这些操作也可以通过手动地在工作表的不同 区域单击鼠标并且输入数据来执行。也可以在立即窗口中提问。假设想要找到下列问题的答案:“现 在选中的是哪些单元格?”、“当前单元格里的值是多少?”、“当前工作表的名称是什么?”、“当前 窗口的编号是多少?”使用立即窗口,可以轻易地找到这些问题以及其它问题的答案。 在前面的例子里,您已经输入了好几个指令。让我们返回到立即窗口中去问几个问题,既使在 关闭了立即窗口后,Excel还能记住在立即窗口里输入的指令。当退出Excel时,立即窗口中的内容 会被自动删除。 1. 鼠标单击立即窗口中的第二行,即输入指令Range("A1:A4").Select的行。 2. 按回车键,使Excel再次选择单元格区域A1:A4。 3. 在立即窗口新的一行输入下面的问题: ?Selection.Address 按回车键后,Excel不会选择工作表中的任何东西,而是在立即窗口上的另一行中显示该指令的 结果。在该例中,Excel返回的是当前被选择的单元格的绝对地址($A$1:$A$4)。问号(?)告诉Excel 在立即窗口中显示指令的结果。除了使用问号外,还可以使用Print关键字。现在,使用关键字Print 询问工作表的名称。 4. 在立即窗口新的一行中,输入下面的问题: Print ActiveWorkbook.Name 按回车键后,Excel在立即窗口新的一行中输入了当前工作簿的名称。 现在,找找应用程序的名称如何?Chap02.xls的父对象是谁? 5. 在立即窗口新的一行中,输入下面的问题: ?Application.Name 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 60 共 612 页 Excel会显示它自己的全名:Microsoft Excel。 立即窗口也可以用来做快速的计算。 6. 在立即窗口新的一行中,输入下面的问题: ?12/3 按回车键后,Excel会在下一行显示该除法运算的结果。但是,假如想立即知道3+2和12*8的结 果呢?可以在一行中输入这些指令,例如: ?3+2:?12*8 注意,冒号将两个代码块分割开来。按下回车键后,Excel在立即窗口的两行中显示结果5和96。 下面是在立即窗口里输入的所有指令以及Excel对问题的回答: Worksheets("Sheet2").Activate Range("A1:A4").Select [A1:A4].Value = 55 Selection.ClearContents ActiveCell.Select ?Selection.Address $A$1:$A$4 Print ActiveWorkbook.Name Chap02.xls ?Application.Name Microsoft Excel ?12/3 4 ?3+2:?12*8 5 96 要清除立即窗口里的指令,只需选中所有指令并且按下Delete键即可。 2.7 学习对象 要在Excel里创建自定义应用程序,需要一些常用对象或者对象集合的知识,例如Range、 Workbook (Workbooks)、Worksheet(Worksheets)、Window (Windows)和Application。在前面部分, 探索了学习VBA的许多方法。下面是关于什么时候使用某种特定工具的总结: „ 当对已存在的VBA过程中有关的对象、属性或方法有疑问时,可以按F1键打开在线帮助。 „ 如果需要快速列出每个可用对象的属性和方法,或者查找一个很难找到的过程时,使用对象浏 览器。 „ 如果想要测试VBA并且立即查看VBA命令的结果时,激活立即窗口。 本章剩余的几页包含一些VBA语言训练,可以帮助您更好地理解VBA语法。如果花些时间在立 即窗口中实践一遍这些训练,您将能够获得更多的知识。 2.7.1 处理电子表格单元格 当准备编写VBA过程自动化特定的电子表格任务时,开始您很可能寻找可以操作电子表格单元 格的指令。您需要知道如何选择单元格、如何在单元格中输入数据、如何给单元格区域命名、如何第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 61 设置单元格格式、以及如何移动、复制和删除单元格。虽然这些任务可以通过鼠标或键盘轻易执行, 但在VBA中掌握这些技术需要一些实践。 必须使用Range对象来引用单个单元格、单元格区域、行或列。如果看看Excel对象模型,将会 注意到Range对象是另一个更大的对象——Worksheet对象——的一部分。有三种属性可以访问 Range对象:Range属性、Cells属性和Offset属性。 图2-25:Excel对象模型里的Range对象(译者注:本书基于Excel 2003翻译,因此屏幕截图均从Excel 2003版中获得,可能与原书截图有所差异) 2.7.2 使用Range属性 Range属性返回一个单元格或者单元格区域。对区域的引用必须是A1样式并且要在引号中(例 如:”A1”),引用可以包括区域运算符——冒号(例如:”A1:B2”)或者联合运算符——逗号(例 如:”A”,”B12”)。 在VBA中表达的操作 在立即窗口中输入的代码 选取单个单元格(例如A5) Range("A5").Select 选取一个单元格区域(例如A6:A10) Range("A6:A10").Select 选取一些不相邻的单元格(例如A1, B6, C8) Range("A1, B6, C8").Select 选取一些不相邻的单元格和单元格区域(例如 Range("A11:D11, C12, D3").Select 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 62 共 612 页 A11:D11, C12, D3) 2.7.3 使用Cells属性 想要选取一个指定的单元格时,Cells属性需要两个参数,第一个参数代表行号,第二个参数代 表列号或者列字母。在括号中输入参数。如果忽略参数,Excel将会选取当前工作表的所有单元格。 在VBA中表达的操作 在立即窗口中输入的代码 选取单个单元格(例如A5) Cells(5, 1).Select或Cells(5, A).Select 选取一个单元格区域(例如A6:A10) Range(Cells(6, 1), Cells(10, 1)).Select 选取工作表中所有单元格 Cells.Select 注意,如何结合使用Range和Cells属性: Range(Cells(6, 1), Cells(10, 1)).Select 在上面的例子里,第一个Cells属性返回单元格A6,而第二个则返回单元格A10。Cells属性返回 的单元格之后又当做Range对象的参数。因此,Excel就选取了顶部单元格为第一个Cells属性返回的 结果和底部单元格为第二个Cells属性返回的结果之间的单元格区域。 工作表是单元格的集合,也可以使用只带一个参数的Cells属性来表示单元格在工作表所有单元 格集合中的位置。Excel按下列方式给单元格编号:单元格A1是工作表中的第一个单元格,B1是第 二个,C1是第三个,依次类推。Cell256是第一行中的最后一个单元格。您也许会想起Excel工作表 只有256列。 在VBA中表达的操作 在立即窗口中输入的代码 选取单元格A1 Cells(1).Select或Cells.Item(1).Select 选取单元格C1 Cells(3).Select或Cells.Item(3).Select 选取单元格IV1 Cells(256).Select或Cells.Item(256).Select 选取单元格A2 Cells(257).Select或Cells.Item(257).Select 注意,Item是返回一个集合中单个成员的属性。因为Item是集合的默认属性,所以可以直接引 用工作表单元格,而不必明确在使用Item属性。 现在,您已经发现有两种方法可以选取单元格(Range属性和Cells属性),也许您很迷惑为什么要 使用更复杂的Cells属性。很明显,Range属性更具有可读性,毕竟,在您决定学习VBA之前就在Excel 公式和函数里面使用了Range引用。然而,当需要将单元格作为集合操作时,使用Cells属性则更方 便。使用这个属性访问单元格集合中的所有单元格或者单个单元格。 2.7.4 使用Offset属性 另一个引用工作表单元格非常灵活的方法是使用Offset属性。当自动化工作表任务时,也许不确 切地知道某个特定单元格的地址。如何能够选取一个不知道地址的单元格?可以让Excel基于已有的 选区来选取一个单元格。Offset属性通过从开始选取的单元格向下或向上移动的具体行数来计算新的 区域,同样也可以从当前选取的单元格区域向右或向左移动具体的列数来获取新的区域。Offset属性第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 63 使用两个参数来获得新单元格区域的位置,第一个参数表示行偏移量,第二个参数则表示列偏移量。 我们来测试一下几个例子: 在VBA中表达的操作 在立即窗口中输入的代码 选取位于单元格A1下面一行和右边三列的单 元格 Range("A1").Offset(1, 3).Select 选取位于单元格D15上面两行和左边一列的单 元格 Range("D15").Offset(-2, -1).Select 选取位于当前单元格上面一行的单元格 ActiveCell.Offset(-1, 0).Select 在上面的第一个例子里,Excel选取单元格D2。一旦输入了第二个例子,Excel将选取单元格C13。 如果已经选取了单元格A1和D15,也可以将上面的两个例子改写为: Selection.Offset(1, 3).Select Selection.Offset(-2, -1).Select 注意,上面第三个例子里的第二个参数是0。Offset属性中所输入的第一个或第二个参数为0时, 相应表示当前行或当前列。如果当前活动单元格在第一行,那么指令ActiveCell.Offset(-1, 0).Select 会导致错误。 当使用Offset属性时,可能有时需要改变选择区域的大小。假设开始选择的区域是A5:A10,如 何将选择区域向下移动两行,向右移动两列,然后再改变新选择区域的大小呢?假设新的选择区域 应该是C7:C8。Offest属性只能完成前面的部分,后面的部分需要另外一个属性来完成。Excel有个 专门的Resize属性,可以结合使用Offset属性和Resize属性来解决上面的问题。在结合使用这两个属 性之前,我们先来看看如何独立地使用它们: 1. 将Excel窗口和VBA窗口并排显示。 2. 激活立即窗口,并且输入下面的指令: Range("A5:A10").Select Selection.Offset(2, 2).Select Selection.Resize(2, 4).Select 上面的第一条指令选取区域A5:A10,当前活动单元格是A5。第二条指令将当前选区偏移到区域 C7:C12。单元格C7处于活动单元格A5的向下两行和向右两列。现在,活动单元格是C7。最后一条 指令改变当前选区的大小,单元格区域C7:C8被选中了,而不再是C7:C12。像Offset属性一样,Resize 属性也接受两个参数,第一个参数是所要选取的行数,第二个参数则是要选取列数。因此,指令 Selection.Resize(2, 4).Select将当前选择区域改为两行和四列 技巧2-6:录制选取单元格 宏录制器默认地使用Range属性录制选取单元格。如果打开宏录制器,并且选择单元格A2,输入任何文本, 再选取单元格A5,将在VBE窗口里得到下面的代码: Range("A2").Select ActiveCell.FormulaR1C1 = "text" Range("A5").Select 如果使用相对引用方式,宏录制器会使用Offset属性。在录制前,单击“录制宏”工具条上的“相对引用” 按钮。宏录制器将得到如下代码: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 64 共 612 页 ActiveCell.Offset(-3, 0).Range("A1").Select ActiveCell.FormulaR1C1 = "text" ActiveCell.Offset(3, 0).Range("A1").Select 当使用相对引用方式录制宏时,过程总是会选择相对于当前活动单元格的单元格。注意,上面指令中的第 一和第三行引用单元格A1,即使没有涉及到关于单元格A1中的任何东西。您可能记得,在第一章中,宏录制器 用它自己的方式处理事情。为了将上面的指令变得简单一些,可以删除对单元格A1的引用: ActiveCell.Offset(-3, 0).Select ActiveCell.FormulaR1C1 = "text" ActiveCell.Offset(3, 0).Select 使用相对引用来录制过程后,如果下次录制一个不需要使用相对地址的过程,则不要忘记再次单击这个按 钮,。 最后两行指令可以合并成下面的语句: Selection.Offset(2, 2).Resize(2, 4).Select 在上面的例子中,Offset属性计算新区域的起始点,Resize属性决定区域的大小,Select方法选 取指定范围的单元格区域。 2.7.5 选取单元格的其它方法 如果需要经常访问工作表里某些“偏远”的单元格,可能已经很熟悉下面的键盘快捷键:End+ 上箭头、End+下箭头、End+左箭头和End+右箭头。在VBA中,可以使用End属性快速地移动到“偏 远”的单元格。 在VBA中表达的操作 在立即窗口中输入的代码 选取任何行的最后一个单元格 ActiveCell.End(xlright).Select 选取任何列的最后单元格 ActiveCell.End(xldown).Select 选取任何行的第一个单元格 ActiveCell.End(xleft).Select 选取任何列的第一个单元格 ActiveCell.End(xlup).Select 注意,End属性需要一个参数来表示移动的方向。使用下列Excel内置常数在指定的方向中跳动: xlright、xlleft、xlup、xldown。 2.7.6 选取行和列 Excel使用EntireRow属性和EntireColumn属性来选取整行或整列。 在VBA中表达的操作 在立即窗口中输入的代码 选取活动单元格所在行的整行 Selection.EntireRow.Select 选取活动单元格所在列的整列 Selection.EntireColumn.Select 选取单元格区域时,也许想要知道所选区域包括的行数或列数。我们来让Excel统计区域A1:D15 中的行数和列数: 1. 在立即窗口里输入下面的VBA语句: 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 65 Range("A1:D15").Select 如果Excel窗口可见,当按下回车键后,VBA会选中区域A1:D15。 2. 输入下面的语句来得到所选区域的行数: ?Selection.Rows.Count 一旦按下回车键,VBA在下一行显示结果。你的选区包括15行。 3. 输入下面的语句来得到所选区域的列数: ?Selection.Columns.Count 现在,VBA将告诉您,所选区域A1:D15占据了四列的宽度。 4. 将光标放在关键字Rows或Columns中的任意位置,并且按下F1键,获取这些有用属性的更多信 息。 2.7.7 获取工作表信息 Excel工作表有多大?它有多少单元格、列和行?如果忘记了这些细节,则使用Count属性。 在VBA中表达的操作 在立即窗口中输入的代码 获得Excel工作表里单元格总数 ?Cells.Count 获得Excel工作表里总行数 ?Rows.Count 获得Excel工作表里总列数 ?Columns.Count Excel 2002工作表里有16,777216个单元格、65,536行和256列。 2.7.8 在工作表中输入数据 输入到工作表中的信息可以是文本、数字或者公式。为了在单元格或单元格区域中输入数据, 可以使用Range对象的两个属性之一:Value属性或者Formula属性。 Value属性: ActiveSheet.Range("A1:C4").Value = "=4 * 25" Formula属性: ActiveSheet.Range("A1:C4").Formula = "=4 * 25" 在上面两个例子中,单元格区域A1:C4中的单元格都显示100,即4乘25的结果。 在VBA中表达的操作 在立即窗口中输入的代码 在单元格A5里输入文本“Amount Due” Range("A5").Formula = "Amount Due" 在单元格D21里输入数字“123” Range("D21").Formula = 123 Range("D21").Value = 123 在单元格B4里输入公式“=D21*3” Range("B4").Formula = "=D21 * 3" 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 66 共 612 页 2.7.9 获取工作表中的信息 毫无疑问,在一些VB过程中可能需要获取单元格或者单元格区域中的内容。虽然既可以使用 Value属性也可以使用Formula属性,但是这次,Range对象的这两个属性是不可互用的。 „ Value属性显示指定单元格中公式的结果。例如,如果单元格A1中含有公式“=4*25”,那么指 令?Range("A1").Value将会返回值100 „ 如果想要显示公式而不是结果,那么必须使用Formula属性:?Range("A1").Formula。Excel将 会显示公式“=4*25”而不是结果100。 2.7.10 单元格格式 我们经常需要给电子表格中所选单元格或区域设置格式。VBA过程可能需要找到应用到某个指 定单元格的格式类型,可以使用NumberFormat属性来获取单元格格式: ?Range("A1").NumberFormat 在立即窗口中输入上面的问题后,Excel显示“General”,表示所选单元格没有设置任何特殊的 格式。要使用VBA改变单元格格式,输入下面的指令: Range("A1").NumberFormat = "$#,##0.00" 在使用上面的指令设置格式后,如果在单元格A1里输入125,在单元格A1中将显示“$125.00”。 可以在Excel窗口的“单元格格式”对话框里查找需要的格式代码(选择菜单“格式”—“单元格” 命令)。 如果想要的格式没有列在“单元格格式”对话框里,那么参考在线帮助关于创建用户自定义格 式向导。 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 67 图2-26:可以使用“单元格格式”对话框里的自定义给所选单元格和单元格区域设置不同的格式 2.7.11 移动、复制和删除单元格 在开发一个新工作表模型的过程中,经常发现要移动、复制和删除单元格内容。VBA可以自动 化这些工作表编辑任务,只要使用一些容易使用的方法:Cut、Copy和Clear。 在VBA中表达的操作 在立即窗口中输入的代码 移动单元格A5的内容到单元格A4里面 Range("A5").Cut Destination:=Range("A4") 复制单元格A3里的公式到区域D5:F5中 Range("A3").Copy Destination:=Range("D5:F5") 清除单元格A4里的内容 Range("A4").Clear Range("A4").Cut 注意,使用在Range对象上的Cut和Copy方法都需要一个叫“Destination”的特殊参数。这个参 数指定想要放置剪切或复制的数据的单元格或单元格区域的地址。在最后一个例子中,使用了没有 Destination参数的Cut方法来去除指定单元格中的数据。 Clear方法将删除指定单元格或单元格区域中的所有内容,包括任何格式和单元格批注。如果想 要指定删除的内容,使用下面的方法: „ ClearContents-仅清除单元格或单元格区域内的数据 „ ClearFormats-仅清除格式 „ ClearContents-清除指定单元格区域里的所有批注 2.7.12 处理工作簿和工作表 既然您已经了解了处理工作表单元格和单元格区域,那现在上一个台阶,学习如何控制单个工 作簿,以及整个工作簿集合。如果不知道如何打开一个新工作簿的话,您就不能够准备一个新的电 子表格了。如果您不知道如何关闭工作簿,那么就不知道如何将工作簿从屏幕上消除。这些重要的 任务由两个VBA方法处理:Add方法和Close方法。下面一系列练习将给你必要的处理工作簿和工作 表的语言技能。 在VBA中表达的操作 在立即窗口中输入的代码 打开一个新工作簿 Workbooks.Add 获得第一个工作簿的名称 ?Workbooks(1).Name 获得打开的工作簿数目 ?Workbooks.Count 激活第二个打开的工作簿 Workbooks(2).Activate 激活工作簿Chap02.xls Workbooks("Chap02.xls").Activate 将当前活动的工作簿保存为NewChap.xls ActiveWorkbook.SaveAs Filename:="NewChap.xls" 关闭第一个工作簿 Workbooks(1).Close 关闭当前活动的工作簿,并且不保存变化 ActiveWorkbook.Close SaveChanges:=False 关闭所有打开的工作簿 Workbooks.Close 如果运行了最后一个例子,那么现在所有的工作簿都被关闭。在工作表上试验上述例子之前,学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 68 共 612 页 确保打开一个新工作簿。 当处理单个工作表时,必须知道如何在工作簿里添加新的工作表、选择一个或一组工作表、命 名工作表、以及复制、移动和删除工作表。在VBA中,这些任务中的每一个都需要专门的方法或属 性。 在VBA中表达的操作 在立即窗口中输入的代码 添加一个新工作表 Worksheets.Add 获得第一个工作表的名称 ?Worksheets(1).Name 选择名为“Sheet3”的工作表 Worksheets(3).Select 选择第一、第三和第四个工作表 Worksheets(Array(1,3,4)).Select 激活名为“Sheet1”的工作表 Worksheets(“Sheet1”).Activate 将工作表“Sheet2”移动到工作表“Sheet1” 之前 Worksheets("Sheet2").Move Before:=Worksheets("Sheet1") 将工作表“Sheet2” 重命名为“Expenses” Worksheets("Sheet2").Name = "Expenses" 获得当前工作簿里的工作表数目 ?Worksheets.Count 删除当前工作簿里的工作表“Expenses” Worksheets("Expenses").Delete 注意Select方法和Activate方法之间的区别: „ 如果只选择一个工作表,那么Select方法和Activate方法可以互换使用。 „ 如果要选择一组工作表,那么Activate方法可以决定所选工作表中哪个工作表要激活。我们知道, 一次只能激活一个工作表。 技巧2-7:Sheets而不是Worksheets 除了工作表之外,工作簿集合里还包括图表。为了在工作簿中添加一个新的图表工作表,使用Add方法: Charts.Add 要统计图表工作表的数目,使用: ?Charts.Count 在 Excel 97 之前的版本中,工作簿集合里包括两种其它的表:DialogSheets(Excel5.0 对话框) 和 Modules(Excel4.0宏表)。Dialogs已经被更友好的用户窗体(UserForms)所取代了。从Excel 97开始,在VBE窗 口中创建对话框和模块。 2.7.13 处理窗口 当处理几个Excel工作簿并且需要比较或合并数据,或者想要查看同一个工作表里的不同部分 时,很可能要用到Excel窗口菜单里的可用选项:新建窗口和重排窗口。 我们来看看如何使用VBA来安排屏幕。 在VBA中表达的操作 在立即窗口中输入的代码 在新窗口里显示活动工作簿 ActiveWorkbook.NewWindow 在屏幕上显示所有打开的工作簿 Windows.Arrange 第 2 章 1BVBA 初步 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 69 激活第二个窗口 Windows(2).Activate 获得当前窗口的名称 ?ActiveWindow.Caption 将当前窗口的标题改为“My Window” ActiveWindow.Caption = "My Window" 在屏幕上显示窗口时,可以决定如何排列它们。Arrange方法由许多参数,可以控制窗口在屏幕 中位置的参数称为ArrangeStyle。如果忽略参数ArrangeStyle,Excel将平铺所有窗口。 常数 值 描述 xlArrangeStyleTiled 1 平铺窗口(默认) xlArrangeStyleCascade 7 层叠窗口 xlArrangeStyleHorizontal 2 水平并排窗口 xlArrangeStyleVertical 3 垂直并排窗口 除了使用常数名称外,也可以使用上面列出的值。 要将所有窗口层叠起来,编写写下面的指令: Windows.Arrange ArrangeStyle:=xlArrangeStyleCascade 或者更简单: Windows.Arrange ArrangeStyle:=7 2.7.14 管理Excel应用程序 在本章的开始部分,您学习了对象组织在一个称作对象模型的专门结构中。在应用程序的对象 模型最上面就是应用程序自身。通过控制Application对象,可以进行很多操作,例如将屏幕显示效 果保存为当日最后显示的效果,或者退出该应用程序。正如您所知道的,Excel可以使用“文件”菜 单里的“保存工作区”命令来保存屏幕设置。在VBA里可以很容易地完成保存工作区的工作: Application.SaveWorkspace "Project" 上面的指令将屏幕设置保存在名叫“Project”的工作区里,下次需要处理相同的文件和窗口排 列时,只要打开“Project”文件,Excel就会打开正确的文件并恢复您要的屏幕。 在VBA中表达的操作 在立即窗口中输入的代码 获取当前应用程序名称 ?Application.Name 将Excel应用程序标题改为“My Application” Application.Caption = "My Application" 将 Excel 应用程序标题恢复为“Microsoft Excel” Application.Caption = "Microsoft Excel" 获取正在使用的操作系统 ?Application.OperatingSystem 获取注册该应用程序人名或公司名 ?Application.OrganizationName 获取Excel.exe所在的文件夹名 ?Application.Path 退出Excel Application.Quit 本章小结和下一章内容简介 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 70 共 612 页 在本章中,您学习了很多基础的VBA术语和可以使程序编写和调试更容易的内置工具。现在, 您应该比较好的了解了绝大多数Excel对象是如何组织和控制的。我努力使描述尽量少,并且注重于 教您如何使用新的语言技巧立即控制Excel而不必先编写过程。正因为如此,我集中讲解了立即窗口。 VBA过程通常包含多于一行的代码,事实上,它们可能变得很复杂。在开始创建完整的VBA过程之 前,您仍然需要学习一些事情。例如,如何保存Excel返回的信息以便于稍后在过程中可以使用?在 立即窗口里输入指令的时候,您学习了如何问Excel一些重要的信息。您得到了类似“当前工作簿里 有多少工作表?”或“单元格A4的内容是什么?”等问题的答案。Excel不会在乎您的问题有多么烦, 只要您输入符合严格的VBA语法规则的问题,Excel就会给出答案。当开始编写您自己的过程时,您 需要知道如何保存Excel的答案。在下章里,您将学习如何通过使用变量保存这种以后需要用到的信 息,您也将会探索关于数据类型和常数的知识。 第3章 理解变量、数据类型和常量 和现实生活中一样,编程时有些事情必须马上做,而其它的 事情可以稍后进行。当推迟一项任务时,您可以先将它记住或者 在纸上列出“要做事情的”清单…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 72 共 612 页 和现实生活中一样,编程时有些事情必须马上做,而其它的事情可以稍后进行。当推迟一项任 务时,您可以先将它记住或者在纸上列出“要做事情的”清单。在清单上零零散散的事情经常按照 它们的类型或者重要性来分类。当将任务交给别人或者最终要开始做时,您需要将该任务从清单里 划掉。本章介绍VBA过程如何记住在稍后语句或计算中要用到的一些重要信息,您将学习过程如何 保留往变量输入“要做的事情”、如何声明变量、以及它们如何与数据类型和常量相联系。 3.1 保存VBA语句的结果 在第二章中,当在立即窗口中操作时,您试过返回一些信息的VBA指令。例如,当输 入?Cells.Count时,可以获得工作表里有16,777,216个单元格。然而,当在立即窗口之外编写VBA 过程时,不能使用问号。在过程中忽略问号而输入Cells.Count,VBA不会突然停下来告诉这个指令 的结果。如果想要知道某个指令执行后的结果,就必须告诉VBA记住它。在编程中,VBA指令返回 的结果可以赋值给变量。 3.2 变量是什么 变量是用来引用某项数据的简单的名称。每次想要记住某个VBA指令的结果时,考虑用一个名 称来代表它。例如,如果必须用数字16,777,216来记住工作表中的单元格总数,可以使用一个名称, 例如AllCells、NumOfCells、TotalCells,等等。 变量名称里可以包含字母、数字和一些标点符号,但不能使用下面这些符号: , # $ % & @ ! 变量的名称中不能以数字开始,也不可以含有空格。如果想在变量名称里包含多于一个词语, 可以使用下划线。虽然变量名称最多可以包含254个字符,但是最好使用短且简单的变量名称。如果 在VBA过程中需要多次引用某个变量的话,使用短名称将会节省输入时间。VBA不管在变量名称里 使用大写字母还是小写字母,然而,大多数编程者使用小写字母,并且当变量名称由一个或多个词 语组成时,使用标题字母,也就是说将每个词语的首字母大写,例如NumOfCells、First_Name。 技巧3-1:保留字不能用作变量名称 除了VBA使用的保留字之外,可以使用想用的任何标签作为变量名称。在VBA中有某种特定意义的VBA语 句和其它某些词语不能用作变量名称。例如,如果使用诸如Name、Len、Empty、Local、Currency或者Exit等 词语作为变量名,将会产生错误。 技巧3-2:富有意义的变量名称 命名变量可以帮助记住它们作用的名称。有些程序员使用前缀来识别变量类型。一个以前缀“str”开头的 变量名称(例如strName)在过程代码中很快就可以知道它代表文本字符串。 3.3 数据类型 创建VBA过程时,在脑海里必然有其目的,想要处理数据。因为过程要处理不同类型的信息, 所以应该理解VBA如何存储数据。“数据类型”决定在计算机内存中如何存储数据,例如,数据可以第 3 章 2B 理解变量、数据类型和常量 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 73 储存为数字、文本、日期、对象,等等。如果忘了告诉VBA数据类型,VBA将分配为“Variant”数 据类型。“Variant”类型有能力解决数据本身的操作类型并且使用该类型。表3-1里列出了VBA的数 据类型。除了内置的数据类型之外,还可以定义自己的数据类型。(您将在第八章里看到用户自定义 数据类型的例子)因为不同的数据类型占据计算机内存的空间是不一样的,一些类型比另外一些占用 空间更多,因此,为了节省内存并确保过程运行更快,应该选择占用字节最少并且又能够处理过程 中必须操作的数据的数据类型。 表3-1:VBA数据类型 数据类型(名称) 大小(字节) 描述 Boolean 2 逻辑值True或False Byte 1 0到255的整数 Integer 2 –32,768到32,767的整数 Long 4 –2,147,483,648到2,147,483,647的整数 Single 4 单精度浮点数值 负数:–3.402823E38到–1.401298E–45 正数:1.401298E–45到3.402823E38 Double 8 双精度浮点数值 负数:–1.79769313486231E308 到 –4.94065645841247E–324 正数:4.94065645841247E–324 到 1.79769313486231E308 Currency 8 (放大的)整数(译者注:整数除以10000得到的数值,参见VBA 帮助)使用在定点计算中: –922,337,203,685,477.5808到922,337,203,685,477.5807 Decimal 14 +/–79,228,162,514,264,337,593,543,950,335不带小数点; +/–7.9228162514264337593543950335小数点后有28位 数字; 最小的非0数字是 +/–0.0000000000000000000000000001 Date 8 从100年1月1日到9999年12月31日的日期 String(变长字符串) 10字节+字 符串长度 变长字符串最多可包含大约 20 亿( 2^31)个字符。 String(定长字符串) 字符串长度 定长字符串最多可包含大约65,400 个字符。 Object 4 对象变量用来引用Excel中的任何对象 Variant(带数字) 16 最大达到Double类型的任何数值 Variant(带字母) 22字节+字 符串长度 和变长字符串的范围一样 用户自定义类型 (使用Type) 成员所需的 数值 每个成员的范围和它的数据类型的范围一致 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 74 共 612 页 3.4 如何创建变量 可以通过一个专门的命令来声明变量或者可以直接在语句里使用变量,从而创建变量。声明变 量可以让VBA知道该变量的名称和数据类型,这称为“强制显式声明变量”。 如果在使用变量前不告诉VBA关于该变量的任何信息,那么此时隐式地告诉VBA想要创建这个变量。没有 明确声明的变量会自动地分配为Variant数据类型(参见表3-1)。虽然不明确声明变量很方便(可以随意创建变 量,并且不用事先知道被赋值的数值的数据类型就可以赋值给该变量),但是,这样会导致很多问题,参见技巧 3-4。 技巧3-3:强制显式声明变量的优点 „ 强制显式声明变量加速过程的执行。因为VBA已知道数据类型,它只会占用实际储存数据需要的内存。 „ 强制显式声明变量使代码更容易阅读和理解,因为所有的变量都已列在过程的最前面。 „ 强制显式声明变量帮助预防由于变量名称拼写错误而导致的错误。VBA会根据变量声明里的拼写自动更正 变量名称。 技巧3-4:隐式声明变量的缺点 „ 如果在过程中错误拼写了一个变量名称,VBA会显示运行时错误,或者创建一个新的变量。这时肯定需要 浪费很多时间来排除问题,然而,如果在过程前声明了变量,这些错误很容易避免。 „ 因为VBA不知道要存储的变量的数据类型,它将分配该变量为Variant数据类型。这样,VBA每次在处理这 个变量时都需要检查其数据类型,会导致过程运行更慢。由于Variant可以存储任何一种数据类型,VBA不 得不占用更多的内存来储存数据。 3.5 如何声明变量 可以使用关键字Dim来声明变量,Dim代表“Dimension”。关键字Dim后面紧跟变量名称,接着 是数据类型。 假设想让某个过程显示员工的年龄,在计算年龄之前,必须给过程提供员工的生日。可以这样 做,声明一个叫DateOfBirth的变量: Dim DateOfBirth As Date 注意,关键字Dim之后是变量名称(DateOfBirth)。如果不喜欢使用这个名称,可以自由地将它改 为其它名称,只要想用的名称不是VBA关键字就行。关键字As后面指定表3-1中的一个数据类型作 为该变量的数据类型。Date数据类型告诉VBA,变量DateOfBirth将用来存储日期。 要存储员工的年龄,按下面的方式声明变量Age: Dim Age As Integer 变量Age将会存储今天和该员工生日之间年数的数字。因为年龄显示为整年,所以变量Age就被 分配为Integer数据类型。 可能还想要该程序记录员工的姓名,因此需要声明另一个变量来保存员工的名和姓: Dim FullName As String 因为词语“Name”为VBA保留字,所以VBA程序中使用该词语会出错。将变量命名为FullName第 3 章 2B 理解变量、数据类型和常量 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 75 并且将它声明为String类型(因为该变量包含的数据是文本),来保存员工姓名。 技巧3-5:隐式声明变量 没有用Dim语句来显式声明变量叫做隐式声明。这些变量会被自动分配一个名为Variant的数据类型,它们 可以保存数字、字符串和其它信息类型。可以在VBA过程的任何地方,简单地赋值给一个变量名称来创建一个 变量。例如,可以按下述方式来隐式声明变量:DaysLeft = 100。 声明变量被认为是好的编程习惯,因为它使程序可读性增强并且帮助避免某些类型的错误。既 然您已经知道了如何声明变量,那让我们来看一下使用这些变量的一个过程: Sub AgeCalc( ) ‘声明变量 Dim FullName As String Dim DateOfBirth As Date Dim Age As Integer '给变量赋值 FullName = "John Smith" DateOfBirth = #01/03/1967# '计算年龄 Age = Year(Now())-Year(DateOfBirth) '在立即窗口里打印结果 Debug.Print FullName & " is " & Age & " years old." End Sub 在程序的开始部分就声明将要使用的变量。在上面的过程里,每个变量声明在单独的行中。也 可以同时在一行里声明几个变量,用逗号分隔开每个变量名,例如: Dim FullName As String, DateOfBirth As Date, Age As Integer 注意,关键字Dim只在变量声明行的开头出现了一次。 当VBA执行变量声明语句时,创建具有指定名称的变量并且占用内存空间来存储它们的值,然 后,特定的值被赋给这些变量。通过变量名称之后加一个等号,然后在等号的右边输入希望存储在 该变量中的数据,从而给变量赋值。这里输入的数据必须是该变量声明的数据类型。文本数据应该 加上一对引号,日期需要在两个#号之间。 VBA使用变量DateOfBirth提供的数据来计算员工的年龄,并且将计算结果存储到变量Age中。 然后,使用指令Debug.Print在立即窗口中打印员工的姓名和年龄。当VBA程序运行结束后,必须打 开立即窗口来查看结果。 我们来看看当声明了错误数据类型的变量时会发生什么情况。下面的过程是计算一个工作表里 单元格的总数,接着使用一个对话框将结果显示给用户。 Sub HowManyCells( ) Dim NumOfCells As Integer NumOfCells = Cells.Count MsgBox "The worksheet has " & NumOfCells & " cells." End Sub 错误的数据类型会导致错误。在上面的过程里,当VBA尝试将Cells.Count语句的结果赋给变量 NumOfCells时,程序运行失败,Excel显示信息“运行时错误’6’——溢出”。这个错误产生的原因是 为变量选择了无效的数据类型,工作表里单元格总数量不在Integer数据范围之内。要更正这个问题,学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 76 共 612 页 应该选择一个可以包含更大数据的数据类型。然而,要快速地解决上面程序遇到的问题,只要删除 变量类型As Integer就行了。重新运行这个过程时,VBA将给变量分配为Variant类型,尽管Variant 类型比其它变量类型使用更多的内存,也降低了程序的运行速度(因为VBA不得不另外检查变量的内 容),但在简短的程序里,使用Variant类型的差异难以察觉。 技巧3-6:什么是变量类型? 通过下述方法,可以快速地查明程序里使用的变量的类型:在变量名称上单击右键,并且从快捷菜单上选 择“快速信息”。 技巧3-7:串联 可以将两个或多个字符串结合成为一个新的字符串,这个操作称为串联。您已经在AgeCalc过程和 HowManyCellss过程里看到了串联的例子。串联用&符号表示。例如,“His name is ” & FirstName将会得到下 面的字符串:His name is John或者His name is Michael,人名取决于变量FirstName的内容。注意,在is和结 束引号之间有一个空格:“His name is ”。字符串的串联也可以使用加号(+)来代表,然而,许多程序员为了消 除混淆,宁愿将加号仅限制于数字的运算。 3.6 指定变量的数据类型 如果在Dim语句里没有指定变量的数据类型,最终将得到没有明确类型的变量,在VBA中没有明 确类型变量总是Variant数据类型。强烈建议创建明确类型的变量。当声明某种数据类型的变量后, VBA过程会运行得更快,因为VBA不需要停下来分析Variant变量到底是什么类型。 VBA可以使用很多类型的数字变量。Integer变量只能保存从–32,768到32,767之间的所有整数, 其它类型的数字变量有Long型、Single型、Double型和Currency型。Long变量可以保存从 –2,147,483,648到2,147,483,647范围的所有整数。与Integer型和Long型变量相反,Single型和 Double型变量可以保存小数。String变量用来引用文本。当声明了一个String数据类型的变量时,最 好告诉VBA这个字符串有多长,例如: Dim extension As String * 3 声明定长字符串变量extension的长度为3个字符。 如果没有分配一个指定的长度,字符串变量将是动态的,这意味着VBA将会占用足够的计算机 内存来处理任意数量的文本。 声明了变量后,只能保存声明语句里指定的信息类型。给数字类型的变量赋文本值,或给文本 类型变量赋数字值,都会导致“类型不匹配”的错误信息,或者导致VBA修正该值。例如,如果声 明变量来保存整数而使用小数数据, 那么VBA会忽略小数部分而只使用数据的整数部分。试验下面 的MyNumber过程,看看VBA是如何修正数据以匹配变量的数据类型: Sub MyNumber() Dim myNum As Integer myNum = 23.11 MsgBox myNum End Sub 如果不使用Dim语句声明变量,仍然能够通过在变量名称后面加上一个特殊字符来指明该变量的 数据类型。例如,可以在变量名称后面加上美元($)符号来声明变量FirstName为String类型,如下所第 3 章 2B 理解变量、数据类型和常量 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 77 示: Dim FirstName$ 上面的声明和Dim FirstName As String相同。其它类型的声明字符如表3-2所示。 表3-2:类型声明字符 数据类型 字符 Integer % Long & Single ! Double # Currency @ String $ 注意,类型声明字符只能用于六种数据类型,将这些字符附在变量名称后面来使用这些类型声 明字符。 过程AgeCalc2演示了表3-2中类型声明字符的使用: Sub AgeCalc2() '声明变量 Dim FullName$ DateOfBirth As Date Dim Age% '给变量赋值 FullName$ = "John Smith" DateOfBirth = #01/03/1967# '计算年龄 Age% = Year(Now())-Year(DateOfBirth) '在立即窗口里输出结果 Debug.Print FullName$ & " is " & Age% & " years old." End Sub 技巧3-8:声明变量类型 可以使用As关键字或者附加类型符号来指定变量类型。如果既不加类型符号也不使用As命令,那么这个变 量将为默认的数据类型,即VBA中的Variant类型。 3.7 变量赋值 知道如何命名和声明变量后,就开始使用它们吧。我们以学习如何创建变量开始,在VBA中可 以在程序的任何地方通过赋一个指定的值来创建变量。 1. 打开一个新工作簿并且保存为Chap03.xls。 2. 激活VBE窗口。 3. 在工程窗口中,选择这个新的工程并在属性窗口里将它的名称改为Chap03。 4. 选择菜单“插入”—“模块”命令在Chap03工程中添加一个新模块。 5. 在属性窗口将该模块名Module1改为Variables。 6. 在代码窗口,输入下面所示的CalcCost过程。这个过程基于下述假设来计算购买一个计算器的 价钱:计算器的价格为35美元,销售税为8.5%。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 78 共 612 页 Sub CalcCost() slsPrice = 35 slsTax = 0.085 Range("A1").Formula = "The cost of calculator" Range("A4").Formula = "Price" Range("B4").Formula = slsPrice Range("A5").Formula = "Sales Tax" Range("A6").Formula = "Cost" Range("B5").Formula = slsPrice * slsTax Cost = slsPrice + (slsPrice * slsTax) With Range("B6") .Formula = Cost .NumberFormat = "0.00" End With strMsg = "The calculator total is " & "$" & Cost & "." Range("A8").Formula = strMsg End Sub CalcCost过程使用了四个变量:slsPrice、slsTax、Cost和strMsg。因为没有显式声明这些变量, 所以它们的数据类型都是Variant。变量slsPrice和slsTax是在过程的开始时通过给它们赋值而产生 的,变量Cost分配的值是下面计算的结果:slsPrice + (slsPrice * slsTax)。价格的计算是使用变量 slsPrice和slsTax提供的值来进行的。变量strMsg将文本信息放在一起给用户,然后这个信息作为一 个完整的句子输入到工作表单元格中。 当给变量赋值时,需要在变量名称后面输入一个等号,等号之后是要输入的值。它可以是数字、 公式或者在引号中的文本。赋给变量slsPrice、slsTax和Cost的值比较容易理解,然而保存在变量 strMsg的值则复杂一些。下面解释变量strMsg的内容。 strMsg = "The calculator total is " & "$" & Cost & "." „ 字符串“The calculator total is ”在引号中,注意,后面的引号前有个空格。 „ 字符&可以将一个字符串附加在另一个字符串或者变量的内容后面 „ 在引号里面的美元符号(“$”)用来表明货币类型。因为美元符号是字符,需要将它置于引号中。 „ 字符&必须用于每次要在前面的字符串后添加新信息的时候。 „ 变量Cost是一个占位符,当过程运行时,计算器的实际价格将显示在这里。 „ 字符&仍然是连接另一字符串。 „ 句号在一对引号中。当需要在句子后面加上句号时,必须单独将它附加在变量名称的后面。 现在来运行这个程序。将光标放在过程CalcCost的任何地方,并选择菜单“运行”—“运行子 过程/用户窗体”。 技巧3-9:变量初始化 VBA创建变量时,就初始化该变量,变量采用它们的默认值,数字型变量设置为0,布尔型变量初始化为 False,字符串变量设置为空字符(””),日期型变量则设置为1899年12月30日。 注意:在运行这个过程时,VBA可能会弹出下面的信息:“编译错误:变量未定义”。如果这个 情况发生,点击“确定”按钮关闭这个信息框。VBA将会选中变量slsPrice并且加亮显示过程名称Sub CalcCost,标题栏则显示“Microsoft Visual Basic-Chap03.xls [中断]”。VBA中断模式允许在继续 之前更正错误。在本书的后面,您将学习如何在中断模式下解决问题。就现在而言,如果遇到上面 提及的错误时,通过选择菜单“运行”—“重新设置”来退出中断模式。接下来,在代码窗口的上第 3 章 2B 理解变量、数据类型和常量 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 79 面删除显示在第一行的语句Option Explicit。Option Explicit语句意味着在本模块里使用的所有变量都 必须经过正式声明。您将在下一节里学习这个语句。删除Option Explicit语句后,重新运行该过程。 运行后,切换到Excel界面,结果应该与图3-1所示一致。 图3-1:VBA过程可以在工作表里输入数据并计算结果 单元格A8显示变量strMsg的内容。注意,在单元格B6里面输入的价格有两位小数,而strMsg表 示的价格却显示三位小数。要在单元格A8里显示带两位小数的计算器价格,必须给变量Cost设置所 需的格式而不是给该单元格设置格式。 VBA有专门的函数来改变数据格式,现在使用Format函数来改变变量Cost的格式。该函数的语 法是: Format(expression, format) Expression表达式是需要设置的格式的值或者变量;format则是想要使用的格式类型。 1. 在CalcCost过程里改变变量Cost的计算: Cost = Format(slsPrice + (slsPrice * slsTax), "0.00") 2. 将With…End With代码块改为下面的指令: Range("B6").Formula = Cost 3. 将语句Range("B5").Formula = slsPrice * slsTax改为下面的指令: Range("B5").Formula = Format((slsPrice * slsTax), "0.00") 4. 重新运行修改后的程序。 试验过程CalcCost之后,您可能会感到如果VBA可以如此好的处理未声明的变量,为什么还要 麻烦再声明变量呢。过程CalcCost非常简短,因此不必担心VBA每次使用这些Variant变量时会占用 多少内存。然而,在简短的过程中,内存问题不重要,但是当输入变量名称时,很可能会出现输入 错误。当第二次使用Cost变量时,忽略了“o”而写成“Cst”,后果会如何呢? Range("B6").Formula = Cst 如果在下面的公式中使用了Tax而没有用slsTax,结果将得到什么呢? Cost = Format(slsPrice + (slsPrice * Tax), "0.00") 采用了上面提及的错误后,过程CalcCost的结果显示在图3-2中。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 80 共 612 页 图3-2:变量名称错误导致错误的结果 注意,在图3-2里单元格B6没有数值,因为VBA没有找到变量Cst的赋值语句。由于VBA不知道 销售税,显示的计算器价格为总价(而没有加税金,见单元格A8。VBA不会猜测,只是简单地做告诉 它的事情。让我们在一个部分,解释如何避免发生这类错误。在继续之前,确保用Cost和slsTax代替 变量Cst和Tax。 3.8 强制声明变量 VBA使用Option Explicit语句自动提醒您正式声明所有的变量,该语句必须放在每个模块的顶 部。如果试图运行一个含有未声明变量的过程时,Option Explicit语句将导致VBA产生一个错误信息。 1. 返回代码窗口输入CalcCost过程的地方。 2. 在模块窗口顶部(第一行)输入Option Explicit并按回车键,Excel将该语句显示为蓝色。 3. 运行过程CalcCost,VBA显示错误信息“编译错误:变量未定义”。 4. 单击“确定”按钮关闭该信息框。 VBA加亮显示变量名称slsPrice,现在需要正式声明这个变量。当声明了变量slsPrice并再 次运行该过程时,VBA一旦遇到另外一个未声明的变量时,将再次产生同样的错误。 5. 在CalcCost过程的开始部分输入下面的声明: '声明变量 Dim slsPrice as Currency Dim slsTax as Single Dim Cost as Currency Dim strMsg as String 6. 按F5键运行该过程。修改后的CalcCost过程代码如下所示: Option Explicit Sub CalcCost() '声明变量 Dim slsPrice As Currency Dim slsTax As Single Dim Cost As Currency Dim strMsg As String slsPrice = 35 slsTax = 0.085 第 3 章 2B 理解变量、数据类型和常量 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 81 Range("A1").Formula = "The cost of calculator" Range("A4").Formula = "Price" Range("B4").Formula = slsPrice Range("A5").Formula = "Sales Tax" Range("A6").Formula = "Cost" Range("B5").Formula = Format((slsPrice * slsTax), "0.00") Cost = Format(slsPrice + (slsPrice * slsTax), "0.00") With Range("B6").Formula = Cost strMsg = "The calculator total is " & "$" & Cost & "." Range("A8").Formula = strMsg End Sub 在模块顶部输入的Option Explicit语句将强制要求声明变量。每个需要声明变量的模块中,必须 包括Option Explicit语句,可以让VBA在每次插入一个新模块时自动输入该语句。 按照下面的步骤在创建的每个新模块中自动添加Option Explicit语句: 1. 选择菜单“工具”—“选项”命令。 2. 在“选项”对话框(“编辑器”选项卡)中,确保选中“要求变量声明”复选框。 3. 单击“确定”按钮关闭“选项”对话框。 从现在开始,每个新模块都会在第一行添加Option Explicit语句。如果需要在以前创建的模块里 强制声明变量,必须在该模块顶部手动输入Option Explicit语句。 技巧3-10:更多Option Explicit的信息 Option Explicit强制正式(显式)声明指定模块里的所有变量。使用该语句的一个很好的优点是,变量名输入 错误会在编译时(VBA试图将源代码转换为可执行代码)被检测到。Option Explicit语句必须放在模块里的任何过 程之前。 3.9 理解变量的作用域 不同的变量在VBA过程里有不同的影响范围。作用域(Scope)指某个特定的变量在同一个过程、 其它过程、或者其它VBA工程里的可用性。在VBA中,变量有下面三种级别的作用域: „ 过程级作用域 „ 模块级作用域 „ 工程级作用域 3.9.1 过程级(本地)变量 从本章起,您已经知道如何通过关键字Dim来声明变量,关键字Dim在模块中的位置决定了该变 量的作用范围。在VBA过程中,用Dim关键字声明的变量拥有过程级作用域。 过程级变量经常被称作本地变量,本地变量只能在声明它的过程中使用。未声明的变量总是过 程级的变量。 变量的名称在它的作用域内必须是唯一的,这意味着不可以在同一个过程里使用相同的名称来 声明两个变量。然而,可以在不同的过程里面使用相同的变量名称。换句话说,CalcCost过程里可学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 82 共 612 页 以有slsTax变量,同一个模块里的ExpenseRep过程也可以有它自己的名为slsTax的变量,两个变量 相互独立。 3.9.2 模块级变量 本地变量有助于节省计算机内存,一旦该过程结束,该变量便立即消失,VBA释放该变量占用 的内存空间。然而,在编程时,经常需要变量在本过程结束后仍然可用于其它过程中,这时就需要 改变变量的作用域,可能需要定义一个模块级的变量,而不是过程级的变量。要定义模块级的变量, 必须将关键字Dim放在模块里任何过程的前面(就是在关键字Option Explicit的下面)。 例如,为了使slsTax变量在Variables模块里的任何过程中都可以使用,按照下面的方法声明 slsTax变量: Option Explicit Dim slsTax As Single Sub CalcCost( ) <在这里放置过程指令> End Sub 在上面的例子里,关键字Dim放置在模块的顶部,紧接着Option Explicit语句的下面。 需要使用变量slsTax的另一个过程,才能查看这是如何工作的。 1. 在代码窗口里的Variables模块中,从CalcCost过程里剪切变量声明行Dim slsTax As Single,并 粘贴到该模块顶部Option Explicit语句的下面。 2. 在CalcCost过程所在的同一个模块里输入ExpenseRep过程代码: Sub ExpenseRep() Dim slsPrice As Currency Dim Cost As Currency slsPrice = 55.99 Cost = slsPrice + (slsPrice * slsTax) MsgBox slsTax MsgBox Cost End Sub ExpenseRep过程里声明了两个货币类型的变量:slsPrice和Cost。随后,slsPrice变量被赋值为 55.99,slsPrice和CalcCost过程里声明的变量slsPrice是相互独立的。ExpenseRep过程计算采购的 费用,该费用包括销售税,因为销售税和CalcCost过程里使用的是一样的,所以将slsTax变量声明为 模块级变量。VBA执行CalcCost过程后,变量slsTax的值等于0.085。如果slsTax是本地变量,在 CalcCost过程中止后,变量slsTax的内容将被清空。过程ExpenseRep结束时显示两个信息框来输出 变量slsTax和Cost的值。 运行CalcCost过程后,VBA清空除了模块级变量slsTax之外的所有变量的内容。一旦试图运行 ExpenseRep过程来计算价格,VBA就会获取slsTax的值,并且将它用到计算中。 技巧3-11:私有变量 当声明模块级变量时,除了关键字Dim之外,还可以使用关键字Private。例如: Private slsTax As Single 私有变量仅仅可用在声明该变量的模块里的过程中。私有变量总是在模块顶部Option Explicit语句之后声第 3 章 2B 理解变量、数据类型和常量 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 83 明。 技巧3-12:保持工程级别的变量为私有变量 为了避免工程级变量的内容被该工程之外使用,可以在Option Explicit语句下面,模块顶部声明语句行的前 面输入Option Private Module语句,例如: Option Explicit Option Private Module Public slsTax As Single Sub CalcCost( ) <这里放置过程指令代码> End Sub 3.9.3 工程级变量 模块级的变量用关键字Public(而不是Dim)声明时,拥有工程级的作用范围,这意味着它们可以 在VBA中的任何模块里使用。当想要在所有打开的VBA工程的所有过程里使用某个变量时,必须用 Public关键字来声明该变量,例如: Option Explicit Public slsTax As Single Sub CalcCost( ) <这里放置过程指令代码> End Sub 注意,变量slsTax在模块顶部以Public关键字声明。现在,它能在任何其它过程或VBA工程中使 用。 3.10 变量的存活期 除了作用域外,变量还有存活期,变量的存活期决定了该变量能保存它的值有多久。一旦该工 程打开,模块级和工程级的变量就会保留它们的值。然而,如果程序的逻辑需要,VBA能够重新初 始化这些变量。使用Dim语句声明的本地变量在过程结束时就会丢失它们的值,本地变量的存活期是 随着过程的运行的,并且它们在程序每次运行时重新初始化。VBA允许通过改变声明的方式延长本 地变量的存活期。 3.11 理解和使用静态变量 使用Static关键字声明的变量是一种特殊类型的本地变量,在过程级声明静态变量。和那些用关 键字Dim声明的本地变量相反,静态变量在程序已经不在它们的过程里时仍然不会丢失它们的内容。 例如,当一个带有静态变量的VBA过程调用另外一个过程时,在VBA执行完被调用的过程语句后, 返回主调过程时,静态变量仍然保留它原来的值。 CostOfPurchase过程演示了名为allPurchase的静态变量的使用: Sub CostOfPurchase() 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 84 共 612 页 '声明变量 Static allPurchase Dim newPurchase As String Dim purchCost As Single newPurchase = InputBox("Enter the cost of a purchase:") purchCost = CSng(newPurchase) allPurchase = allPurchase + purchCost '显示结果 MsgBox "The cost of a new purchase is: " & newPurchase MsgBox "The running cost is: " & allPurchase End Sub 上面的过程在开始声明一个名为allPurchase的静态变量和两个本地变量:newPurchase和 purchCost。该过程中使用的InputBox函数显示一个对话框并且等着用户输入数值,一旦用户输入数 值并单击“确定”按钮后,VBA就会将该数值赋给变量newPurchase。在第四章中介绍InputBox函 数。因为InputBox函数返回的结果总是字符串,所以声明变量newOurchase为字符串数据类型。然 而,不能在数学计算中使用字符串,这就是为什么需要在下一指令中使用一个类型转换函数(CSng) 将字符串值转换为单精度浮点类型的数字。函数CSng只需要一个参数——需要转换的数值。接着, 函数CSng转换的数字结果保存在变量purchCost中。 技巧3-13:类型转换函数 在CSng中的任意地方按下F1键,可以找到更多关于函数CSng(和其它类型转换函数)的信息。 下一行指令:allPurchase = allPurchase + purchCost,将InputBox函数提供的新数值加到目前 的采购数值中。当第一次运行这个过程的时候,变量allPurchase和变量purchCost的内容相同。当第 二次运行该过程时,这个静态变量的值通过对话框提供的值增加。可以随意多次运行过程 CostOfPurchase,只要该工程是打开的,变量allPurchase就会不断的变化。 依照下面的步骤来试验该过程: 1. 将光标放在过程CostOfPurchase里的任意地方并且按下F5键。 2. 当对话框出现时,输入一个数字,例如,输入100然后按回车键。VBA显示信息““The cost of a new purchase is: 100.” 3. 单击“确定”按钮,VBA显示第二个信息“The running cost is: 100.” 4. 重新运行该过程,当对话框出现时,输入另外一个数字,例如输入50后按回车键。VBA显示信 息“The cost of a new purchase is 50.” 5. 单击“确定”按钮,VBA显示第二个信息“The running cost is: 150.” 6. 多次运行该程序,看看VBA是如何记录运行总量的。 3.12 声明和使用对象变量 您已经学习的变量是用于存储数据的,存储数据是在过程中使用“普通的”变量的主要原因。 除了存储数据的普通变量外,还有引用VBA对象的特殊变量,这些变量称为对象变量。在第二章中 已经学习了多种对象,现在,学习如何用对象变量来代表对象。 对象变量不存储数据,它们告诉数据在哪儿。例如,可以用对象变量告诉VBA数据在当前工作 表的单元格E10中。对象变量使得定位数据更容易。编写VBA程序时,经常需要写一些很长的指令,第 3 章 2B 理解变量、数据类型和常量 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 85 例如: Worksheets("Sheet1").Range(Cells(1,1), Cells(10, 5).Select 可以声明一个对象变量来告诉VBA数据在哪儿,而不必使用很长的指令来指向该对象。声明对 象变量和已经学习的变量声明类似,唯一的不同是在关键字As后面,输入词语Object作为数据类型, 例如: Dim myRange As Object 上面的语句声明了一个名为myRange的对象变量。 然而,实际上只声明对象变量是不够的,在过程中使用这个变量前,还必须给这个对象变量赋 上确定的值。使用关键字Set来给对象变量赋值,关键字Set后面是等号和该变量指向的值,例如: Set myRange = Worksheets("Sheet1").Range(Cells(1,1), Cells(10, 5)) 上面的语句给对象变量myRange赋值,这个值指向工作表Sheet1的单元格区域A1:E10。如 果 忽 略了关键字Set,VBA将会显示一个错误信息——“运行时错误91:对象变量或With块变量未设置”。 现在,我们来看一个实例。下面的UseObjVariable过程演示了名为myRange的对象变量的使用: Sub UseObjVariable() Dim myRange As Object Set myRange = Worksheets("Sheet1"). _ Range(Cells(1, 1), Cells(10, 5)) myRange.BorderAround Weight:=xlMedium With myRange.Interior .ColorIndex = 6 .Pattern = xlSolid End With Set myRange = Worksheets("Sheet1"). _ Range(Cells(12, 5), Cells(12, 10)) myRange.Value = 54 Debug.Print IsObject(myRange) End Sub 我们来逐行分析一下过程UseObjVariable里的代码。过程开始时声明对象变量myRange,下一 语句将对象变量设置为Sheet1的区域A1:E10。从现在开始,每次要引用这个区域时,不需要写下对 象的整个地址,而只要使用快捷方式——该对象变量名称就可以了。这个过程的目的是在区域 A1:E10外围设置边框,不必使用下面这样冗长的指令: Worksheets("Sheet1").Range(Cells(1, 1), _ Cells(10, 5)).BorderAround Weight:=xlMedium 而可以通过使用对象变量名称简化为: myRange.BorderAround Weight:=xlMedium 接下来的一系列语句是改变区域A1:E10的底色。同样,不需要编写长的指令相用所要操作的对 象的地址,可以使用对象变量名称myRange。下一句代码是给对象变量myRange分配一个新的引用 区域,VBA将放弃旧的引用,在下次使用myRange时,它会引用另一个区域E12:J12。在新区域 (E12:J12)中输入了54后,过程会显示如何确定某个变量的对象类型。如果myRange是对象变量,指 令Debug.Print IsObject(myRange)将在立即窗口里面输入True。IsObject是VBA中指明某变量是否 是对象变量的函数。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 86 共 612 页 技巧3-14:使用对象变量的好处 使用对象变量的好处有: „ 它们可以代替真实对象使用。 „ 它们比指向的真实对象更短且更容易记住。 „ 当过程运行时,可以改变它们的意思。 3.12.1 使用明确的对象变量 对象变量可以引用任意一种对象,因为VBA有很多种对象,所以要让程序可读性更强、运行更 快,最好创建引用具体对象类型的对象变量。例如,在过程UseObjVariable中,可以将myRange对 象变量声明为Range对象,而不是通常的对象变量(Object): Dim myRange As Range 如果要引用一个具体的工作表,可以声明Worksheet对象: Dim mySheet As Worksheet Set mySheet = Worksheets("Marketing") 当对象变量不再需要时,可以给它赋值Nothing,这将释放内存和系统资源: Set mySheet = Nothing 您将在第九章里看到更多的使用对象变量的例子。 3.13 查找变量定义 当在VBA过程里找到一行给变量赋值的指令时,可以通过选择该变量名称并且按下Shift+F2组合 键快速地定位到该变量的定义(声明)语句中。或者,也可以选择菜单“视图”—“定义”命令,VBA 将跳到变量的声明行。要回到刚才的位置,只要按下Ctrl+Shift+F2组合键或选择“视图”—“最后 位置”。我们来试试: 1. 定位到过程CostOfPurchase的代码里。 2. 定位到语句purchCost = CSng(newPurchase)。 3. 在变量名称上单击右键,从快捷菜单中选择“定义”命令。 4. 通过按Ctrl+Shift+F2组合键返回到刚才的位置。 5. 试试在本章所创建的其它过程中查找其它变量的定义,每次使用不同的方法跳到变量声明的位 置。 技巧3-15:这个变量是什么类型? 可以使用一个VBA内置函数来发现变量的类型。参见第四章中使用函数VarType的例子。 3.14 在VBA过程中使用常量 当过程执行时,变量的内容可以变化。如果过程需要重复引用不变的值,那么应该使用常量。 常量就像一个命名的变量一样,总是引用相同的值。VBA要求在使用前要声明常量。使用Const语句第 3 章 2B 理解变量、数据类型和常量 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 87 来声明常量,如下所示: Const dialogName = "Enter Data" As String Const slsTax = 8.5 Const ColorIdx = 3 常量,像变量一样拥有作用范围。要让常量仅在一个过程里可用,将它声明为过程级即可,例 如: Sub WedAnniv( ) Const Age As Integer = 25 <在这里放置过程指令> End Sub 如果想要在一个模块的所有过程中都可以使用某常量,则在Const语句前加上关键字Private。例 如: Private Const dsk = "B:" As String 私有常量必须在模块顶部第一个Sub语句之前声明。 如果想要创建一个该工作簿所有模块都可使用的常量,则在Const语句之前加上关键字Public。 例如: Public Const NumOfChar = 255 As Integer 公共常量必须在模块顶部第一个Sub语句之前声明。 声明常量时,可以使用下列数据类型之一:Boolean、Byte、Integer、Long、Currency、Single、 Double、Date、String或者Variant。 像变量一样,通过逗号分隔后,在一行中可以声明多个常量,例如: Const Age As Integer = 25, City As String = "Denver", PayCheck As Currency = 350 使用常量可以使VBA过程可读性更强、更容易维护。例如,在过程里多次引用某个特定值,就 可以使用常量,而不是这个值本身。这样,如果以后这个值变了(例如,销售税率上升了),只要简单 地在Const语句声明里改变这个常量的值就可以了,而不必追踪该值所有出现的地方。 3.14.1 内置常量 Excel和VBA都有大量不需要声明的预定义常量,这些内置常量可以使用“对象浏览器”查找。 我们已经在第二章里详细讨论过“对象浏览器”。 让我们来打开“对象浏览器”并查找Excel常量列表: 1. 在VBE窗口中,选择菜单“视图”—“对象浏览器”命令。 2. 在“工程/库”下拉列表框里选择Excel。 3. 在“搜索”文本框里输入“Constants”并按回车键,或者单击“搜索”按钮。VBA在“搜索结 果”区域中显示搜索结果。 4. 在“类”列表框中向下滚动,选择“Constants”(参见图3-3)。对象浏览器右边区域显示所有 Excel对象库里可用的内置常量。注意,所有常量的名称以前缀“xl”开头。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 88 共 612 页 图3-3:使用对象浏览器查找任意的内置常量 5. 要查找VBA常量,在“工程/库”下拉列表框里选择VBA。注意,所有VBA的内置常量以前缀“vb” 开头。 学习预定义常量的最好方法是使用宏录制器。让我们来花上几分钟来录制最小化当前窗口的过 程: 1. 在Excel窗口中,选择菜单“工具”—“宏”—“录制新宏”命令。 2. 输入MiniWindow作为宏名,在“保存在”列表框中选择“当前工作簿”,然后单击“确定”按 钮。 3. 单击最小化按钮。确保最小化文档窗口,而不是Excel应用程序窗口。 4. 在“停止录制”工具条上单击“停止录制”按钮。 5. 最大化该被最小化了的窗口。 6. 切换到VBE窗口,并且在工程窗口中双击模块文件夹。代码窗口显示如下的过程: Sub MiniWindow( ) ActiveWindow.WindowState = xlMinimized End Sub 有时可能会看到VBA过程使用数值而不是内置常量名称,例如,常量xlMaximized的实际数值是 -4137、常量xlMinimized的值是-4140、以及常量xlNormal的值为-4143(参见图3-4)。 第 3 章 2B 理解变量、数据类型和常量 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 89 图3-4:可以在对象浏览器里面选择常量名称然后在下面的窗口里看到它的实际值 本章小结和下一章内容简绍 本章介绍了几个新的VBA概念,包括数据类型、变量和常量的相关信息。学习了如何声明各种 类型的变量,也看到了变量和常量之间的区别。既然知道什么是变量,也知道如何使用它们,就可 以使用比前两章更有意义的方法创建操作数据的过程了。在下一章中,您将通过使用带有参数的过 程和函数过程来扩展您的VBA知识。另外,您还将学习让VBA过程与用户交互的相关函数知识。 第4章 VBA 过程:Sub 子过程和 ction过程 Fun   在第二章中,您学习了过程是一组指令,能够在程序运行时 完成一些特定的任务。VBA 有三类过程…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 92 共 612 页 在第二章中,您学习了过程是一组指令,能够在程序运行时完成一些特定的任务。VBA有三类 过程: „ Sub子过程(子程序)执行一些有用的任务但是不返回任何值,它们以关键字Sub开头并以关键字 End Sub结束。Sub子过程可以用宏录制器(见第一章)录制或者在VBE窗口里直接编写(见第二章 和第三章)。在第一章里已经学习了多种运行这种过程的方法。 „ Function过程(函数)执行返回值的特定的任务,它们以关键字Function开头并以关键字End Function结束。在本章中,您将创建第一个Function过程。Function过程可以从Sub子过程里执 行,也可以像Excel的内置函数一样从工作表里访问。 „ Property过程用于自定义对象。使用属性过程可以设置和获取对象属性的值,或者设置对另外 一个对象的引用。您将在第十一章中学习如何创建自定义对象和使用Property过程。 在本章中,您将学习如何创建和执行自定义函数。另外,您将知道如何使用变量(见第3章)给Sub 子过程和Function过程传递数值。在本章后面,您将彻底了解VBA中两个最有用的函数:MsgBox函 数和InputBox函数。 4.1 关于Function过程 使用成百个Excel的内置函数,可以进行各种自动计算,然而,有时可能需要自定义计算。使用 VBA编程,可以通过创建Function过程快速的完成这项特殊需求。可以创建任何Excel没有提供的函 数。 4.1.1 创建Function过程 像Excel函数一样,Function过程执行计算并返回值。学习Function过程的最好方法就是自己创 建一个自定义函数。因此,让我们开始吧。设置完一个新的VBA工程,用来创建一个Function过程计 算两个数值之和。 1. 打开一个新的Excel工作簿,并保存为Chap04.xls。 2. 切换到VBE窗口并选择VBAProject(Chap04.xls)。 3. 在属性窗口中,将“VBAProject”改为“MyFunctions”。 4. 在工程窗口中,选择MyFunctions(Chap04.xls),然后选择菜单“插入”—“模块”。 5. 在属性窗口中,将“模块1”改为“Sample1”。 6. 在工程窗口中,单击Sample1并选择菜单“插入”—“过程”(译者注:需要激活右边的代码窗 口)。弹出“添加过程”对话框,如图4-1所示。 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 93 图4-1:使用“添加过程”对话框时,VBA自动创建所选择的过程类型 7. 在“添加过程”对话框中,输入并进行设置: 名称:SumItUp 类型:函数 范围:公共的 8. 单击“确定”按钮退出“添加过程”对话框。VBA输入一个空的Function过程: Public Function SumItUp() End Function 第一句声明Function过程名称,关键字Public表明该函数可以在所有模块的所有过程里访问。关 键字Public是可选的。注意,关键字Function后面是函数名称(SumItUp)和一对空括号。在括号里, 可以列出计算中需要的数据项目。每个Function过程都以End Function语句结束。 技巧4-1:关于函数名称 函数名称应该表明该函数的作用,并且必须和变量的命名规则一致。 技巧4-2:VBA过程作用域 在上一章学习了变量的作用域决定它可以在哪些模块和过程里使用。和变量一样,VBA过程也有作用域。 过程的作用域决定其它模块里的过程是否可以调用该过程。默认情况下,所有的VBA过程都是公共的,这意味 着它可以被任何模块里的其它过程调用。因为过程默认为公共的,所以如果愿意也可以忽略关键字Public。但 是,如果用关键字Private取代关键字Public,那么该过程只能被同一模块里的其它过程调用,而不能被其它模 块里的过程调用。 9. 将函数声明作如下修改: Public Function SumItUp(m,n) End Function 这个函数的目的是求两个数值之和。不要在括号中列出实际值,而应该给函数提供变量形式的 参数以确保该函数具有灵活性。这样,该自定义函数就能够将所提供的任何两个数值相加。每个变 量代表一个数值,在运行该函数时要给每个变量提供数值。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 94 共 612 页 技巧4-3:使用函数的一些理由 自定义VBA函数可以用于: „ 分析数据和进行计算 „ 修正数据和汇报信息 „ 基于提供的或计算的数据采取特定的行为 10. 在Public Function和End Function之间输入下面的语句: SumItUp = m + n 这条语句的意思是将储存于变量n上的数值加在储存于变量m的数值中,并且将结果返回给函数 SumItUp。为了获得该函数返回的值,输入函数名称,在名称后输入等号和想要返回的值。在上面 的语句中,设置函数名称等于m + n的和。该自定义函数过程完整的代码如下: Public Function SumItUp(m,n) SumItUp = m + n End Function 祝贺您!您现在已经创建了第一个函数!然而,Function过程并没有什么作用,除非您知道如何 执行它。下一节将介绍如何使函数工作。 4.1.2 执行Function过程 在第一章里,您学习了用多种方法来运行Sub子过程。和Sub子过程不同,Function过程只能使 用两种方法来执行。您可以在工作表的公式中使用它,或者从另外一个过程里调用它。在VBA里创 建的Function过程不能通过在Excel窗口选择菜单“工具”—“宏”—“宏”访问,它们也不能通过 按F5键来运行。接下来,您将学习执行函数的专门技术。 从工作表里运行函数过程 自定义Function过程和内置函数一样,如果您不知道该函数确切的名称或者它的参数,那么可以 使用“插入函数”对话框来帮助在工作表里输入需要的函数。 1. 切换到Excel窗口,并选择任何一个单元格。 2. 单击“公式”工具条上的“插入函数(fx)”按钮,Excel弹出“插入函数”对话框,该对话框下 面的部分按字母顺序排列显示了所选类别里所有函数。 3. 在类别下拉列表框里选择“全部”或者“用户定义”,然后在函数名称框中向下滚动,找到并选 择本章前面创建的函数SumItUp。当选中这个函数的名称时,在“插入函数”对话框的底部显 示了该函数的语法:SumItUp(m,n)。 技巧4-4:私有函数用户是看不到的 使用关键字Private声明的函数不会出现在“插入函数”对话框中,私有函数不能用在公式里,它们只能从 另一个VBA过程里调用。 技巧4-5:快速访问自定义函数 一旦创建了一个公共的VBA函数,Excel就会将它加入到“插入函数”对话框的“用户定义”的类别里。通 过选择这个类别,可以快速地访问自定义函数。 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 95 图4-2:VBA自定义函数和Excel内置函数显示在同一列表中 4. 单击“确定”按钮,开始写公式。弹出如下图4-3所示的“函数参数”对话框。该对话框显示 了函数名称和所有参数:m和n。 图4-3:公式选项板功能有助于输入任何工作表函数,不论是内置或使用VBA编程创建的自定义函数 5. 输入参数数值,如图4-3所示,或者任意输入数值。当在参数文本框中输入数值时,Excel显示 所输入的值和函数当前的结果。因为两个参数(m和n)都是必须的,所以,如果忽略了其中的某 个参数,那么函数会返回错误。 6. 单击“确定”按钮退出“函数参数”对话框。 Excel在所选的单元格里输入函数SumItUp,并且显示它的结果。要编辑该函数,选择显示函数 结果的单元格,并且单击“插入函数(fx)”按钮,选择函数并单击“确定”按钮来访问“函数参数” 对话框。在函数参数m和n上输入不同的数值,并单击“确定”按钮。也可以直接在包含函数的单元 格上双击,直接修改函数的参数值。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 96 共 612 页 技巧4-6:确保自定义函数可用 只有在存储该自定义函数的工作簿打开时,VBA自定义函数才可用。如果关闭该工作簿,该函数便不再可 用。要确保自定义函数每次在使用Excel时都能用到,可以使用下面的方式之一: „ 保存函数在个人宏工作簿中。 „ 将含有自定义函数的工作簿保存在XLStart文件夹里。 „ 创建含有该自定义函数的工作簿的引用(请参见第二章设置对另一工程引用的信息) 从另一个 VBA 过程里运行 Function 过程 正如前面提到的,不能在VBE窗口将光标放在代码里并且按F5键来运行Function过程,也不能 通过选择菜单“运行”—“运行子过程/用户窗体”来运行Function过程。要运行函数,必须从另一 个过程里调用该函数。要执行自定义函数,编写一个VBA子过程并且在需要的时候调用该函数。 下面的过程调用SumItUp函数并且在立即窗口中输出计算结果: Sub RunSumItUp() Dim m As Single, n As Single m = 370000 n = 3459.77 Debug.Print SumItUp(m,n) MsgBox "打开立即窗口来查看结果." End Sub „ 上面的子过程使用Dim语句声明变量m和n,它们用来给函数提供数据。 „ 接下来的两行语句给变量赋值。 „ 再下来,VBA调用SumItUp函数并且将存储于变量m和n的数值传递给它。当Function过程里 SumItUp = m + n语句执行完后,VBA返回到RunSumItUp子过程里,并且使用Debug.Print语句 在立即窗口中输出函数的结果。 „ MsgBox函数通知用户在哪里查看结果。 依照下述步骤来试验上面的例子: 1. 在输入SumItUp函数的同一个模块里输入RunSumItUp过程。 2. 将光标放在该过程的任意地方,按下F5键。 技巧4-7:快速测试函数 编写了自定义函数后,可以在立即窗口快速的测试它。打开立即窗口,在函数名称前输入一个问号(?),可 以显示该函数的计算结果。记住,要在括号里输入函数的参数值。 例如,输入: ? SumItUp(54, 367.24) 然后按回车键。Function过程使用参数m和n传递的数值进行计算,然后在下一行显示结果: 421.24 4.2 传递参数 到目前为止,您已经创建了执行特定任务的简单的VBA过程,这些过程在它们运行前无须提供 额外的数据。然而,在现实生活中,过程(Sub子过程和Function过程)经常接受参数。参数是过程工 作时需要的一个或多个数值。参数通常输入在括号之间,多个参数用逗号分隔。 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 97 使用Excel有一阵了,您已经知道Excel内置函数会根据提供的数据产生不同的结果。例如,如 果单元格A4和A5分别含有数字5和10,Sum函数=SUM(A4:A5)将会返回15,除非更改单元格里面的 数值。就像可以传递任意数值给Excel内置函数一样,也可以传递数值给自定义VBA过程。 现在,我们来看看如何从Sub子过程传递数值给函数SumItUp。这个自定义函数的目的是获取一 个人的姓和名的字符数。 1. 在放置函数SumItUp的模块里输入下列子过程NumOfCharacters: Sub NumOfCharacters() Dim f As Integer Dim l As Integer f = Len(InputBox("Enter first name:")) l = Len(InputBox("Enter last name:")) MsgBox SumItUp(f,l) End Sub 2. 将光标放在过程NumOfCharacters代码的任意地方并按下F5键。VBA将显示信息输入框询问名 字,这个输入框由下面的函数产生: InputBox("Enter first name:") 3. 输入任何名字,按回车键或单击“确定”按钮。VBA接受输入的文本并且将它作为一个参数传 递给函数Len。函数Len计算提供的文本的字符数。VBA将函数Len的结果存储于变量f中以供以 后使用。之后,VBA显示下一个输入框询问姓。 4. 输入任何姓,按回车键或单击“确定”按钮。 VBA将输入的姓传递给函数Len来获得姓的字符数,然后将数值存储在变量l中。接下来会发生 什么呢?VBA遇到了函数MsgBox,这个函数告诉VBA显示函数SumItUp的结果。然而,因为这个结 果还没有准备好,VBA迅速跳到函数SumItUp里,使用变量f和l存储的数值来进行计算。在函数过程 内部,VBA用变量f的值取代参数m和用变量l的值取代参数n。完成参数值替换后,VBA将两个数值 相加,并且将结果返回给函数SumItUp。函数SumItUp内部没有其它的任务了,因此,VBA又马上返 回子过程并且将函数SumItUp的结果作为一个参数提供给函数MsgBox。现在,显示字符总数的信息 出现在屏幕上。 5. 单击“确定”按钮退出信息框。 可以多次运行过程NumOfCharacters,每次输入不同的姓名。 我们来看看另外一个使用变量传递参数的例子: 1. 在工程MyFunctions (Chap04.xls)里添加一个新模块,并重命名为Sample2。 2. 激活模块Sample2并且输入子过程EnterText: Sub EnterText() Dim m As String, n As String, r As String m = InputBox ("Enter your first name:") n = InputBox("Enter your last name:") r = JoinText(m, n) MsgBox r End Sub 3. 输入下面的Function过程: Function JoinText(k,o) JoinText = k + " " + o End Function 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 98 共 612 页 4. 运行过程EnterText。 当VB执行过程中的语句时,它收集用户输入的数据并且将这些数据存储在变量m和n中,然后传 递这些数据给函数JoinText。VBA将这些变量的值取代函数JoinText的参数,并且将结果赋给函数名 (JoinText)。当VBA返回到过程EnterText中时,存储函数的值在变量r中。然后,函数MsgBox在信息 框里显示变量r的内容,结果就是用户的完整姓名(姓和名用空格分开)。 要从函数传递指定的数值给子程序,需要将该值赋值给函数名称。例如,下面的函数NumOfDays 将数值7传递给子程序DaysInAWeek: Function NumOfDays() NumOfDays = 7 End Function Sub DaysInAWeek() MsgBox "一周有" & NumOfDays & "天." End Sub 技巧4-8:Function过程不能做什么 Function过程不能进行任何动作,例如,它们不能在工作表里执行插入、删除或设置数据格式操作、不能 打开文件、或改变屏幕显示外观。 明确参数类型 在上一节中,您学习了函数根据它们的参数传递的值进行计算。在声明函数时,将参数名称列 在一对括号里面。参数名称就像变量一样,每个参数名称引用函数调用时所提供的数值。当Sub子过 程调用Function过程时,它以变量形式传递必须的参数。一旦函数处理后,它将结果赋给函数名称。 注意,Function过程的名称当作变量来使用。 像变量一样,函数也有类型。Function过程的结果可以是字符串型、整型、长整型等。要明确函 数结果的数据类型,在函数声明行后添加关键字As和想要的数据类型。例如: Function MultiplyIt(num1, num2) As Integer 如果不明确数据类型,VBA将函数结果设置为默认类型(Variant数据类型)。当明确函数结果的数 据类型时,就像明确变量的数据类型那样有相同的好处,程序将更有效地使用内存,因此它运行得 更快。 我们来看看一个返回整型数字的函数示例,尽管在调用的过程中传递给它的参数被声明为单精 度浮点型: 1. 在工程MyFunctions(Chap04.xls)里添加一个新模块,重命名为Sample3。 2. 激活模块Sample3并且输入子过程HowMuch,如下所示: Sub HowMuch() Dim num1 As Single Dim num2 As Single Dim result As Single num1 = 45.33 num2 = 19.24 result = MultiplyIt(num1, num2) MsgBox result End Sub 3. 在子过程HowMuch下输入下面的Function过程: 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 99 Function MultiplyIt(num1,num2) As Integer MultiplyIt = num1 * num2 End Function 因为存储于变量num1和num2中的数值不是整数,要确保它们相乘后的结果为整数,就得将函 数结果设置为整型。如果不给函数MultiplyIt的结果设置数据类型,过程HowMuch则会将结果按变量 result声明的数据类型显示,相乘的结果将是872.1492,而不是872。 可以在每次运行该过程时,使用InputBox函数传递不同的数值,而不必在相乘时使用硬编码, 这样使得MultiplyIt函数更有用。依照上一节中的子程序EnterText,花几分钟来修改过程HowMuch。 按地址和按值传递参数 在一些过程中,将参数作为变量传递时,VBA可能突然改变该变量的数值。要确保被调用的函 数不改变传递的参数的值,应该在函数声明行中参数名称之前加上关键字ByVal。我们来看看下面的 例子: 1. 在工程MyFunctions(Chap04.xls)里添加一个新模块,将该模块的名字改为Sample4。 2. 激活模块Sample4并输入下面的代码: Sub ThreeNumbers() Dim num1 As Integer, num2 As Integer, num3 As Integer num1 = 10 num2 = 20 num3 = 30 MsgBox MyAverage(num1,num2,num3) MsgBox num1 MsgBox num2 MsgBox num3 End Sub Function MyAverage(ByVal num1, ByVal num2, ByVal num3) num1 = num1 + 1 MyAverage = (num1 + num2 + num3) / 3 End Function 在参数名称前使用关键字ByVal,可以防止函数改变参数的值。 子过程ThreeNumbers先给三个变量赋值,再调用函数MyAverage来计算并返回存储在这三个变 量中数值的平均值。函数的参数就是变量num1、num2和num3。注意,所有变量名的前面都有关键 字ByVal。同时,注意在计算平均值之前,函数MyAverage改变了变量num1的值。在函数内部,变 量num1等于11(10+1)。因此,当函数将计算的平均值传递给过程ThreeNumbers时,函数MsgBox 显示的结果是20.3333333333333,而不是期望的20,接下来的三个函数显示每个变量的内容,这些 变量存储的内容和它们开始被赋的值相同——10、20和30。 如果在函数声明行忽略了参数num1前面的关键字ByVal,结果会怎样呢?函数的结果仍然相同, 但是函数MsgBox显示的变量num1的内容现在是11了。函数MyAverage不但返回了出乎意料的结果 (20.3333333333333而非20),而且改变了储存在变量num1里的原始数值。要避免VBA永久地改变 提供给函数的数值,就得使用关键字ByVal。 技巧4-9:关键字:ByRef和ByVal 因为每个要传递给Function过程(或Sub子过程)的任何变量,都可能被接收这些变量的过程改变,所以知道 如何来保护变量的原始数值是非常重要的。VBA有两个关键字,允许或者不允许改变变量的内容——ByRef和学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 100 共 612 页 ByVal。 默认情况下,VBA按地址(关键字ByRef)给Function过程(或Sub子过程序)传递信息,这时被调用的函数相 用函数参数中特定的原始数据。因此,如果函数改变了参数的值,原始的数值也被改变了。如果在函数MyAverage 声明行中的参数num1前忽略关键字ByVal,就会得到这种结果。如果想要Function过程改变原始数值,则不必 专门在参数前加关键字ByRef,因为,变量数值的传递默认就是ByRef。 当在参数名称前使用关键字ByVal时,VBA按值传递参数,这意味着VBA复制了一份原始数据,然后将复制 的值传递给函数,如果函数改变了参数的数值,原始数据不会改变——只有复制的值变化。这就是为什么函数 MyAverage改变了变量num1的数值,而它的原始值仍然保持不变的原因。 使用可选的参数 有时,也许要给函数提供额外的参数,例如,有一个计算每人一餐价格的函数。然而,有时希 望函数为二人或多人进行相同的计算。为了指明过程的参数不是必须的,在参数名称前面加上关键 字Optional。可选参数应放在参数列表的后面,接在所有必须的参数之后。可选参数必须总是Variant 数据类型,这意味着不能使用关键字As来指定可选参数的类型。 在上一节,创建了一个计算三个数值的平均值的函数,假设,有时只想要使用这个函数计算两 个数的平均值,就可以将该函数的第三个参数设置为可选的。为了保护原来的函数MyAverage,我 们创建一个新的函数Avg,来计算两个或三个数字的平均值: 1. 在工程MyFunctions(Chap04.xls)里添加一新模块,并将该模块的名称改为Sample5。 2. 激活模块Sample5并输入下面的Function过程: Function Avg(num1, num2, Optional num3) Dim totalNums As Integer totalNums = 3 If IsMissing(num3) Then num3 = 0 totalNums = totalNums –1 End If Avg = (num1+num2+num3)/totalNums End Function 3. 现在来从立即窗口调用该函数: ?Avg(2,3) 一旦按下回车键,VBA就显示结果:2.5。 ?Avg(2,3,5) 这次的结果为:3.3333333333333。 正如所看到的,函数Avg允许计算两个或三个数字的平均值。可以决定想要计算哪些值或多少个 值(2或3个)的平均值。当在立即窗口开始输入函数的参数时,VBA将在方括号里显示可选的参数的名 称。 让我们来分析一下函数Avg。该函数可以接受三个参数,参数num1和num2是必须的,而第三个 参数num3是可选的。注意,可选参数以关键字Optional开头,可选参数列在参数清单的后面。因为 没有声明参数num1、num2和num3的类型,VBA将这些参数作为Variant型。 Function过程内部,变量totalNums声明为整型,并且赋给初始值为3。因为该函数必须能够计 算两个或三个数字的平均值,内置函数IsMissing检查所提供参数的个数。如果第三个参数(可选的)第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 101 没有提供,函数IsMissing在该参数的位置放置的值为0,同时变量totalNums的值被减去1,因此,如 果可选参数忽略,totalNums为2。接下来的代码根据提供的数据计算平均值,并且将结果传递到函 数名称。 函数IsMissing用来检测是否提供了可选参数。如果没有提供第三个参数,那么该函数返回逻辑 值True,如果提供了第三个参数时,则返回False。在这里,函数IsMissing是和一个作出判断的语句 If…Then一起使用的(参见第五章有关该语句和VBA中其它判断语句详细描述)。如果忽略(IsMissing) 参数num3,那 么 (Then)VBA提供第三个参数的值为0(num3=0),并且将变量totalNums所存储的值减 去1(totalNums = totalNums – 1)。 还能使用别的方法来运行函数Avg吗?使用自己的方法在工作表里运行该函数,确保使用两个参 数,然后使用三个参数来运行该函数。 技巧4-10:测试Function过程 可以编写一个简单的Sub子过程调用自定义函数并且显示它的结果,测试该函数是否能得到设计的结果。 除此之外,该Sub子过程还应该能显示参数的原始数据,这样,你才能很快确定参数数值是否改变了。如果该 函数使用了可选参数,也需要检查不使用可选参数时的情况。 4.3 找出内置函数 VBA自带了很多内置函数,可以在在线帮助里很容易找到这些函数。在VBE窗口中选择菜单“帮 助”—“Microsoft Visual Basic帮助”命令,可以访问按字母顺序排列的所有VBA函数清单。在“目 录”标签中,打开“Visual Basci语言参考”文件夹,然后单击“函数”, 以MsgBox或InputBox函数为例。一个好程序的功能之一就是要与用户互动。当使用Excel时, 通过使用各种各样的对话框与该应用程序交互,当发生错误时,对话框会弹出来,并且告诉有错误。 当编写自己的过程时,也可以将出乎意料的错误或某个计算的结果通知用户。可以使用函数MsgBox 帮助做这些。到目前为止,您已看到了这个函数的一些简单应用,在下面一节,您将了解如何控制 信息框的外观,也将学习如何使用函数InputBox从用户获取信息。在我们详细讨论这些函数之前, 我们来看一个VBA函数,它对现在已经熟悉变量及其数据类型的你特别有用。 VBA有个VarType函数,它返回一个代表变量类型的整数。图4-4显示了函数VarType的语法和 它返回的值。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 102 共 612 页 图4-4:使用内置函数VarType,可以告诉变量所持有的数据类型 现在,看看如何在立即窗口里使用这个函数: 1. 打开立即窗口。 2. 输入下列给变量赋值的语句: age = 18 birthdate = #1/1/1981# firstName = "John" 3. 现在询问VBA每个变量持有的数据类型是什么: ?varType(age) 按下回车键,VBA返回2,如图4-4所示,数字2代表整数数据类型。 ?varType(birthdate) VBA返回7,代表日期。如果在变量名称上犯错(比如说,输入了birthday而不是birthdate),VBA 将返回0。 ?varType(firstName) VBA告诉您变量firstName中所存储的数据是字符串(8)。 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 103 4.3.1 使用MsgBox函数 到目前为止,所使用的MsgBox函数局限于给用户显示一个带有一个按钮的简单的信息框,单击 “确定”按钮或者按回车键关闭该信息框。通过在MsgBox函数名称后面带上一个用引号包括起来的 文本就可以创建一个简单的信息框。换句话说,要显示信息“过程已完成”,应该准备下面的语句: MsgBox "过程已完成" 可以输入到立即窗口快速地测试上面的指令,当输入完这条指令并且按回车键后,VBA就显示 如图4-5所示的信息框。 图4-5:将文本作为MsgBox函数的参数,来给用户显示信息 MsgBox函数允许使用其它参数,可以决定在信息框中可用的按钮数目,或者将默认的信息框的 标题(Microsoft Excel)改变,也可以设置自己的帮助主题。MsgBox的语法如下: MsgBox (prompt [, buttons] [, title], [, helpfile, context]) 注意,MsgBox函数有五个参数,只有第一个参数Prompt是必须的,列在方括号里面的参数都是 可选的。 当为prompt参数输入一个长的文本字符串时,VBA决定如何断句,使文本适合信息框的大小。 让我们在立即窗口里做一些练习,学习不同的文本格式技术。 1. 在立即窗口中输入下面的指令,确保在一行里输入整个文本,然后按回车键。 MsgBox "All done. Now open ""Chap04.xls"" and place an empty disk in the diskette drive. The following procedure will copy this file to the disk." 一旦按回车键,VBA显示最终的信息框,如图4-6所示。 图4-6:如果设置一下文本格式,长信息看上去将会更吸引人 如果遇到编译错误,可以单击“确定”按钮,然后确保文件名用双层双引号括起来—— ““Chap04.xls””。 当信息文本特别长时,可以使用VBA 的Chr函数将它分割为几行。Chr函数需要参数,该参数是 0到255之间的数字,返回该数字代表的字符。例如,Chr(13)返回的是回车符(与按下回车键相同), Chr(10)返回换行字符(这在文本行之间添加空行很有用)。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 104 共 612 页 2. 将上面的指令修改如下: MsgBox "All done." & Chr(13) & "Now open ""Chap04.xls"" and place" & Chr(13) & "an empty disk in the diskette drive." & Chr(13) & "The following procedure will copy this file to the disk." 图4-7:通过使用Chr(13)函数可以将长文本分割成几行 你必须将每段文本用引号括起来,在文本字符串中内嵌的引用文本需要额外再添加一对双引号, 例如““Chap04.xls””。Chr(13)函数表明希望开始新的一行的地方。字符串连接符(&)用来返回字符连 接后的字符串。 在一行输入极其长的文本信息时,很容易失误。回想一下,VBA有一个专门的行连续字符(下划 线_),可以将长的VBA语句分割为几行,不幸的是,行连续符不能在立即窗口中使用。 3. 在工程MyFunctions(Chap04.xls)里添加一个新模块并将该模块改名为Sample6。 4. 激活模块Sample6并且输入如下所示的子过程MyMessage,确保在每个行连续符前面加个空 格: Sub MyMessage() MsgBox "All done." & Chr(13) _ & "Now open ""Chap04.xls"" and place" & Chr(13) _ & "an empty disk in the diskette drive." & Chr(13) _ & "The following procedure will copy this file to the disk." End Sub 运行过程MyMessage时,VBA显示与图4-7相同的信息。正如您所看到的,在几行输入的文本 更具可读性,而且代码更容易维护。 可以在文本行之间添加空白行,来增加信息的可读性。使用两个Chr(13)或两个Chr(10)函数就可 以做到,如下面的步骤所示。 5. 输入下面的MyMessage2过程: Sub MyMessage2() MsgBox "All done." & Chr(10) & Chr(10) _ & "Now open ""Chap04.xls"" and place" & Chr(13) _ & "an empty disk in the diskette drive." & Chr(13)& Chr(13) _ & "The following procedure will copy this file to the disk." End Sub 图4-8显示了MyMessage2过程产生的信息框。 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 105 图4-8:可以通过在文本行之间添加空行增加信息的可读性 既然已经掌握了文本格式的技术,那么我们就来仔细地看看MsgBox函数的下一个参数吧。尽管 buttons参数是可选的,但是它经常被用到。 参数buttons指定想要出现在信息框上的按钮数和类型,这个参数可以是个常量(参见表4-1), 也可以是数字。如果忽略这个参数,那么最终的信息框中只会有一个“确定”按钮,正如在前面的 例子里所看到的那样。 表4-1:MsgBox按钮参数的设置 常量 值 描述 按钮设置 vbOKOnly 0 仅显示“确定”按钮,这是默认值 vbOKCancel 1 显示“确定”和“取消”按钮 vbAbortRetryIgnore 2 显示“终止”、“重试”和忽略按钮 vbYesNoCancel 3 显示“是”、“否”和“取消”按钮 vbYesNo 4 显示“是”和“否”按钮 vbRetryCancel 5 显示“重试”和“取消”按钮 图标设置 vbCritical 16 显示“重要信息”图标 vbQuestion 32 显示“问号”图标 vbExclamation 48 显示“警告信息”图标 vbInformation 64 显示“信息”图标 默认按钮设置 vbDefaultButton1 0 第一个按钮是缺省值 vbDefaultButton2 256 第二个按钮是缺省值 vbDefaultButton3 512 第三个按钮是缺省值 vbDefaultButton4 768 第四个按钮是缺省值 信息框形式 vbApplicationModal 0 应用程序强制返回;应用程序一直被挂起,直到 用户对消息框作出响应才继续工作。 vbSystemModal 4096 系统强制返回;全部应用程序都被挂起,直到用 户对消息框作出响应才继续工作。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 106 共 612 页 MsgBox显示的其它设置 vbMsgBoxHelpButton 16384 将Help按钮添加到消息框 vbMsgBoxSetForeground 65536 指定消息框窗口作为前景窗口 vbMsgBoxRight 524288 文本为右对齐 vbMsgBoxRtlReading 1048576 指定文本应为在希伯来和阿拉伯语系统中的从右 到左显示 什么时候应该使用buttons参数呢?假设要用户对一个问题回答“是”或“否”,那么信息框就需 要两个按钮。当信息框有一个以上的按钮时,就需要将其中一个设置为缺省值,当用户按回车键时, 这个默认的按钮就会自动地被选上。 因为可以显示各种各样的信息(重要、警告、信息),所以需要通过按钮参数设置图形代表(图标) 来可视化地指明信息的重要性。 除了信息类型之外,buttons参数还可以设置是否用户必须先关闭该信息框才能切换到另外的应 用程序。很多情况下,用户需要在对信息框的问题做出反应之前,切换到另外的程序或者进行另外 的操作。如果这个信息框是应用程序模式(vbApplication Modal),那么用户必须先关闭该信息框后才 能继续使用应用程序。另一方面,如果想要在用户对信息框响应之前,将所有应用程序挂起,那么 必须在buttons参数里加上系统强制返回设置(vbSystemModal)。 buttons参数的设置分为五组:按钮设置、图标设置、默认按钮设置、信息框形式和其它的MsgBox 显示设置(参见表4-1)。在buttons参数中只能从每组里面选一个设置。 可以将每种需要的设置相加来设置按钮参数。例如,要显示一个带两个按钮(“是”和“否”)、 问号图标以及将“否”按钮设置为缺省值的信息框,可以在表4-1里查找相应的值并且相加,应该 得到292(4+32+256)。可以在立即窗口里面输入下面的代码,快速查看使用该计算参数的信息框: MsgBox "Do you want to proceed?", 292 下面显示的就是最终的信息框。 图4-9:可以使用可选的按钮参数来确定信息框上的按钮数 当直接使用常量值相加的结果作为buttons参数时,程序可读性就不高了,因为没有参考表格供 检查292所隐藏的意思。要改善MsgBox函数的可读性,最好使用常量,而不要使用它们的值。例如, 在立即窗口输入下面修改后的语句: MsgBox "Do you want to proceed?", vbYesNo + vbQuestion + vbDefaultButton2 上面的语句得到与图4-9所示相同的结果。 下面的例子介绍如何在VBA过程里使用buttons参数: 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 107 1. 在工程MyFunctions(Chap04.xls)里添加一新模块,并将该模块名改为Sample7。 2. 激活Sample7模块,并且输入如下所示的子过程MsgYesNo: Sub MsgYesNo() Dim question As String Dim myButtons As Integer question = "您想打开一个新工作簿吗?" myButtons = vbYesNo + vbQuestion + vbDefaultButton2 MsgBox question, myButtons End Sub 在上面的子过程里,变量question存储了信息文本,而buttons参数的设置则存储于变量 myButtons中。除了使用常量名称之外,还可以使用它们的值,例如: myButtons = 4 + 32 + 256 但是,通过明确buttons参数的常量名称,可以使过程对自己以及将来可能要使用该过程的人来 说更容易理解。 变量question和myButtons用作MsgBox函数的参数。运行该过程后,将看到如图4-9所示的结 果。注意,现在按钮“否”是被选中的,它是该对话框的默认按钮,如果按下回车键,Excel将从屏 幕上移除该信息框,因为过程中MsgBox函数后面没有任何指令,所以不会发生其它操作。将默认按 钮换成vbDefaultButton1设置,可以更改默认按钮。 MsgBox函数的第三个参数是title,虽然这也是一个可选参数,但是它很方便,它允许创建不提 供可视的提示的过程,即您使用Microsoft Excel编写它们的事实。使用这个参数,将标题栏设置为想 要的任何文字。 假设想要过程MsgYesNo显示标题为“新工作簿”,下面的过程MsgYesNo2演示了title参数的用 法: Sub MsgYesNo2() Dim question As String Dim myButtons As Integer Dim myTitle As String question = "您想打开一个新工作簿吗?" myButtons = vbYesNo + vbQuestion + vbDefaultButton2 myTitle = "新工作簿" MsgBox question, myButtons, myTitle End Sub title参数的文本存储在变量myTitle中。如果没有明确title参数的内容,VBA将显示默认的文本 “Microsoft Excel”。 注意,所列的参数是按MsgBox函数决定的顺序列出的,如果要按任意顺序列出这些参数,就必 须在每个参数值之前加上该参数名称,例如: MsgBox title:=myTitle, prompt:=question, buttons:=myButtons 最后两个参数——helpfile和context——经常为那些对Windows环境下的帮助文件有经验的程 序员使用。helpfile参数指明某个包含要显示给用户附加信息的具体帮助文件的名称,当指定这个参 数后,Help按钮就会添加到信息框中。当使用helpfile参数时,同时也使用context参数,这个参数表 明在帮助文件里要显示的帮助主题。假设Help.hlp是所创建的帮助文件,55是要使用的帮助主题,那学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 108 共 612 页 就可以按下面的指令在MsgBox函数中包括该信息: MsgBox title:=mytitle, _ prompt:=question _ buttons:=mybuttons _ helpFile:= "HelpX.hlp", _ context:=55 上面是一条单独的VBA语句,使用行连续符打断为几行。 从 MsgBox 函数返回的值 当显示带有一个按钮的简单的信息框时,可以单击“确定”按钮或者按回车键从屏幕上移除该 信息框。然而,当信息框有两个或以上的按钮时,过程需要知道按的是哪个按钮,可以将信息框结 果保存在一个变量中来实现。表4-2 显示了MsgBox函数的返回值。 表4-2:MsgBox函数的返回值 选择的按钮 常数 值 OK(确定) VbOK 1 Cancel(取消) vbCancel 2 Abort(终止) vbAbort 3 Retry(重试) vbRetry 4 Ignore(忽略) vbIgnore 5 Yes(是) vbYes 6 No(否) vbNo 7 MsgYesNo3过程是MsgYesNo2过程修改后的版本,演示如何确定用户选择的按钮: Sub MsgYesNo3() Dim question As String Dim myButtons As Integer Dim myTitle As String Dim myChoice As Integer question = "您想打开一个新工作簿吗?" myButtons = vbYesNo + vbQuestion + vbDefaultButton2 myTitle = "新工作簿" myChoice = MsgBox(question, myButtons, myTitle) MsgBox myChoice End Sub 在上面的过程里,将MsgBox函数的结果赋值给变量myChoice。注意,MsgBox函数的参数现在 被列在括号中: myChoice = MsgBox(question, myButtons, myTitle) 技巧4-11:MsgBox函数——使用或不使用括号? 当需要使用MsgBox函数返回的结果时,使用括号将该函数的参数包括起来。不使用括号,意味着告诉VBA 忽略该函数的结果。当MsgBox函数包含两个或以上的按钮时,很可能想要使用该函数的结果。 当运行MsgYesNo3过程时,出现一个带有两个按钮的信息框。单击“是”按钮,MsgBox myChoice 语句将显示数字6;单击“否”按钮,则显示数字7。您将在第五章里面学习如何让程序根据按钮的第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 109 选择执行任务。 4.3.2 使用InputBox函数 InputBox函数显示一个带有提示用户输入数据信息的对话框,该对话框有两个按钮——“确定” 按钮和“取消”按钮。单击“确定”按钮时,InputBox函数返回用户输入在文本框里的信息;当选 择“取消”按钮时,函数则返回空字符串(” ”)。InputBox函数的语法如下: InputBox(prompt [, title] [, default] [, xpos] [, ypos] _ [, helpfile, context]) 第一个参数prompt是想要显示在对话框中的文本信息,可以使用Chr(13)函数或Chr(10)函数将 输入的长文本分为几行(参见本章前面使用MsgBox函数的例子)。剩下所有的InputBox函数的参数都 是可选的。 第二个参数title可以改变对话框的默认标题,默认的标题是“Mictosoft Excel”。 InputBox函数的第三个参数default可以显示文本框里的默认值。如果忽略该参数,将显示空白 编辑框。 接下来的两个参数xpos和ypos,设置该对话框在屏幕上出现的具体位置。如果忽略这两个参数, 输入框就会出现了当前窗口的中央。xpos参数决定对话框距屏幕左侧的水平位置,忽略它时,对话 框显示在水平中央。ypos参数决定对话框距屏幕顶部的垂直位置,忽略它时,对话框就在竖直大约 三分之一的位置。xpos和ypos都使用一个叫twips的专门单位衡量,1twip大约等于0.0007英寸。 最后两个参数helpfile和context,和本章前面讨论的MsgBox函数相应参数的使用方法一样。 现在知道InputBox参数的意义了,我们来看看使用这个函数的一些示例: 1. 在MyFunctions (Chap04.xls)工程里添加一个新模块,并将该模块的名称改为Sample8。 2. 激活Sample8模块,并且输入下面的子过程: Sub Informant() InputBox prompt:="请输入您的出生地:" & Chr(13) _ & " (e.g., Boston, Great Falls, etc.) " End Sub 上面的过程显示一个带有两个按钮的对话框,输入提示显示在两行里 图4-10:Informant子过程产生的对话框 像使用MsgBox函数一样,如果想要使用用户输入的数据,那么应该使用一个变量来存储该对话 框的结果。下面所示的子过程Informant2将InputBox函数的结果赋值给变量town: Sub Informant2() Dim myPrompt As String 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 110 共 612 页 Dim town As String Const myTitle = "输入数据" myPrompt = "请输入您的出生地:" & Chr(13) _ & "(e.g., Boston, Great Falls, etc.)" town = InputBox(myPrompt, myTitle) MsgBox "您出生在 " & town & ".", , "您的回答" End Sub 注意,这次InputBox函数的参数列在了括号中。如果需要在稍后的过程中使用InputBox函数的 结果,那么括号是必须的。Informant2子过程使用常数来确定显示在输入框标题上的文本。因为,这 个值在过程执行中保持不变,所以可以将输入框的标题声明为一个常量。然而,如果您愿意,也可 以使用变量。 当运行使用InputBox函数的过程时,通过该函数产生的对话框总是出现在屏幕的同一位置,您 可以按前面说明的那样,提供xpos和ypos参数来改变对话框的位置。 3. 如下所示,修改过程Informant2中的InputBox函数,让对话框显示在屏幕的左上角: town = InputBox(myPrompt, myTitle, , 1, 200) 注意,参数myTitle后面紧跟两个逗号,第二个逗号是忽略掉的default参数的位置。紧接着的两 个参数决定对话框的水平和竖直位置。如果忽略了参数myTitle后面的第二个逗号,VBA将会使用数 字1作为default参数的值。如果在参数值之前加上参数名称,(例如,prompt:=myPrompt, title:=myTitle, xpos:=1, ypos:=200),则不必记住在每个忽略了参数的地方加上逗号。 如果输入了一个数字,而不是一个城镇的名称,后果会怎样?因为用户经常会在输入对话框里 提供不正确的数据,所以过程必须验证用户输入的数据是否可以在将来的数据操作里使用。InputBox 函数本身并没有提供验证数据的工具,要验证用户的输入,必须使用其它的VBA指令,这将在下一 章里讲述。 转换数据类型 InputBox函数的结果总是字符串,如果用户输入一个数字,用户输入的字符串值必须转换成为 数字值之后,才能用于过程里的数学计算。VBA有能力转换数据类型,不过,在早期的Excel版本里, 这是不可能的。 1. 在MyFunctions(Chap04.xls)工程中激活模块Sample8,并输入下面的过程AddTwoNums: Sub AddTwoNums() Dim myPrompt As String Dim value1 As String Const myTitle = "输入数据" Dim mySum As Single myPrompt = "输入一个数字:" value1 = InputBox(myPrompt, myTitle, 0) mySum = value1 + 2 MsgBox mySum & " (" & value1 & " + 2)" End Sub 上面的过程显示如图4-11所示的对话框。注意,这个对话框通过使用InputBox函数的可选参数 title和default,包含了两个专门的功能。该对话框显示了由常量myTitle的内容指定的文本字符串作为 标题,而不是默认的“Microsoft Excel”。在编辑框里面输入的默认值0提示用户输入数字而不是文本。 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 111 一旦用户输入了数据并单击“确定”按钮,用户的输入就被赋值给了变量value1。 value1 = InputBox(myPrompt, myTitle, 0) 图4-11:要提示用户输入确定的数据类型,可以在文本框里提供一个默认值 变量value1的数据类型是字符串型,可以在上面指令的下方加上如下语句容易地查看它的数据 类型: MsgBox varType(value1) 当VBA运行上面的代码时,将显示带有数字8的信息框,可以在本章前面的图4-4里查看该数字 代表字符串数据类型。 技巧4-12:定义常量 可以将标题文本赋值给一个常量,以确保某个VBA过程里所有的标题栏都显示相同的文本。依照这个技巧, 可以在多次输入某标题文本时节省时间。 技巧4-13:避免类型不匹配错误 如果试图在Excel的早期版本里(2000以前版本)运行AddTwoNums过程,当VBA试图执行下面的代码行时, 将得到类型不匹配的错误: mysum = value1 + 2 使用内置函数CSng将储存于value1的字符串转换为一个单精度浮点类型的数字,可以避免类型不匹配错 误,代码如下: mysum = CSng(value1) + 2 下一行,mySum = value1 + 2,在用户输入的数据中加上2并且将计算结果赋值给变量mySum。 因为变量value1的数据类型是字符串型,在使用它计算之前,VBA在后台进行数据类型的转换,VBA 知道转换的需要。没有它,两种不兼容的数据类型(文本和数字)将会产生类型不匹配错误。 过程序以MsgBox函数结束,显示计算的结果并给用户显示总数是如何得到的。 4.3.3 使用InputBox方法 除了InputBox函数之外,还有InputBox方法,如果激活“对象浏览器”,在搜索框中输入 “inputbox”,然后按下回车键,VBA将显示两个InputBox——一个在Excel库中,另一个在VBA库中 (见图4-12)。 InputBox方法在Excel库里面可用,它的语法和本章前面讲的InputBox函数的语法有轻微差别, 其语法为: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 112 共 612 页 expression.InputBox(Prompt, [Title], [Default], [Left], [Top], [HelpFile], [HelpContextID], [Type] ) 所有方括号里面的参数都是可选的。Prompt参数是显示在对话框上的信息,Title是对话框的标 题,而Default是对话框初始显示时在文本框里显示的值。Left和Top参数是明确对话框在屏幕上的位 置,这些参数的输入值的单位是Point(1point等于1/72英寸)。当用户单击帮助按钮时,参数HelpFile 和HelpContextID明确帮助文件的名称以及指定帮助主题数字。InputBox方法的最后一个参数Type明 确返回的数据类型,如果忽略这个参数,InputBox方法将会返回文本格式。Type参数的值列在表4 -3里。 图4-12:别忘记使用对象浏览器来搜索VBA函数和方法 表4-3:通过InputBox方法返回的数据类型 值 返回的数据类型 0 公式 1 数字 2 字符串(文本) 4 逻辑值(True或False) 8 单元格引用,作为一个Range对象 16 错误值,例如#N/A 64 数组 如果使用3作为Type参数的值,用户将在文本框中既可以输入数字也可以输入文本。这个值是将 代表数字的值(1)和代表字符串的值(2)相加得到的,如表4-3所示。InputBox方法很适合那些需要用 户选择工作表单元格区域的VBA过程。 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 113 1. 如果已经打开了对象浏览器,则先关闭它。 2. 在模块Sample8里面,输入下面的过程WhatRange: Sub WhatRange() Dim newRange As Range Dim tellMe As String tellMe = "使用鼠标选择单元格区域:" Set newRange = Application.InputBox(prompt:=tellMe, _ Title:="格式化区域", _ Type:=8) newRange.NumberFormat = "0.00" newRange.Select End Sub 过程WhatRange开始声明对象变量newRange。试回想一下第三章,对象变量指向数据的地址。 用户选择的单元格被赋值给对象变量newRange。注意变量名称前面的关键字Set: Set newRange = Application.InputBox(prompt:=tellMe, _ Title:="格式化区域", _ Type:=8) Type参数(Type:=8)使用户能够选择任何单元格区域。当用户选中单元格区域时,下一句指令: newRange.NumberFormat = "0.00" 改变所选单元格的格式。最后一句选择用户选中的单元格区域。 3. 运行过程WhatRange。VBA显示一个对话框,提示用户在工作表里选择一个单元格区域。 4. 使用鼠标选择想要的任意单元格。当鼠标在单元格上拖动时,VBA就会将选择的区域引用输入 到编辑框里面。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 114 共 612 页 图4-13:使用Excel的InputBox方法,可以从用户处获得区域地址 5. 选择了单元格后,单击对话框上的“确定”按钮,被选择的区域就已经设置好格式了。要检查 是否按要求进行了格式设置,可以在该区域的任意单元格里输入一个完整的数字,该数字应该 显示带有两位小数。 6. 重新运行该过程,并且当出现对话框时,单击“取消”按钮。如果在选择了一个单元格或者一 个区域后单击“确定”按钮,过程WhatRange将工作正常。不幸地是,当单击“取消”按钮或 按Esc按钮后,VBA将显示一错误信息——“要求对象”。单击错误对话框上的“调试”按钮, VBA就会加亮显示导致错误的代码行。因为不希望在取消对话框时选择任何单元格,所以必须 想法忽略VBA显示的这个错误。使用一个专门的语句,On Error GoTo 标志,当错误发生时可 以绕过错误。该指令的语法如下: On Error GoTo 标志 这个指令应该放在变量声明行的下面。标志可以是除VBA关键字之外的任何词语。如果错 误发生,VBA就会直接跳到该特别的标志处,如下面步骤8所示。 7. 选择菜单“运行”-“重新设置”以取消正在运行的程序。 8. 将过程WhatRange修改为如下所示的过程WhatRange2: Sub WhatRange2() Dim newRange As Range Dim tellMe As String On Error GoTo VeryEnd 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 115 tellMe = "使用鼠标选择单元格区域:" Set newRange = Application.InputBox(prompt:=tellMe, _ Title:="格式化区域", _ Type:=8) newRange.NumberFormat = "0.00" newRange.Select VeryEnd: End Sub 9. 运行过程WhatRange2,一旦出现输入对话框,就单击“取消”按钮。 注意,在删除对话框时过程没有产生错误。当VBA遭遇错误时,就会跳到位于过程结尾的标志 VeryEnd处。位于On Error Goto VeryEnd和标志VeryEnd之间的语句被忽略了。您将在第十三章里 面找到更多的诱捕VBA过程里错误的例子。 技巧4—14:Sub子过程和Function过程:应该使用哪个? 创建Sub子过程时,当: „ 需要执行一些动作 „ 需要获取用户信息 „ 需要在屏幕上显示信息 创建Function过程,当: „ 需要不只一次的做一些简单的计算 „ 需要做复杂的计算 „ 需要不只一次地调用相同的指令块 „ 需要检查某些表达式是True或False 4.4 使用主过程和子过程 当VBA过程序越来越大,维护这么多的代码行是很困难的。要让程序更容易编写、理解和改变, 就应该使用的结构化的方式。创建一个结构化的程序时,只要简单地将大问题分成一些一次可以解 决一个的小问题就行。在VBA中,可以通过创建一个主过程和一个或多个子过程来实现它。因为主 过程和子过程都是子程序,所以使用关键字Sub声明它们。主过程可以调用所需的子过程,并且将参 数传递给它们。也可以调用函数。 下面的例子显示了过程AboutUser。该过程要求用户的姓和名,并且将姓和名从全名中分离出来。 最后的语句显示用户的姓,随后是逗号和名。继续读下去,该过程将被分割成几个任务,以示范使 用主过程、子过程和函数的概念。 Sub AboutUser() Dim fullName As String Dim firstName As String Dim lastName As String Dim space As Integer '从用户获取信息 fullName = InputBox("Enter first and last name:") '获得代表姓和名的字符串 space = InStr(fullName, " ") firstName = Left(fullName, space – 1) 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 116 共 612 页 lastName = Right(fullName, Len(fullName) – space) '显示姓和名 MsgBox lastName & ", " & firstName End Sub 过程AboutUser可以分成一些更小的任务。第一个任务便是获取用户的全名;下一个任务则需要 将用户提供的数据分为两个字符串:姓和名,这些任务可以交给不同的函数(例如:GetLast和 GetFirst);最后的任务是显示重新排列的姓名字符串信息。既然已经知道了应该注重于哪些任务, 我们现在就来看看如何完成每个任务。 1. 在当前的VBA工程里面添加一个新模块,并重命名为Sample9。 2. 在Sample9模块窗口里面输入下面的过程AboutUserMaster: Sub AboutUserMaster() Dim first As String, last As String, full As String Call GetUserName(full) first = GetFirst(full) last = GetLast(full) Call DisplayLastFirst(first, last) End Sub 上面显示的主过程通过调用适当的子程序和函数来控制程序的主流程。该主过程以变量声明开 始,第一条语句Call GetUserName (full)调用子过程GetUserName(见第三步)并且给它传递参数—— 变量full的内容。 因为变量在执行调用语句之前没有赋任何值,所以它是一个空字符串(“ ”)。注意,子程序的 名称在Call语句之后。尽管在调用过程时并没有要求使用关键字Call,但是,在调用一个需要参数的 过程时就必须使用它。参数列表必须包括在括号里面。 3. 输入下面的GetUserName子程序: Sub GetUserName(fullName As String) fullName = InputBox("Enter first and last name:") End Sub 过程GetUserName示范了两个非常重要的VBA编程概念:如何传递参数给子程序以及如何将值 从子程序传递返回给主过程。 在主过程(见第二步)中,调用了过程GetUserName,并且传递给它一个参数:变量full。该变量 被参数fullName接收,该参数在子过程GetUserName的Sub语句里已声明。因为在VBA调用子过程 GetUserName时,变量full包含一空字符串,参数fullName同样也接收了这个空字符串。当VBA显示 对话框并且获得用户的姓时,这个姓将赋给参数fullName。赋给参数的值被传递返回给子过程执行 后相匹配的参数。因此,当VBA返回主过程时,变量full就包含用户的姓。 传递给子过程的变量将被其参数接收。注意,参数名称(fullName)后面紧跟着数据类型的声 明(As String) 。虽然参数的数据类型必须和相匹配的变量的数据类型一致,但是变量和它相应的参 数还是可以使用不同的名称。 技巧4—15:自变量(Arguments)和参数(Parameters) „ 自变量是传递给子程序的变量、常量或表达式。 „ 参数则只是接收传递给子过程的值的变量。 4. 输入下面的GetFirst函数过程: 第 4 章 3BVBA 过程:Sub 子过程和 Function 过程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 117 Function GetFirst(fullName As String) Dim space As Integer space = InStr(fullName, " ") GetFirst = Left(fullName, space - 1) End Function 主过程中的第二条语句(见第二步):first = GetFirst(full),将变量full的值传递给函数GetFirst,函 数的参数fullName接收到该值。要从用户提供的全名字符串中分出名字,就必须找到姓和名中间的 空格。因此,该函数以本地变量space的声明开始。 下一条语句则使用VBA内置函数InStr返回字符串fullName里空格(“ ”)的位置,然后将获得的 数字赋值给变量space。最后,Left函数用来提取字符串fullName从左起某特定个数(space -1)的字符。 名字的长度比储存在变量space的值少一个字符。函数的结果(用户的名字)赋值给函数名。当VBA返 回到主过程时,它就将结果放置于变量first中。 5. 输入下面的GetLast函数过程: Function GetLast(fullName As String) Dim space As Integer space = InStr(fullName, " ") GetLast = Right(fullName, Len(fullName) - space) End Function 主过程中的第三条语句(见第二步):last = GetLast(full),将变量full的值传递给函数GetLast。该 函数的目的是提取用户提供的全名字符串中的姓。函数GetLast使用内置函数Len来计算字符串 fullName的总字符数。函数Right提取字符串fullName从右边起指定的字符数(Len(fullName) – space) 的字符。然后,获得的字符串赋值给函数名称,一旦返回主过程,它就储存于变量last中。 6. 输入下面的子过程DisplayLastFirst: Sub DisplayLastFirst(firstName As String, lastName As String) MsgBox lastName & ", " & firstName End Sub 主过程中的第四条语句(见第二步):Call DisplayLastFirst(first, last),调用子过程DisplayLastFirst 并且给该过程传递两个参数:first和last。为了接收这些参数,子过程DisplayLastFirst声明了两个相 匹配的参数(firstName和lastName)。回想我们前面说过,在变量和与之相应的参数上可以使用不同 的名称。然后,子过程DisplayLastFirst显示用户的姓后紧跟着逗号和名字的消息框。 技巧4—16:使用Sub子过程的好处 „ 维护多个子过程要比维护一个大过程要容易得多 „ 由一个子过程执行的任务可以给好几个过程使用 „ 每个子过程在放入主程序之前可以单独测试 „ 多个程序员可以负责各自的子过程,这些子过程再构建一个大的过程 本章小结和下一章内容简介 在本章里,您学习了Sub子过程和Function过程之间的区别:Sub子过程执行操作;Function过 程返回数值。而且可以通过录制或者输入来创建Sub子过程,但Function过程则不可以录制,因为它 们可能需要参数,您必须手动输入。您看到了从工作表或者其他VBA过程调用Function过程的例子。 您学习了如何给函数传递参数,决定函数结果的数据类型。您增加了VBA关键字:ByVal、ByRef学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 118 共 612 页 和Optional等关键字。您也看到如何将问题分为更小更简单的任务,以使您的程序更容易理解。最后, 您学习了子过程如何在参数的帮助下,将数值传递返回到主调过程。 通过本章,您应该能够创建适合于您特定需要的自己的自定义函数,也应该可以通过使用 MsgBox函数和InputBox函数轻松地和使用您过程的用户互动。第五章将向您介绍使用程序做出决 定,您将学习如何基于所提供的条件改变VBA过程的进程。 第5章 使用VBA作判断 我们每天要做出成千上万个判断,有些判断是无意识的,我 们没有停下来思考就自动地做出了这些判断,其它的判断则需要 我们考虑两个或者更多的选择甚至事先做出几个计划…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 120 共 612 页 我们每天要做出成千上万个判断,有些判断是无意识的,我们没有停下来思考就自动地做出了 这些判断,其它的判断则需要我们考虑两个或者更多的选择甚至事先做出几个计划。VBA和其它的 编程语言一样,提供了专门的语句允许您在自己的程序中包含抉择点。但是,什么是作判断呢?举 例说某人问您这个问题:“您喜欢红色吗?”想过这个问题之后,您将回答“是”或者“否”。如果 您不确定或者根本就不关心这个问题,您可能回答“也许”或“可能”。在编程中,您必须决断,回 答“是”或“否”。在编程中,所有的判断都是基于提供的答案做出的。如果答案是肯定的,程序就 会执行某段特定的指令;如果答案是否定的,程序则将执行另外一段指令或者干脆不做任何操作。 在本章中,您将学习如何使用VBA条件语句来改变程序的流向。条件语句通常称为“控制结构”,因 为它们使您能够控制VBA过程的走向:跳过某些语句以及“分岔”到过程的另外一部分。 5.1 关系运算符和逻辑运算符 在VBA过程中通过在专门的控制结构里使用条件表达式来作出判断。条件表达式是使用关系运 算符(见表5-1)、逻辑运算符(见表5-2)、或者两者结合的表达式。当VBA在程序里遇到条件表达式 时,它将推断该表达式是真(true)还是假(false)。 表5-1:VBA中的关系运算符 运算符 描述 = 等于 <> 不等于 > 大于 < 小于 >= 大于等于 <= 小于等于 表5-2:VBA中的逻辑运算符 运算符 描述 AND 在采取某操作前,所有的条件都必须为真(TRUE) OR 在采取某操作前,至少有一个条件为真(TRUE) NOT 用来否定条件。如果该条件为真,NOT使它为假;如果该条件为 假,NOT使它变为真 5.2 If…Then语句 在VBA过程里作出判断的最简单的方法是使用If…Then语句。假使想要基于某个条件选择一个操 作,那么可以使用下面的结构: If 条件 Then 语句 第 5 章 4B 使用 VBA 作判断 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 121 例如,要删除工作表里面的空行,首先得检查当前单元格是否为空,如果测试的结果为真,则 继续并删除包含该单元格在内的整行: If ActiveCell = "" Then Selection.EntireRow.Delete 如果当前单元格不为空,那么VBA将忽略关键字Then后面的语句。 有时,当条件为真时,可能想要执行好几个操作,虽然可以在同一行通过使用冒号(:)分隔而加 上其它的语句,但是如果在If…Then中使用多行,代码将更清晰,如下所示: If 条件 Then 语句1 语句2 语句N End If 例如,若当前单元格的数值大于50时,则执行一些操作,可以编写下面的指令: If ActiveCell.Value >50 Then MsgBox "确切的值为" & ActiveCell.Value Debug.Print ActiveCell.Adress & ": " & ActiveCell.Value End If 在上面的例子中,如果当前单元格数值小于或等于50,那么在关键字Then和End If之间的语句 就不会执行。注意,If…Then语句必须以关键字End If结束。VBA如何作出判断呢?它推断在关键字 If和Then之间找到的条件。 让我们试着评估下面的条件:ActiveCell.Value >50 1. 在一个空白工作表上选择任意一个单元格并输入50。 2. 切换到VBE窗口。 3. 激活立即窗口。 4. 输入下面的语句,并且按下回车键。 ? ActiveCell.Value >50 按下回车键后,VBA写下测试的结果——false。当测试的结果为假时,VBA将不会读代码中关 键字Then之后的语句,它将直接跳过去读过程中的下一行代码,但是,如果没有其它的代码行时, 过程就将结束。 5. 现在,将运算符改为小于等于号,并且让VBA评估下面的条件: ? ActiveCell.Value <= 50 这次,测试的结果返回真(true),VBA跳到关键字Then后面的语句上去执行。 6. 关闭立即窗口。 现在,您已经知道了VBA如何推断条件,我们就来在VBA过程里试试If…Then语句吧。 1. 打开一个新工作簿并保存为Chap05.xls。 2. 切换到VBE窗口,并将VBA工程重命名为Decisions。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 122 共 612 页 3. 在Decisions(Chap05.xls)工程中插入一个新模块并重命名为IfThen。 4. 在IfThen模块里,输入下面的过程: Sub SimpleIfThen() Dim weeks As String weeks = InputBox("一年有多少个星期:", "问题") If weeks<>52 Then MsgBox "再试一次" End Sub 过程SimpleIfThen将用户的答案存储于一个名为weeks的变量上,然后,该变量的值将会和数值 52进行比较。如果比较的结果为真(也就是说,变量weeks的值不等于52),VBA就会显示信息“再试 一次”。 5. 运行过程SimpleIfThen并输入一个除52之外的数字。 6. 重新运行过程SimpleIfThen并输入数字52。 当输入正确的周数,VBA将不会执行任何操作,过程就直接结束了。当用户猜对时,最好也显 示一条信息。 7. 在关键字End Sub前的单独一行中输入下面的指令: If weeks = 52 Then MsgBox "祝贺您!" 8. 再次运行过程SimpleIfThen并输入52。 当输入正确的答案时,VBA不会执行语句MsgBox “再试一次”。当执行该过程时,如果提供的条 件测试结果为假时,就会忽略关键字Then右边的语句。回想一下,VBA过程可以调用另外一个过程, 让我们来看看它是否可以调用本身。 技巧5—1:If…Then语句的两种格式 If…Then语句有两种格式——单行和多行。短格式适合于可以写在一行里的语句,例如: If secretCode <> 01W01 Then MsgBox “Access denied” 或者: If secretCode = 01W01 Then alpha=True : beta = False 这里的secreCode、alpha和beta是变量名称。在第一个例子里,如果变量secretCode的值不等于01W01, 那么VBA显示信息“Access denied”。在第二个例子里,当变量secretCode值等于01W01时,VBA就将变量alpha 的值设置为真,将变量beta的值设置为假。注意,执行的第二条语句用冒号与第一条语句分隔开来。 如果当条件为真时需要执行多条语句或将要执行的语句极其长时,多行的If…Then语句会更清楚,如下所 示: If ActiveSheet.Name = "Sheet1" Then ActiveSheet.Move after:=Sheets _ (Worksheets.Count) End If 在这个例子中,VBA将会检查当前工作表的名称。如果它是“Sheet1”,条 件 ActiveSheet .Name = “Sheet1” 将为真,并且VBA将继续执行关键字Then后面的代码行。结果,在工作簿中的当前工作表将会被移动到最后。 9. 修改过程SimpleIfThen里的第一个If语句如下: If weeks <> 52 Then MsgBox "再试一次" : SimpleIfThen 第 5 章 4B 使用 VBA 作判断 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 123 我们在原来的过程SimpleIfThen后面添加了一个冒号和SimpleIfThen过程名称。如果用户输入了 不正确的答案,他将看到一条信息,并且一旦他单击“确定”按钮,将获得另一次机会来提供正确 的答案——输入框将再次出现。用户将能够不断地猜测答案,事实上,他不能适当地退出该过程, 直到提供了正确的答案。如果他单击了“取消”按钮,他将不得不去处理不友好的错误信息“类型 不匹配”。在上一章,您看到了如何使用On Error GoTo 标志 语句来绕过错误,至少在第十三章里 学习更多的关于错误处理的知识之前,您可以那么做。现在,你可以修改过程SimpleIfThen如下: Sub SimpleIfThen() Dim weeks As String On Error GoTo VeryEnd weeks = InputBox("一年有多少个星期:", "问题") If weeks<>52 Then MsgBox "再试一次": SimpleIfThen If weeks=52 Then MsgBox "祝贺您!" VeryEnd: End Sub 10. 运行几遍过程SimpleIfThen并提供一些不正确的答案。在过程中添加的错误捕捉指令使得用户 可以退出猜测,而不必处理这个讨厌的错误信息。 5.3 基于多个条件的判断 在前一节中所处理的过程SimpleIfThen里,If…Then语句仅评估一个条件,然而,这个语句可以 使用一个以上的条件。要指定多个条件,必须使用逻辑运算符AND和OR(参见本章前面的表5-2)。 这里是使用AND运算符的语法: If 条件1 AND 条件2 Then 语句 在上面的语法中,条件1和条件2都必须为真,VBA才会执行关键字Then右边的语句。例如: If sales = 10000 AND salary <45000 Then SlsCom = Sales * 0.07 在这个例子中, 条件1: sales=10000 条件2: salary<45000 当AND使用在该条件表达式中时,两个条件都必须为真,VBA才会计算销售佣金(SlsCom)。如 果两个条件中只要有一个为假,或者都为假,VBA将会忽略Then后面的语句。 如果只须满足其中一个条件时,就应该使用运算符OR。其语法为: If 条件1 OR 条件2 Then 语句 运算符OR更灵活一些,只要任何一个条件为真时,VBA就会执行关键字Then后面的语句。我们 来看看这个例子: If dept = "S" OR dept = "M" Then bonus = 500 在上面的例子里,如果至少有一个条件为真,VBA就将给值500赋给变量bonus。如果两个条件 都为假,那么VBA就会忽略该行剩余的代码。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 124 共 612 页 现在我们来看一个完整的例子。假设如果采购50套及以上数量的产品,就可以获得10%的折扣, 每套的单价为$7.00。过程IfThenAnd演示了运算符AND的使用。 1. 在Decisions(Chap05.xls)工程的IfThen模块里输入下面的过程: Sub IfThenAnd() Dim price As Single Dim units As Integer Dim rebate As Single Const strmsg1 = "必须买更多才能得到折扣" Const strmsg2 = "单价必须等于$7.00" units = Range("B1").Value price = Range("B2").Value If price = 7 And units >= 50 Then rebate = (price * units) * 0.1 Range("A4").Value = "折扣是: $" & rebate End If If price = 7 And units < 50 Then Range("A4").Value = strmsg1 & 50 - units & " unit(s)." End If If price <> 7 And units >= 50 Then Range("A4").Value = strmsg2 End If If price <> 7 And units < 50 Then Range("A4").Value = "您不满足条件." End If End Sub 上面的过程IfThenAnd使用了四个If…Then语句来评估两个变量price和units的内容。在If…Then 关键字之间的运算符AND允许测试一个以上的条件。使用了AND运算符时,所有条件都必须为真, VBA才会去执行关键字Then和End If之间的语句。 因为IfThenAnd过程的运行依赖于工作表单元格里输入的数据,所以从Excel窗口来运行它更方 便。 2. 切换到Excel应用程序窗口,并且选择菜单“工具”-“宏”-“宏”命令。 3. 在“宏”对话框里,选择IfThenAnd并单击“选项”按钮。 4. 给宏设置的快捷键为:Ctrl+Shift+I,然后退出“宏选项”对话框。 5. 在工作表里输入以下数据: 6. 按下Ctrl+Shift+I组合键运行过程IfThenAnd。 7. 改变单元格B1和B2中的值,使得在每次运行该过程时,执行不同的If…Then语句。 技巧5—2:If指令块和缩进 要使If程序块更容易阅读和理解,可以使用缩进。对比下面代码的书写方式: If condition Then action1 End If If condition Then 第 5 章 4B 使用 VBA 作判断 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 125 action End If 看看下方的代码,能够容易地知道该程序块在哪里开始,在哪里结束。 5.4 The If…Then…Else语句 现在,您知道当一个或多个条件为真或为假时如何显示信息或采取某项操作。然而,如果程序 需要在条件为真时采取某项操作,而条件为假时采取另一项操作,应该怎么办呢?通过添加一个Else 子句就可以根据测试的结果将过程引导到一个合适的语句。 If…Then…Else语句有两种格式——单行和多行。单行的格式为: If 条件 Then 语句1 Else 语句2 当条件为真时,执行关键字Then后面的语句,当条件为假时,则执行Else后面的语句。例如: If Sales>5000 Then Bonus = Sales * 0.05 Else MsgBox “没有奖金” 如果存储在变量Sales中的值大于5000的话,那么VBA将使用下面的公式:Sales * 0.05来计算 奖金。然而,如果变量Sales的值不大于5000,VBA就会显示信息“没有奖金”。 If…Then…Else语句用于判断执行两个操作中的哪一个。 当条件为真或假时需要执行多个语句,最好使用多行格式的If…Then…Else语句: If 条件 Then 条件为真时要执行的语句 Else 条件为假时要执行的语句 End If 注意,多行的If…Then…Else语句以关键字End If结束。使用上面显示的缩进使得程序结构更易 于阅读。 在下面的例子中,如果条件ActiveSheet.Name = “Sheet1”为真,VBA就执行Then和Else之间的 语句,而忽略Else和End If之间的语句。如果条件为假,VBA就忽略Then和Else之间的语句,并执行 Else和End If之间的语句。 If ActiveSheet.Name = "Sheet1" Then ActiveSheet.Name = "My Sheet" MsgBox "该工作表已重命名." Else MsgBox "该工作表重命名失败." End If 让我们来看看过程示例: 1. 在工程Decisions(Chap05.xls)里插入一个新模块。 2. 重命名该模块为IfThenElse。 3. 输入下面的过程WhatTypeOfDay: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 126 共 612 页 Sub WhatTypeOfDay() Dim response As String Dim question As String Dim strmsg1 As String, strmsg2 As String Dim myDate As Date question = "Enter any date in the format mm/dd/yyyy:" _ & Chr(13)& " (例如, 11/22/1999)" strmsg1 = "工作日" strmsg2 = "周末" response = InputBox(question) myDate = Weekday(CDate(response)) If myDate >= 2 AND myDate <= 6 Then MsgBox strmsg1 Else MsgBox strmsg2 End If End Sub 上面的过程要求用户输入任意一个日期,然后将用户提供的字符串通过一个内置函数CDate转换 为Date数据类型,最后,函数Weekday将日期转变为一个指明该日期在一周里的天数(参见表5-3)。 该整数存储在变量myDate里。条件测试用以检查变量myDate是否大于等于2以及小于等于6。如果 测试结果为真,那么用户就被告知所提供的数据是工作日;否则,程序宣布是周末。 表5-3:内置函数Weekday返回的值 常数 值 vbSunday 1 vbMonday 2 vbTuesday 3 vbWednesday 4 vbThursday 5 vbFriday 6 vbSaturday 7 4. 从VBE窗口运行该程序。运行几次,每次提供不同的日期,对照桌面或日历检查VBA提供的答 案是否正确。 技巧5—3:什么是结构化的程序设计? 结构化编程要求所有的程序具有模块化的设计,并只使用三种逻辑结构:顺序、判断和循环。顺序结构为 一条接一条地执行语句;判断结构可以基于一些条件的测试结果来执行特定的语句;而循环结构只要某特定的 条件为真,就重复地执行一条或更多的语句。循环是下一章要介绍的主题。在结构化编程中,其它一些逻辑语 句,例如GoTo,是不允许的。结构化程序的代码容易跟踪——它从上到下平稳地走下来,没有任何跳跃到特定 标志去的语句。下面就是一个结构化程序和非结构化程序的例子: 非结构化程序: Sub GoToDemo() Dim num, mystr num = 1 If num = 1 Then GoTo line1 Else 第 5 章 4B 使用 VBA 作判断 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 127 GoTo Line2 Line1: mystr = “Number equals 1” GoTo LastLine Line2: mystr = “Number equals 2” LastLine: Debug.Print mystr End sub 结构化程序: Sub Structure() Dim num, mystr num = 1 If num = 1 Then mystr = “Number equals 1” Debug.Print mystr Else mystr = “Number equals 2” End if End Sub 当编写VBA程序,并且需要从程序中的一行跳到另一行时,可能企图使用GoTo语句。不要跳跃。依赖于使 用GoTo语句来改变程序的路径将导致令人迷惑的代码,被称为意大利式面条代码。使用结构化编程,可以轻易 地到达程序里的目的地。 下面是另一个演示If…Then…Else语句的示例过程: Sub EnterData() Dim cell As Object Dim strmsg As String On Error GoTo VeryEnd strmsg = "选择任一单元格:" Set cell = Application.InputBox(prompt:=strmsg, Type:=8) cell.Select If IsEmpty(ActiveCell) Then ActiveCell.Formula = InputBox("输入文本或数值:") Else ActiveCell.Offset(1, 0).Select End If VeryEnd: End Sub 上面所示的子过程EnterData提示用户选择任意单元格,然后将单元格地址赋值给对象变量cell。 If…Then…Else结构检查被选择的单元格是否为空。IsEmpty是用来判断某个变量是否已经被初始化 的内置函数。如果该变量没有被初始化,那么IsEmpty函数返回真(true)。回想我们说过,当变量被 赋予第一个值时,它就被初始化了。在本过程中,如果当前单元格为空,VBA将它当作一个零长度 的字符串(” ”)。除了使用指令: If IsEmpty(ActiveCell) Then 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 128 共 612 页 还可以使用下面的指令: If ActiveCell.Value = "" Then 如果当前单元格为空,就会执行Then后面的语句。该语句提示用户输入一个文本或数字,并且 一旦输入完成,该数据就会输入到当前单元格中。如果当前单元格不为空,VBA将跳到子句Else后 面的指令,该指令将让VBA选择同列里的下一个单元格。 当运行该过程且输入框提示选择一个单元格时,在工作表上单击任一单元格,被选择的单元格 地址就会出现编辑框中。单击“确定”按钮退出输入框。VBA检查被选择单元格的内容并且跳到过 程里的true或false指令块(true指令块在Then后面,而false指令块在Else后面)。 5.5 If…Then…ElseIf语句 很多时候,您需要检查几个不同条件的结果,可以使用子句ElseIf来将一些If条件结合在一起。 使用If…Then…ElseIf语句,可以比上一节中的If…Then…Else语句评估更多的条件。下面是 If…Then…ElseIf语句的语法: If 条件1 Then 条件1为真时要执行的语句 ElseIf 条件2 Then 条件2为真时要执行的语句 ElseIf 条件3 Then 条件3为真时要执行的语句 ElseIf 条件N Then 条件N为真时要执行的语句 Else 所有条件都为假时要执行的语句 End If Else子句是可选的。如果当所有条件为假时没有要执行的操作,那么可以忽略它。 技巧5—4:ElseIf子句 过程里可以包括任意数量的ElseIf子句和条件。ElseIf子句总是出现在Else子句之前,只有当ElseIf子句的条 件为真时,它的语句才会被执行。 我们来看看下面的例子: If ActiveCell.Value = 0 Then ActiveCell.Offset(0, 1).Value = "zero" ElseIf ActiveCell.Value >0 Then ActiveCell.Offset(0, 1).Value = "positive" ElseIf ActiveCell.Value <0 Then ActiveCell.Offset(0, 1).Value = "negative" End If 第 5 章 4B 使用 VBA 作判断 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 129 这个例子检查当前单元格的值,并且在相邻的列上输入适当的“标签”(零、正和负)。注意,此 时没有使用Else子句。如果第一个条件(ActiveCell.Value = 0)的结果为假,VBA将跳到下一个ElseIf 语句,并且评估该条件(ActiveCell.Value>0),如果该值不大于0,VBA将跳到下一个ElseIf并检查条 件ActiveCell.Value<0。 我们来看看If…Then…Else语句在一个完整的过程中如何工作: 1. 在当前工程里插入一个新模块。 2. 重命名该模块为IfThenElseIf。 3. 输入下面的过程WhatValue: Sub WhatValue() Range("A1").Select If ActiveCell.Value = 0 Then ActiveCell.Offset(0, 1).Value = "zero" ElseIf ActiveCell.Value > 0 Then ActiveCell.Offset(0, 1).Value = "positive" ElseIf ActiveCell.Value < 0 Then ActiveCell.Offset(0, 1).Value = "negative" End If End Sub 因为需要运行过程WhatValue几次来测试各种条件,所以给该过程设置一个临时的快捷键。 4. 打开立即窗口,并输入下面的语句: Application.OnKey "^+y", "WhatValue" 按下回车键后,VBA就会运行OnKey方法将过程WhatValue赋予组合键Ctrl+Shift+Y。这个键盘 快捷键只是临时的——当重新启动Excel后它就不起作用了。同样也可以在Excel窗口中的“工具” 菜单中使用“宏”对话框里的“选项”来设置快捷键。 5. 切换到Excel窗口,并激活Sheet1。 6. 在单元格A1里输入0,并且按下Ctrl+Shift+Y。VBA将调用过程WhatValue并在单元格B1中输入 “zero”。 7. 在单元格A1里输入任意大于0的数字,并按下Ctrl+Shift+Y,VBA将再次调用WhatValue过程。 VBA评估第一个条件,因为该测试的结果为假,所以它跳到ElseIf语句。第二个条件为真,因此 VBA执行Then后面的语句,并且跳到End if语句的下一条语句。因为End If后面没有其它的语句, 该过程结束,单元格B1现在显示“positive”。 8. 在单元格A1里输入任意小于0的数字,并按下Ctrl+Shift+Y。这次,前面两个条件都返回假,因 此VBA继续检查第三个条件。因为这次的测试结果为真,VBA就在单元格B1里输入标签 “negative”。 9. 在单元格A1里输入任何文本,并按下Ctrl+Shift+Y,VBA的反应是“positive”,然而,这不是一个 满意的答案。也许希望VBA通过显示“text”来区分开正数和文本。要使过程WhatValue更“聪明” 些,就需要学习如何通过使用嵌套的If…Then语句来作一些更复杂的判断。 5.6 嵌套的If…Then语句 将一个If…Then语句或If…Then…Else语句放在另外一个If…Then语句或If…Then…Else语句里 面,可以在VBA过程里作出更复杂的判断。这种一个If语句里包含另一个If指令块的结构称为嵌套的学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 130 共 612 页 If语句。 下面的过程TestConditions是上一节中所创建的过程WhatValue的修订版,演示了嵌套的 If…Then语句是如何工作的: Sub TestConditions() Range("A1").Select If IsEmpty(ActiveCell) Then MsgBox "当前单元格为空." Else If IsNumeric(ActiveCell.Value) Then If ActiveCell.Value = 0 Then ActiveCell.Offset(0, 1).Value = "zero" ElseIf ActiveCell.Value > 0 Then ActiveCell.Offset(0, 1).Value = "positive" ElseIf ActiveCell.Value < 0 Then ActiveCell.Offset(0, 1).Value = "negative" End If Else ActiveCell.Offset(0, 1).Value = "text" End If End If End Sub 为了使过程TestConditions更容易理解,每个If…Then语句都显示为不同的格式,现在可以清楚 地看到该过程使用了三个If…Then程序块。 第一个If块(粗体)检查当前单元格是否为空,如果条件的结果为真,就会显示信息,然后VBA将 跳过Else部分找到相匹配的End If,该语句位于关键字End Sub之前。 如果当前单元格不为空,IsEmpty (ActiveCell)条件的结果返回假,并且VBA运行粗体Else下面 的带有单下划线的If块。这个单下划线的If…Then…Else语句嵌套在第一个If块(粗体)中。该语句检查 当前单元格是否是数字。注意,我们通过另一个内置函数IsNumeric来进行处理。如果当前单元格的 值不是一个数字,条件的结果就为假,因此,VBA跳到单下划线的Else处,并且在单元格B1里输入 “text”。 然而,如果当前单元格包含数字,VBA就会运行带有双下划线的If块,评估每个条件并作出相应 的判断。 第一个If块(粗体)被称为外层的If语句,这个外层语句包含两个内层的If语句(即带有单下划线和双 下划线的部分)。 技巧5—5:嵌套语句 嵌套是指将一种控制结构放在另一个控制结构里面。您将在第六章里介绍的循环结构中看到更多的嵌套的 例子。 5.7 Select Case语句 为了避免难以弄清的复杂的嵌套的If语句,可以使用Select Case语句代替。它的语法为: Select Case 测试表达式 第 5 章 4B 使用 VBA 作判断 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 131 Case 表达式1 如果表达式1匹配时测试的语句 Case 表达式2 如果表达式2匹配时测试的语句 Case 表达式N 如果表达式N匹配时测试的语句 Case Else 如果没有表达式匹配时测试要执行的语句 End Select 可以在关键字Select Case和End Select之间放置任意多个条件进行测试。子句Case Else是可选 的,当预料到可能有条件表达式返回假时使用它。在Select Case语句里,VBA将每个表达式和测试 表达式的值相比较。 下面是Select Case语句背后的逻辑。当VBA遇到Select Case子句时,它记下测试表达式的值。 然后开始测试下面的第一个Case子句,如果这个表达式(表达式1)的值和测试表达式的值相匹配, VBA就会执行语句直到遇到另外一个Case子句并且跳到End Select语句。然而,如果第一个Case子 句后面的表达式测试结果和测试表达式的值不匹配,VBA就会检查每一个Case子句后面表达式的值, 直到它找到一个相匹配的为止。如果没有一个Case子句后面的表达式的值与存储在测试表达式中的 值相匹配,VBA就会跳到Case Else子句并执行该语句直到遇到关键字End Select。注意,Case Else 子句是可选的,如果过程序里面没有使用Case Else并且没有一个Case子句的表达式的值和测试表 达式的值相匹配,VBA就会跳到End Select后面的语句,并且继续执行过程。 我们来看一个使用Select Case语句的过程示例。在第四章里,您学习了MsgBox函数,该函数 可以显示带有一个或多个按钮的信息,您也学习了MsgBox函数的结果可以赋予一个变量。使用 Select Case语句,您现在可以根据用户按下的按钮决定采取哪个操作。 1. 在当前工程里插入一个新模块。 2. 重命名新模块为SelectCase。 3. 输入下面的过程TestButtons: Sub TestButtons() Dim question As String Dim bts As Integer Dim myTitle As String Dim myButton As Integer question = "您想打开一个新工作簿吗?" bts = vbYesNoCancel + vbQuestion + vbDefaultButton1 myTitle = "新工作簿" myButton = MsgBox(prompt:=question, buttons:=bts, title:=myTitle) Select Case myButton Case 6 Workbooks.Add Case 7 MsgBox "您可以稍后手动打开一个新工作簿." 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 132 共 612 页 Case Else MsgBox "您按了取消按钮." End Select End Sub 过程TestButtons的第一部分显示一个带有三个按钮的信息框:是、否和取消。用户选择的按钮 的值赋予变量myButton。 如果用户单击“是”,那么变量myButton就会被赋值常量vbYes或它对应的值6;如果用户单击 “否”,那么变量myButton则赋值为常量vbNo或它对应的值7;最后,如果单击了“取消”,变量 myButton的内容就等于vbCancel或2。 Select Case语句对照存储在变量myButton里的值检查Case子句提供的值。当有匹配时,就会 执行适当的Case语句。 如果使用常量代替按钮的值,过程TestButtons将做同样的工作。 Select Case myButton Case vbYes Workbooks.Add Case vbNo MsgBox "您可以稍后手动打开一个新工作簿." Case Else MsgBox "您按了取消按钮." End Select 可以忽略Else子句,按下面的方法简单地修订Select Case语句: Select Case myButton Case vbYes Workbooks.Add Case vbNo MsgBox "您可以稍后手动打开一个新工作簿." Case vbCancel MsgBox "您按了取消按钮." End Select 4. 运行过程TestButtons三次,每次选择一个不同的按钮。 技巧5—6:通过Case Else捕捉错误 尽管在Select Case语句里使用Case Else不是强制的,但使用它总是一个好惯,以防止万一测试的变量有 一个没预料到的值。Case Else子句是放置错误信息的好地方。 5.7.1 使用带有Is的Case子句 有时候,基于测试表达式的条件作出判断,例如它是否大于、小于、等于或使用一些其它的关 系运算符(参见表5-1)。关键字Is使得在Case子句里能够使用条件表达式。使用关键字Is的Select Case语句的语法如下: Select Case 测试表达式 Case Is 条件1 第 5 章 4B 使用 VBA 作判断 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 133 如果条件1为真时执行的语句 Case Is 条件2 如果条件2为真时执行的语句 Case Is 条件N 如果条件N为真时执行的语句 End Select 例如,我们来比较一些数字: Select Case myNumber Case Is <10 MsgBox "这个数字小于 10" Case 11 MsgBox "您输入了11." Case Is >=100 MsgBox "这个数字大于等于100." Case Else MsgBox "这个数字在12和99之间." End Select 假设变量myNumber为120,那么第三个Case子句为真,并且只会执行Case Is >=100和Case Else之间的语句。 5.7.2 指定Case子句里数值的范围 在前面的例子里,您看到了在每个Case子句里使用一个表达式的简单的Select Case语句。然而, 很多时候,可能需要在Case子句里指定一个数值范围。可以通过在表达式的数值之间使用关键字To 来实现,如下所示: Select Case unitsSold Case 1 to 100 Discount = 0.05 Case Is <= 500 Discount = 0.1 Case 501 to 1000 Discount = 0.15 Case Is >1000 Discount = 0.2 End Select 我们来分析一下上面的Select Case代码块,假设变量unitsSold当前的值为99。VBA将变量 unitsSold的值与Case子句的条件表达式进行比较。第一个和第三个Case子句演示了如何通过使用关 键字To在条件表达式里使用数值范围。因为unitsSold=99,第一个Case子句里的条件表达式为真, 因此,VBA将0.05赋给变量Discount。 第二个Case子句如何呢?它也为真。尽管99小于等于500是很明显的,但VBA不会执行相关的 语句Discount=0.1。原因是,一旦VBA找到了一个条件为真的Case子句,它就不会去管其它的Case 子句,而是跳过那些代码,继续执行End Select语句后面可能有的语句。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 134 共 612 页 我们再来练练使用Select Case语句,在function过程里使用它。回想在第四章里,function过程 可以将结果返回给sub子过程。假设该sub子过程必须根据销售的套数来显示折扣。 可以从用户那里获得销售套数,然后运行一个函数来确定需要的折扣: 1. 在模块SelectCase里输入下面的sub子过程: Sub DisplayDiscount() Dim unitsSold As Integer Dim myDiscount As Single unitsSold = InputBox("请输入销售的套数:") myDiscount = GetDiscount(unitsSold) MsgBox myDiscount End Sub 2. 输入下面的function过程: Function GetDiscount(unitsSold As Integer) Select Case unitsSold Case 1 To 200 GetDiscount = 0.05 Case Is <=500 GetDiscount = 0.1 Case 501 To 1000 GetDiscount = 0.15 Case Is >1000 GetDiscount = 0.2 End Select End Function 3. 将光标放在过程DisplayDiscount的任意地方并且按下F5键来运行。 过程DisplayDiscount将存储于变量unitsSold中的值传递给函数GetDiscount。当 VBA遇到Select Case语句时,它检查第一个Case子句里的值是否与存储于unitsSold里面的值相匹配。如果匹配, VBA给函数名称赋值百分之五(0.05),然后跳到关键字End Select。因为,在函数过程里面没有更多 需要运行的语句,VBA就返回主调过程——DisplayDiscount,在这里,它将函数的结果赋予变量 myDiscount。最后的语句用信息框来显示获得的折扣。 5.7.3 在Case子句里指定多个表达式 可以使用逗号在一个单独的Case子句里指定多个条件: Select Case myMonth Case "January", "February", "March" Debug.Print myMonth & ": 1st Qtr." Case "April", "May", "June" Debug.Print myMonth & ": 2nd Qtr." Case "July", "August", "September" Debug.Print myMonth & ": 3rd Qtr." Case "October", "November", "December" Debug.Print myMonth & ": 4th Qtr." End Select 技巧5—7:Case子句的多个条件 用来分隔开Case子句里面多个条件的逗号,和用于If语句里的OR运算符有相同的意义。只要这些条件中有 一个为真,Case子句就为真。 第 5 章 4B 使用 VBA 作判断 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 135 本章小结和下一章内容简介 在本章介绍的条件语句可以控制过程的走向。通过测试条件的真假,可以决定哪些语句需要执 行,哪些要跳过。换句话说,不必从上到下,一行一行地运行过程,可以只执行某些行。如果您不 确定应该使用哪种条件语句,这里是一些指南: „ 如果仅需要一个条件,简单的If…Then语句是最好的选择。 „ 如果需要决定执行两个操作中的一个,那么使用If…Then…Else语句。 „ 如果过程需要两个或多个条件,那么使用If…Then…ElseIf或者Select Case语句。 „ 如果过程有很多条件,那么就使用Select Case语句。这个语句比If…Then…ElseIf语句更灵活并 且更容易理解。 有些判断是需要重复的,例如,可能需要在工作表里的每个单元格中或者一个工作簿里的每个 工作表中重复同样的操作。下一章将教您如何一次又一次地执行同样的操作。 第6章 在VBA中重复操作 现在您已经学习了条件语句如何为VBA过程提供作出判断的 能力,下面进一步深入学习。不是所有的判断都是容易的。有时 需要运行一些语句好几次才能达成某个条件…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 138 共 612 页 现在您已经学习了条件语句如何为VBA过程提供作出判断的能力,下面进一步深入学习。不是 所有的判断都是容易的。有时需要运行一些语句好几次才能达成某个条件。然而,另一方面,当满 足条件后,只要条件为真或者直到某条件为真,都可能需要一直运行某些语句。在编程中,重复地 执行任务被称为循环。VBA有不同的循环结构,允许多次重复一系列的语句。在本章中,您将学习 如何循环代码。 6.1 Do循环: Do…While和Do…Until VBA有两种Do循环语句,只要一旦某条件为真或者直到某条件为真,就会重复执行一系列的语 句。 只要条件为真,Do…While循环就允许重复执行某个操作。该循环的语法如下: Do While 条件 语句1 语句2 语句N Loop 当VBA遇到这个循环时,首先检查条件的真假。如果条件为假,循环内部的语句就不会被执行, VBA将继续执行关键字Loop后面的第一条语句。如果条件为真,循环里面的语句则会被一条一条地 执行,直到遇到Loop语句。只要Do While语句里的条件为真,Loop语句就会告诉VBA重复这整个过 程。 现在,我们来看看如何在Excel里面好好利用Do…While循环语句。在第四章里,您学习了如何 根据一个单元格的内容来作出判断。让我们再进一步,看看如何在一系列单元格上作同样的判断。 该判断将给一列中的非空单元格设置粗体格式。 1. 打开一个新工作簿并命名为Chap06.xls。 2. 切换到VBE编辑器,并且将该新工程改名为Repetition(Chap06.xls)。 3. 在工程Repetition里插入一个新模块,并重命名为DoLoops。 4. 输入下面的过程: Sub ApplyBold() Do While ActiveCell.Value <>"" ActiveCell.Font.Bold = True ActiveCell.Offset(1, 0).Select Loop End Sub 5. 在单元格A1:A7里输入任意数据(文本或数字)。 6. 选择单元格A1。 7. 选择菜单“工具”-“宏”-“宏”。在“宏”对话框里,双击过程ApplyBold(或者选中该过程然 后单击执行)。 当运行ApplyBold过程时,VBA首先评估Do While语句里的条件——ActiveCell.Value<>””,该 条第 6 章 5B 在 VBA 中重复操作 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 139 件的意思是:只要当前单元格的值不是一个空字符串(””),就执行下面的语句。因为您已经在单元 格A1里输入了数据并且使该单元格为当前单元格(见上面的第六步),所以第一个测试结果返回真, VBA执行语句ActiveCell.Font.Bold = True,即给当前单元格设置粗体格式。接下来,VBA选择了下 一行的单元格(参见第二章里的Offset属性)。因为该语句之后就是关键字Loop,VBA返回到Do While 语句,并且再次检查条件。如果新选中的单元格不为空,那么VBA就会重复循环内部的语句。这个 过程会继续,直到检查到单元格A8的内容,此时,因为该单元格为空,测试条件的结果为假,因此, VBA就跳过循环内部的语句。由于在关键字Loop后面没有其它的语句了,所以该过程结束。 再看看另一个Do…While循环的例子。是不是很想知道如何在Excel的状态栏里显示今天的日期 和时间?这里有个例子,如何让它显示十秒钟: Sub TenSeconds() Dim stopme stopme = Now + TimeValue("00:00:10") Do While Now < stopme Application.DisplayStatusBar = True Application.StatusBar = Now Loop Application.StatusBar = False End Sub 在上面的过程里,只要函数Now返回的时间小于变量stopme的值,Do…While循环里的语句就 会被执行。变量stopme存储的值为当前时间加上十秒(参见在线帮助里使用内置函数TimeValue的其 它例子)。 语句Application.DisplayStatusBar告诉VBA开启状态栏的显示,下一条语句则是将当前日期和时 间放在状态栏上。当显示时间时(只显示10秒),用户无法使用系统(光标变成了沙漏形)。十 秒 钟 后(也 就是,当条件Now < stopme为真结束),VBA跳出循环并且执行关键字Loop后面的语句,该语句将 状态栏返回到默认信息“就绪”。 技巧6—1:什么是循环? 循环是一种导致一部分程序代码重复执行的编程结构。VBA提供了多种结构在过程里执行循环: Do…While、Do…Until,、For…Next、For…Each和While…Wend。 Do…While循环还有另外一种语法,可以在循环的底部测试条件: Do 语句1 语句2 语句N Loop While 条件 当在循环的底部测试条件时,循环里面的语句至少执行一次。看下面的例子: Sub SignIn() Dim secretCode As String Do secretCode = InputBox("输入您的密码:") If secretCode = "sp1045" Then Exit Do Loop While secretCode <> "sp1045" 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 140 共 612 页 End Sub 注意,在条件被测试时,VB已经执行了一次循环里的语句。除了将条件放在循环末尾外,过程 SignIn还示范了条件满足时如何退出循环。当Exit Do语句执行时,循环立即结束。 技巧6—2:避免无限循环 如果没有正确地设计循环,将导致一个无限循环——永无休止的循环。您将无法使用Esc键来停止该循环。 在下面的过程里,因为用户忘了放置测试条件而导致了永无休止的循环: Sub SayHello() Do MsgBox "Hello." Loop End Sub 必须按下Ctrl+Break键(译者:现在,有些电脑使用别的组合键来中断程序。例如我的手提电脑就是 Fn+Break)才能终止该无限循环。当VBA显示信息“代码执行被中断”时,单击“结束”按钮结束过程。 另外一种方便的循环Do…Until,也可以重复一条或多条语句,直到条件为真。换句话说,只要 当某个条件为假,Do…Until语句就重复一块代码。其语法为: Do Until 条件 语句1 语句2 语句N Loop 使用上面的语法,可以将前面的过程ApplyBold重新写成下面的方式: Sub ApplyBold2() Do Until IsEmpty(ActiveCell) ActiveCell.Font.Bold = True ActiveCell.Offset(1, 0).Select Loop End Sub 该过程的第一条语句的意思是,执行下列语句,直到遇到第一个空单元格。结果,如果当前单 元格不为空,VBA就执行循环内部的两条语句。只要条件IsEmpty(ActiveCell)测试的结果为假,这个 过程就反复执行。因为过程ApplyBold2在循环的开始就测试条件,如果第一个单元格为空,循环内 部的语句就不会运行。您将有机会在下节试验这个过程。 和Do…While循环类似,Do…Until循环也有第二种语法,可以在循环的底部测试条件: Do 语句1 语句2 语句N Loop Until 条件 第 6 章 5B 在 VBA 中重复操作 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 141 如果想要语句至少执行一次,那么就将条件放置于带有Loop语句的行,而无论条件的值是什么。 试试下面的例子,该例子将工作簿里的空工作表删除。 1. 在前面创建的DoLoops模块里输入下面的过程DeleteBlankSheets: Sub DeleteBlankSheets() Dim myRange As Range Dim shcount As Integer shcount = Worksheets.Count Do Worksheets(shcount).Select Set myRange = ActiveSheet.UsedRange If myRange.Address = "$A$1" And _ Range("A1").Value = "" Then Application.DisplayAlerts = False Worksheets(shcount).Delete Application.DisplayAlerts = True End If shcount = shcount - 1 Loop Until shcount = 1 End Sub 2. 手动在当前工作簿里面插入三个工作表。在一个工作表里的单元格A1中输入一些数据;在另一 个工作表的单元格B2和C10里输入一些数据;第三个工作表里不要输入任何数据。 3. 运行过程DeleteBlankSheets。 当运行该过程时,只要两个条件都为真——UsedRange属性返回单元格A1且A1为空,VBA就 会删除所选的工作表。UsedRange属性应用于Worksheet对象,包含工作表中的每个非空单元格以 及它们之间的空单元格。例如,如果在单元格B2和C10里输入数据(译者:包括格式),所使用的区域 为$B$2:$C$10。如果之后又在单元格A1里输入了数据,那么UsedRange将会是$A$1:$C$10。已使 用的区域是工作表中通过最左上角到最右下角非空单元格包围着的区域。 因为工作簿至少要包含一个工作表,所以代码执行到变量shcount等于1时就停止了。语句 shcount = shcount-1确保变量shcount在循环里面的代码每执行一次就减少1。变 量 shcount的值在过 程的开始处用下面的语句初始化: Worksheets.Count 注意,当删除工作表时,Excel通常会显示一个确认对话框,如果不想看到这个确认提示框,使 用下面的语句: Application.DisplayAlerts = False 当完成任务时,使用下列语句返回系统设置: Application.DisplayAlerts = True 技巧6—3:计数器 计数器是个数字变量,用来跟踪项目已进行的次数。上面的过程DeleteBlankSheets声明了变量shcount来 跟踪检查工作表的个数。计数器变量应该在程序的开始就被初始化(赋值),这可以确保总能在开始使用之前知道 计数器的确切值。计数器可以按照确定的值增加或减少。参见本章后面的使用计数器的For…Next循环。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 142 共 612 页 6.2 观察过程执行 当使用循环结构运行过程时,有时很难看到该过程是否按预期执行。有时候,您很想在慢速运 行情况下观察过程执行,以便于检查该程序的逻辑。我们来看看VBA如何可以一行一行地执行过程。 1. 在单元格区域A1:A5里面输入任何数据。 2. 选择单元格A1。 3. 在Excel窗口,选择“工具”-“宏”-“宏”。 4. 在“宏”对话框里,选择ApplyBold2过程并单击“单步执行”按钮。VBA编辑器屏幕将出现, 过程的名称被黄色加亮(参见图6-1)。注意代码窗口左边的黄色箭头。 图6-1:观察程序代码一行接一行地执行 5. 可以单击VBA标题栏中的“还原”按钮使窗口缩小一些,排列后的屏幕如图6-1所示。 6. 按下F8键,代码窗口中黄色加亮区跳到DoUntil IsEmpty(ActiveCell)行。 7. 继续按F8,同时观察代码窗口和工作表窗口。 6.3 While…Wend循环 While…Wend循环功能上和Do…While循环一样,它是从Microsoft Basic的早期版本中遗留下来 的,VBA保留它是为了向后兼容。该循环以关键字While开始以关键字Wend结束。语法如下: While 条件 语句1 语句2 第 6 章 5B 在 VBA 中重复操作 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 143 语句N Wend 条件在循环开始就被测试,只要提供的条件为真,这些语句就会被执行。一旦条件为假,VBA 就将退出该循环。我们来看一个使用While…Wend循环结构的过程示例: 1. 在当前工程里插入一个新模块,并重命名为WhileLoop。 2. 在WhileLoop模块中输入下面的过程: Sub ChangeRHeight() While ActiveCell <>"" ActiveCell.RowHeight = 28 ActiveCell.Offset(1, 0).Select Wend End Sub 3. 在单元格区域B1:B4里输入一些数据。 4. 选择单元格B1并且运行过程ChangeRHeight。 当前单元格不为空时,过程ChangeRHeight将行高设置为28。 6.4 For…Next 循环 当知道需要重复运行一组语句的次数时,可以使用For…Next语句。它的语法如下: For 计数器 = 开始 To 结束 [Step 步长] 语句1 语句2 语句N Next [计数器] 在括号里面的代码是可选的。计数器是存储存重复次数的数字型变量,开始是期望的起始计数 数字,结束则表明循环应该执行多少次。 例如,如果想要重复执行循环里的语句5次,则使用下面的For语句: For counter = 1 To 5 这里放置您的语句 Next 当VBA遇到关键字Next时,它将回到循环的开始处,并且再次执行循环里面的代码,直到计数 器到达结束值。一旦计数器的值大于关键字To后面的数值,VBA就会退出循环。因为计数器变量在 每次执行循环后会自动地变化,存储在它里面的值早晚会超出在结束中指定的值。 缺省情况下,每次VBA执行循环里的语句后,计数器变量的值会增加1,可以使用Step语句来改 变这个缺省设置。例如,要使计数器变量每次增加3,可以使用下面的语句: For counter = 1 To 5 Step 3 这里放置您的代码 Next counter 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 144 共 612 页 当VBA遇到上面的语句时,它会执行循环里的语句两次。在第一次的循环里,计数器等于1,第 二次则等于4(3+1)。在执行两次循环后,计数器就等于7(4+3),这导致VBA退出该循环。 注意,Step(步长)增量是可选的。可选语句总是显示则方括号里面(参见在本节开始部分的语法)。 一般不需要明确步长,除非它的值不等于1。可以在Step后面放置一个负值作为步长,那么VBA每次 遇到关键字Next后都会减小计数器的值。 关键字Next后面的变量名称(计数器)也是可选的。然而,在关键字Next后面加上计数器是好的编 程习惯。 如何在Microsoft Excel电子表格中使用For…Next循环呢?假使您只想在您的销售报告里面包括 某个特定月份的产品,当您从Microsoft Access表中导入数据时,同样也会将那些销售量为0的数据 行一起导入。如何快速除去数据为0的行呢?尽管有很多种方法可以解决这个问题,但让我们来看看 如何使用For…Next循环来处理它。 1. 在VBA窗口中,在当前工程里插入一个新模块并且重命名为ForNextLoop。 2. 在ForNextLoop模块里输入下面的过程: Sub DeleteZeroRows() Dim totalR As Integer Dim r As Integer Range("A1").CurrentRegion.Select totalR = Selection.Rows.Count Range("B2").Select For r = 1 To totalR-1 If ActiveCell = 0 Then Selection.EntireRow.Delete totalR = totalR – 1 Else ActiveCell.Offset(1, 0).Select End If Next r End Sub 3. 切换到Excel界面,并且准备下面的表格: A B 1 Product Name Sales (in Pounds) 2 Apples 120 3 Pears 0 4 Bananas 100 5 Cherries 0 6 Blueberries 0 7 Strawberries 160 4. 运行过程DeleteZeroRows。 我们来一行接一行地检查一下过程DeleteZeroRows。开头的两个语句计算当前单元格区域的总 行数,并且将该值存储在变量totalR中。接下来,VBA选择单元格B2然后遇到关键字For。因为电子 表格的第一行包含了列标题,所以要从总行数里减掉1(totalR-1)。VBA需要执行循环里面的指令6次。 嵌套在循环里面的条件语句(If…Then…Else)告诉VBA根据当前单元格的值作出判断。如果该值 为0,VBA就删除当前行并将总行数减1。否则,条件为假,VBA将选择下一行的单元格。每完成一 次循环,它就跳到关键字For来比较r的值和totalR-1的值。当过程结束后,销售表里就不会包含没有第 6 章 5B 在 VBA 中重复操作 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 145 销售的产品了。 技巧6—4:成对的语句 For和Next必须是成对的,如果漏掉一个,VBA就将产生一个错误信息“For没有Next”。 6.5 For Each…Next循环 当过程需要在一个集合中所有对象或者一个数组里的所有元素(数组将在第七章里涉及)中循环 时,应该使用For Each…Next循环。该循环不需要计数器变量,VBA可以判断应该执行多少次循环。 我们拿工作表集合作为例子。要删除工作簿里面的工作表,您必须首先选择它,然后选择“编 辑”-“删除工作表”。如果要只留一个工作表在工作簿里面,您就不得不使用同样的命令几次,次数 取决于工作表的总数。因为每个工作表都是工作表集合里的一个对象,所以可以通过使用For Each…Next循环来加速删除工作表。该循环的形式是: For Each 元素 In 组合 语句1 语句2 语句N Next [元素] 在上面的语法中,元素是一个变量,代表将要赋予的一个数组或者集合的所有元素。对于数组, 该变量必须为Variant数据类型;对于集合,则必须为Object数据类型。组合是集合的名称或者数组 的名称。 现在,我们来使用For Each…Next循环删除工作表。 1. 在当前工程里插入一个新模块并且重命名为ForEachNextLoop。 2. 在模块ForEachNextLoop里输入下面的过程: Sub RemoveSheets() Dim mySheet As Worksheet Application.DisplayAlerts = False Workbooks.Add Worksheets("Sheet2").Select For Each mySheet In Worksheets ActiveWindow.SelectedSheets.Delete Next mySheet End Sub 3. 运行过程RemoveSheets。 VBA将会打开一个新工作簿并且删除除Sheet1之外的所有工作表。注意,变量mySheet代表工 作表集合里的一个对象。除了按通常的方法将对象变量声明为Object类型外,还可以将它声明为更 具体的对象类型,这样会运行得更好。在这个特定的例子里,可以使用下面的声明: Dim mySheet As Worksheet 代替: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 146 共 612 页 Dim mySheet As Object 第一条指令Application.DisplayAlerts = False确保Excel在过程运行的时候不要显示警告和信 息。如果忽略该语句,Excel将会询问是否删除所选工作表。接下来,过程打开一个新工作簿并且选 择工作表Sheet2。For Each…Next循环遍历每个工作表(从所选的Sheet2开始)并且删除它们。当过 程结束时,该工作簿只剩一个工作表Sheet1。 下面是另外一个例子,该例子检查某个工作表是否存在于一个工作簿中: Sub IsSuchSheet() Dim mySheet As Worksheet Dim counter As Integer counter = 0 For Each mySheet In Worksheets If mySheet.name = "Sheet2" Then counter =counter + 1 End If Next mySheet If counter = 1 Then MsgBox "该工作簿包含Sheet2." Else MsgBox "没有找到Sheet2." End if End Sub 6.6 提前退出循环 有时候,您并不想等到循环自己结束,可能是用户输入了错误的数据、过程遇到了错误、或者 可能是任务已经完成并且没有必要作更多的循环。可以提前跳出循环,而不必等到条件正常结束。 VBA有两种Exit语句: „ Exit For语句用来提前退出For…Next或者For Each…Next循环。 „ Exit Do语句立即退出任何VBA Do 循环。 下面的过程示范了如何使用Exit For语句提前退出For Each…Next循环: 1. 在当前模块里输入下面的过程: Sub EarlyExit() Dim myCell As Range For Each myCell in Range("A1:H10") If myCell.Value = "" Then myCell.Value = "empty" Else Exit For End If Next myCell End Sub EarlyExit过程检查指定区域A1:H10里每个单元格的内容,如果当前单元格为空,VBA就会在当 前单元格里输入文本“empty”。当VBA遇到第一个非空单元格时,它就会退出循环。 2. 打开一个新工作簿并且在单元格区域A1:H10的任意单元格里输入数据。 3. 运行过程EarlyExit。 第 6 章 5B 在 VBA 中重复操作 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 147 技巧6—5:退出过程 如果想提前退出sub子过程,可以使用Exit Sub语句。如果该过程是一个函数,则使用Exit Function语句代 替。 6.7 嵌套的循环 到目前为止,您已经在本章里偿试了多种循环,每个过程演示了每种单独的循环结构的使用。 然而,在编程实践中,一个循环经常被放置在另一个循环中。VBA允许在同一过程里嵌套多种类型 的循环(For和Do循环)。当编写嵌套的循环时,必须确保每个内部的循环完全包含在外部的循环里。 另外,每个循环都必须有其自己独立的计数器变量。使用循环嵌套时,可以更有效地执行特定的任 务。 下面显示的过程ColorLoop演示了一个For…Next循环如何嵌套在另一个For…Next循环里: Sub ColorLoop() Dim myRow As Integer Dim myCol As Integer Dim myColor As Integer myColor = 0 For myRow = 1 To 8 For myCol = 1 To 7 Cells(myRow, myCol).Select myColor = myColor + 1 With Selection.Interior .ColorIndex = myColor .Pattern = xlSolid End With Next myCol Next myRow End Sub 上面的过程ColorLoop使用了两个For…Next循环来改变工作表中前面八行和七列里的每个单元 格的颜色。当外部的循环在跟踪行号的时候,内部的循环在做更多的事情,它首先确定当前的列号, 基于当前的行号的列号选择适当的单元格,然后给所选的单元格设置颜色。 内部的For…Next循环给工作表的第一行的七个单元格(A1、B1、C1、D1、E1、F1和G1)设置 不同的颜色。当变量myCol大于7时,VBA跳回外部循环并且变量myRow增加1,再回到内部循环去 设置下一行单元格的颜色。当过程结束时,56个单元格(8*7)被设置了当前调色板上可用的所有颜色。 第一个单元格A1被设置为黑色(颜色索引号为1),第二个单元格B1则被设置为白色(颜色索引号为2)。 每次单元格地址变化——Cells(myRow, myCol).Select——变量myColor的内容也会改变—— myColor = myColor + 1。 本章小结和下一章内容简介 在本章里,您学习了如何在循环里重复一组代码。当使用几种类型的循环时,看到了每种循环 执行重复的轻微差异。您有了经验后,将更容易地选择合适的控制结构来执行任务。 在本书的后续章节中,将会有更多使用循环的例子。例如,在下章里,您将看到如何使用数组 和嵌套的循环来创建一个VBA过程,该过程将帮您选择彩票号码。在下章里,您还将学习如何处理学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 148 共 612 页 大量的数据,而不会迷失在变量的海洋里。 第7章 利用VBA管理列表和数据表 在前面的章节里,您在很多 VBA 过程中使用变量来包含关于 对象、属性或数值的特定信息,属性或者数值。对于您想要过程 操纵的每个单独的数值,可以声明变量…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 150 共 612 页 在前面的章节里,您在很多VBA过程中使用变量来包含关于对象、属性或数值的特定信息,属 性或者数值。对于您想要过程操纵的每个单独的数值,可以声明变量。但是,假设有一系列的数值 呢?如果您必须编写VBA过程来处理大量的数据,则需要声明足够的变量来处理所有的数据。你能 想象将世界上所有国家的货币交换利率存储在您的程序中的噩梦吗?要创建一个包含必要数据的表 格,至少要给每个国家创建三个变量:国家名称、货币名称和交换比率。幸运的是,VBA有方法来 解决这个问题。通过将相关的变量归为一类,VBA过程可以轻松管理大量的数据。在本章里,您将 学习如何使用数组来操作列表和数据表。 7.1 理解数组 在VBA里,数组一种特殊类型的变量,代表拥有相同数据类型(字符串型、整型、货币型、日期 型、等等)的一组相似的数值。两种最常用的数组类型是一维数组(列表)和二维数组(表)。 有时,一维数组被称为列表。一维数组的例子有:购物列表、星期名称列表、或员工列表、或 数字列表。列表里面的每个值都有一个索引。下面是一个含有六个元素(项目)的列表的图解: 项目(1) 项目(2) 项目(3) 项目(4) 项目(5) 项目(6) 注意,代表一维数组的列当前为空。如果想用数据填充这个数组,只要使用一个变量名称后跟 一个带有数字的括号,而不是六个单个的标签。在上面的图解里,“项目”是一个变量名称,括号里 的数字——(1)、(2)、(3)、(4)、(5)和(6)——识别数组里的单独的元素。 数组的所有元素都必须具有相同的数据类型,换句话说,一个数组不能同时存储字符串和整型 数据。下面的图解是一维数组的两个例子:第一个称作cities的一维数组由文本组成(字符串数据类型 ——$),第二个称作lotto的一维数组则包含六个抽奖号码(整数数据类型——%)。 一维数组cities$(字符串数据类型) 一维数组lotto%(整数数据类型) Cities(1) Baltimore Lotto(1) 25 Cities(2) Atlanta Lotto(2) 4 Cities(3) Boston Lotto(3) 31 Cities(4) Washington Lotto(4) 22 Cities(5) New York Lotto(5) 11 Cities(6) Trenton Lotto(6) 5 正如您所看到的,每个数组元素的内容与变量的数据类型相匹配。如果想要在同一个数组里面 存储不同数据类型的数据,那么必须将数组声明为Variant。 二维数组是由行和列代表的数据表。表中每个元素的位置由它的行号和列号决定。下面是一个 空的二维数组的图解: 第 7 章 6B 利用 VBA 管理列表和数据表 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 151 行号 1 2 3 列号 1 (1,1) (1,2) (1,3) 2 (2,1) (2,2) (2,3) 3 (3,1) (3,2) (3,3) 4 (4,1) (4,2) (4,3) 5 (5,1) (5,2) (5,3) 注意,二维数组里的项目是如何由行和列索引指定的?在该图解里,数组里的第一个元素位于 第一行和第一列里(1,1),而最后一个元素则位于第五行和第三列里(5,3)。让我们来给该数组填充一 些数据。下面显示的二维数组存储了国家名称、它的货币名称以及和美元的汇率。 Japan (1,1) Japanese Yen (1,2) 128.2 (1,3) Mexico (2,1) Mexican Peso (2,2) 9.423 (2,3) Canada (3,1) Canadian Dollar (3,2) 1.567 (3,3) Norway (4,1) Norwegian Krone (4,2) 8.351 (4,3) Hungary (5,1) Hungarian Forint (5,2) 266.7 (5,3) 尽管VBA数组最大可以达到60维,但是,大多数人发现想象超过三维的数组是困难的。三维的 数组是一个具有相同行数和列数的表的集合。在三维数组里的每个元素由下面三个数据标识:行号、 列号和表号。 技巧7—1:数组变量是什么? 数组是拥有共同名称的变量的集合。一个典型的变量只能包含一个数据,然而,一个数组变量却能够存储 大量的单个数值。可以使用变量名称和索引号来指向数组中某个特定的值。 技巧7—2:下标变量 数组变量的括号里的数字称为下标,而每个单独的变量则称为下标变量或元素。例如,cities(6)是数组cities() 里的第六个下标变量(元素)。 7.2 声明数组 因为数组也是变量,所以必须用声明其它变量相似的方法声明数组——使用Dim语句。当声明 一个数组变量时,便设定了该数组储存数据所需要的内存空间。 我们来看看一些数组声明的例子: Dim cities(6) As String Dim daysOfWeek(7) As String Dim lotto(6) As Integer Dim exchange(5, 3) As Variant 注意,变量名称后面带有括号以及括号里有数字。一维数组要求括号里带一个数字,这个数字 决定了数组能够储存的最大元素数。二维数组后面总是带有两个数字——第一个数字是行索引号, 而第二个数字是列索引号。在上面的例子里,数组exchange最多可以包含15个值(5*3=15)。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 152 共 612 页 数组声明的最后一部分是数组将要存储的数据的数据类型定义。数组可以存储下面任何一种数 据类型:Integer、Long、Single、Double、Variant、Currency、String、Boolean、Byte、或Date。 当声明了一个数组,VBA会自动保留足够的内存空间,分配的内存空间取决于该数组的大小和 数据类型。当声明一个名为lotto带有6个元素的一维数组时,VBA将留出12个字节——数组的每个元 素各占2个字节(整型数据类型为2个字节,因此2*6=12)。数组越大,存储数据需要的内存空间就越 大。因为数组会耗尽很多内存并影响计算机的运行,因此,建议仅仅根据将要使用的元素数来声明 数组。 7.3 数组的上界和下界 缺省情况下,VBA指定索引号为0的元素为数组的第一个元素,因此,数字1代表数组中的第二 个元素,而数字2则代表第三个,依次类推。因为数字索引号起始于0,所以,一维数组cities(6)包含 从0到6的七个元素。如果愿意从1开始计数数组里的成员,那么可以使用Option Base 1语句来强制 指定该数组的下界。该指令必须置于VBA模块任何Sub语句前面的声明部分。如果在使用数组的过程 中不明确指定Option Base 1,那么VBA在使用数组时就会假定使用Option Base 0来从0开始编号数 组元素。 也可以让数组从除0或1之外的数字开始编号,此时,在声明数组变量时就必须明确该数组的边 界。数组的边界是指它的最小和最大的索引号。我们来看看下面的例子: Dim cities(3 To 6) As Integer 上面的语句声明了一个带有四个元素的一维数组。数组名称后面括号里的数字指定了数组的下 界(3)和上界(6)。该数组的第一个元素编号为3,第二个为4,第三个为5,第四个为6。注意下界和上 界之间的关键字To。 技巧7—3:数组的范围 Dim语句指定的数组下标区间称作数组的范围,例如:Dim mktgCodes(5 To 15)。 7.4 在VBA过程里使用数组 声明数组后,就必须给该数组的每个元素赋值,这经常称作“填充数组”。我们来偿试使用一维 数组有规划地显示六个美国城市的列表: 1. 打开一个新工作簿,并保存为Chap07.xls。s 2. 切换到VBE编辑器窗口,并重新命名VBA工程为Tables。 3. 在Tables(Chap07.xls)工程中插入一个新模块,并将该模块重新命名为StaticArrays。 4. 输入下面的过程FavoriteCities: ' 数组元素索引号从1开始 Option Base 1 Sub FavoriteCities() '声明数组 Dim cities(6) As String '给数组元素赋值 第 7 章 6B 利用 VBA 管理列表和数据表 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 153 cities(1) = "Baltimore" cities(2) = "Atlanta" cities(3) = "Boston" cities(4) = "Washington" cities(5) = "New York" cities(6) = "Trenton" '显示列表 MsgBox cities(1) & Chr(13) & cities(2) & Chr(13) _ & cities(3) & Chr(13) & cities(4) & Chr(13) _ & cities (5) & Chr(13) & cities(6) End Sub 在FavoriteCities过程开始之前,改变了数组缺省的索引编号方式,注意,Option Base 1语句Sub 语句之前模块窗口的顶部。该语句告诉VBA给数组的第一个成员赋值数字1,而不是缺省的0。 数组cities()声明为带有六个元素的字符串数据类型。然后,给数组的每个元素都赋值。最后的 语句使用Msgbox函数显示城市列表。运行该过程时,城市名称将会显示在单独的行上(参见图7-1)。 您可以通过变换索引号来改变所显示的数据的顺序。 图7-1:可以用Msgbox函数来显示一维数组的元素 5. 运行FavoriteCities过程并检查结果。 6. 修改FavoriteCities过程,让它逆序显示城市名称(从6到1)。 技巧7—4:数组元素的初始值 在给数组元素赋值之前,该元素具有缺省值。数字变量的缺省值为0,而字符串变量的缺省值为空字符串(“”)。 7.5 数组和循环语句 执行诸如填充数组或显示数组元素的任务时,您在第六章里学过的一些循环语句(参见 For…Next和For Each …Next循环)就变得非常方便了。现在是将您所学到的技巧结合起来使用的时 候了。如何重新编写FavoriteCities过程,让每个城市名称在单独的信息框里显示出来? 下面显示的过程FavoriteCities2将原过程的最后部分用For Each…Next循环来代替: Sub FavoriteCities2() '声明数组 Dim cities(6) As String Dim city As Variant '给数组元素赋值 cities(1) = "Baltimore" 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 154 共 612 页 cities(2) = "Atlanta" cities(3) = "Boston" cities(4) = "Washington" cities(5) = "New York" cities(6) = "Trenton" '在单独的信息框中显示城市列表 For Each city in cities MsgBox city Next End Sub 注意,For Each…Next循环使用的是Variant数据类型的变量city。回想在前面的章节里,For Each…Next可以遍历一个集合中所有的对象或者一个数组的所有元素,并且对每个对象或元素执行 同样的操作。当运行过程FavoriteCities2时,数组里有多少个元素,循环就会执行多少次。 我们来看一下过程FavoriteCities的另一种变化。在第四章里,您练习了将参数作为变量传递给 sub子过程和函数。过程FavoriteCities3演示了如何将数组元素传递给另一个过程。 1. 在当前模块里,输入下面两个过程: Sub FavoriteCities3() '声明数组 Dim cities(6) As String '给数组元素赋值 cities(1) = "Baltimore" cities(2) = "Atlanta" cities(3) = "Boston" cities(4) = "Washington" cities(5) = "New York" cities(6) = "Trenton" '调用另一个过程并将数组作为参数传递 Hallo cities() End Sub Sub Hallo (cities() As String) Dim counter As Integer For counter = 1 to 6 MsgBox "Hello " & cities(counter) Next End Sub 过程Hallo的声明里有一个数组类型的参数——cities()。 2. 运行过程FavoriteCities3。从一个sub子过程传递数组元素到另一个sub子过程或者函数过程可 以在许多过程里使用相同的数组,而不需要重复的程序代码。 技巧7—5:在过程之间传递数组 当在过程里声明数组时,它是局部的并且是不为其它过程所知。然而,通过在调用语句里写上后面紧跟一 对空括号的数组名称作为一个参数,可以将局部数组传递给其它的过程。例如,语句Hallo cities()调用一个名叫 Hallo的过程,并且将数组cities()传递给该过程。 这里有个例子,如何将新学到的关于数组和循环的知识运用到现实生活中。如果您是个狂热的 彩票玩家并厌倦了选择幸运号码,则可以让VBA帮您选择。下面的过程Lotto使用1到51中的六个数 字填充数组: 第 7 章 6B 利用 VBA 管理列表和数据表 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 155 Sub Lotto() Const spins = 6 Const minNum = 1 Const maxNum = 51 Dim t As Integer ‘外部循环中的变量 Dim i As Integer ‘内部循环中的变量 Dim myNumbers As String ‘存储选号的字符串 Dim lucky(spins) As String ‘存储产生的选号的数组 myNumbers = "" For t = 1 To spins Randomize lucky(t) = Int((maxNum-minNum+1) * Rnd )+ minNum '是否该数字已被选取 For i = 1 To (t-1) If lucky(t)=lucky(i) Then lucky(t) = Int((maxNum–minNum+1) * Rnd)+ minNum i = 0 End If Next i MsgBox "幸运号码是" & t & lucky(t) myNumbers = myNumbers & " –" & lucky(t) Next t MsgBox "幸运号码是" & myNumbers End Sub Randomize语句初始化随机数字生成器。指令Int((maxNum-minNum+1) * Rnd + minNum使用 函数Rnd来产生一个在指定的minNum和maxNum之间的随机数值。函数Int将随机数转变为一个整 数。除了给minNum和maxNum赋予常量之外,也可以使用函数InputBox从用户那里获得数据。 内部的For…Next循环确保每个选出的数字是唯一的——它不能是之前选出的任何一个数字。如 果忽略了内部循环并且多次运行该过程,很可能看到重复的号码。 7.6 使用二维数组 现在您已经知道了如何有计划地产生一个列表(一维数组),是到学习如何使用数据表的时候了。 下面的过程产生一个二维数组,该数组存储三个国家的国家名称、货币名称和交换汇率。 Sub Exchange() Dim t As String Dim r As String Dim Ex(3, 3) As Variant t = Chr(9) 'tab r = Chr(13) 'Enter Ex(1, 1) = "Japan" Ex(1, 2) = "Yen" Ex(1, 3) = 128.2 Ex(2, 1) = "Mexico" Ex(2, 2) = "Peso" Ex(2, 3) = 9.423 Ex(3, 1) = "Canada" Ex(3, 2) = "Dollar" Ex(3, 3) = 1.567 MsgBox "国家" & t & t & "货币" & t & "每美元" _ 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 156 共 612 页 & r & r _ & Ex(1, 1) & t & t & Ex(1, 2) & t & Ex(1, 3) & r _ & Ex(2, 1) & t & t & Ex(2, 2) & t & Ex(2, 3) & r _ & Ex(3, 1) & t & t & Ex(3, 2) & t & Ex(3, 3) End Sub 运行过程Exchange时,将看到一个信息框,显示三列信息(见图7-2)。 图7-2:显示在信息框上的文本是可以自定义格式的 7.7 静态和动态数组 到目前为止,本章介绍的都是静态数组。静态数组是具有确定大小的数组,当事先知道数组的 大小时使用静态数组,静态数组的大小在数组的声明语句里确定,例如,语句Dim Fruits(10) As String 声明了一个由10个元素组成的称作Fruits的静态数组。 但是,假如不确定数组里包含多少个元素呢?如果过程由用户的输入决定,那么每次程序执行 时,用户提供的元素数可能会变化。如何确保声明的数组不会浪费内存呢? 回想在声明数组后,VBA会留出足够的内存来存储数组。如果所声明的数组包含的元素比需要 的元素更多,将浪费计算机资源。这个问题的解决方法是使数组变为动态的。动态数组是大小可以 改变的数组。如果数组的大小每次都由程序运行而决定,就使用动态数组。 技巧7—6:固定大小的数组 静态数组包含固定元素数。静态数组的元素个数在它声明后不会改变。 要声明动态数组,就不要在数组名称后面的括号里放置数字: Dim Fruits( ) As String 动态数组通过在数组名称后面附带空括号来声明。在过程中使用动态数组之前,必须使用ReDim 语句来动态设置数组的上界和下界。ReDim语句随着程序代码的执行重新设定数组大小,ReDim语 句告知VBA数组的新大小,这个语句可以在同一个过程里多次使用。现在,我们来看看如何使用动 态数组。 1. 在当前工程里插入一个新模块并且将该模块重新命名为DynamicArrays。 2. 输入下面的过程DynArray: Sub DynArray( ) Dim counter As Integer '声明一个动态数组 Dim myArray( ) As Integer 第 7 章 6B 利用 VBA 管理列表和数据表 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 157 '指定数组的初始大小 Redim myArray(5) Workbooks.Add '用值填充数组 For counter = 1 to 5 myArray(counter) = counter +1 ActiveCell.Offset(counter-1, 0).Value = myArray(counter) Next '改变myArray的大小来包含10个元素 Redim Preserve myArray(10) '在myArray中添加新元素 For counter = 6 To 10 myArray(counter) = counter * counter With ActiveCell.Offset(counter-1, 0) .Value = myArray(counter) .Font.Bold = True End with Next counter End Sub 3. 将Excel窗口和VBE编辑器窗口并排显示。 4. 一步一步运行过程DynArray。可以将鼠标指针置于代码中间,然后按F8键执行每条语句。 DynArray过程的结果如下图所示。 图7-3:显示10个数据的数组 在过程DynArray里,Dim myArray() As Integer语句声明了一个称作myArray的动态数组。虽然 该语句声明了数组,但是没有分配任何内存给该数组。第一条ReDim语句指定了myArray的初始大小 并且占据了10个字节的内存来存储5个元素。正如您所知,每个整型数据需要两个字节的内存。语句 Workbooks.Add打开一个新工作簿,然后For…Next循环用数据填充数组myArray并且将数组元素写 入工作表。在循环开始之前,变量counter的值等于1。循环里的第一条语句: myArray(counter) = counter + 1 给myArray的第一个元素赋值2。第二条语句: ActiveCell.Offset(counter-1, 0).Value = myArray(counter) 将myArray的元素的当前值输入到活动单元格中。活动单元格为A1。因为变量counter等于1,所 以上面语句的结果如下: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 158 共 612 页 ActiveCell.Offset(1—1, 0).Value = myArray(1) 或者 ActiveCell.Offset(0,0).Value = myArray(1) 上面的语句在单元格A1里输入数据。循环里面的语句被执行5次。VBA在合适的工作表单元格里 输入数据并且进行到下一语句: ReDim Preserve myArray(10) 通常,当改变数组大小时,将失去该数组原来的所有值。语句ReDim单独重新初始化数组。 然而,可以通过在语句ReDim后面带上关键字Preserve将新元素加入到现存的数组中。换句话 说,关键字Preserve保证重新改变大小的数组不会弄丢现有的数据。如果忽略它,新数组将会是空 的。 第二个For…Next循环给数组myArray的第六、第七、第八、第九和第十个元素赋值。这次,数 组元素的值通过相乘获得:counter * counter。VBA使用粗体将数组其它的数值输入到合适的工作表 单元格里。 技巧7—7:确定数组大小 在使用数组之前,必须在Dim或ReDim语句里确定数组的大小。这意味着使用Dim或者ReDim语句声明数组 之前,不可以给数组元素赋值。 7.8 数组函数 可以通过五个VBA内置函数来操作数组:Array、IsArray、Erase、LBound和UBound。接下来 的章节将示范每个函数在VBA过程里的使用。 7.8.1 Array函数 Array函数允许在代码执行期间创建数组,而不必事先确定其大小。该函数总是返回一个Varant 类型的数组。使用函数Array,可以快速地将一系列数据放置在列表中。下面的过程CarInfo创建了一 个称作auto的固定大小、一维的、包括三个元素的数组。 1. 在当前工程里插入一个新模块,并将该模块重命名为Array_Function。 2. 输入下面的过程CarInfo: Option Base 1 Sub CarInfo() Dim auto As Variant auto = Array("Ford", "Black", "1999") MsgBox auto(2) & " " & auto(1) & ", " & auto(3) auto(2) = "4-door" MsgBox auto(2) & " " & auto(1) & ", " & auto(3) End Sub 另一个例子演示了如何使用Array函数在工作表中输入列标题: Sub ColumnHeads() Dim heading As Variant Dim cell As Range 第 7 章 6B 利用 VBA 管理列表和数据表 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 159 Dim i As Integer i = 1 heading = Array("First Name", "Last Name", "Position", _ "Salary") Workbooks.Add For Each cell in Range("A1:D1") cell.Formula = heading(i) i = i+1 Next Columns("A:D").Select Selection.Columns.AutoFit Range("A1").Select End Sub 7.8.2 IsArray函数 使用IsArray函数可以测试某个变量是否是数组。如果该变量是数组,那么IsArray函数返回True, 否则返回False。下面是一个示例: 1. 在当前工程里插入一个新模块,并重命名为IsArray_Function。 2. 输入下面的过程IsThisArray: Sub IsThisArray() '声明动态数组 Dim sheetNames() As String Dim totalSheets As Integer Dim counter As Integer '统计当前工作簿中的工作表数 totalSheets = ActiveWorkbook.Sheets.Count '指定数组的大小 ReDim sheetNames(1 To totalSheets) '输入并显示工作表名 For counter = 1 to totalSheets sheetNames(counter) = ActiveWorkbook.Sheets(counter).Name MsgBox sheetNames(counter) Next counter '检查是否确实是数组 If IsArray(sheetNames) Then MsgBox "The sheetNames是数组." End If End Sub 7.8.3 Erase函数 当想要清除数组里的数据时,应该使用Erase函数。该函数删除静态或动态数组里存储的所有数 据。此外,对于动态数组,Erase函数将重新分配原来分配给该数组的所有内存。如果过程必须再次 使用动态数组,必须使用ReDim语句指定数组的大小。下面的例子展示了如何删除数组cities里的数 据。 1. 在当前工程里插入一个新模块,并将该模块重命名为Erase_Function。 2. 输入下面的过程FunCities: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 160 共 612 页 ' 数组元素的索引号从1开始 Option Base 1 Sub FunCities() '声明数组 Dim cities(1 to 5) As String '给数组元素赋值 cities(1) = "Las Vegas" cities(2) = "Orlando" cities(3) = "Atlantic City" cities(4) = "New York" cities(5) = "San Francisco" '显示城市列表 MsgBox cities(1) & Chr(13) & cities(2) & Chr(13) _ & cities(3) & Chr(13) & cities(4) & Chr(13) _ & cities (5) Erase cities '显示所有已清除的 MsgBox cities(1) & Chr(13) & cities(2) & Chr(13) _ & cities(3) & Chr(13) & cities(4) & Chr(13) _ & cities (5) End Sub 函数Erase清除数组里的数据后,函数MsgBox显示一个空信息框。 7.8.4 LBound函数和UBound函数 LBound函数和UBound函数分别返回表示数组的下界和上界的数字。 1. 在当前工程里插入一个新模块,并将该模块重命名为L_and_UBound_Function。 2. 输入下面的代码FunCities2: Sub FunCities2() '声明数组 Dim cities(1 to 5) As String '给数组赋值 cities(1) = "Las Vegas" cities(2) = "Orlando" cities(3) = "Atlantic City" cities(4) = "New York" cities(5) = "San Francisco" '显示城市列表 MsgBox cities(1) & Chr(13) & cities(2) & Chr(13) _ & cities(3) & Chr(13) & cities(4) & Chr(13) _ & cities (5) '显示数组边界 MsgBox "下界: " & LBound(cities) & Chr(13) _ & "上界: " & UBound(cities) End Sub 当要确定一个二维数组的上下界时,就必须指定维数:1表示第一维,2表示第二维。 为了判断一个二维数组的上下界,在本章前面的Exchange过程后加上如下语句(将下面的代码加 入到关键字End Sub之前): 第 7 章 6B 利用 VBA 管理列表和数据表 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 161 MsgBox "下界(第一维)是" _ & LBound(Ex, 1) & "." MsgBox " 上界(第一维)是" _ & UBound(Ex, 1) & "." MsgBox "下界(第二维)是" _ & LBound(Ex, 2) & "." MsgBox " 上界(第二维)是" _ & UBound(Ex, 2) & "." 7.9 数组中的错误 使用数组时,很容易出错。如果试图给数组赋予比声明数组时更多的元素,VBA就会显示错误 信息“下标越界”。 图7-4:该错误出现于试图访问并不存在的数组元素时 假设声明了一个包含6个元素的一维数组,而试图给第八个元素赋值,当运行该过程时,VBA不 能找到第八个元素,所以显示上面的错误信息。单击调试按钮,VBA将高亮显示导致错误的代码行(见 图7-5)。检查数组的声明语句,并更改被加亮代码行括号里的索引号。 “下标越界”错误经常在使用循环的过程中被激发。下面的过程Zoo1就是这种情况的一个例子。 在用户取消在输入框里输入数据之前,循环里的语句反复被执行。在执行该过程时,当变量 i 等于4 的时候,VBA无法在这个只有三个元素的数组里找到第四个元素,因此错误信息就出现了。修改后 的过程Zoo2演示了如何使用前面章节里介绍的LBound和UBound函数避免试图访问不存在的数组元 素时所导致的错误。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 162 共 612 页 图7-5:当单击错误信息框里的“调试”按钮时,VBA就会加亮引发错误的语句 1. 在当前工程里插入一个新模块,并重命名为Errors_In_Arrays。 2. 输入下面的过程Zoo1和Zoo2: Sub Zoo1() '这个过程产生”下标越界”错误 Dim zoo(3) As String Dim i As Integer Dim response As String i = 0 Do i = i +1 response = InputBox("输入动物名:") zoo(i) = response Loop until response = "" End Sub Sub Zoo2() '这个过程避免”下标越界”错误 Dim zoo(3) As String Dim i As Integer Dim response As String i = 1 Do While i>=LBound(zoo) And i <=UBound(zoo) response = InputBox("输入动物名:") If response = "" Then Exit Sub zoo(i) = response i = i + 1 Loop For i = LBound(zoo) To UBound(zoo) MsgBox zoo(i) Next End Sub 另外一个使用数组时经常碰到的错误是类型不匹配。要避免这类错误,就要记住数组中的每个 元素都必须具有相同的数据类型。如果试图给数组元素赋予和声明数组的数据类型相矛盾的数据, 就将在执行代码时出现“类型不匹配”的错误。要在数组中包含不同数据类型的值,声明数组为Variant 类型。 第 7 章 6B 利用 VBA 管理列表和数据表 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 163 7.10 数组作为参数 在第四章里面,您学习了可以在sub子过程或者函数过程之间传递值作为必须的或者可选的参 数。如果传递的参数不是过程执行时一定需要的话,那么在这个参数名称前面加上关键字Optional。 然而,有时事先并不知道要传递多少个参数。 一个典型的例子就是加法。可能想要将两个数字相加,或者,也许要将3个、10个、或者15个数 字相加。使用关键字ParamArray,可以将一个由任意数量的元素组成的数组传递到sub子过程和函 数过程中。 下面的函数过程AddMultipleArgs将相加所需要的一些数字。该函数以数组myNumbers的声明开 始,注意关键字ParamArray的使用。该数组必须声明为Variant类型,并且它必须是过程定义里的最 后一个参数。 1. 在当前工程里插入一个新模块,并将该模块重命名为ParameterArrays。 2. 输入下面的函数过程AddMultipleArgs: Function AddMultipleArgs(ParamArray myNumbers() As Variant) Dim mySum As Single Dim myValue As Variant For each myValue in myNumbers mySum=mySum+myValue Next AddMultipleArgs = mySum End Function 3. 激活立即窗口来测试上面的函数,在立即窗口里输入下面的指令: ?AddMultipleArgs(1, 23.24, 3, 24, 8, 34) 按下回车键后,VBA返回括号中所有数字的总和:93.24。可以提供无限制的参数数目,要添加 更多的值,在括号中输入其它值并按下回车键。注意,每个参数之间要用逗号分隔。 本章小结和下一章内容简介 在本章里,您学习了通过创建数组,可以编写需要大量变量的过程。通过例子,示范了如何声 明和使用一维数组(列表)和二维数组(表)。您也学习了静态数组和动态数组之间的差别。本章的结尾 介绍了经常用于数组的五个VBA内置函数和关键字ParamArray。现在您已经知道了可以使程序更智 能的所有控制结构:条件语句、循环和数组。 在本书的后面,您将学习如何使用集合代替数组来操作更大量的数据。通过使用从第一章到第 七章学到的知识,您现在可以开始编写VBA过程让任务自动化,而这些在您开始学习本书之前似乎 是不可能的事情。接下来的一章将讲述如何有计划地管理文件和文件夹。 第8章 利用VBA操作文件和文件夹 在工作过程中,您肯定访问、创建、复制和删除过成百上千 的文件和文件夹。然而,您可能从未用程序执行过这些任务…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 166 共 612 页 在工作过程中,您肯定访问、创建、复制和删除过成百上千的文件和文件夹。然而,您可能从 未用程序执行过这些任务。所以,现在就是机会。本章侧重于介绍专门处理文件和文件夹的VBA函 数和指令。通过使用这些函数,您将能够: • 获取当前文件夹的名称(CurDir函数) • 更改文件或文件夹的名称(Name函数) • 检查硬盘中是否存在某文件或文件夹(Dir函数) • 获取某文件最后修改的日期和时间(FileDateTime函数) • 获取文件的大小(FileLen函数) • 检查和更改文件属性(GetAttr和SetAttr函数) • 更改缺省的文件夹或者硬盘驱动器(ChDir和ChDrive语句) • 创建和删除文件夹(MkDir和RmDir语句) • 复制和删除文件或文件夹(FileCopy和Kill语句) 此外,本章也介绍了在三种类型的文件中写入或者读取数据的知识:连续的、随机的和二进制 文件。除了使用Excel应用程序界面之外,您将学习如何直接处理文件。在本章的结尾,将给您介绍 通过利用称为Windows Scripting Host (WSH)的工具处理文件和文件夹的最新方法。 8.1 操作文件和文件夹 本节将介绍多种操作文件和文件夹的函数。 8.1.1 获取当前文件夹的名称(CurDir函数) 当处理文件时,经常会需要知道当前文件夹的名称,可以使用CurDir函数轻易地获取该信息: CurDir([drive]) Drive是一个可选参数,如果忽略它,VBA将使用当前驱动器(drive)。 CurDir函数返回一个Variant类型的文件路径。如果要返回字符串型的路径,则使用CurDir$(这里 的$是字符串的类型声明字符)。让我们在立即窗口里对这些函数的使用做些练习: 1. 打开一个新工作簿,并切换到VBE编辑器窗口。 2. 激活立即窗口并输入下面的代码: ?CurDir 当按下回车键后,VBA显示当前文件夹的名称,例如: C:\ 如果有第二个硬盘驱动器(或者光驱),可以获取D盘上的当前文件夹,例如: ?CurDir(“D:\”) 如果提供了一个并不存在的驱动器字母,VBA将显示下述错误信息:“设备不可用”。 3. 要存储当前驱动器名称到变量myDrive中,可以输入下面的指令: myDrive = Left(CurDir$,1) 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 167 当按下回车键后,VBA将当前驱动器的字母存储到变量myDrive中。 输入下面的指令并按回车键,可以检查变量myDrive的内容: ?myDrive 可以将上面的指令修改如下: myDrive = left(CurDir$,1) & ":" VBA将返回驱动器字母,后面带有一个冒号。 8.1.2 更改文件或文件夹的名称(Name函数) 使用函数Name可以重命名文件或者文件夹,例如: Name old_pathname As new_pathname Old_pathname是想要重命名的文件或文件夹的名称和路径,New_pathname则指定文件或文件 夹的新名称和位置。使用函数Name,可以将一个文件从一个文件夹移动到另外一个文件夹,但是, 不能够移动文件夹。 请在立即窗口里试试该函数(用文件的实际名称替换示例名称)。这里有些需要考虑的注意事项: • 在New_pathname里的文件名称不要指向已经存在的文件。 Name "C:\System.1st " As "C:\test.txt" 因为文件C:\test.txt已经存在于C盘中,VBA将显示错误信息:“文件已存在”,同样,如果要重 命名的文件不存在,就会出现“文件未找到”的错误信息。 • 如果New_pathname已经存在,并且和Old_pathname不同, Name函数在必要时将指定的文件 移动到新文件夹并且更改它的名称。 Name "C:\System.1st " As "D:\test.txt" 因为文件test.txt在D盘的根目录下并不存在,VBA将C:\System.1st移动到指定的驱动盘,然而, 并不重命名该文件。 • 如果New_pathname和Old_pathname指向不同的目录并且提供的文件名称相同,那么Name函 数将指定的文件移到新位置,不更改文件名。 Name "D:\test.txt " As "C:\DOS\test.txt" 上面的指令将test.txt移动到C盘下的DOS文件夹里。 技巧8—1:不能够重命名开启的文件 在重命名文件之前,必须关闭该文件。文件名称里不能包含通配符“*”或者“?”。 8.1.3 检查文件或文件夹是否存在(Dir函数) 返回文件或者文件夹名称的Dir函数的语法如下: Dir[(pathname[, attributes])] Dir函数的两个参数都是可选的,pathname是文件或文件夹的名称。对于参数attributes,可 以 使学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 168 共 612 页 用下面的常量或者数值之一: 表8-1:文件属性 常量 数值 属性名称 vbNormal 0 Normal 普通文件 vbHidden 2 Hidden 隐藏文件 vbSystem 4 System 系统文件 vbVolume 8 Volume label 卷标 vbDirectory 16 Directory or Folder 目录或文件夹 Dir函数常用来检查某个文件或文件夹是否存在于磁盘中,如果不存在,那么就返回空字符串(””)。 我们到立即窗口里试验几个Dir函数的练习: 1. 在立即窗口中,输入下面的指令: ?Dir("C:\", vbNormal) 一旦按下回车键,VBA就会返回该指定文件夹下的第一个文件名。普通文件(vbNormal)就是除 隐藏、卷标、目录、文件夹或系统文件属性之外的任何文件。 要返回当前目录下的其它文件名称,就使用不带参数的Dir函数: ?Dir (并且按回车键) 2. 在立即窗口里输入下面的指令,并且在按回车键后检查其结果: mfile = Dir("C:\", vbHidden) ?mfile mfile = Dir ?mfile mfile = Dir ?mfile 3. 在立即窗口中输入下面的指令: If Dir("C:\stamp.bat") = "" Then Debug.Print "文件未找到。" 因为stamp.bat文件不在C盘上,所以VBA就在立即窗口里写下特定的文本信息“文件未找到。” 4. 在立即窗口中输入下面的语句,可以检查某文件是否存在于某驱动盘上: If Dir ("C:\Autoexec.bat") <>"" Then Debug.Print "该文件不在C盘上。" 函数Dir允许在指定的文件路径名中使用通配符——星号(*)代表多个字符,问号(?)代表单个字 符。例如,要在WINDOWS文件夹中查找所有配置设置的文件,可以查找所有的INI文件,如下: ?Dir("C:\WINNT\*.ini", vbNormal) system.ini ?dir WIN.INI ?dir WINFILE.INI ?dir control.ini ?dir EQUIP32.INI ?dir sxpwin32.ini 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 169 下面显示的过程在立即窗口里写上了指定目录下的文件名称。函数LCase$让文件名称显示为小 写字母。 1. 打开一个新工作簿,并保存为Chap08.xls。 2. 切换到VBE编辑器窗口并重命名VBA工程为FileMan。 3. 插入一个新模块,并将该模块重命名为DirFunction。 4. 在代码窗口中输入下面的VBA过程: Sub MyFiles() Dim mfile As String Dim mpath As String mpath = InputBox("输入路径名,例如,C:\Excel") If Right(mpath, 1) <> "\" Then mpath = mpath & "\" mfile = Dir(mpath & "*.*") If mfile <> "" Then Debug.Print "文件在" & mpath _ & "文件夹中" Debug.Print LCase$(mfile) If mfile = "" Then MsgBox "文件未找到." Exit Sub End If Do While mfile <> "" mfile = Dir Debug.Print LCase$(mfile) Loop End Sub 上面的myFiles过程询问用户文件路径名。如果该路径结尾没有反斜杠,函数Right就会将反斜杠 附加在路径名字符串上。接下来,VBA在该指定的文件夹里搜索所有文件(*)。如果没有文件,显示 相应的信息;如果文件存在,那么文件名就会被写入立即窗口。 5. 在同一个模块里输入另外一个过程: Sub GetFiles() Dim nfile As String Dim nextRow As Integer '下一行号 nextRow = 1 With Worksheets("Sheet1").Range("A1") nfile = Dir("C:\", vbNormal) .Value = nfile Do While nfile <> "" nfile = Dir .Offset(nextRow, 0).Value = nfile nextRow = nextRow + 1 Loop End With End Sub 过程GetFiles获取C盘根目录下的所有文件名并且将每个文件名写入工作表中。 8.1.4 获取文件修改的日期和时间(FileDateTime函数) 如果过程需要检查某文件最后修改的时间,可以使用FileDateTime函数: FileDateTime(Pathname) 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 170 共 612 页 Pathname是一个字符串,指定想要处理的文件,可以包括驱动器和文件夹的名称。该函数返回 某文件的日期和时间印记。日期和时间的格式取决于Windows控制面板的区域设置。 我们在立即窗口里来练习使用该函数: 1. 在立即窗口里输入: ?FileDateTime("C:\config.sys") 按回车键后,VBA返回下面格式的日期和时间: 5/4/2001 10:52:00 AM 要单独返回日期和时间,可以将函数FileDateTime作为函数DateValue或TimeValue的参数来使 用。例如: ?DateValue(FileDateTime("C:\config.sys")) ?TimeValue(FileDateTime("C:\config.sys")) 2. 在立即窗口里输入下面的语句: If DateValue(FileDateTime("C:\config.sys"))< Date then Debug.Print "该文件今天没有被修改.” Date函数返回在Windows控制面板的日期/时间对话框里设定的当前系统日期。 8.1.5 获取文件的大小(FileLen函数) 如果需要检查某文件是否能够保存在某磁盘上(即该盘能否容纳下这个文件),那么应该按照下面 的方式使用FileLen函数: FileLen(Pathname) Pathname是一个字符串,指定想要处理的文件,可以包括驱动器和文件夹的名称。FileLen函数 以字节的方式返回文件的大小。如果该文件已打开,那么VBA将返回该文件最后一次保存时的大小。 假设想要获取Windows目录下进行配置设置的所有文件的总大小: 1. 在当前工程里插入一个新模块,并将该模块重命名为FileLenFunction。 2. 在代码窗口中输入过程TotalBytesIni: Sub TotalBytesIni() Dim iniFile As String Dim allBytes As Long iniFile = Dir("C:\WINDOWS\*.ini") allBytes = 0 Do While iniFile <> "" allBytes = allBytes + FileLen("C:\WINDOWS\" & iniFile) iniFile = Dir Loop Debug.Print "Total bytes: " & allBytes End Sub 8.1.6 返回和设置文件的属性(GetAttr函数和SetAttr函数) 文件和文件夹具有诸如“只读”、“隐藏”、“系统”和“档案”的特点,这些特点就是属性。可 以使用GetAttr函数来获得文件或文件夹的属性。该函数的唯一参数就是想处理的文件或文件夹的路 径名: 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 171 GetAttr(Pathname) 上面的函数返回一个整数,代表下表显示的常量中一个或多个常量之和。 表8-2:文件和文件夹属性 常量 数值 属性名称 vbNormal 0 普通文件(没有设置其它属性) VbReadOnly 1 不可修改的文件或文件夹 vbHidden 2 在普通设置下不可见的文件或文件夹 vbSystem 4 系统文件 vbDirectory 16 对象为一个目录 vbArchive 32 档案(在最后一次备份后,该文件已被修改) 要知道某文件是否具有上述属性,可以使用AND运算符将GetAttr函数的结果与常量数值相比较。 如果函数返回一个非零数值,那么该指定路径的文件或文件夹具有所测试的属性。 C:\MsDos.sys的属性是什么呢?可以在立即窗口里快速地获得: ?getattr("C:\MsDos.sys") AND vbReadOnly 1 ?getattr("C:\MsDos.sys") AND vbHidden 2 ?getattr("C:\MsDos.sys") AND vbSystem 4 ?getattr("C:\MsDos.sys") AND vbArchive 32 现在,我们将这些信息一起放在一个过程里: 1. 插入一个新模块,并将该模块重命名为GetAttrFunction。 2. 输入下面的过程GetAttributes: Sub GetAttributes() Dim attr As Integer Dim msg As String attr = GetAttr("C:\MSDOS.SYS") msg = "" If attr AND vbReadOnly Then msg = msg & "Read-Only (R)" If attr AND vbHidden Then msg = msg & Chr(10) & "Hidden (H)" If attr AND vbSystem Then msg = msg & Chr(10) & "System (S)" If attr AND vbArchive Then msg = msg & Chr(10) & "Archive (A)" MsgBox msg, , "MSDOS.SYS" End Sub 3. 当运行上面的过程时,将看到如图8-1所示的信息框。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 172 共 612 页 图8-1:使用GetAttr函数可以获得任何文件的属性 GetAttr函数的反函数是SetAttr函数,它允许设置一个文件或文件夹的属性。其语法如下: SetAttr Pathname,Attributes Pathname是一个字符串,指定要处理的文件或文件夹。第二个参数Attributes是一个或多个要设 置的属性常量。参见本章前面的表8-1中的常量列表。 假设有一个称作“C:\stamps.txt”的文件,并且要设置两个属性:“只读”和“隐藏”。在立即窗 口里输入下面的指令来设置文件属性(可以找一个硬盘上存在的文件代替“C:\stamps.txt”): SetAttr "C:\stamps.txt", vbReadOnly + vbHidden 技巧8—2:调用SetAttr语句 不能给打开的文件设置属性,在使用SetAttr函数之前,必须关闭该文件。 8.1.7 更改缺省的文件夹或硬盘驱动器(ChDir语句和ChDrive语句) 使用ChDir语句可以轻易更改缺省文件夹,例如: ChDir Path 在上面的语句中,Path是新的缺省文件夹名称。Path可以包含磁盘驱动器的名称。如果Path没 有包括驱动器名称,那么缺省文件夹将会更改为当前驱动器。当前驱动器不变。 假设缺省文件夹为“C:\DOS”,语句: ChDir "D:\MyFiles" 将缺省文件夹更改为“D:\MyFiles”,然而,当前驱动器仍然是C盘。 要更改当前驱动器,应该按下面的格式使用ChDrive语,: ChDrive drive Drive参数指定新的缺省驱动器字母。例如,在立即窗口里输入下面的指令将缺省驱动器设置为 D驱或者E驱: ChDrive "D" 或者 ChDrive "E" 如果指向一个并不存在的驱动器,就会看到一个信息框“设备不可用”。 8.1.8 创建和删除文件夹(MkDir语句和RmDir语句) 依照下面的MkDir语句语法,可以创建一个新文件夹: MkDir Path Path指定要创建的新文件夹名称。如果没有写驱动器的名称,VBA将在当前驱动器上创建新文 件夹。现在,我们来看几个例子: 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 173 1. 在立即窗口里输入指令,在C盘上创建一个名为“Mail”的文件夹: MkDir "C:\Mail" 2. 将缺省文件夹更改为“C:\Mail”: ChDir "C:\Mail" 3. 获取当前文件夹的名称: ?CurDir 使用RmDir函数来删除不需要的文件夹。该函数的语法如下: RmDir Path Path指定要删除的文件夹名称,Path可以包含驱动器名称。如果忽略了驱动器名称,那么VBA 就会试图删除当前驱动下存在的具有相同名称的文件夹;否则,VBA将显示错误信息:“路径未找到”。 4. 删除刚才创建的文件夹C:\Mail: RmDir "C:\Mail" 技巧8—3:RmDir移除空文件夹 如果文件夹包含文件,则不可以该文件夹删除它(使用RmDir)。应该先用Kill语句(在本章后面介绍)删除这些 文件。 8.1.9 复制文件(FileCopy语句) 使用FileCopy语句,可以在文件夹之间复制文件: FileCopy source,destination 该语句的第一个参数source指定要复制的文件名称,该名称可以包含驱动器名称。第二个参数 destination是复制的目的地,可以包括驱动器和文件夹的名称。两个参数都是必需的。 假设要将用户指定的一个文件复制到一个名为“C:\Abort”的文件夹中,可以使用下面的过程: Sub CopyToAbort() Dim folder As String Dim source As String Dim dest As String Dim msg1 As String Dim msg2 As String Dim p As Integer Dim s As Integer Dim i As Long On Error GoTo ErrorHandler folder = "C:\Abort" msg1 = "所选文件已存在于文件夹中." msg2 = "复制到" p = 1 i = 1 ' 从用户获取文件名 source = Application.GetOpenFilename ' 如果取消则不做任何操作 If source = "False" Then Exit Sub ' 获取source变量中反斜杠总数 Do Until p = 0 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 174 共 612 页 p = InStr(i, source, "\", 1) If p = 0 Then Exit Do s = p i = p + 1 Loop '创建目标文件名 dest = folder & Mid(source, s, Len(source)) '使用该名称创建新文件夹 MkDir folder ' 检查是否指定的文件已存在于目标文件夹中 If Dir(dest) <> "" Then MsgBox msg1 Else '复制所选文件到C:\Abort文件夹中 FileCopy source, dest MsgBox source & " " & msg2 & " " & dest End If Exit Sub ErrorHandler: If Err = "75" Then Resume Next End If If Err = "70" Then MsgBox "不可以复制已打开的文件." Exit Sub End If End Sub 过程CopyToAbort使用了Excel应用程序的方法GetOpenFilename从用户那里获取文件名称。该 方法导致弹出内置的“打开”对话框。使用该对话框,可以在任何驱动器的任何文件夹里选择任何 文件。如果用户“取消”该对话框,VBA就返回值“False”并且程序结束。如果用户选取了某个文 件并且单击“打开”,那么该选中的文件就会赋值到变量source。因为复制的目的仅需要文件名称(而 无须路径名),所以Do…Until循环用来找到最后一个反斜杠(“\”)在变量source里的位置。 接下来,VBA给FileCopy语句的第二个参数准备了一个字母字符串,并且将其赋值到变量dest。 该变量存储的字符串是目标文件夹(C:\Abort)和用户指定的文件名前面加反斜杠所连接起来的。如果 C:\Abort文件夹不存在,则MkDir函数创建了一个名为C:\Abort的新文件夹。如果该文件夹已存在, 那么VBA就需要处理错误75。这个错误会被在程序后面的错误处理代码捕获。注意,错误处理器是 一段代码片断,以带冒号的标签ErrorHandler开始。 当VBA遇到Resume Next语句时,就会继续执行过程里面导致错误的代码行下面的代码。这意 味着语句MkDir folder不会被执行。在这之后,程序将检查所选择的文件是否已经存在于目标文件夹 中。如果文件已经存在,那么用户将收到存储于变量msg1里面的信息;如果文件不存在于目标文件 夹中并且该文件当前没有被打开,VBA就会将文件复制到指定的文件夹,并且用相应的信息通知用 户。如果该文件被打开,VBA将遇到运行时错误70,并因此而运行ErrorHandler里面的相应指令。 1. 在一个名为FileCopyStatement的新模块里输入过程CopyToAbort。 2. 运行该程序几次,每次从不同的文件夹里选择文件。 3. 试着复制该程序之前复制过的文件到文件夹C:\Abort中。 4. 打开某个文件,并且在其开着的情况下试图用过程CopyToAbort来复制它。 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 175 5. 运行本章前面的过程MyFiles,在立即窗口里列出文件夹C:\Abort里面的内容。 注意,不要删除文件夹C:\Abort和已复制以该文件夹中的文件,您将在下一节里面使用一个名为 RemoveMe的VBA过程来同时删除该文件夹和文件。 8.1.10 删除文件(Kill语句) 您已经从前面的章节里知道了不能删除含有文件的文件夹,要从文件夹里面删除文件,可以使 用下面的Kill语句: Kill Pathname Pathname指定想要删除的一个或多个文件的名称,同时也可以将驱动器和文件夹名称包括在其 中。可以在Pathname参数里使用通配符(*或?)来快速删除文件,不能删除已打开的文件。 如果您已作了前一节的练习,那么现在您的硬盘上应该含有包括几个文件的文件夹C:\Abort。在 下面的练习里,您将首先删除文件夹Abort里面的所有文件,然后再删除文件夹本身: 1. 在当前工程里插入一个新模块,并将该模块重命名为KillStatement。 2. 在过程RemoveMe里输入下面的代码: Sub RemoveMe() Dim folder As String Dim myFile As String ‘给文件夹变量赋文件夹名,注意结尾处的反斜杠”\” folder = "C:\Abort\" myFile = Dir(folder, vbNormal) Do While myFile <> "" Kill folder & myFile myFile = Dir Loop RmDir folder End Sub 3. 运行过程RemoveMe,当程序运行结束后,单击Windows浏览器看看该文件夹是否已经被删除。 8.1.11 从文件写入和读取数据(Input/Output) 您已经从前面的章节里知道了如何使用VBA打开一个电子表格,例如,下面的指令: Application.Workbooks.Open Filename:= "C:\Excel\Report.xls" 打开位于文件夹C:\Excel里面的文件Report.xls。除了在专门的应用程序里打开文件之外,如果 想要创建能够打开其它类型文件的VBA过程并处理它们的内容,那就应该学习一些关于被称作底层 文件I/O(input/output)。接下来关于顺序、随机和二进制文件的章节将会带你直接接触这方面的内容。 8.2 文件访问类型 计算机使用的文件类型有三种: • 顺序访问文件是指按存储的顺序获取数据的文件,例如以CSV格式(逗号分割的文本)、TXT格式 (以Tab键分割的文本)、或者PRN格式(以空格分隔的文本)存储的文件。顺序文件访问经常用来学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 176 共 612 页 写文本文件,例如错误日志、配置设置和报告。顺序文件有下列模式:Input、Output和Append, 模式决定了文件打开后如何处理它。 • 随机访问文件是文本文件,它的数据以同等长度的记录存储并通过逗号分割字段。随机访问文件 只有一个模式——Random。 • 二进制访问文件是图形文件和其它非文本文件。二进制文件只能够在Binary模式下访问。 8.2.1 处理顺序文件 您的计算机硬盘上有成百上千个顺序文件。配置文件、错误日志、HTML文件以及所有类型的无 格式文本文件都是顺序文件。这些文件以字符的顺序在硬盘上存储。新文本行的开始以两个专门的 字符表示,一个称作carriage return(回车),另一个称作line feed(换行)。当处理顺序文件时,从文件 的开头开始,一个字符一个字符的向前移动,一行接一行,直到文件的结尾。顺序文件容易打开和 操作,任何文本编辑器都可以。 技巧8—4:什么是顺序文件? 顺序文件就是访问它里面的记录时必须按它占据的顺序进行的文件,这意味着在您可以访问第三个记录之 前,必须先访问第一个记录,接着是第二个记录。 技巧8—5:使用Open语句打开文件 当使用顺序访问来打开一个文件时,该文件必须已经存在。 读取存储在顺序文件里的数据 我们利用一个已经在您计算机中的顺序文件,在Excel VBE编辑器窗口直接使用VBA来读取它的 内容。您可以读取Autoexec.bat文件或任何其它您想读取的文件。要从一个文件中读取数据,必须先 使用Open语句打开该文件。下面是该语句的通用语法,以及每个构成元至少的说明: Open pathname For mode [Access access][lock] As [#]filenumber [Len=reclength] Open语句有三个必需的参数,它们是pathname、mode和filenumber。在上面的语法里,这三 个参数前面都有用粗体显示的关键字。 • Pathname是想要打开的文件名称。 • Pathname可以包括驱动器和文件夹名称。 • Mode是决定文件如何打开的关键字。顺序文件可以以下列模式之一来打开:Input、Output或 Append。使用Input读取文件;Output通过覆盖任何已存在的文件写文件;Append通过在任何 已存在的信息中添加来写入文件。 • Access是决定文件读写许可的关键字,Access可以是:Shared(共享)、Lock Read(锁定读)、Lock Write(锁定写)或Lock Read Write(锁定读写)。 • Lock决定了哪些文件的操作是允许其它过程进行的。例如,如果某文件是在网络环境下打开的, lock决定了其他人如何访问它。下列lock关键字是可用的:Read、Write或者Read Write。 • Filenumber是从1到511的数字,该数字用来指向顺序操作中的文件。通过使用VBA内置函数 FreeFile,可以获得一个唯一的文件号码。 • Open语句里的最后一个元素reclength指定顺序文件里总字符数,或者是随机访问文件里记录的第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 177 大小。 考虑一下前面的例子,为了读取数据而打开C:\Autoexec.bat或者其它顺序文件,应该使用下面 的指令: Open "C:\Autoexec.bat" For Input As #1 如果某文件为输入打开,那么仅能从中读取数据。在打开一个顺序文件后,就可以使用下面的 语句读取它的内容:Line Input #或者 Input # 或者使用Input函数。 逐行读取文件 使用下面的语句来逐行读取Autoexec.bat或者其它任何顺序文件里的内容: Line Input #filenumber, variableName #filenumber是用Open语句打开文件时使用的文件号,variableName是个String或者Variant变 量,用来存储读取的行。 Line Input #语句读取一个打开的顺序文件里的一行并且存储在一个变量里。记住,Line Input # 语句一次读取顺序文件里的一个字符,直到遇到回车字符(Chr(13))或者回车-换行字符(Chr(13) & Chr(10))。这些字符在读取过程中返回的文本里是会忽略掉的。 下面的过程ReadMe演示了如何使用Open和Line Input #语句逐行读取Autoexec.bat文件的内 容。试试用同样的方法来读取其它顺序文件。 1. 在当前工程里插入一个新模块并重命名为SeqFiles。 2. 输入下面的过程ReadMe: Sub ReadMe() Dim rLine As String Dim i As Integer ' 行号 i = 1 Open "C:\Autoexec.bat" For Input As #1 '在循环里直到过程结束 Do While Not EOF(1) Line Input #1, rLine MsgBox "行" & i & " 在Autoexec.bat中读取: " _ & Chr(13) & Chr(13) & rLine i = i + 1 Loop MsgBox i & "行被读取." Close #1 End Sub 3. 通过按F8键,逐句运行该过程。 为了读取内容,过程ReadMe将文件Autoexec.bat在模式Input里作为文件号码1打开。Do…While 循环告诉VBA一直执行循环里面的语句,直到到达文件结尾。文件的结尾由函数EOF的结果决定。 当下个要读取的字符已经过了文件结尾时,EOF函数返回逻辑值True。注意,EOF函数需要一 个参数——要检查的打开的文件号码,与前面Open语句使用的数字相同。使用EOF函数来确保VBA 不会超出文件的结尾。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 178 共 612 页 Line Input # 语句在变量rLine里存储每行的内容,然后,信息框显示行号和它的内容。接下来, 如果函数EOF的结果为假(还未到达文件结尾处),VBA给行计数器增加1,并且开始读取打开文件的 下一行。当函数EOF结果为真时,VBA退出循环。在VBA过程结束前,还会再运行两条语句,显示 读取行的总数,以及关闭该打开的文件。 从顺序文件中读取字符 假设您的过程需要检查文件Autoexec.bat里出现了多少个冒号,可以使用函数Input来返回指定 的字符数,而不必读取整行。接下来,使用If语句来比较获取的字符和寻找的字符。在编写处理这项 任务的过程之前,先看看函数Input的语法: Input(number, [#]filenumber) Input函数的两个参数都是必需的,number指定想要读取的字符数,而filenumber是与用来打开 文件的Open语句的数字相同。Input函数返回读取的所有字符,包括逗号、回车、文件结束标记、引 号和前导空格。 1. 在SeqFile模块里输入下面的过程Colons: Sub Colons() Dim counter As Integer Dim char As String counter = 0 Open "C:\Autoexec.bat" For Input As #1 Do While Not EOF(1) char = Input(1, #1) If char = ":" Then counter = counter + 1 End If Loop If counter <> 0 Then MsgBox "找到字符: " & counter Else MsgBox "没有找到指定的字符." End If Close #1 End Sub 2. 逐句执行该过程。 3. 将冒号换成其它想寻找的字符并重新执行该程序。 Input函数允许从顺序文件中返回任何字符。如果使用VB函数LOF作为Input函数的第一个参数, 则能够快速地读取顺序文件里的内容,而不需要遍历整个文件。LOF函数返回文件里的字节数。每 个字节对应文本文件里的一个字符。过程ReadAll展示了如何将文件System.ini的内容读取到立即窗 口里: Sub ReadAll() Dim all As String Open "C:\WINNT\System.ini.bat" For Input As #1 all = Input(LOF(1), #1) Debug.Print all Close #1 End Sub 除了将文件内容打印到立即窗口中之外,还可以将其读取到放置在工作表中的文本框里(见图8第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 179 -2),程序如下: Sub WriteToTextBox() Dim mysheet As Worksheet Set mysheet = ActiveWorkbook.Worksheets(1) On Error GoTo CloseFile Open "C:\WINNT\System.ini" For Input As #1 mysheet.Shapes(1).Select Selection.Characters.Text = Input(LOF(1), #1) CloseFile: Close #1 End Sub 在运行上面的过程之前,在Chap08.xls工作簿上的工作表Sheet1中画一个文本框。注意,On Error GoTo CloseFile语句激活错误捕捉,如果错误在程序的执行过程中发生,就会立即跳到 CloseFile标签处,Close #1语句无论有无错误发生都会被执行。 图8-2:文件System.ini的内容显示在工作表中的文本框里 读取分隔文本文件 在某些文本文件中(文件通常保存为CSV、TXT或PRN格式 ),输入在每个文本行中的数据由逗 号、Tab或者空格分隔。这种类型的文件用Input # 语句读取可以比前面介绍的Line Input #语句更快 些。Input #语句允许从一个打开的文件中读取数据到几个变量中,该函数如下所示: Input #filenumber, variablelist filenumber与Open语句打开文件时的号码相同,variablelist是一个以逗号分隔的变量列表,用来 存储读取的数据。不能使用数组或对象变量,然而,可以使用用户定义的变量 (这种变量类型将在 本章后面介绍)。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 180 共 612 页 下面是一个用逗号分隔数据的顺序文件的例子: Smith,John,15 Malloney,Joanne,28 Ikatama,Robert,15 要读取这种方式格式的文本,必须给每个数据指定一个变量:姓、名和年龄。 1. 打开一个新工作簿,并输入下面的数据: 2. 以CSV格式(逗号分隔)将文件保存为C:\Winners。Excel将显示一条信息,选定的文件类型不支 持包含多份工作表的工作簿。单击“确定”只保存当前工作表。 3. 输入下面的过程Winners: Sub Winners() Dim lname As String, fname As String, age As Integer Open "C:\Winners.csv" For Input As #1 Do While Not EOF(1) Input #1, lname, fname, age MsgBox lname & ", " & fname & ", " & age Loop Close #1 End Sub 4. 在运行过程Winners之前,要确保该文件在指定的路径下,或者在程序里指定文件Winners.csv 文件在硬盘中的实际确位置。 上面的程序打开文件Winners.csv读取数据,并建立了一个Do…While循环,遍历整个文件直到 文件的结尾。Input #1语句用来将每行的内容读取到三个变量:lname、fname和age,然后,一个信 息框显示这三个变量的内容。程序通过关闭文件Winners.csv结束。 往顺序文件里写数据 当要往一个顺序文件里写入数据时,应该以Append或者Output模式打开该文件。这些模式之间 的区别解释如下: • Append允许在一个现存文件的结尾处添加数据。例如,如果以Append模式打开Readme.txt文 件,并将文本“谢谢您阅读本文档”添加到这个文件中,VBA不会删除或者以任何方式改变该文 件中已经存在的文本,而是在文件的结尾处加上新的文本。 • Output。当以Output模式打开一个文件时,VBA将会将文件里现存的数据删除,而且,如果该 文件不存在,就会创建一个全新的文件。例如,如果以Output模式打开文件Readme.txt,并试 图往里面写数据,那么以前存储在该文件里的文本就会被删除。如果在写入数据之前没有备份该 文件,那么失误可能会付出相当大的代价。如果想要用新数据取代整个内容,就应该以Output 模式打开已存在的文件。 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 181 这里有些例子,说明了什么时候应该用Append模式打开文件、什么时候用Output模式打开文件: • 要在文件C:\Readme.txt后面添加新文本,按下面的Append模式打开该文件: Open "C:\Readme.txt" For Append As #1 • 要在一个名为C:\Result.txt的全新文件里输入一些文本,那么以Output模式打开该文件: Open “C:\Result.txt” For Output As #1 • 要取代现存的带有新的获胜者清单的文件C:\Winners.csv中的内容,首先将原始文件备份,然后 将原始文件以Output模式打开: FileCopy "C:\Winners.csv","C:\Winners.old" Open "C:\Winners.csv" For Output As #1 技巧8—6:不可以同时读写 顺序文件必须分别打开来执行读和写的操作,不可以同时执行这些操作。例如,在一个文件已经打开并且 写入数据后,该文件必须先关闭,之后才能再打开来读取数据。 技巧8—7:顺序文件的优势和劣势 尽管顺序文件容易创建和使用,并且不浪费空间,但是它们也有很多缺点。例如,要是不读取一大部分文 件内容,很难找到某个指定的项目。同时,文件中单个的项目不容易改变或删除——必须重写整个文件。还有 在技巧8—6中所讲的,顺序文件必须分开进行读和写的操作。 使用 Write # 和 Print # 语句 您已经知道了打开一个文本文件来写入数据的两种方法(Append或Output),现在来学习Write # 和Print #语句,使用它们可以将数据发送到文件。 当使用Input #语句从一个顺序文件读取数据时,通常使用Write #语句往该文件写数据,如下所 示: Write #filenumber, [outputlist] filenumber指定正在处理的文件号,它是Write #语句唯一必需的参数。outputlist是想要写入到文 件中的文本,可以是想要写入的单个文本字符,也可以是包含数据的变量列表。如果仅指定了文件 号,VBA就会在打开的文件里写入一个单独的空行。 我们了准备一个文本文件,里面是三个人的名、姓、生日和兄弟姐妹的数目,用该文件来演示 如何将数据写入文件: 1. 在当前模块里输入过程DataEntry: Sub DataEntry() Dim lname As String Dim fname As String Dim birthdate As Date Dim s As Integer Open "C:\My Documents\Friends.txt" For Output As #1 lname = "Smith" fname = "Gregory" birthdate = #1/2/63# s = 3 Write #1, lname, fname, birthdate, s lname = "Conlin" fname = "Janice" birthdate = #5/12/48# s = 1 Write #1, lname, fname, birthdate, s 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 182 共 612 页 lname = "Kaufman" fname = "Steven" birthdate = #4/7/57# s = 0 Write #1, lname, fname, birthdate, s Close #1 End Sub 上面的过程打开文件C:\My Documents\Friends.txt来写入数据。因为该文件还不存在于硬盘中, 所以VBA就创建了一个全新的文件并写入三个记录。写入文件的数据存储在变量上。注意,这些字 符串由双引号(“”)分隔,而生日则有井号(#)包围起来了。 当使用Windows记事本打开文件Friends.txt时,您将看到下面的输入: "Smith","Gregory",#1963—01—02#,3 "Conlin","Janice",#1948—05—12#,1 "Kaufman","Steven",#1957—04—07#,0 注意,Write #语句自动在每条记录的单个数据项之间插入逗号并且将行结束符(Chr(13) & Chr(10))放在每行文本的结尾,以便于每行新记录都从新的行开始。在上面的例子里面,每个文本行 显示一条记录——每条记录以姓开始,以同胞数目结束。 如果想要将数据显示在一列中,而不是用逗号分隔数据,那么就使用Print #语句。例如,如果 将上面程序DataEntry里的Write #语句用Print #语句替换,那么VBA就会按下面的方式写入数据: Smith Gregory 1/2/63 3 Conlin Janice 5/12/48 1 Kaufman Steven 4/7/57 0 尽管Print #语句与Write #语句的语法相同,但是Print #以一个准备打印的格式将数据写入顺序 文件。列表里的变量可以用分号或者空格分隔。要打印多个空格,则应该使用Spc(n)指令,这里的n 是空格数。类似地,要将数据输入到第五列,应该使用指令Tab(5)。 我们来看一些格式的例子: • 使用带逗号的Write #语句,往文件里添加空行: Write #1, • 在第五列输入文本“fruits”: Write #1, Tab(5); “fruits” • 用五个空格分隔开单词“fruits”和“vegetables”: Write #1, “fruits”; Spc(5); “vegetables” 8.2.2 处理随机访问文件 当某文件包含结构化的数据时,就可用随机(Random)模式打开它。以随机模式打开文件可以: • 同时读/写数据 • 快速访问某特定的记录 在随机访问文件里,所有记录都是等长的,并且每条记录都有相同数目的固定大小的字段。记 录或者字段的长度必须在文件写入数据之前就确定。 如果写入某字段的字符串长度小于该字段的大小,那么VBA就会自动在该字符串后面加空格来第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 183 填充字段。如果写入的文本比字段大小更大,超出的字符就不会被写入。 为了知道如何处理随机访问文件,现在创建一个小数据库用作外语学习。该数据库将包含由两 个字段组成的记录,存储英语词组和其外语等同语。 技巧8—8:什么是随机访问文件? 随机访问文件是可以任意的顺序访问所存储记录的文件,这意味着随机访问文件里的任何记录都可以读取, 而不必读取该记录之前的每条记录。 创建用户定义的数据类型 除了第三章里介绍的内置数据类型(参见表3-1)之外,VBA允许在模块的顶部使用Type…End Type语句定义一个非标准的数据类型。该非标准数据类型也经常被称为用户自定义的数据类型。用 户自定义数据类型可以包含各种数据类型(字符串型、整型、日期型,等等)。当处理为随机访问打开 的文件时,经常要创建一个用户定义的变量,因为该变量可以容易地访问记录中的单个字段。 1. 在当前工程里插入一个新模块并重命名为RandomFiles。 2. 在模块顶部,Option Explicit语句的下面,输入下面的类型定义: Option Explicit ' define a user-defined type Type Dictionary en As String * 16 ' English word up to 16 characters sp As String * 20 ' Spanish word up to 20 characters End Type 用户定义的名为Dictionary的类型包含两项,均声明为指定大小的String(字符串)。en项最多可以 接受16个字符,第二个项目sp的大小不能超过20个字符。如果将这两项的长度加起来,那么记录长 度将为36(16+20)。 如果模块中已经有了Option Explicit语句,则不必再输入它。 3. 输入下面的过程EnglishToSpanish: Sub EnglishToSpanish() Dim d As Dictionary Dim RecNr As Long Dim choice As String Dim totalRec As Long RecNr = 1 '打开为随机访问的文件 Open "Translate.txt" For Random As #1 Len = Len(d) Do ' 获取English单词 choice = InputBox("输入一个English单词", "ENGLISH") d.en = choice '如果取消则退出循环 If choice = "" Then Exit Do choice = InputBox("输入等同的 Spanish 单词为" _ & d.en, "SPANISH EQUIVALENT " & d.en) If choice = "" Then Exit Do d.sp = choice '写入记录 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 184 共 612 页 Put #1, RecNr, d '增加记录计数器 recNr = recNr + 1 Loop Until choice = "" '要求单词直到取消 totalRec = LOF(1) / Len(d) MsgBox "这个文件包含" & totalRec & "条记录." '关闭文件 Close #1 End Sub 过程EnglishToSpanish以四个变量的声明开始,变量d声明为用户定义的类型Dictionary。该类 型在前面用Type 语句声明(见第二步)。在给变量RecNr赋予了初始值之后,VBA打开文件 Translate.txt,并且将其作为文件编码1随机访问。指令Len(d)告诉VBA每条记录的大小为36个字符 (变量d包含两个元素:sp占用20个字符,en占用16个字符,结果,一条记录总的大小为36)。接下来 VBA执行Do…Until循环里面的语句,直到取消。循环里的第一条语句提示输入一个English单词并将 其赋给变量choice,然后该值被传递给用户定义的变量d的第一个元素(d.en)。 一旦停止输入数据,VBA就会退出Do循环,并且执行程序里的最后的语句计算和显示文件里的 记录总数。最后一条语句关闭文件。如果输入了English单词并单击“确定”,那么下一个对话框就会 提示您输入外语的等同语。 当然,如果决定现在就退出,VBA就会退出循环并继续执行剩下的语句。如果一切正常并且输 入了外语翻译的词语,那么VBA就会将它赋给变量choice,然后传递给用户自定义变量d的第二个元 素(d.sp)。接下来,VBA使用下面的语句将整条记录写入到文件里: Put #1, recNr, d 写入第一条记录后,VBA会给记录计数器增加1,然后重复循环里的语句。过程EnglishToSpanish 允许输入任意多条记录到字典中。当退出提供词语时,过程使用LOF和Len函数来计算文件里的总记 录数。VBA在显示信息后关闭该文本文件(Translate.txt)。 创建随机访问文件仅仅是开始,接下来,过程VocabulartDrill演示了如何处理一个开启的随机文 件的记录。这里,您将学习快速找到文件中适合的数据的语句。 技巧8—9:理解Type语句 Type命令允许创建一个自定义组,包括混合的变量类型,称为“用户自定义数据类型”。Type语句通常用 于随机访问文件,将信息作为固定大小的记录里面的字段。我们可以将随机访问文件使用的字段用Type语句集 中成一个用户自定义变量,而不必为每个字段都声明一个变量。例如,用下面的方式定义一个包含三个字段的 记录: Type MyRecord country As String * 20 city As String * 14 rank As Integer End Type 一旦定义了类型,你必须给使用这种类型的变量一个名称: Dim myInfo As MyRecord 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 185 变量名称后面加上句点(.)和内部变量可以访问内部变量(country、city、rank),例如,要指定城市,可输入: MyInfo.city = "Warsaw" 4. 在过程EnglishToSpanish的下面输入如下所示的过程VocabularDrill,稍后对代码进行解释。 Sub VocabularyDrill() Dim d As Dictionary Dim totalRec As Long Dim recNr As Long Dim randomNr As Long Dim question As String Dim answer As String '打开一个随机访问文件 Open "Translate.txt" For Random As #1 Len = Len(d) ' 打印该文件中总的字节数 Debug.Print "该文件中有" & LOF(1) & "个字节." '找到并打印总的记录数 recNr = LOF(1) / Len(d) Debug.Print "总的记录数: " & recNr Do '获取一个随机的记录号 randomNr = Int(recNr * Rnd) + 1 Debug.Print randomNr '找到随机的记录 Seek #1, randomNr '读取记录 Get #1, randomNr, d Debug.Print Trim(d.en); " "; Trim(d.sp) '为变量指定答案 answer = InputBox("什么是等同的 Spanish 单词?", d.en) '如果取消则结束 If answer = "" Then Close #1: Exit Sub Debug.Print answer '检查是否答案正确 If answer = Trim(d.sp) Then MsgBox "祝贺您!" Else MsgBox "无效的答案!!!" End If '保持问问题,直到按下取消按钮 Loop While answer <> "" '关闭文件 Close #1 End Sub 声明变量之后,过程VocabularyDrill为随机访问打开一个文件,并且告诉VBA每个记录的长度: Len = Len(d),接下来的两个语句在立即窗口里打印文件的总字节数和总记录数。通过语句LOF(1) 返回字节数。 记录数通过总字节数(LOF)除以一个记录的长度——Len(d)来计算。接下来,VBA执行循环里的学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 186 共 612 页 语句直到按下Esc键或者取消按钮。循环里的第一条语句将函数Rnd的结果赋给变量randomNr,下 一条语句将这个数字写入到立即窗口,指令Seek #1, randomNr在打开的文件中移动光标到变量 randomNr指定的记录处。 接下来的指令读取找到的记录内容。要在为随机访问而打开的文件中读取数据,必须使用Get 语句。指令: Get #1, randomNr, d 告诉VBA要读取的记录号码(randomNr)以及要读取数据的变量(d)。随机访问文件中的第一个记 录在位置1,第二个记录在位置2,依次类推。忽略记录号码会导致VBA读取下一个记录。 然后,用户定义类型字典的两个元素都被写入到立即窗口。函数Trim(d.en)和Trim(d.sp)将读取 的记录前后可能含有的空格去掉。 接下来,VBA显示一个输入框提示用户提供与所显示单词相同意思的的外语单词,该单词赋给 变量answer。如果按下Esc而不是单击“确定”按钮,VBA就会关闭文件并且结束过程。否则,VBA 将在立即窗口中打印答案,并且通知答案是否正确。无论何时都可以按下Esc键或单击对话框中的“取 消”按钮退出单词训练程序。 图8-3:在记事本里打开的一个随机访问文件的内容 图8-4:试图用Microsoft Excel打开随机访问文件的内容注意,Excel正确的识别原始的数据类型——随 机访问文件里的数据有固定的宽度 如果决定继续并且单击了“确定”按钮,那么程序就会产生一个新的随机号码,并且会重新获 取一个English单词并且要求您输入相对应的Spanish词。 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 187 可以修改过程VocabularyDrill,以便将每个不正确翻译的单词写到工作表中。同样,也许您想将 文件Translate.txt里的所有记录都写到工作表里,便于总可以知道字典的内容。您可以在本书随带的 CD里找到这两个程序。 技巧8—10:随机文件的优点和缺点 不像顺序文件,存储在随机文件里的数据可以被很快地访问,而且,在往里面写信息和读信息时不需要关 闭文件。随机访问文件不必按顺序读写。随机访问文件也有一些缺点,例如,存储数据的效率低。因为它们有 固定长度的字段和记录,所以不管存储的字符有多少,使用的字节数总是一样的,因此,如果有些字段左边为 空或者包含的字符串短于所声明的字段大小,就会浪费许多空间。 8.2.3 处理二进制文件 不像随机文件那样以固定长度的记录存储数据,二进制文件是一些可变长度的记录的集合。例 如,第一个记录可以包含10个字节,第二个记录可以仅有5个字节,而第三个记录却可以有15个字节。 这种存储数据的方法节省很多的硬盘空间。 因为VBA不需要在存储的字符串后面加上多余的空格来确保所有字段具有相同的长度(像写入数 据到随机文件中那样),在二进制文件里没有被浪费的空间。因此,二进制文件比前面所讲的两种文 件占用的硬盘空间要少。就像随机访问文件一样,二进制文件也可以被打开同时进行读和写的操作。 然而,因为二进制文件里各条记录的长度不同,所以,操作这些文件更困难。要正确地获取数据, 必须存储每个字段和记录大小的信息。 可以使用下面的四种语句来处理二进制文件: • Get语句用于读取数据。 • Put语句允许往二进制文件里输入新数据。 • Loc语句返回所读取的最后字节数(在随机访问文件里,Loc语句返回最后读取记录的数字). • Seek语句将光标移动到文件中合适的位置。 为了快速掌握上面语句的使用,打开立即窗口,并且输入下面表格里左边的指令。本练习的目 的是在一个名为MyData.txt的文件里输入您的姓和名,然后再找回输入的数据。 在立即窗口中输入的代码 解释 Open "MyData.txt" For Binary As #1 打开文件“MyData.txt”作为文件编号1来作二进制访问 MsgBox "Total bytes: " & LOF(1) 显示正打开文件的字节数(该文件现在为空) fname = "Julitta" 给变量fname赋值 ln = len(fname) 将储存于变量fname中的字符串长度赋给变量In Put #1, , ln 将变量In的值输入到二进制文件的下一个字节 MsgBox "The last byte: " & LOC(1) 显示最后一个字节的位置 Put #1, , fname 在下一个位置放置变量fname的内容 lname = "Korol" 给变量lname赋值 ln = len(lname) 将储存于变量lname中的字符串长度赋给变量In Put #1 , ,ln 将变量In的值输入到二进制文件的下一个字节 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 188 共 612 页 Put #1,,lname 在下一个位置放置变量lname的内容 MsgBox "The last byte: " & LOC(1) 显示最后一个字节的位置 Get #1,1, entry1 读取存储在第一个字节位置的值并将其赋给变量entry1 MsgBox entry1 显示变量entry1的内容 Get #1, , entry2 读取下一个值并将其赋给变量entry2 MsgBox entry2 显示变量entry2的内容 Get #1, , entry3 读取下一个值并将其存储在变量entry3中 MsgBox entry3 显示变量entry3的内容 Get #1, , entry4 读取下一个值并将其存储在变量entry4中 MsgBox entry4 显示变量entry4的内容 Debug.Print entry1;entry2;entry3;entry4 在立即窗口中打印所有数据 7 Julitta 5 Korol 立即窗口里显示的上述指令的结果 Close #1 关闭文件 注意,上面的指令可以在随书CD里的EnterAndDisplay过程中找到。 当二进制文件中输入数据时,使用下面的原则: • 在写入字符串到二进制文件中之前,将该字符串的长度赋给一个整型变量,通常可以使用下面的 代码块: 字符串长度 = Len(变量名称) Put #1, , 字符串长度 Put #1, , 变量名称 • 当从二进制文件中读取数据时,先读取该字符串的长度,然后才是字符串的内容。可以使用Get 语句和String$函数来实现: Get #1, , 字符串长度 变量名称=String(字符串长度, " ") Get #1, , 变量名称 技巧8—11:二进制文件的优点和缺点 与顺序文件和随机访问文件相比,二进制文件是最小的,因为它使用可变长度的记录,能够节省硬盘空间。 和为随机访问打开文件一样,可以在文件打开时同时读写二进制文件。二进制访问文件的一个最大的缺点就是 必须要准确地知道要获取或者要操作的数据是如何存储在文件里的。 8.3 处理文件和文件夹的新式方法 在计算机中有一个被隐藏的宝贝称作Windows Scripting Host(WSH),使用它可以创建一些控制 Windows操作系统和应用程序以及从操作系统中获取信息的小程序。WSH是在Wxhom.ocx文件中的 一个ActiveX控件,如果运行的是Windows 95、98、NT5.0、2000、XP或者IE4、5或者6,该文件会 自动安装在Windows System32文件夹里。 WSH是一种脚本语言。脚本语言是一组可以自动运行的命令。可以使用Command Scripting 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 189 Host (Cscript.exe)从命令提示或者使用Windows Scripting Host (Wscript.exe)从视窗中直接创建或 者运行脚本。在本章接下来的部分,您将学习WSH如何和VBA一起工作的。 WSH有它自己的对象层次。使用CreatObject函数,可以在VBA过程里引用WSH对象。在开始 编写使用WSH对象的VBA过程之前,我们来看看能够控制的一些对象。 图8-5:Windows Scription Host是一个ActiveX控件,用来创建一些以前仅能通过在MS-DOS操作系统中 编写批处理文件(.bat)执行简单或复杂操作的脚本 1. 在VBE编辑器窗口,选择菜单“工具”-“引用”。 2. 在“引用”对话框中,找到并选择Microsoft Scripting Runtime。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 190 共 612 页 图8-6:创建对Microsoft Scripting Runtime的引用 3. 按下F2键打开对象浏览器。 4. 在“所有库”的下拉列表里选择“Scripting”。您将看到Windows Scription Host库中的部分对 象列表。 图8-7:建立对Microsoft Scripting Runtime的引用之后(见图8-6),对象浏览器显示了很多允许处理硬盘、 文件夹、文件和它们的内容的对象 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 191 Windows Scripting Host可以使您导速地获得一些问题的答案,例如“在哪个硬盘上我可以找到 某个文件?”(GetDrive方法)、“某文件的扩展名是什么?”(GetExtensionName方法)、“该文件最 后一次修改的时间?”(DateLastModified属性)、以及“在给定的硬盘上存在某个文件夹或者文件 吗?”(FolderExists和FileExists方法)。 8.3.1 使用WSH获取文件信息 Windows Scripting Host公布了一个称作FileSystemObject的对象,该对象有好几种方法用来处 理文件系统。 我们来看看如何获取某个指定文件的信息: 1. 在当前工程里插入一个新模块,并将该模块重命名为WSH。 2. 在模块WSH里,输入下面的过程FileInfo: Sub FileInfo() Dim fs As Object Dim objFile As Object Dim strMsg As String Set fs = CreateObject("Scripting.FileSystemObject") Set objFile = fs.GetFile("C:\WINNT\System.ini") strMsg = "文件名: " & _ objFile.Name & vbCrLf strMsg = strMsg & "硬盘: " & _ objFile.Drive & vbCrLf strMsg = strMsg & "创建日期:" & _ objFile.DateCreated & vbCrLf strMsg = strMsg & "修改日期:" & _ objFile.DateLastModified & vbCrLf MsgBox strMsg, , "文件信息" End Sub 上面显示的过程FileInfo 使用VBA 函数CreateObject 来创建一个ActiveX 对象 (FileSystemObject),该对象是Widows Scripting库的一部分,提供访问计算机的文件系统。 Dim fs As Object Set fs = CreateObject("Scripting.FileSystemObject") 上面的代码声明了一个名为fs的对象变量,然后使用函数CreateObject创建一个ActiveX对象并 将该对象赋给对象变量。 上面过程中代码的第二行: Set objFile = fs.GetFile("C:\WINNT\System.ini"), 创建和返回对C:\WINNT文件夹中System.ini文件的File对象的引用,并且将其赋给对象变量 objFile。File对象有一些可以读取的属性,例如,objFile.Name语句返回该文件的完整文件名, objFile.Drive 语句返回该文件所处的硬盘名称, objFile.DateCreated 语句和 objFile.DateLastModified语句分别返回该文件的创建日期和最后一次修改日期。这个程序可以容易 地修改成返回文件类型、属性和它的父文件夹的名称。请试图将下列指令放置在代码中来修改该过 程:objFile.Type、objFile.Attributes、 objFile.ParentFolder和objFile.Size。在对象浏览器里点击File学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 192 共 612 页 对象,查看相关文件,还可以学习到其它知识。 8.3.2 FileSystemObject的方法和属性 FileSystemObject是一个用于访问计算机文件系统的ActiveX控件。该对象提供了很多方法,其 中一些列于下表8-3中。 表8-3:FileSystemObject的一些方法 方法 描述 FileExists 如果指定的文件存在,则返回True Sub FileExists() Dim fs As Object Dim strFile As String Set fs = CreateObject("Scripting.FileSystemObject") strFile = InputBox("输入文件的全名:") If fs.FileExists(strFile) Then MsgBox strFile & "被找到." Else MsgBox "文件不存在." End If End Sub GetFile 返回一个File对象 GetFileName 返回带路径的文件名 GetFileVersion 返回文件版本 CopyFile 复制文件 Sub CopyFile() Dim fs As Object Dim strFile As String Dim strNewFile As String strFile = "C:\Hello.doc" strNewFile = "C:\Program Files\Hello.doc" Set fs = CreateObject("Scripting.FileSystemObject") fs.CopyFile strFile, strNewFile MsgBox "创建了指定文件的副本." Set fs = Nothing End Sub MoveFile 移动文件 DeleteFile 删除文件 Sub DeleteFile() Dim fs As FileSystemObject Set fs = New FileSystemObject fs.DeleteFile "C:\Program Files\Hello.doc" MsgBox "删除了所请求的文件." End Sub DriveExists 如果指定的硬盘存在,则返回True Function DriveExists(disk) Dim fs As Object Dim strMsg As String Set fs = CreateObject("Scripting.FileSystemObject") 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 193 If fs.DriveExists(disk) Then strMsg = "驱动器" & UCase(disk) & "盘存在." Else strMsg = UCase(disk) & "盘未找到." End If DriveExists = strMsg ' 在工作表任意单元格中通过输入=DirveExists(“E:\”)运 行该函数 End Function GetDrive 返回Drive对象 Sub DriveInfo() Dim fs, disk, infoStr, strDiskName strDiskName = InputBox("输入驱动器字母:", _ "驱动器名", "C:\") Set fs = CreateObject("Scripting.FileSystemObject") Set disk = fs.GetDrive(fs.GetDriveName(strDiskName)) infoStr = "驱动器: " & UCase(strDiskName) & vbCrLf infoStr = infoStr & "驱动器字母: " & _ UCase(disk.DriveLetter) & vbCrLf infoStr = infoStr & "驱动器类型: " & disk.DriveType & vbCrLf infoStr = infoStr & "驱动文件系统: " & _ disk.FileSystem & vbCrLf infoStr = infoStr & "驱动器系列号: " & _ disk.SerialNumber & vbCrLf infoStr = infoStr & "字节的总大小: " & _ FormatNumber(disk.TotalSize / 1024, 0) & " Kb" & vbCrLf infoStr = infoStr & "驱动器中的自由空间: " & _ FormatNumber(disk.FreeSpace / 1024, 0) & " Kb" & vbCrLf MsgBox infoStr, vbInformation, "驱动器信息" End Sub GetDriveName 返回一个包含硬盘名称或者网络共享名称的字符串 Function DriveName(disk) Dim fs As Object Dim strDiskName As String Set fs = CreateObject("Scripting.FileSystemObject") strDiskName = fs.GetDriveName(disk) DriveName = strDiskName ' 通过在立即窗口中输入?DriveName("D:\")来运行该函 数 End Function FolderExists 如果指定的文件夹存在,则返回True Sub DoesFolderExist() Dim fs As Object Set fs = CreateObject("Scripting.FileSystemObject") MsgBox fs.FolderExists("C:\Program Files") End Sub 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 194 共 612 页 GetFolder 返回Folder对象 Sub FilesInFolder() Dim fs As Object Dim objFolder As Object Dim objFile As Object Set fs = CreateObject("Scripting.FileSystemObject") Set objFolder = fs.GetFolder("C:\") Workbooks.Add For Each objFile In objFolder.Files ActiveCell.Select Selection.Formula = objFile.Name ActiveCell.Offset(0, 1) _ .Range("A1").Select Selection.Formula = objFile.Type ActiveCell.Offset(1, -1) _ .Range("A1").Select Next Columns("A:B").Select Selection.Columns.AutoFit End Sub GetSpecialFolder 返回操作系统文件夹路径: 0 — Windows文件夹 1 — System(系统)文件夹 2 — Temp(临时)文件夹 Sub SpecialFolders() Dim fs As Object Dim strWindowsFolder As String Dim strSystemFolder As String Dim strTempFolder As String Set fs = CreateObject("Scripting.FileSystemObject") strWindowsFolder = fs.GetSpecialFolder(0) strSystemFolder = fs.GetSpecialFolder(1) strTempFolder = fs.GetSpecialFolder(2) MsgBox strWindowsFolder & vbCrLf _ & strSystemFolder & vbCrLf _ & strTempFolder, vbInformation + vbOKOnly, _ "Special Folders" End Sub CreateFolder 创建文件夹 Sub MakeNewFolder() Dim fs, objFolder Set fs = CreateObject("Scripting.FileSystemObject") Set objFolder = fs.CreateFolder("C:\TestFolder") MsgBox "一个名为" & _ objFolder.Name & "的文件夹被创建." End Sub CopyFolder 复制文件夹 Sub MakeFolderCopy() Dim fs As FileSystemObject Set fs = New FileSystemObject If fs.FolderExists("C:\TestFolder") Then fs.CopyFolder "C:\TestFolder", "C:\FinalFolder" MsgBox "该文件夹被复制." End If 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 195 End Sub MoveFolder 移动文件夹 DeleteFolder 删除文件夹 Sub RemoveFolder() Dim fs As FileSystemObject Set fs = New FileSystemObject If fs.FolderExists("C:\TestFolder") Then fs.DeleteFolder "C:\TestFolder" MsgBox "该文件夹被删除." End If End Sub CreateTextFile 创建文本文件 OpenTextFile 打开文本文件 Sub ReadTextFile() Dim fs As Object Dim objFile As Object Dim strContent As String Dim strFileName As String strFileName = "C:\WINNT\System.ini" Set fs = CreateObject("Scripting.FileSystemObject") Set objFile = fs.OpenTextFile(strFileName) Do While Not objFile.AtEndOfStream strContent = strContent & objFile.ReadLine & vbCrLf Loop objFile.Close Set objFile = Nothing ActiveWorkbook.Sheets(3).Select Range("A1").Select Selection.Formula = strContent End Sub FileSystemObject对象只有一个名为Drives的属性,它返回对硬盘驱动器集合的引用。使用该属 性,可以创建一个计算机驱动硬盘清单,如下所示: Sub DrivesList() Dim fs As Object Dim colDrives As Object Dim strDrive As String Set fs = CreateObject("Scripting.FileSystemObject") Set colDrives = fs.Drives For Each Drive In colDrives strDrive = "驱动器" & Drive.DriveLetter & ": " Debug.Print strDrive Next End Sub 8.3.3 File对象的属性 File对象允许访问某指定文件的所有属性,下面的代码创建了对File对象的引用: Set fs = CreateObject("Scripting.FileSystemObject") Set objFile = fs.GetFile(“C:\My Documents\myFile.doc”) 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 196 共 612 页 在本章前面所创建的的过程FileInfo里可以找到使用File对象的例子。 表8-4:File对象的一些属性 属性 描述 Attributes 返回文件属性(将该属性与本章前面介绍的VBA函数GetAttr比较) DateCreated 文件创建日期 DateLastAccessed 最后访问日期 DateLastModified 最后修改日期 Drive 驱动器名称,后面带冒号 Name 文件名 ParentFolder 文件的父文件夹 Path 文件的完整路径 Size 以字节表示的文件大小(将该属性与本章前面介绍的VBA函数FileLen 比较) Type 文件类型。这就是显示在Windows文件浏览器类型列中的文本(例如, 配置设置、应用程序、快捷方式) 8.3.4 Folder对象的属性 Folder对象提供了对指定文件夹的所有属性的访问。下面的代码行创建了对Folder对象的引用: Set fs = CreateObject("Scripting.FileSystemObject") Set objFolder = fs.GetFolder(“C:\My Documents”) 表8-5:Folder对象的一些属性 属性 描述 Attributes 文件夹属性 DateCreated 文件夹创建日期 Drive 包含该文件夹的驱动器名 Files 文件夹中的文件集合 Sub CountFilesInFolder() Dim fs, strFolder, objFolder, colFiles strFolder = InputBox("输入文件夹名:") If Not IsFolderEmpty(strFolder) Then Set fs = CreateObject("Scripting.FileSystemObject") Set objFolder = fs.GetFolder(strFolder) Set colFiles = objFolder.Files MsgBox "文件夹中的文件数 " & _ strFolder & "=" & colFiles.Count End If End Sub 上面的过程调用函数IsFolderEmpty,该函数将在本表的Size属性中介绍。 IsRootFolder 如果文件夹为根文件夹,则返回True 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 197 Name 文件夹名称 ParentFolder 指定文件夹的父文件夹 Path 文件夹的完整路径 Size 文件夹大小,以字节表示 Function IsFolderEmpty(myFolder) Dim fs, objFolder Set fs = CreateObject("Scripting.FileSystemObject") Set objFolder = fs.GetFolder(myFolder) IsFolderEmpty = (objFolder.Size = 0) End Function SubFolders 文件夹中的子文件夹集合 Type 文件夹类型(例如,文件夹或回收站) 8.3.5 Drive对象的属性 Drive对象提供了对计算机或服务器上指定驱动器的属性的访问。下面的代码创建了对Drive对象 的引用: Set fs = CreateObject("Scripting.FileSystemObject") Set objDrive = fs.GetDrive(“C:\”) 在下表中,您将看到几个使用Drive对象的程序示例。 表8-6:Drive对象的一些属性 属性 描述 AvailableSpace 可用的空间,用字节表示 FreeSpace 同AvailableSpace DriveLetter 驱动器字母(没有冒号) DriveType 驱动器类型: 0 — 未知 1 — 可移动 2 — 固定 3 — 网络 4 — CD-ROM 5 — RAM磁盘 Sub CDROM_DriveLetter() Const CDROM = 4 Dim fs, colDrives Set fs = CreateObject("Scripting.FileSystemObject") Set colDrives = fs.Drives For Each Drive In colDrives If Drive.DriveType = CDROM Then MsgBox " CD-ROM驱动器: " & _ Drive.DriveLetter End If Next End Sub 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 198 共 612 页 FileSystem 文件系统,例如FAT、NTFS或CDFS IsReady 如果插入了合适的媒介(例如CD-ROM盘)并且可以访问,则返回True Function IsCDROMReady(strDriveLetter) Dim fs, objDrive Set fs = CreateObject("Scripting.FileSystemObject") Set objDrive = fs.GetDrive(strDriveLetter) IsCDROMReady = (objDrive.DriveType = 4) And _ objDrive.IsReady = True '通过在立即窗口中输入: ?IsCDROMReady("D:") 来运行该函数 End Function Path 根文件夹路径 SerialNumber 驱动器系列号 TotalSize 驱动器总容量,以字节表示 8.3.6 使用WSH创建文本文件 Windows Scripting Host(WSH)提供了三种创建文本文件的方法:CreateTextFile、OpenTextFile 和OpenAsTextStream。下表列出了每种方法和示例。 表8-7:创建文本文件的不同方法 方法/语法 示例 CreateTextFile object.CreateTextFile(filename[, overwrite[, unicode]]) Object是FileSystemObject对象或Folder对象的名称。 filename是指定创建文件的字符串表达式。 Overwrite(可选的)是一个布尔值,表明是否要覆盖已经存在的文件。如 果覆盖那么该值为True,如果不覆盖则为False,如果忽略,那么不覆盖 现存的文件。 Unicode(可选的)是一个布尔值,表明该文件创建为Unicode或者ASCII 类型的文件。如果文件创建为Unicode类型那么该值为真,如果文件为 ASCII那么该值为假。如果忽略,则创建ASCII类型的文件。 Sub CreateFile_Method1() Dim fs, objFile Set fs = CreateObject("Scripting.FileSystemObject") Set objFile = fs.CreateTextFile("C:\Phones.txt", True) objFile.WriteLine ("Margaret Kubiak: 212—338—8778") objFile.WriteBlankLines (2) objFile.WriteLine ("Robert Prochot: 202—988—2331") objFile.Close End Sub 上面的过程创建了一个文本文件用来存储两个人的姓名和电话号码。因 为参数overwrite为True,所以,如果C:\Phones.txt已经存在的话就会被 覆盖。 OpenTextFile object.OpenTextFile(filename[, iomode[, create[, format]]]) Object是FileSystemObject对象的名称。 Filename是指定要打开的文件名称的字符串表达式。 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 199 Iomode(可选的)是一个布尔值,表示如果指定的文件不存在时是否创建 一个新文件,如果创建新文件则该值为真,否则为假。如果忽略该参数, 那么不会创建新文件(注:译自原文,有误)。参数iomode可以是下列常 数之一: ForReading (1) ForWriting (2) ForAppending (8) Create(可选的)是一个布尔值,表示如果指定的文件不存在时是否创建一 个新文件,如果创建新文件则该值为真,否则为假。如果忽略该参数, 那么不会创建新文件。 Format(可选的)是用于表示打开的文件格式的三种类型之一。如果忽略, 文件就会打开为ASCII。 TristateTrue = 打开文件为ASCII。 TristateFalse =打开文件为Unicode。 TristateUseDefault = 使用系统默认方式打开文件。 Sub CreateFile_Method2() Dim fs, objFile Set fs = CreateObject("Scripting.FileSystemObject") Set objFile = fs.OpenTextFile("C:\Shopping.txt", _ ForWriting, True) objFile.WriteLine ("Bread") objFile.WriteLine ("Milk") objFile.WriteLine ("Strawberries") objFile.Close End Sub OpenAsTextStream object.OpenAsTextStream([iomode, [format]]) Object是File对象名称。 Iomode(可选的)表示输入/输出模式,可以是下列三个常数之一: ForReading (1) ForWriting (2) ForAppending (8) Format (可选的)是用于表示打开的文件格式的三种类型之一。如果忽略, 文件就会打开为ASCII。 TristateTrue = 打开文件为ASCII。 TristateFalse =打开文件为Unicode。 TristateUseDefault = 使用系统默认方式打开文件。 Sub CreateFile_Method3() Dim fs, objFile, objText Set fs = CreateObject("Scripting.FileSystemObject") fs.CreateTextFile "New.txt" Set objFile = fs.GetFile("New.txt") Set objText = objFile.OpenAsTextStream(ForWriting, _ TristateUseDefault) objText.Write "Wedding Invitation" objText.Close Set objText = objFile.OpenAsTextStream(ForReading, _ TristateUseDefault) MsgBox objText.ReadLine objText.Close End Sub 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 200 共 612 页 8.3.7 使用WSH进行其它操作 WSH使得操作任何安装在计算机上的自动化对象成为可能。除通过FileSystemObject访问文件 系统外,WSH也允许进行其它的一些操作,例如处理WSH和ActiveX对象、设定和去除打印机和远 程驱动器、操纵注册表、创建Windows和Internet快捷方式、以及访问Windows NT Active Directory 服务。WSH对象模型由下列三种主要对象组成:WScript、WshShell和WshNetwork。本节演示如何 利用WshShell对象来编写程序启动其它应用程序和创建快捷方式。 运行其它的应用程序 在本书的下一章,您将学习多种从Excel里启动外部应用程序的方法,您可以加上即将在本节找 到的三种方法。 假设您想从VBA过程里启动Windows记事本。接下来的过程,您将看到使用作为Windows Scripting Host一部分的WshShell对象来运行一个应用程序是多么容易。如果您要启动内置的计算 器,只要将记事本应用程序名称改为Calc就可以了。 Sub RunNotepad() Dim WshShell As Object Set WshShell = CreateObject("WScript.Shell") WshShell.Run "Notepad" Set WshShell = Nothing End Sub 上面的过程通过声明和创建一个Wshshell对象开始: Dim WshShell As Object Set WshShell = CreateObject("WScript.Shell") 下一条语句则使用Run方法来运行要求的应用程序: WshShell.Run "Notepad" 使用相同的概念,很容易运行Windows实用程序,例如计算器或浏览器: WshShell.Run “Calc” WshShell.Run “Explorer” 过程的最后一行消毁对象WshShell,因为,不再需要它了。 Set WshShell = Nothing 可以启动带有指定文档的应用程序,而不是打开一个空的应用程序窗口,如下所示: Sub OpenTxtFileInNotepad() Dim WshShell As Object Set WshShell = CreateObject("WScript.Shell") WshShell.Run "Notepad C:\Phones.txt" Set WshShell = Nothing End Sub 试验下面的过程来打开MS-DOS窗口并且将当前目录下的文件列表打印出来: Sub RunDOSCommand() Dim WshShell As Object 第 8 章 7B 利用 VBA 操作文件和文件夹 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 201 Set WshShell = CreateObject("WScript.Shell") WshShell.Run ("Command /c Dir >lpt1:") End Sub 创建快捷方式 当开始发布VBA应用程序时,用户可能会要求自动在他们的桌面上放置一个应用程序的快捷方 式。VBA没有提供创建快捷方式的方法。幸运的是,您现在知道如何使用WSH了,可以使用它的Shell 对象创建应用程序或者网页的快捷方式,不需要任何用户的干涉。WshShell对象公布了 CreateShortcut方法,可以按照下面的方式使用该方法: Set myShortcut = WshShell.CreateShortcut(Pathname) Pathname是明确快捷文件完整路径的字符串。所有的快捷方式文件都有扩展名.lnk,并且该扩 展名必须包括在文件路径名里面。CreateShortcut方法返回快捷方式对象,下表列出该对象的一些属 性和一个方法。 表8-8:CreateShortcut对象的属性/方法 方法/语法 示例 TargetPath TargetPath属性是可执行文件的路径 WshShell.TargetPath = ActiveWorkbook.FullName WindowStyle WindowStyle属性明确快捷方式使用的窗口类型。 1 – 普通窗口 3 – 最大化窗口 7 – 最小化窗口 WshShell.WindowStyle = 1 HotKey HotKey属性是键盘快捷方式(例如,Alt+f、Shift+g、Ctrl+Shift+z,等等) WshShell.Hotkey = "Ctrl+Alt+w" IconLocation IconLocation属性是快捷方式图标的位置。因为图标文件里通常不止一个 图标,所以应该提供图标文件的路径,并且后面标明图标在文件里的索 引号。如果不指定,Windows 会使用缺省的图标。 WshShell.IconLocation = "notepad.exe, 0" Description Description属性包含一个描述快捷方式的字符串。 WshShell.Description = "Wordware Web Site" WorkingDirectory WorkingDirectory属性明确快捷方式的工作目录。 strWorkDir = WshShell.SpecialFolders("Desktop") WshShell.WorkingDirectory = strWorkDir Save 这是Shortcut对象的唯一方法。在使用CreateShortcut方法创建一个快捷 方式对象并且设置该快捷方式的属性后,必须使用Save方法将快捷方式 对象保存到硬盘上。 创建快捷方式有三个步骤: 1. 创建一个WshShortcut对象实例。 2. 初始化它的属性(见上表8-1)。 3. 用Save方法将它保存到硬盘中。 下面的例子创建一个WshShell对象并使用CreateShortcut方法创建两个快捷方式:一个到当前学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 202 共 612 页 Excel工作簿的Windows快捷方式和一个到Wordware Publishing网页的Internet快捷方式。两个快捷 方式都放在用户的桌面上。该过程使用WshShell对象的SpecialFolders属性来返回到Windows桌面的 路径。 Sub CreateShortcut() '创建两个桌面的快捷方式 Dim WshShell As Object Dim objShortcut As Object Set WshShell = CreateObject("WScript.Shell") ' 创建一个internet快捷方式 Set objShortcut = WshShell.CreateShortcut(WshShell. _ SpecialFolders("Desktop") & "\Wordware.url") objShortcut.TargetPath = "http://www.wordware.com" objShortcut.Save '创建一个文件快捷方式 Set objShortcut = WshShell.CreateShortcut(WshShell. _ SpecialFolders("Desktop") & "\" & ActiveWorkbook.Name & ".lnk") With objShortcut .TargetPath = ActiveWorkbook.FullName .WindowStyle = 7 .Save End With Set objShortcut = Nothing Set WshShell = Nothing End Sub 技巧8—12:使用SpecialFolders属性 可以使用SpecialFolders属性在您的机器上找到特殊文件夹的位置。下面的特殊文件夹是可用的: AllUsersDesktop(所有用户桌面)、AllUsersStartMenu(所有用户开始菜单)、AllUsersPrograms(所有用户程序)、 AllUsersStartup(所有用户启动)、Desktop(桌面)、Favorites(收藏)、Fonts(字体)、MyDocuments(我的文档), NetHood(网络连接)、PrintHood(打印机连接)、Programs(程序)、Recent(最近)、SendTo(发送到)、StartMenu(开 始菜单)、Startup(启动)和 Templates(模版)。如果请求的特殊文件夹不可用,那么SpecialFolders属性就会返回 一个空字符串。 本章小结和下一章内容简介 在本章里,您学习并测试了处理文件系统的VBA函数和语句。您知道了如何读取和修改与文件 和文件夹有关的信息、如何执行对打开的顺序、随机和二进制文件的读和写操作。您也学习了如何 使用Windows Scripting Host(WSH)来访问FileSystemObject对象和进行其它操作,例如启动应用程 序和使用WshShell对象创建Windows快捷方式。如果您对所介绍的函数和语句的详细情况感兴趣的 话,就花些时间浏览一下VBA在线帮助。 下一章将介绍更多的自动化任务。例如,您将学习如何使用VBA来控制其它应用程序、启动应 用程序的多种方法并且研究如何直接从Microsoft Excel里操纵其它应用程序。 第9章 利用VBA控制其它应用程序 您每天在办公室或者家里的计算机上工作时,都要用到很多 种应用程序。为了从硬盘或者软盘上查找某个文件,要打开 Windows 浏览器…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 204 共 612 页 您每天在办公室或者家里的计算机上工作时,都要用到很多种应用程序。为了从硬盘或者软盘 上查找某个文件,要打开 Windows浏览器。当要设置系统时间或者更改屏幕外观时,可以单击控制 面板上相应的图标。如果您的计算机上安装了全套Microsoft Office软件,就可以使用Word创建各种 各样的文档,并且依靠Excel进行所有的计算。Microsoft Access对于保存非常重要的数据表是强大 的,而PowerPoint则有助于使用声音和图片。最后,Mcrosoft Outlook易于与他人保存联系、安排计 划和组织约会并且易于与他人分享。使用这些应用软件时,经常要在它们之间切换,可以使用键盘 直接输入数据或者简单地在应用程序间复制或移动数据。这些操作——打开应用程序以及在它们之 间传输数据——不需要手动处理,它们可以通过一些很有趣的VBA函数和指令来自动完成。在本章, 您将学习多种从VBA过程里打开应用程序的方法,并且找到如何使用称为自动化的技术从Microsoft Excel直接控制其它应用程序。 9.1 启动应用程序 启动应用程序的方法不止一个,实际上,至少可以使用五种方法手动打开某个程序:通过“开 始”|“程序”菜单、快捷方式菜单、“运行”命令、MS-DOS窗口、或者在Windows浏览器里双击可 执行文件。 本节假设您熟悉手动启动应用程序的技术,并且很想从Excel内部的VBE编辑器窗口体验启动应 用程序的其它技术。 我们从最简单的开始吧——Shell函数,该函数可以从VBA过程里直接打开任何程序。假设过程 必须打开Windows记事本,您所需做的就是在关键字Sub和End Sub之间加上一条语句,或者更好的 方法是在立即窗口里输入下面的语句并按下回车键,立即看到结果: Shell "notepad.exe", vbMaximizedFocus 在上面的语句里,“notepad.exe”是要打开的程序的名称。如果您担心找不到程序,那么在名 称中包括完整的路径(驱动器和文件夹名称)。注意,程序名称用双引号括起来。Shell函数的第二个 参数可以忽略,该参数指定窗口的样式(也就是当程序启动时,如何显示在屏幕上)。在上面的例子里, 记事本将显示为最大化的窗口。如果没有指定窗口的样式,那么程序就会被最小化(参见表9-1)。 表9-1: 窗口样式常数 值 窗口显示情况 vbHide 0 窗口被隐藏 vbNormalFocus 1 普通大小,并带焦点 vbMinimizedFocus(默认设置) 2 最小化,并带焦点(这是缺省设置) vbMaximizedFocus 3 最大化,并带焦点 vbNormalNoFocus 4 普通大小,没有焦点 vbMinimizedNoFocus 6 最小化,没有焦点 如果Shell函数能够启动某个可执行文件,那么它就会返回一个称作Task ID的数字,这个数字是 标识已启动的应用程序的唯一号码。如果Shell函数不成功(也就是说没有打开指定的应用程序),VBA第 9 章 8B 利用 VBA 控制其它应用程序 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 205 就会产生错误。 如果要使用Shell函数启动的应用程序,就不要在过程中Shell函数后输入任何语句。Shell函数不 同时地开启程序,这意味着VBA通过Shell函数开启指定的应用程序,在启动程序后就立即回到过程 里面去继续执行剩余的指令(因此,没有机会立即使用该应用程序)。 如何使用Shell函数来启动控制面板呢? 1. 打开一个新工作簿,并保存为Chap09.xls。 2. 在VBE编辑器窗口,在Chap09.xls VBA工程中插入一个新模块。 3. 重新命名工程为WorkWApplets,模块名为ShellFunction。 4. 输入下面的过程StartPanel: Sub StartPanel() Shell "Control.exe", vbNormalFocus End Sub 控制面板含有几个图标,每个图标执行一项或者多项任务。正如您所知道的,在每个图标后面 都有一个程序,用户双击图标或者用箭头键选择图标后按下Enter键可以激活该程序。作为一个规律, 总是可以通过查看某个图标的属性来检查什么文件名驱动某个图标。不巧的是,控制面板里的图标 禁止了属性选项。然后,可以通过创建一个到该图标的快捷方式来查找控制面板里图标文件名。例 如,在创建一个更改计算机区域设置的过程前,找到激活该图标的文件名称。 1. 从“开始”菜单里选择“设置”,然后选择“控制面板”。 2. 在控制面板窗口里,右键单击“区域选项”图标,并且从快捷菜单中选择创建快捷方式。 3. 单击“是”,将快捷方式放在桌面上。 4. 关闭控制面板窗口。 5. 返回到桌面,在“快捷方式到区域设置”图标上单击右键,然后选择“属性”。 6. 在“属性”窗口,单击“快捷方式”选项卡,然后单击“更改图标”按钮。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 206 共 612 页 图9-1:每个控制面板里的图标都有一个后缀名为.cpl的文件 7. 写下.cpl文件的名称(Control Panel Library)或者动态链接库文件(.dll)并关闭该练习中开启的所有 窗口。 表9-2:激活控制面板图标的一些文件示例 控制面板中的图标 .cpl或者.dll文件 电话和调制解调器选项 TELEPHON.CPL或MODEM.CPL 添加/删除程序 APPWIZ.CPL 网络和拨号连接 NETCPL.CPL或NETSHELL.DLL 32-Bit ODBC ODBCCP32.CPL 系统 SYSDM.CPL 邮件 MLCFG32.CPL 用户和密码 PASSWORD.CPL或NETPLWIZ.DLL 日期/时间 TIMEDATE.CPL 区域选项 INTL.CPL Internet选项 INETCPL.CPL 声音和多媒体属性 MMSYS.CPL 显示 DESK.CPL 鼠标 MAIN.CPL 下面的ChangeSettings过程演示如何使用Shell函数来启动控制面板的区域设置图标。注意,如 果需要在程序的后面使用Shell函数的返回值,那么其参数必须写在括号里。 1. 在当前模块里输入过程ChangeSettings,如下所示: Sub ChangeSettings() Dim nrTask nrTask = Shell("Control.exe intl.cpl", vbMinimizedFocus) Debug.Print nrTask End Sub 2. 将过程ChangeSettings运行几次,每次从表9-2里列出的清单里提供一个不同的.cpl文件。可能 需要将程序修改为: Sub ChangeSettings2() Dim nrTask Dim iconFile As String iconFile = InputBox("输入CPL或DLL文件的名称:") nrTask = Shell("Control.exe " & iconFile, vbMinimizedFocus) Debug.Print nrTask End Sub 如果想要启动的程序是Microsoft应用程序,那么除了使用Shell函数外,还可以很方便地使用VBA 的方法ActivateMicrosoftApp来实现。该方法在Microsoft Excel Application对象中是可用的,例如, 要从立即窗口中启动PowerPoint,所有要做的事情就是输入下面的指令并且按下Enter: Application.ActivateMicrosoftApp xlMicrosoftPowerPoint 注意,ActivateMicrosoftApp方法要求一个常量来指定要启动的程序。如果PowerPoint没有运行,第 9 章 8B 利用 VBA 控制其它应用程序 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 207 上面的语句就会打开PowerPoint;如果该程序已经打开,这条指令不会再打开一个新的PowerPoint 界面,只是简单激活已经在运行的应用程序。可以结合ActivateMicrosoftApp方法使用下列常量,常 量的名称表示应用程序名称。 应用程序名称 常量 Access xlMicrosoftAccess FoxPro xlMicrosoftFoxPro Mail xlMicrosoftMail PowerPoint xlMicrosoftPowerPoint Project xlMicrosoftProject Schedule xlMicrosoftSchedulePlus Word xlMicrosoftWord 9.2 在应用程序之间切换 因为用户可以同时在Windows环境下使用多个应用程序,所以VBA过程必须要知道如何在打开 的程序之间切换。假设除了Microsoft Excel之外,还打开了两个其它的应用程序:Microsoft Word和 Windows Explorer。可以按照下面的语法使用AppActivate语句来激活已经打开的程序: AppActivate title [, wait] 只有参数title是必需的,这是应用程序的名称,显示在当前应用程序窗口的标题栏,或者也可以 是Shell函数返回的任务ID号码。注意,参数title要跟每个正运行的应用程序的标题字符串进行对比, 如果没有精确的匹配,那么任何以标题字符串以title字符串开始的应用程序就会被激活。 第二个参数wait是可选的,它是一个布尔值(True或False),在VBA激活应用程序时指定,如果 在这里的值是False,就立即激活指定的应用程序,即使正调用的应用程序并没有焦点;如果在wait 参数处放置True,那么被调的应用程序就会等到它有了焦点,然后才会激活该应用程序。 例如,要激活Microsoft Word,应该输入下面的语句: AppActivate “Microsoft Word” 注意,应用程序名称用双引号括起来。 也可以使用Shell函数的返回值作为AppActivate语句的参数: ‘运行Microsoft Word ReturnValue = Shell("C:\Microsoft Office\Office\Word.exe",1) ‘激活Microsoft Word AppActivate ReturnValue AppActivate语句用来在应用程序之间切换,所以要求这些程序已经在运行。该语句仅仅改变焦 点,指定的应用程序变为当前活动的窗口。AppActivate语句不会启动任何应用程序,使用该语句的 例子可参见下节的过程FindCPLFiles。 我们来练习一下刚刚介绍的几个VBA语句: 1. 通过在立即窗口里输入下面的VBA语句来打开Windows资源管理器: Shell "Explorer" 按下回车键后,被请求的应用程序就被打开了,带有“我的文档”文件夹的图标就会出现在学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 208 共 612 页 Windows任务栏上。 2. 在立即窗口里输入下面的语句: AppActivate "My Documents" 按下回车键后,焦点就会移至“我的文档”窗口。 9.3 控制另一个应用程序 现在您已经知道了如何使用VBA语句来启动一个程序以及在应用程序之间切换,那么我们来看 看一个应用程序是如何与另外一个应用程序交流的。一个应用程序控制另一个应用程序的最简单的 方式就是使用SendKeys语句,该语句允许将一系列按键发送到当前应用程序窗口,可以发送一个按 键或组合键并且得到与直接在当前应用程序中使用键盘同样的结果。SendKeys语句如下所示: SendKeys string [, wait] 这个必需的参数string是要发送到当前应用程序窗口的按键或组合键,例如,使用下面的指令来 发送字母“f”键: SendKeys "f" 要发送组合键Alt+f,使用: SendKeys "%f" 百分符号(%)是表示Alt键的字符串。 要发送例如Shift+Tab的组合键,使用下面的语句: SendKeys "+{TAB}" 加号(+)表示Shift键。要发送其它键或者其它组合键,请参见表9-3列出的相应字符串。 技巧9—1:SendKeys和其它应用程序 只能发送按键到那些为Microsoft Windows操作系统设置的应用程序。 技巧9—2:SendKeys和保留字符 有些字符在和SendKeys语句一起使用时具有特殊的意义,这些按键是:加号(+)、脱字符号(^)、鼻音符号 (~)和括号()。要发送这些字符到另一个应用程序中,就必须将它们用括号{}括起来。要发送括号时,则需要输 入{{}和{}} SendKeys语句的第二个参数是可选的,wait是个逻辑值True或者False。如果为False(缺省), 那么VBA在发送按键后立即返回过程;如果为True,那么VBA只有在发送的按键执行后才能返回到 过程。 要发送按按键时不显示的字符,使用表9-3中的代码,记住这些代码必须用引号括起来,例如: SendKeys “{BACKSPACE}” 表9-3:SendKeys语句里使用的按键代码 键 代码 键 代码 空格键 {BACKSPACE} 滚动锁定 {SCROLLLOCK} {BS} Tab {TAB} 第 9 章 8B 利用 VBA 控制其它应用程序 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 209 {BKSP} 向上箭头键 {UP} Break键 {BREAK} F1 {F1} 大写锁定键 {CAPSLOCK} F2 {F2} 删除键 {DELETE} F3 {F3} {DEL} F4 {F4} 向下箭头 {DOWN} F5 {F5} End键 {END} F6 {F6} 回车键 {ENTER} F7 {F7} ~ F8 {F8} Esc键 {ESC} F9 {F9} 帮助键 {HELP} F10 {F10} Home键 {HOME} F11 {F11} 插入键 {INSERT} F12 {F12} {INS} F13 {F13} 向左箭头键 {LEFT} F14 {F14} 数字锁定键 {NUMLOCK} F15 {F15} 向下翻页键 {PGDN} F16 {F16} 向上翻页键 {PGUP} Shift + 屏幕打印键 {PRTSC} Ctrl ^ 向右箭头键 {RIGHT} Alt % 技巧9—3:SendKeys语句区分大小写 当使用SendKeys语句发送按键时,一定要牢记必须区分字符的大小写。因此,要发送组合键Ctrl+d,必须 使用^d,而发送Ctrl+Shift+D,则必须使用字符串:^+d。 在本章前面,您学习了.cpl文件启动多种控制面板的图标。现在要创建的VBA过程的目的是要查 找硬盘上所有扩展名为.cpl的文件。 1. 使用立即窗口输入下面的语句来启动Windows资源管理器: Shell “Explorer.” “我的文档”图标将出现在屏幕底部的Windows任务栏上。 2. 在当前工程里插入一个新模块并重命名为SendKeysStatement。 3. 输入过程FindCPLFiles,如下所示: Sub FindCPLFiles() ' Windows 2000中的按键 AppActivate "我的文档" '激活搜索窗口 SendKeys "{F3}", True '将光标指针移到名为搜索文件和文件夹的文本框中 SendKeys "%m", True '输入要搜索的字符串 SendKeys "*.cpl", True '移到搜索范围下拉框 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 210 共 612 页 SendKeys "{Tab}{Tab}", True '改变根目录 SendKeys "C:\", True '执行搜索 SendKeys "%s", True End Sub 4. 切换到Microsoft Excel应用程序窗口并且运行过程FindCPLFiles(使用Alt+F8打开”宏”对话框,选 择过程名称,再单击“执行”)。 上面过程的第一条语句使用AppActivate语句(参见前一节)来激活已经运行的应用程序,您曾在 立即窗口里使用Shell语句激活了Windows资源管理器。剩余的语句发送一些必要的按键到当前应用 程序中。本过程的结果是带有扩展名为.cpl的控制面板文件列表的搜索结果窗口。也可以使用一个 SendKeys语句来发送所有必需的按键(参见下面的例子),然而,一步一步发送按键使得程序更容易 理解。 Sub FindCPLFiles2() AppActivate "我的文档" SendKeys "{F3}% m*.cpl{Tab}{Tab}C:\%s", True End Sub 9.4 控制应用程序的其它方法 虽然可以使用SendKeys语句来传递命令给其它应用程序,但是必须求助于其它方法来获得对另 一个应用程序的完全控制。有两种标准方法可以供应用程序和另一个应用程序交流。最新的方法被 称为自动控制(Automation),它允许访问和操纵另一个应用程序的对象。可以通过自动控制编写VBA 过程引用其它应用程序的对象、属性和方法来控制其它应用程序。在本章接下来的章节里,您将学 习如何通过自动控制来控制其它应用程序。称为DDE(动态数据交换)的老式数据交换技术是一个协 议,允许通过创建一个专门发送和接收信息的通道在两个应用程序之间动态发送数据。DDE非常慢, 使用困难,只有当需要与一个不支持自动控制的老应用程序交流时,才需要使用DDE。 9.4.1 理解自动控制(Automation) 当和另一个应用程序交流时,可能需要更多的功能,而不只是激活它来发送按键。例如,可能 需要在该应用程序里创建和操纵对象。可以在Excel电子表格中插入整个Word文档。因为Excel和 Word都支持自动控制,所以可以在Excel里编写一个VBA过程来操作Word对象,比如文档或者段落。 支持自动控制的应用程序称为自动控制服务器 (Automation servers)或者自动控制对象 (Automation objects) 。能够操作服务器对象的应用程序称为自动控制控制器(Automation controllers)。有些应用程序只能是一个服务器或者控制器,而另一些则既可以是服务器也可以是控 制器。Microsoft Office 2000和2002都可以作为自动控制服务器和控制器。自动控制控制器可以是安 装在计算机上的所有ActiveX控件。您将在下一章里学习这些对象。 9.4.2 理解链接(Linking)和嵌入(Embedding) 在学习如何使用自动控制从VBA过程控制其它应用程序之前,我们来看一看如何手动链接和插第 9 章 8B 利用 VBA 控制其它应用程序 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 211 入对象。对象链接和嵌入与所知道的OLE一样,允许创建组合文档(compound documents)。组合文 档包含其它应用程序创建的对象。例如,如果在Excel工作表里嵌入了一个Word文档,Excel只需知 道创建该对象的应用程序名称以及该对象在屏幕上显示的方法。组合文档通过链接或者嵌入对象来 创建。当使用手动方法来嵌入对象时,首先要在一个应用程序里复制它,然后再粘贴到另一个应用 程序里。链接对象和嵌入对象的主要区别是对象被储存和更新的方式。我们来试验一下: 1. 激活Word并打开任意一个文档。 2. 选择和复制任意一段文本。 3. 在Excel工作表里,使用下述四种方法之一将复制的文本进行粘贴: • 粘贴为文本(选择编辑|粘贴)。复制的文本就会出现在活动单元格(见图9-2,单元格A2)。 • 作为嵌入对象粘贴(选择编辑|选择性粘贴,单击“粘贴选项”按钮,并且在列表里选择 “Microsoft Word Document 对象”) 粘贴的文本将作为一个嵌入的对象(见图9-2,单 元 格 A5),该嵌入的对象成为了目标文件的 一部分。因为该嵌入的对象没有和原始数据链接,所以该信息是静态的。当源文件中的数据改变 时,该嵌入的对象不会被更新。如果要更改嵌入的数据,就必须双击它,这样就会在源程序中打 开该对象编辑它。当然,源程序必须已经安装计算机上。当嵌入对象时,所有的数据都会存储在 目标文件里,这会导致文件大小显著增大。注意,当嵌入一个对象后,Excel的编辑栏里将显示: =EMBED("Word.Document.8","") • 作为链接对象粘贴(选择编辑|选择性粘贴,单击“粘贴链接”选项,然后在列表里选择 “Microsoft Word Document 对象”)。 虽然目标文件显示了所有的数据,但是它仅仅存储了该数据的地址。当双击该链接的对象时 (见图9-2,单元格A9),原应用程序就会被启动。链接对象是一种动态的操作,这意味着当源文 件里的数据改变时,链接的数据就会自动更新。因为目标文档只包含对象如何与源文档链接的信 息,所以对象链接并不会增加目标文件的大小。下面的公式用于在Excel中链接对象: =Word.Document.8|'C:\vba2002\Chap09.doc'!'!OLE_LINK2' • 作为超链接粘贴(选择粘贴|超链接)。 粘贴的数据在工作表里显示为带下划线、有颜色的文本(见图9-2,单元格A12)。通过单击 该超链接可以快速地激活源文件。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 212 共 612 页 图9-2:链接和嵌入演示 9.4.3 使用VBA进行链接和嵌入 过程InsertLetter演示了如何使用程序在Excel电子表格中嵌入一个Word文档。用自己的文档名 称代替对“C:\Hello.doc”的引用。过程InsertLetter使用了AddOLEObject方法,该方法创建了一个 OLE对象,并且返回一个代表该新OLE对象的Shape对象。在VBA在线帮助文档中,可以找到 AddOLEObject方法可用的其它参数。 1. 在当前工程里面插入一个新模块,并重命名为OLE。 2. 输入过程InsertLetter,如下所示: Sub InsertLetter() Workbooks.Add ActiveSheet.Shapes.AddOLEObject FileName:="C:\Hello.doc" End Sub 上面的过程打开一个新工作簿,然后在该工作簿中嵌入指定的Word文档。要链接一个文档,就 必须指定另外一个参数Link,如下所示: ActiveSheet.Shapes.AddOLEObject _ FileName:="C:\Hello.doc", Link:=True 技巧9—4:对象链接和嵌入 当必须做出是否使用嵌入还是链接对象的决定时,只要有下列条件之一,那么就使用对象嵌入: „ 不在乎文档大小,或者有足够的硬盘空间和内存来处理大文件。 „ 不需要在其它复合文档里的源文件或者使用源文本。 „ 想要将该文档通过电子邮件或者磁盘发送给别人,并且确保他们能够顺利地读取数据。 第 9 章 8B 利用 VBA 控制其它应用程序 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 213 9.4.4 COM和自动控制 在自动控制后面的驱动力是组件对象模型(COM),它决定了通过服务器应用程序创建对象的规 则,也指定服务器和控制应用程序在使用这些对象时必须应用的方法。COM标准包含使自动控制界 面(Automation interfaces)可用的函数集合。 当服务器应用程序创建一个对象时,它会自动地制作一个和它一起可用的界面。该界面包括该 对象可识别的属性、方法和事件。控制器应用程序不需要为了控制该对象去了解它的内部结构,只 需要知道如何操作服务器应用程序制作的对象界面。 9.5 理解绑定 对于控制器应用程序与自动控制对象(服务器)相交互,必须将VBA过程中的对象变量和服务器实 际的自动控制对象联系起来,这个过程称作绑定(binding)。这里有两种类型的绑定:后期绑定(late binding)和早期绑定(early binding)。绑定的选择对应用程序的执行有很大的影响。 9.5.1 后期绑定 当声明一个变量 As Object 或者As Variant时,VBA使用的是后期绑定。后期绑定也叫运行时 绑定(run-time binding)。简单地说,后期绑定意味着VBA在设计时不会将对象变量和自动控制对象联 系起来,而是要等到实际运行该过程时才联系起来。因为声明As Object或者As Variant实质上是非 常常见的,所以VBA在编译时不能决定变量引用的对象真正具有VBA过程使用的属性和方法。 下面的声明导致对指定对象的后期绑定: Dim mydoc As Object 后期绑定的优势是所有的自动控制对象都知道如何使用。 后期绑定的缺点是不支持内置常量。因为在设计时VBA并不知道对象引用的类型库,所以必须 通过在应用程序文档里查找值在代码里定义常量。同样,在运行时询问应用程序将降低程序执行的 速度。 注意,后期绑定使得访问另一个应用程序的类型库里的对象成为可能,而不需要首先建立对该 对象库的引用。如果你不肯定用户是否在他们的机子上安装了要引用的类型库,那么就使用后期绑 定。 下面的过程演示了如何使用后期绑定来打印一篇Word文档。 Sub PrintWordDoc() Dim objWord As Object Set objWord = CreateObject("Word.Application") With objWord .Visible = True .Documents.Open "C:\Hello.doc" .Options.PrintBackground = False .ActiveDocument.PrintOut End With objWord.Documents.Close objWord.Quit 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 214 共 612 页 Set objWord = Nothing End Sub 技巧9—5:这是什么类型的绑定? 无论何时使用常用的Object或Variant数据类型声明对象变量,考虑后期绑定。后期绑定和早期绑定的主要 区别是如何声明对象变量。 9.5.2 早期绑定 当声明对象变量为指定的对象类型时,VBA使用的是早期绑定。早期绑定也称为编译时绑定 (compile-time binding)。这意味着VBA在源代码转变为可执行代码时,就将对象变量和自动控制对象 联系起来了。常见的语法如下所示: Dim objectVariable As Application.ObjectType 在上面的语法中,Application是应用程序的名称,出现在对象浏览器里的工程库下拉列表里(例 如Word和Excel)。ObjectType是对象类型名称(例如应用程序、文档、工作簿、工作表) 。 下面的声明导致早期绑定: Dim mydoc As Word.Document Dim mydoc As Excel.Worksheet 早期绑定可以充分利用VBE编辑器上可用的许多调试工具。例如,可以使用对象浏览器查找外 部对象、属性和方法。VBA的自动语法检测、自动列出成员以及自动显示快速信息(这些都在第二章 里已介绍)有助于在编写代码时更快且更少出错。另外,早期绑定允许使用内置常量作为方法和属性 设定的参数,因为这些常量在设计时在类型库里面就可用,所以不需要定义它们。这些非常方便的 内置语法检测、智能特点和对内置常量的支持在后期绑定里不可用。虽然使用早期绑定的VBA过程 执行得更快一些,但是一些非常老的Windows应用程序只能使用后期绑定。 注意,为了使用早期绑定,必须首先建立对对象库的引用 (参见下面的章节)。当确定用户安装 了需要引用的类型库时,就使用早期绑定。 9.5.3 建立对对象库的引用 如果决定通过自动控制使用早期绑定连接到另一个应用程序,那么首先就应该建立对包括要操 作对象的对象库的引用。依照下面的步骤创建对Microsoft Word 对象库的引用: 1. 激活VBE编辑器窗口。 2. 在工程浏览器里选择当前工程,并选择“工具”|“引用”。 3. 在“引用”对话框里,选择“可使用的引用”列表框里面的应用程序名称。例如,单击Microsoft Word 9.0 Object Library或者 Microsoft Word 10.0 Object Library旁边的复选框(见图9-3)。向 下拉动“可使用的引用”列表框的滚动条定位到该对象库。如果已经安装了某个应用程序但是它 的类型库没有出现在可使用的引用列表框里,那么可以单击“浏览”按钮。 4. 单击“确定”按钮关闭“引用”对话框。 “引用”对话框列出了可用于VBA工程中的引用的名称。没有使用的引用按字母顺序列出,勾 选上了的引用按优先顺序列出。例如,在Excel里Microsoft Excel 10.0 Object Library比Microsoft 第 9 章 8B 利用 VBA 控制其它应用程序 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 215 Word 10.0或者9.0 Object Library具有更高的优先顺序。当过程引用一个对象时,VBA从“引用”对 话框里列出的库中按顺序搜索每一个被引用的对象库。 图9-3:为了操作另一个应用程序中的对象,应该建立对该需要的对象库的引用(译者注:由于本机上装的 是Office 12,因此所选库为Microsoft Word 12.0 Object Library,下同) 在设置了对所需要的对象库的引用后,就可以通过对象浏览器浏览该对象的属性和方法。 图9-4:添加了对Microsoft Word 9.0 Object Library的引用后(见图9-3),Microsoft Word的所有对象、学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 216 共 612 页 属性和方法都可以从Excel VBA工程里访问了 9.6 创建自动控制对象 依照下列步骤在VBA过程里创建一个自动控制对象: • 使用Dim…As Object或者Dim…As Application.ObjectType子句声明对象变量(参见前面章节里 使用后期和早期绑定介绍)。 • 如果使用早期绑定,那么使用“引用”对话框来建立对应用程序对象类型库的引用。 • 如果自动控制对象仍不存在,那么使用CreateObject函数;如果自动控制对象已经存在,那么就 使用GetObject函数来建立对该对象的引用。 • 使用关键字Set将CreateObject或者GetObject函数返回的对象赋给对象变量。 9.6.1 使用CreateObject函数 按照下面的语法,使用CreateObject函数从VBA过程里创建对自动控制对象的引用: CreateObject(class) 参数class是想要引用的应用程序名称。该名称包含前面介绍的对象类类型(参见早期绑定部分)。 必须使用关键字Set将自动控制对象赋给对象变量,如下所示: Set variable_name = CreateObject(class) 例如,使用自动控制对象来激活Word,在VBA过程里需要包括下面的声明语句: '早期绑定 Dim wordAppl As Word.Document Set wordAppl = CreateObject("Word.Application") 或者 '后期绑定 Dim wordAppl As Object Set wordAppl = CreateObject("Word.Application") 通常,CreateObject函数创建指定的自动控制对象的新实例。然而,一些应用程序注册为称作 “单一实例”的应用程序,这就是说不能同时运行一个以上的应用程序实例。Microsoft Word和 PowerPoint就是这种单一实例的应用程序。因此,如果Word或者PowerPoint已经在运行,那么 CreateObject函数就会直接引用正运行的实例,而不会再创建一个新的实例。 9.6.2 使用自动控制创建一个新的Word文档 我们来看看如何将前面章节学习到的关于绑定的知识应用到实际的例子中。也许有时需要从 Excel直接通过程序打开一个Word文档,并且往里面写入数据,下面的例子使用了早期绑定。 1. 在当前工程里插入一个新模块,并重命名为Automation。 2. 在工程浏览器窗口里,选择当前工程,并且选择“工具”|“引用”。 3. 如果在“可使用的引用”列表里没有选取Microsoft Word 9.0 Object Library或者Microsoft Word 第 9 章 8B 利用 VBA 控制其它应用程序 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 217 10.0 Object Library,那么找到它们并选取,单击“确定”退出。 4. 输入下面的过程WriteLetter: Sub WriteLetter() Dim wordAppl As Word.Application Application.StatusBar = "创建Word应用程序对象…..." Set wordAppl = CreateObject("Word.Application") With wordAppl .Visible = True Application.StatusBar = "创建一个新文档…..." .Documents.Add .ActiveDocument.Paragraphs(1).Range.InsertBefore "Invitation" Application.StatusBar = "正保存文档..." .ActiveDocument.SaveAs "C:\Invite.doc" Application.StatusBar = "退出Word…..." .Quit End With Set wordAppl = Nothing Application.StatusBar = False End Sub 5. 切换到Excel应用程序窗口,并选择“工具”|“宏”|“宏”,在宏列表中找到过程WriteLetter并 单击“执行”。 过程WriteLetter开始时声明对象变量为指定的对象类型(Word.Application)。回想这种类型声明 (早期绑定) 需要建立对Word对象库的引用(本章的前面已介绍)。CreateObject函数返回的自动控制 对象赋给称作wordAppl的对象变量,因为通过自动控制启动的应用程序不会出现在屏幕上,所以使 用语句: wordAppl.Visible = True 使启动的Word应用程序可见,以便于观察VBA的工作情况。剩下的语句打开一个新文档(Add方 法),并在第一段输入文本(InsertBefore方法),将文档保存到硬盘(SaveAs方法),以及关闭Word应 用程序(Quit方法)。每条语句之前都有一条指令,将信息显示在Excel应用程序窗口底部的状态栏上。 当Word应用程序关闭后,指令: Set wordAppl = Nothing 清除对象变量,释放该对象占用的内存。语句: Application.StatusBar = False 将状态栏上的信息恢复为默认的“就绪”。 前面提到过,Word是一个单一实例(single-instance)的应用程序,这意味着不能同时运行一个以 上的Word实例。简单说,如果没有启动Word,WriteLetter过程里面的CreateObject函数将会启动 Word,否则,它将会使用当前活动的Word实例。 9.6.3 使用GetObject函数 如果确定某自动控制对象已经存在或者已经打开,那么就考虑使用GetObject函数,如下所示: GetObject([pathname][, class]) 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 218 共 612 页 GetObject函数有两个参数,它们都是可选的。使用第一个参数来指定要打开的文件名称,应该 提供完整的文件路径。如果忽略该参数,那么必须指定参数class来表示要使用的对象类型,例如: Excel.Application Excel.Sheet Excel.Chart Excel.Range Word.Application Word.Document PowerPoint.Application 为了在Invite.xls的基础上创建一个Excel对象,并且强制设置该对象为Excel 5工作表,可以使用 下面的声明: ‘后期绑定 Dim excelObj As Object Set excelObj = GetObject("C:\Invite.xls", Excel.Sheet.5") 要设定对象变量为某个特定的 Word 文档,可以使用: ‘早期绑定 Dim wordObj As Word.Application Set wordObj = GetObject("C:\Invite.doc") 如果要访问一个正在运行的Office应用程序对象,那么可以将第一个参数空出: Dim excelObj As Object Set excelObj = GetObject(, "Excel.Application") 当调用不带第一个参数的GetObject函数时,就会返回一个对该应用程序实例的引用,如果该应 用程序没有运行,就会产生错误。 9.6.4 打开已存在的Word文档 下面的过程CenterText演示了使用GetObject函数访问Invite.doc文件。回想一下,在本章前面的 过程WriteLetter里已创建了该文件。过程CenterText会将指定Word文档里的第一段居中,该过程使 用了一个名为DocExists的自定义函数来检查指定的文档是否存在。另一个自定义函数(IsRunning) 检查Word是否已经在运行。基于上述检查结果,使用CreateObject或者GetObject函数。如果出现错 误,则会显示错误编号和错误描述。 Sub CenterText() Dim wordDoc As Word.Document Dim wordAppl As Word.Application Dim mydoc As String Dim myAppl As String On Error GoTo ErrorHandler mydoc = "C:\Invite.doc" myAppl = "Word.Application" '首先查找是否指定的文档存在 If Not DocExists(mydoc) Then MsgBox mydoc & "不存在." & Chr(13) & Chr(13) _ & "运行 WriteLetter 过程创建" & mydoc & "." Exit Sub End If 第 9 章 8B 利用 VBA 控制其它应用程序 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 219 '现在检查 Word 是否正在运行 If Not IsRunning(myAppl) Then MsgBox "Word 没有运行- 将创建新的 Word 实例. " Set wordAppl = CreateObject("Word.Application") Set wordDoc = wordAppl.Documents.Open(mydoc) Else MsgBox "Word 正在运行- 将获取指定的文档. " '将变量 wordDoc 与指定的 Word 文档绑定 Set wordDoc = GetObject(mydoc) End If '将页面中的第一段水平居中 With wordDoc.Paragraphs(1).Range .ParagraphFormat.Alignment = wdAlignParagraphCenter End With wordDoc.Application.Quit SaveChanges:=True Set wordDoc = Nothing Set wordAppl = Nothing MsgBox "文档" & mydoc & "已被重新格式化." Exit Sub ErrorHandler: MsgBox Err.Description, vbCritical, "错误: " & Err.Number End Sub Function DocExists(ByVal mydoc As String) As Boolean On Error Resume Next If Dir(mydoc) < > "" Then DocExists = True Else DocExists = False End If End Function Function IsRunning(ByVal myAppl As String) As Boolean Dim applRef As Object On Error Resume Next Set applRef = GetObject(, myAppl) If Err.Number = 429 Then IsRunning = False Else IsRunning = True End If '清除对象变量 Set applRef = Nothing End Function 9.6.5 使用关键字New 除了使用CreateObject函数来引用其它的应用程序之外,可以使用关键字New。关键字New告 诉VBA创建一个新的对象实例,返回对该实例的引用,以及将引用赋给该对象变量。例如,可以按 下面的方式使用关键字New: Dim objWord As Word.Application Set objWord = New Word.Application Dim objAccess As Access.Application Set objAccess = New Access.Application 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 220 共 612 页 使用关键字New声明的对象变量总是早期绑定。使用关键字New比使用CreateObject函数更高 效。每次使用关键字New时,VBA就会创建应用程序的一个新实例。如果该应用程序已经运行,就 不需要打开另一个实例,则应该使用GetObject函数。 关键字New也可以用来在声明对象变量的同时,创建一个该对象的新实例,例如: Dim objWord As New Word.Application 注意,当使用关键字New在Dim语句里声明对象变量时,不需要使用Set语句。然而,不建议使 用这种创建对象变量的方法,因为当该对象变量真正被创建后,就会失去对它的控制。在声明语句 中使用关键字New会导致创建对象变量,即使它没有被使用到。因此,如果想要控制创建的对象变 量,那就使用下面的语法声明对象变量: Dim objWord As Word.Application Set objWord = New Word.Application Set语句可以在需要使用该对象的代码处放置,下一节将演示如何使用关键字New来创建 Microsoft Outlook的一个新实例,并且编写联系地址到Excel工作表中。 9.6.6 使用自动控制访问Microsoft Outlook 要从Excel直接访问Outlook的对象模型,首先就要建立对Microsoft Outlook 10.0或者9.0 Object Library的引用。下面的示例过程将在Excel工作表里插入Outlook里面的联系信息。 Sub GetContacts() Dim objOut As Outlook.Application Dim objNspc As NameSpace Dim objItem As ContactItem Dim Headings As Variant Dim cell As Object Dim i As Integer '数组元素 Dim r As Integer '行号 r = 2 Set objOut = New Outlook.Application Set objNspc = objOut.GetNamespace("MAPI") Headings = Array("Full Name", "Street", "City", _ "State", "Zip Code", "E-Mail") Sheets(1).Activate For Each cell In Range("A1:F1") cell.FormulaR1C1 = Headings(i) i = i + 1 Next For Each objItem In objNspc.GetDefaultFolder _ (olFolderContacts).Items With ActiveSheet .Cells(r, 1).Value = objItem.FullName .Cells(r, 2).Value = objItem.BusinessAddress .Cells(r, 3).Value = objItem.BusinessAddressCity .Cells(r, 4).Value = objItem.BusinessAddressState .Cells(r, 5).Value = objItem.BusinessAddressPostalCode .Cells(r, 6).Value = objItem.Email1Address End With r = r + 1 Next objItem Set objItem = Nothing 第 9 章 8B 利用 VBA 控制其它应用程序 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 221 Set objNspc = Nothing Set objOut = Nothing End Sub 过程GetContacts开始声明一个名为objOut的对象变量来存储到Outlook应用程序的引用,该变 量定义为指定的对象类型(Outlook.Application),因此,VBA使用早期绑定。 注意在该过程中,使用关键字New(在前面部分已介绍)来创建Outlook应用程序对象的一个新实 例,返回引用到该实例,并且将该引用赋给声明的变量objOut。 为了访问Outlook里的联系项目,也需要声明对象变量来引用Outlook的NameSpace和Item对象。 NameSpace对象代表了存储为MAPI(Messaging Application Programming Interface)的信息。 NameSpace对象包含了文件夹(联系地址、日志、任务,等等),每个文件夹依次包含项目。一个项 目是Outlook数据的一个特定的实例,例如邮件信息、或者联系地址。 使用For…Each…Next循环在工作表里写入列标题之后,过程使用另一个For Each…Next循环 来遍历联系地址(Contacts)文件夹中的项目(Items)集合。GetDefaultFolde方法返回一个联系地址文 件夹的对象变量,该方法有一个参数,该常量表示要访问的文件夹。当所有的联系地址都被写入Excel 工作表后,该过程通过将它们设置为Nothing释放所有对象变量。 注意,当运行过程GetContacts时,可能会看到一个警告信息,告诉程序试图访问电子邮件地址, 单击“确定”允许操作。 本章小结和下一章内容简介 在本章中,您学习了如何从VBA程序里启动、激活和控制其它应用程序(Word和Outlook),学习 了如何使用SendKeys方法发送按键到另一个应用程序,也学习了如何手动和编程链接和嵌入对象。 最后,使用自动控制从Excel里创建一个新的Word文档,以及后来访问该文档并设置一些格式。您也 学习了如何从Outlook里获取联系地址并将它们放置到Excel工作表中。您使用的两个新函数 CreateObject和GetObject扩展了您的VBA知识。您也学习了如何以及何时使用关键字New。要学习 如何从Excel里控制Microsoft Access,见第15章。 在下一章中,您将学习如何通过自定义窗体从用户处收集更多的数据。 第10章 对话框和自定义窗体 在第四章,您学习了如何使用 Excel 内置的 InputBox 函数在 VBA 过程执行期间从用户处收集单一的数据。但是,假设程序在 运行时需要多个数据呢…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 224 共 612 页 在第四章,您学习了如何使用Excel内置的InputBox函数在VBA过程执行期间从用户处收集单一 的数据。但是,假设程序在运行时需要多个数据呢?用户也许希望一次就提供所有数据,或者从项 目列表中作出合适的选择。如果过程必须收集数据,那么可以: • 使用内置对话框集合 • 创建一个自定义窗体 本章将教您如何从VBA过程里显示内置的对话框,以及从零开始设计自己的自定义窗体。 10.1 Excel对话框 在开始创建自己的窗体之前,应该花一些时间学习如何利用Excel内置的对话框并准备使用它 们。我所指的不是您手动选择适合的选项的能力,而是如何从VBA过程里调用这些对话框。 Excel有一个专门的内置对话框集合,它们用开头为xlDialog的常量表示,例如xlDialogClear、 xlDialogFont、xlDialogDefineName和xlDialogOptionsView。这些内置对话框是Excel对象,属于内 置的Dialos集合,每个dialog对象代表一个内置对话框。 表10—1:常用的内置对话框 对话框名称 常量 新建 xlDialogNew 打开 xlDialogOpen 另存为 xlDialogSaveAs 页面设置 xlDialogPageSetup 打印 xlDialogPrint 字体 xlDialogFont 按照下面的格式使用Show方法来显示对话框: Application.Dialogs(常量).Show 例如,下面的语句显示“字体”对话框: Application.Dialogs(xlDialogFont).Show 在对象浏览器里面选择Excel库后,再输入xlDialog进行搜索,那些代表Excel内置对话框的常量 列表就会显示在对象浏览器中(参见图10-1)。 1. 打开一个新工作簿并保存为Chap10.xls。 2. 切换到VBE编辑器窗口。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 225 图10-1:前缀为“xlDialog”的常量识别Excel内置对话框 3. 打开立即窗口。 4. 输入下面的语句并查看结果: Application.Dialogs(xlDialogClear).Show Application.Dialogs(xlDialogFont).Show Application.Dialogs(xlDialogFontProperties).Show Application.Dialogs(xlDialogDefineName).Show Application.Dialogs(xlDialogOptionsView).Show 最后一条指令显示“选项”对话框的“视图”选项卡。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 226 共 612 页 图10-2:以常量xlDialogOptionsView代表的“选项”对话框“视图”选项卡的可用设置 显示内置对话框后,可以选择合适的选项,然后Excel就会格式当前所选择的单元格、区域或者 整个工作表。虽然不能更改内置对话框的外观和行为,但是当从VBA过程显示内置对话框时,可以 决定它的初始设置。如果不更改初始设置,那么VBA将显示带有缺省设置的对话框。 假设要显示已选取“全部”选项按钮的“清除”对话框。通常,Excel显示这个对话框时,所选 取的是“内容”选项按钮。在立即窗口里输入下面的语句: Application.DialogS(xlDialogClear).Show 1 可以在Show方法后面加上一系列的参数。在“清除”对话框里,“全部”选项按钮出现在四个 选项按钮组的第一个。Excel通常将可用的选项进行编号,因此,“全部”=1、“格式”=2、“内容” =3、“批注”=4。在线帮助中可以搜索到内置对话框的参数列表(参见图10-3)。 在立即窗口里输入下面的语句,可以显示“字体”对话框,并且当前选择为14号的“Arial”字 体: Application.Dialogs(xlDialogFont).Show "Arial", 14 如果只要指定字号,那么可以在第一个参数的位置输入一个逗号: Application.Dialogs(xlDialogFont).Show , 8 下面的指令显示“定义名称”对话框,并且在工作簿中的“名称”文本框中输入“John”,“引 用位置”里引用到单元格A1: Application.Dialogs(xlDialogDefineName).Show "John", "=$A$1" 如果单击“确定”,Show方法就返回True,单击“取消”则为False。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 227 图10-3:Excel内置对话框参数列表 10.2 打开文件对话框和保存文件对话框 OfficeXP中一个新的且相当强大的对象是FileDialog。该对象允许从VBA过程里显示打开文件和 保存文件对话框。因为FileDialog对象是Microsoft Office 10.0 Object Library的一部分,所以它在所 有的Office XP应用程序里都是可用的。在前期的Excel版本中,程序员使用两种特殊的方法来显示打 开文件和保存文件对话框,这些方法(GetOpenFilename和GetSaveAsFilename)将在本章后面介绍。 要在VBA过程里面使用新的FileDialog对象来显示打开文件对话框,可以输入下面的语句: Application.FileDialog(msoFileDialogOpen).Show 要显示保存文件对话框,则使用下面的语句: Application.FileDialog(msoFileDialogSaveAs).Show 现在,我们在立即窗口里输入上面的语句来看看打开文件和保存文件对话框。 除了打开文件和保存文件对话框之外,FileDialog对象也能够显示带有文件和文件夹列表(参见图 10-4)或者文件夹列表(图10-5)的“浏览”对话框: ‘浏览文件和文件夹列表 Application.FileDialog(msoFileDialogFilePicker).Show 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 228 共 612 页 图10-4:文件选取对话框允许用户选择一个或多个文件,该对话框显示文件和文件夹列表,并且标题栏 显示为“浏览” ‘浏览文件夹列表 Application.FileDialog(msoFileDialogFolderPicker).Show 图10-5:文件夹选取对话框允许用户选择一个路径,该对话框显示目录列表,并且标题栏显示为“浏览” FileDialog对象的常量列于下表中,前缀“mso”表明这些常量都是Microsoft Office对象模型的 一部分。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 229 msoFileDialog常量 值 msoFileDialogOpen 1 msoFileDialogSaveAs 2 msoFileDialogFilePicker 3 msoFileDialogFolderPicker 4 可以使用FileDialog对象的Filters属性来控制所显示文件的类型。如果打开了“打开文件”对话 框底部的“文件类型”下拉列表框时,将看到许多可选择的文件过滤器。那里有24种预先设置好的 文件过滤器,也可以在该列表里添加自己的过滤器。 在立即窗口里输入下面的语句,可以得到缺省的文件过滤器数: set f = Application.FileDialog(msoFileDialogOpen).Filters ?f.count FileDialog对象的过滤器存储在FileDialogFilters集合里面。我们来创建一个简单的过程,将缺省 的文件过滤器列表返回到Excel工作表中: 1. 在当前VBA工程里插入一个新模块,并且重命名为DialogBoxes。 2. 在DialogBoxes代码窗口里输入下面的ListFilters过程: Sub ListFilters() Dim fdfs As FileDialogFilters Dim filt As FileDialogFilter Dim c As Integer Set fdfs = Application.FileDialog(msoFileDialogOpen).Filters Sheets(3).Cells(1, 1).Select Selection.Formula = "缺省的过滤器列表" With fdfs c = .Count For Each filt In fdfs Selection.Offset(1, 0).Formula = filt.Description & _ ": " & filt.Extensions Selection.Offset(1, 0).Select Next MsgBox c & "个过滤器被写入到Sheet3中." End With End Sub 该过程声明了两个对象变量,变量fdfs返回对FileDialog对象里FileDialogFilters集合的引用,而 对象变量filt存储对FileDialogFilter对象的引用。FileDialogFilters集合的Count属性返回文件过滤器的 总数。之后,过程遍历过滤器集合,并且获取每个过滤器的描述和扩展名。 使用FileDialogFilters集合的Add方法,可以很容易地将自己的过滤器添加到缺省的过滤器中。 下面修改后的ListFilters2过程演示了如何将临时文件(*.tmp)过滤器添加到过滤器列表中。该过程里的 最后一句将打开“打开文件”对话框,以便检查自定义的过滤器(*.tmp)是否已经被添加到了文件类 型下拉列表框里。 Sub ListFilters2() Dim fdfs As FileDialogFilters Dim filt As FileDialogFilter Dim c As Integer Set fdfs = Application.FileDialog(msoFileDialogOpen).Filters Sheets(3).Cells(1, 1).Select Selection.Formula = "缺省的过滤器列表" 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 230 共 612 页 With fdfs c = .Count For Each filt In fdfs Selection.Offset(1, 0).Formula = filt.Description & _ ": " & filt.Extensions Selection.Offset(1, 0).Select Next MsgBox c & "个过滤器被写入到Sheet3中." .Add "临时文件", "*.tmp", 1 c = .Count MsgBox "现在有" & c & "个过滤器." & vbCrLf _ & "自已检查." Application.FileDialog(msoFileDialogOpen).Show End With End Sub 可以使用FileDialogFilters集合的Clear方法清除所有预设的过滤器。修改一下上面的过程,在添 加自定义的“临时文件(*.tmp)”过滤器之前,清除内置的过滤器。 当从“打开文件”对话框里选择一个文件时,所选文件的文件名称和路径就会被放置在 FileDialogSelectedItems集合里。使用SelectedItems属性可以返回FileDialogSelectedItems集合。 通过设置FileDialog对象的AllowMultiSelect属性为True,用户就可以同时按下Shift键或者Ctrl键和文 件名称,选择一个或多个文件。 接下来的过程演示了如何使用上面提及的属性,该过程打开一个新的工作簿并且插入一个列表 框控件。允许用户选择一个以上的文件,接着所选文件将加入到该列表框控件里,并且加亮显示第 一个文件名。 Sub ListSelectedFiles() Dim fd As FileDialog Dim myFile As Variant Dim lbox As Object Set fd = Application.FileDialog(msoFileDialogOpen) With fd .AllowMultiSelect = True If .Show Then Workbooks.Add Set lbox = Worksheets(1).Shapes. _ AddFormControl(xlListBox, _ Left:=20, Top:=60, Height:=40, Width:=300) lbox.ControlFormat.MultiSelect = xlNone For Each myFile In .SelectedItems lbox.ControlFormat.AddItem myFile Next Range("B4").Formula = _ "您已经选择了下面的 " & _ lbox.ControlFormat.ListCount & "个文件:" lbox.ControlFormat.ListIndex = 1 End If End With End Sub 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 231 图10-6:通过使用过程ListSelectedFiles(见上面)将用户选择的文件添加到工作表中的列表框控件中 注意,Show方法不会将用户所选的文件打开,它仅仅显示“打开文件”对话框。当用户单击“打 开”按钮时,文件名称通过SelectedItems属性从SelectedItems集合里获得。如果希望用户单击“打 开”按钮时立即执行文件的打开操作,就应该使用FileDialog对象的Execute方法。 下面的过程演示如何立即打开用户选择的文件: Sub OpenRightAway() Dim fd As FileDialog Dim myFile As Variant Set fd = Application.FileDialog(msoFileDialogOpen) With fd .AllowMultiSelect = True If .Show Then For Each myFile In .SelectedItems .Execute Next End If End With End Sub 10.3 GetOpenFilename和GetSaveAsFilename方法 Excel很多年前就给程序员们提供了两种方便的VBA方法来显示“保存文件”和“打开文件”对 话框:GetOpenFilename和 GetSaveAsFilename。这些方法只在Excel里可用,并且仍然在 Excel2002里面可用以保持向后兼容。 GetOpenFilename方法显示“打开”对话框,在那里可以选择要打开的文件名称。第二个方法 (GetSaveAsFilename)则显示“另存为”对话框。 1. 在立即窗口中试试下面的指令: Application.GetOpenFilename Application.GetSaveAsFilename Application.GetSaveAsFilename ("Plan2.xls") GetOpenFilename方法从用户处获得文件名称,而不必实际打开某特定的文件。该方法有四个 可选的参数,经常使用的是第一个和第三个参数,如下表所示: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 232 共 612 页 GetOpenFilename参数 描述 fileFilter 该参数决定了对话框的文件类型下拉框的内容。例如,要在文件类型下 拉框里显示“Excel Files(*.xls)”的话,就应该输入下列文本作为fileFilter: “Excel Files(*.xls), *.xls”。过滤器的前面部分(逗号前)决定文件类型下拉 框要显示的文本,第二部分(逗号后)指定要显示的文件类型。确保试试表 格里的例子。 title 这是对话框的标题,如果忽略,对话框将显示缺省的标题“打开”。 在立即窗口里输入下面的语句(确保在一行输入),来看看这些参数是如何使用的: Application.GetOpenFilename("Excel Files(*.xls), *.xls"),,"Highlight the File" GetOpenFilename方法返回所选的或者指定的文件名称,该名称以后可以在VBA过程里用来打 开该文件,例如: yourFile = Application.GetOpenFilename ?yourFile C:\EXCEL\Mark.xls Workbooks.Open Filename:=yourFile 在上面的例子里,文件名称被赋给变量yourFile,接下来的两条输入为询问文件名称(?yourFile) 和显示该文件的名称(C:\EXCEL\Mark.xls)。第四条语句打开变量yourFile指定的文件。如果通过按下 Esc键或者单击对话框上的“取消”按钮来取消对话框,那么GetOpenFilename方法就会返回False。 GetSaveAsFilename方法返回文件名和路径,然而,不会自动地保存该指定的文件。输入下面 的指令提供文件名称: Application.GetSaveAsFilename ("Plan2.xls") 如果忽略文件名称,Excel就会显示当前文件的名称。当使用GetSaveAsFilename方法时,可以 指定文件过滤器和对话框自定义标题: yourFile = Application.GetSaveAsFilename(“Plan2.xls”, "Excel Files(*.xls), *.xls",,"Name your file") 要显示“另存为”对话框,就要将GetSaveAsFilename方法的结果赋给一个变量,如上所示。 10.4 创建窗体 尽管内置的对话框虽时可以使用且很方便,但是它并不能满足所有的VBA应用程序的需要。除 了将对话框显示在屏幕上和指定它的初始设置外,不能控制对话框的外观、不能决定添加加哪个按 钮、删除哪个按钮、移动哪个按钮,也不能改变内置对话框的大小。如果想提供一个自定义的界面, 那么唯一的解决方法就是创建一个用户窗体。 用户窗体看上去就像一个自定义对话框,可以在上面添加各种各样的控件,设置这些控件的属 性以及编写VBA过程响应窗体和控件事件。窗体是单独的对象,可以在VBE编辑器菜单里选择“插 入”|“用户窗体”来添加窗体。 窗体可以在不同的应用程序间分享使用,例如,可以在Word或者任何其它使用VBE编辑器的应 用程序里面,重复使用Excel里设计的窗体。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 233 按照下述步骤创建自定义窗体: • 切换到VBE编辑器窗口。 • 选择“插入”|“用户窗体”。 一个名为“窗体”的新文件夹显示在工程浏览器窗口,该文件夹包含一个空白的用户窗体。工 作区域自动显示这个窗体和为添加控件而带有必要工具的工具箱。 图10-7:通过选择“插入”菜单里面的“用户窗体”可以添加一个新的窗体到打开的VBA工程中 属性窗口(见图10-8)显示很多种属性,可以根据需要进行设置。该窗体属性被整理成七个类别: 外观、行为、字体、杂项、图片、位置和滚动。要按类别显示窗体属性,可以单击属性窗口上的“按 分类序”选项卡。要查找某特定属性的信息,可以单击该属性名称并且按下F1键,将启动该属性描 述主题的在线帮助。 在VBA工程里添加新窗体后,应该通过设置“名称”(Name)属性给该窗体赋给一个独特的名称。 除了名称之外,每个窗体还应该包含一个标题,可以使用Caption属性给窗体设置标题。 技巧10—1:在VBA应用程序之间共享窗体 所有使用VBE编辑器的VBA应用程序在创建自定义窗体时共享一些功能特点,可以通过从文件中导出和导 入,或者通过拖曳窗体对象到另一个工程来共享窗体。要导入或者导出窗体文件,可以选择“文件”|“导入” 或者“文件”|“导出”。在导出某个窗体文件之前,确保在工程浏览器窗口里选中了该窗体。将窗体拖曳到一学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 234 共 612 页 个不同的VBA应用程序之前,要排列好VBE窗口以便能同时看到两个应用程序的工程浏览器窗口,然后拖曳该 窗体到工程浏览器里的另一个工程名中。 图10-8:使用属性窗口,可以很容易地更改自定义窗体的外观、行为和其它特点 10.4.1 创建用户窗体的工具 当设计一个窗体时,可能要插入一些合适的控件来使它有用。工具箱包含了所有可以添加到窗 体上的标准的VBA控件按钮,也可以包含已经安装在计算机上的其它控件。工具箱中可用的控件被 称为ActiveX控件,这些控件能够响应特定的用户行为,例如单击控件或改变它的值。您将在本章剩 余的部分学习如何使用工具箱控件。 图10-9:工具箱显示了可以添加到自定义窗体中的控件 Microsoft Office集成套装版提供了可以放置在工具箱上以快速访问的其它ActiveX控件,如果计 算机上还安装了其它包含ActiveX的应用程序,那么也可以将它们放置在工具箱上。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 235 依照下述步骤在工具箱上添加其它的ActiveX控件: 1. 在工具箱上单击右键并且点击“新建页”,选择重命名。 2. 在“题注”框里面输入新名称“其它控件”,在“控件提示文字”框里面输入“其它的ActiveX 控件”,单击“确定”返回到工具箱 3. 在新页的任意地方单击右键,并从快捷菜单中选择“附加控件”。如果该选项不可用,那么确定 在该页区域单击右键,而不是单击“其它控件”本身。 4. 当“附加控件”对话框出现时,单击想要添加的每个控件前面的复选框。图10-10显示了加亮 的日历控件,单击“确定”按钮,这个控件就会出现在工具箱的当前页上。 图10-10:已安装在计算机上可以添加到工具箱中的附加ActiveX控件 在接下来几节中将描述标准的VBA控件。 标签 标签允许在窗体上添加文本,标签控件经常用来添加标题、名称、题头和说明。可以使用标签 给那些不带Caption属性的控件(例如文字框、列表框、滚动条和旋转按钮)加上标题。可以给标签定 义快捷键,例如,在添加完标签控件和设置它的Accelerator属性后,通过按下Alt键和某个特定的字 母键,就可以立即激活该控件。要给某个已经存在的控件添加标题或者快捷键,应该添加一个标签 控件,并且在它的属性窗口中的Accelerator属性里输入一个字母。接下来,选择“视图”|“Tab键 顺序”,并且确保该标签名称出现在要使用快捷键激活的控件名称之前。您将在本章后面学习如何使 用“Tab键顺序”对话框(参见图10-14)。 文字框 文字框是最流行的窗体控件,因为它们可以用来显示或者从用户处获取数据。可以在文字框里 输入文本、数字、单元格引用或者公式。通过更改MultiLine属性设置,可以在文字框里输入一行以 上的文本。当设置了WordWrap属性时,文本行可以自动换行。此外,如果将EnterKeyBehavior属性 设置为True,同时MultiLine属性也为真,那么将能够在文字框里面通过按下回车键开始新的一行。学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 236 共 612 页 另一个属性EnterFieldBehavior,决定当用户选择文本字段时是否选择文本,将该属性设置为 0(fmEnterFieldBehaviorSelectAll) ,将会选择该字段内的所有文本;将该属性设置为 1(fmEnterFieldBehaviorRecallSelection),将仅仅选择用户上次激活该字段时选择的文本。如果想要 限制用户在文字框输入的字符数,那么可以指定MaxLength属性里字符的确切数目。 框架 框架允许可视地组织和逻辑地聚合窗体上放置的各种控件。当使用框架控件包围选项按钮时, VBA将这些按钮视为相互排斥的,同时只能选择其中的一个按钮,因此,如果用户选择了可用的选 项按钮之一,那么就不能再选择其它的选项按钮。在本章的后面,将找到使用两个框架的“信息调 查”窗体示例。其中一个将“硬件”和“软件”选项按钮组织成一个逻辑化的集合,而第二个框架 则将与计算机类型相关的复选框集中到一起(参见图10-11)。 选项按钮 选项按钮让您在许多相互排斥的选项中选择一个,选项按钮通常以两个或者多个按钮组织在一 起并且包围在一个框架之内。在任何时刻,只能选择一个选项按钮。当选择一个新的选项按钮时, 之前被选择的选择按钮就会自动取消选定。要激活或者解除一个选项按钮,只需将它的Value属性设 置为True或者False。True意味着该选项被激活,False则意味着该选项解除。 复选框 复选框用来打开或者关闭指定的按钮,不同于选项按钮只能同时选择一个选项,用户可以同时 选取一个或者多个复选框。如果复选框被选择,那么它的Value属性就设置为True,如果复选框没有 被选择,那么它的Value属性则设置为False。 切换按钮 切换按钮看上去像命令按钮,而工作情况与选项按钮类似。当单击切换按钮时,该按钮会保持 按下,如果再次单击该按钮,则恢复正常(没按下)。按下的切换按钮的Value属性为True。 列表框 除了可以使用文字框提示用户输入明确的数据之外,有时提供一个可供选择的列表可能会更好, 列表框减少了输入错误数据的可能性。列表框里的内容可以在工作表中输入,也可以直接从VBA过 程里使用AddItem方法添加。RowSource属性指定显示在列表框里面的数据源,例如引用$A$1:$B$8, 列表框里将显示这些区域里的内容。 当设置ColumnCount属性时,列表框可以显示一列或者多列数据。另一个属性ColumnHeads可 以设置为True,显示列表框的列标题。用户也没有限制只选择一个选项,如果过程需要选择两个或 者多个列表项目,也可以设置MultiSlect属性为True。 复合框 复合框是一个将文字框和列表框相结合的控件,该控件经常用来节省窗体上的空间。当用户单 击复合框右边的下拉箭头时,它会打开显示一系列可供选择的项目。如果里面没有适用的选项,可 以将MatchRequired属性设置为False,允许用户直接输入一个新的数据。ListRows属性决定当用户 下拉列表时显示的项目数。Style属性决定了复合框的类型,使用0(fmStyleDropDownCombo)可以允第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 237 许用户从列表里选择一项或者在文字框里输入一个新项。如果要限制用户只能在复合框里的可用列 表中选择,那么就将Style属性设置为2(fmStyleDropDownList)。 滚动条 使用该控件可以在窗体上放置水平的或者竖直的滚动条。尽管滚动条通常使用于定位窗口,但 也可以用在窗体上来输入一些预设范围的数值。滚动条的当前值由Value属性设置或者返回。滚动条 的Max属性用来设置它的最大值,而Min属性则决定了它的最小值。LargeChange属性决定了当用户 单击滚动条内部时Value属性的改变值。同样,当使用滚动条编程时,不要忘记设置SmallChange属 性,它决定当单击滚动条的箭头时Value属性如何改变。 旋转按钮 旋转按钮的工作类似于滚动条。可以单击旋转按钮来增加或者减小某个数值。旋转按钮经常和 文字框一起使用,因为这样用户就可以使用旋转按钮在文字框里输入精确的值或者选择数值。和文 字框一起使用旋转按钮的技术将会在本章后面介绍。 图像 使用图像控件可以在窗体上显示图像,该控件支持下列文件格式:*.bmp、*.cur、*gif、*.ico、 *.jpg和*.wmf。就像工具箱里的其它控件一样,图像控件也有许多属性可以设定。例如,可以使用 PictureSizeMode属性控制图片的外观,该属性有三种设置:0(fmPictureSizeModeClip将不在图片框 里面的部分截除)、1(fmPictureSizeModeStretch水平或竖直拉伸图片,使之正好适合图片框)和 3(fmPictureSizeModeZoom按比例放大图片)。 多页控件 多页控件可以在窗体顶部显示一系列的选项卡(参见图10-17),每个选项卡作为单独的页面使 用。使用多页控件,可以设置包含两页或多页的窗体,可以在每页上放置不同的控件。当一个窗体 包含很多的数据时,它的可读性便降低了。单击窗体选项卡要比在一个使用滚动条的长窗体上移动 要轻松的多。默认情况下,每个多页控件在窗体上显示两页,通过快捷菜单或者使用VBA过程里的 Add方法可以添加新页。本章中的第二个实践练习时演示了如何使用该控件来跟踪学生的考试成绩。 TabStrip 控件 虽然TabStrip和多页控件看上去非常相似,但是它们有各自不同的作用。TabStrip控件(参见图10 -17)允许使用相同的控件来显示多组相同的数据。假设窗体显示学生的考试,每个学生必须通过相 同科目的考试。每个科目可以放在一个单独的页上(tab),每页包含相同的控件来收集成绩和考试日 期。当激活任何页时,将看到相同的控件,只是这些控件里的数据变化了。参见本章的第二个实践 练习,看看如何使用TablStrip控件。 RefEdit 控件 RefEdit控件是专门在Excel里面创建的窗体控件,它允许在工作表里选择一个单元格或者单元格 区域并传递到VBA过程。看一下一些Excel内置对话框,就可以看到这个控件是如何工作的,例如, “数据”菜单里的“合并计算”对话框就有一个标为“引用位置”的RefEdit控件,可以选定想要进 行合并计算的数据区域。单击RefEdit控件右边的按钮,可以在选择单元格区域时暂时隐藏对话框。 本章的第二个实践练习使用RefEdit控件给列表框添加学生姓名。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 238 共 612 页 10.4.2 在窗体上放置控件 当创建自定义窗体时,将工具箱里可用的很多控件(参见图10-9)放置在一个空窗体上。选择什 么样的控件取决于控件需要存储的数据类型以及窗体的功能。当使用窗体时,工具箱总是可见的, 您可以在屏幕上移动它、改变它的大小、或者当将所有需要的控件放在窗体上并且要做的只是设置 它们的属性时可以关闭它。从屏幕上临时移除的工具箱可以通过选择“视图”|“工具箱”重新显示。 使用工具箱是很容易的。要在窗体上添加新控件,可以先单击工具箱上面的控件图标,然后在 窗体上单击一下或者画一个框。在窗体上单击一下(不画框)将会在窗体上放置一个缺省大小的控件。 每个控件的标准设置可以在它的属性窗口里找到。例如,标准的文字框大小为18X72磅(参见文字框 的Height和Width属性)。 在窗体上放置控件后,“选定对象”按钮(用箭头代表)成为工具箱上的活动控件。如果双击工具 箱上的控件时,可以随需要在窗体中画上很多这个控件。例如,要快速在窗体上放置三个文字框, 可以双击文字框控件,然后在窗体上单击三次。单击工具箱上的“选定对象”按钮,可以取消所选 的控件。 技巧10—2:设置网格选项 当在窗体上拖曳控件时,VBA将调节控件以使得它和窗体的网格对齐。通过使用“选项”对话框可以按您 的喜好设置窗体的网格。要访问网格选项,可以选择“工具”|“选项”,然后单击“选项”对话框的“通用” 选项卡,窗体网格设置区域允许关闭网格、调整网格大小、以及决定是否需要控件和网格对齐。 10.5 应用程序示例 1:信息调查 现在您已经通读了创建用户窗体的理论知识,并且理解了工具箱上不同控件之间的区别,已经 可以来做一些实践练习了。正如您所知道的,理解一个复杂功能的最好方式就是在实际项目中实用 它。在本节中,您将为同事创建一个自定义窗体,他要求将给工作表输入调查数据的单调过程简单 化。 使用该窗体时(参见图10-11),您将有机会体验许多控件和它们的属性,您也将学习如何将数据 从自定义窗体转移到工作表中(参见图10-12)。 本节结束后,您将拥有创建自定义窗体的必要技能,来适应VBA应用程序独特的要求。 1. 在工程浏览窗口,选中当前工程VBAProject(Chap10.xls)并且在属性窗口将工程名称改为 CustomForms。 2. 选择“插入”|“用户窗体”,添加一个空白窗体。 3. 在属性窗口,双击Name属性并输入InfoSurvey将窗体的缺省名称(UserForm1)更改掉。您将在 VBA过程里使用这个名称来引用该用户窗体对象。 4. 双击Caption属性,并输入窗体的新标题:信息调查。该名称将出现在窗体的标题栏上。 5. 双击BackColor属性,单击“调色板”选项卡,选择一种颜色作为窗体的背景色。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 239 图10-11:Info Survey自定义窗体允许用户通过作一些适当的选择就可以快速地输入数据 图10-12:每次使用Info Survey窗体后,用户的选择就会写入到工作表里面 10.5.1 在窗体上添加按钮、复选框和其它控件 为自定义窗体设置了初始属性(Name和Caption)后,继续来给窗体放置需要的控件。这里是一步 一步的指导如何准备如图10-11显示的窗体。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 240 共 612 页 更改窗体的大小 当在工程里插入的缺省窗体太小而不够放置VBA程序所需要的控件时,可以按照下述 方法之一来更改它的大小: a. 使用鼠标调整窗体大小 • 单击窗体的空白部分,窗体周围便会出现好几个选择句柄。 • 将鼠标放在窗体右边中间的选择句柄上,并且将其向右拖曳至想要的位置,释放鼠标。 • 将鼠标放在窗体下边中间的选择句柄上,并且将其向下拖曳至想要的位置,释放鼠标。 b. 通过属性窗口调整窗体大小: 每个新建的窗体缺省大小为180 X 240。窗体的尺寸单位是磅,一磅等于1/72英寸。在窗 体的两个属性:Height和Width中输入新值,可以改变窗体的大小。 • 单击窗体的标题栏(显示“信息调查”的地方)。 • 在属性窗口,双击属性Height并且输入值:252.75,同样更改Width属性的值为405.75。 为了避免重复工作,总是在添加需要的控件之前调整窗体的大小。 添加框架 • 单击工具箱上的“框架”控件,这时鼠标光标变成一个十字并跟随着所选控件的标志。 • 指向窗体的左上角,然后单击并拖曳鼠标画出一个小矩形。当释放鼠标后,将看到一个 标题为“Frame1”的小矩形。当选择该框架时,它旁边就会出现一些选择句柄,并且属 性窗口的标题栏会显示“属性-Frame1”。 • 在属性窗口,双击Caption属性并将默认的标题Frame1改为“主要的兴趣”。 添加选项按钮 • 单击工具箱上的“选项按钮”控件,将鼠标移动到刚才在窗体上添加的框架“主要的兴 趣”内部,单击并且向右拖曳鼠标,直到看到一个带有标签“OptionButton1”的矩形。 • 在属性窗口将该选项按钮的Caption属性改为“硬件”。 • 使用相同的技术,在“主要的兴趣”框架里添加另一个选项按钮并且将Caption属性改为 “软件”。 无论何时当用户必须从一组相互排斥的选项中选择一个时,就要使用选项按钮,如果用户 必须选择多余一个的选项,就要使用复选框。 添加列表框 • 单击工具箱上的列表框控件,这时鼠标光标变成了一个十字并且跟随着被选控件的标 志。在“主要的兴趣”框架下面单击并向下和向右拖曳鼠标画出一个列表框,当释放鼠 标后,将看到一个白的矩形。 图10-11显示了填充各种硬件后的列表框。在本节的后面,您将学习如何在该列表框里 面显示合适的项目。 添加带选项按钮的框架 • 按照上面所讲的第二步在第四步已添加的列表框下面插入一个框架,将框架的Caption 属性改为“性别”。在该框架里面添加两个选项按钮,并且将第一个按钮的Caption属性第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 241 改为“男”,第二个为“女”。参见图10-11。 技巧10—3:复制和移动控件 如果想要复制控件,那么就选择该控件(被选择的控件周围有选择句柄),按住Ctrl键,将光标置于控件中央 然后按下鼠标左键,拖曳光标到所需要的位置,然后释放鼠标,更改按钮的Caption属性。 要选择和移动一组控件,单击工具箱中的“选定对象”工具,并且在需要移动的一组控件周围画一个矩形 框,当释放鼠标后,所有控件都将被选择上。(也可以通过按住Shift键,并单击每个要选择的控件来选择一个以 上的控件——不要只看,动手试试)。要移动被选择的控件组到窗体上的另外位置,可以单击选择区域并拖曳鼠 标到预定的位置。 添加带复选框的框架 • 单击工具箱上的“框架”控件,并且在“主要的兴趣”框架右边画一个矩形。 • 将其Caption属性改为“计算机类型”。 • 单击工具箱上的“复选框”按钮,并且在刚添加的框架内部单击,CheckBox1控件应该 出现在框架中。 • 更改CheckBox1的Caption属性为IBM/Compatible。 • 在“计算机类型”框架里再放置两个复选框,使用Caption属性将这两个复选框标题设置 为:Notebook/Laptop和Macintosh。最后的结果应该和图10-11一致。 不像选项按钮那样相互排斥,复选框允许用户同时激活一个或者多个选项。复选框在特定 的时候可以是选定的、未选定的或者不可用的。不可用的复选框会变灰并且不能被选定。选定 的复选框的标题前面有一个x号,具有焦点的复选框的标题周围由虚线包围。 技巧10—4:复选框还是选项按钮? 一次只能选择一个选项时使用选项按钮,而复选框让用户选择任意多个适合的选项。 添加标签和复合框 • 单击工具箱上的“标签”控件。 • 单击“计算机类型”框架下面的空白处,Label1控件应该就会出现在那里。 • 将Label1的Caption属性改为“用于哪里”。 • 单击工具箱上的“复合框”控件。 • 单击标签“用于哪里”下面的空白处并且拖曳鼠标画出一个长方形,释放鼠标。 只有当单击控件右边的向下箭头时,复合框才会显示可用的选项列表。复合框有时也被称 为下拉列表并且用来节省屏幕上宝贵的空间。尽管用户某时只能看见列表中的一项,但是通常 单击向下箭头可以很快地改变当前选择。 添加标签、文字框和旋转按钮 • 单击工具箱上的“标签”控件。 • 在“用于哪里”复合框下面的空白处,将会出现一个标签控件,更改其Caption属性为“使 用的百分数 (%)”。 • 单击工具箱上的“文字框”控件。 • 在“使用的百分数(%)”标签右边单击,放置一个默认大小的文字框。 • 单击工具箱上的“旋转按钮”控件,然后单击文字框控件的右侧,出现一个默认大小的学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 242 共 612 页 旋转按钮。最后的结果如图10-11所示。 旋转按钮有两个箭头,用来在一个给定范围内增加或者减少数值。最大值由Max属性的设 置决定,而最小值则由Min属性设定。旋转按钮和滚动条有相同的属性,但是也有两个不同之 处:旋转按钮没有滚动条,而且它没有LargeChange属性。文字框通常放置在旋转按钮的旁 边,这样允许用户直接在文字框里面输入数据,或者使用旋转按钮控制数值。如果旋转按钮必 须和文字框一起使用,那么VBA过程必须确保文字框里输入的数据和旋转按钮的值保持同步。 在本练习里,您将使用旋转按钮来显示用户所选硬件或者软件产品的兴趣百分比。 添加命令按钮 • 双击工具箱上的“命令按钮”控件。回想一下双击工具箱上的控件时,表明您想要添加 一个以上的所选控件。 • 单击窗体的右上角,这将导致出现CommandButton1。 • 单击CommandButton1的下面,出现CommandButton2。 • 将CommandButton1的Caption属性改为“确定”,CommandButton2改为“取消”。 多数自定义窗体都有两个命令按钮,“确定”和“取消”,使用户能够接受窗体上输入的数 据,或者离开窗体。在本例中,“确定”按钮将输入在窗体上的数据转移到工作表里。无论何 时当完成数据输入时,用户都可以单击“取消”按钮。您将在本章后面编写合适的VBA过程, 使按钮响应用户的操作。 添加图像控件 • 单击工具箱上的“图像”。 • 在“取消”按钮下面用鼠标单击,并且拖曳鼠标画一个长方形,释放鼠标,最后的结果 如图10-11所示。窗体将根据选择的是“硬件”还是“软件”选项按钮来显示不同的图 片。图像将用VBA程序上载。 检查窗体的外观 • 单击窗体标题栏,或者单击窗体上的任意空白区域来选定窗体。 • 按下F5键或者选择“运行”|“运行子过程/用户窗体”来显示用户将看到的窗体。 • VBA将切换到Excel窗口的当前工作表,并且显示所设计的自定义窗体。如果忘了选择窗 体,就会出现“宏”对话框,关闭“宏”对话框,然后重复前面两步。 • 单击窗体右上角的关闭按钮(x)来关闭该窗体并返回到VBE编辑器窗口。回想一下,我们 放置在窗体上的“确定”和“取消”按钮都还没有任何功能,它们需要VBA过程让它们 工作。 在窗体上添加完控件后,可以使用鼠标或者“格式”菜单命令来调节控件的对齐和空间。 技巧10—5:使用用户窗体工具栏 用户窗体工具栏包含很多有用的处理窗体的快捷键,例如使控件大小一样、水平或者垂直居中、控件边缘 对齐、以及组合或取消组合控件。选择“视图”|“工具栏”|“用户窗体”来显示该工具栏。 10.5.2 更改控件名称 在开始编写程序控制窗体之前,应该给每个放置在窗体上的控件分配自己的名称。尽管VBA自第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 243 动给每个控件分配了一个缺省名称,但是要在一个过程里引用多个几乎具有一样的名称的同类对象, 这些名称是很难区分的,例如:OptionButton1、 OptionButton2,等等。给窗体上的控件指定有意 义的名称可以让引用这些控件的VBA过程更具可读性。 给控件指定新的名称: • 单击窗体上合适的控件。 • 双击属性窗口里的Name属性。 注意:在更改Name属性之前,确保属性窗口的标题栏显示正确的控件类型。例如,要给一个框 架控件重命名,单击窗体上的框架控件。当属性窗口显示“属性-Frame1”时,双击Name属性,并 且在默认名称加亮区域输入新的名称。 不要将控件的名称和标题(Caption)相混淆。例如,在“信息调查”窗体上,框架控件的缺省名 称是Frame1,但是该控件的标题是“主要的兴趣”。控件的标题可以通过设置其Caption属性来更改。 控件的标题允许用户明确控件的目的、以及建议预期的数据类型,然而控件的名称用于VBA过程的 代码里来产生操作。 给窗体Info Survey上的控件指定如下表所示的名称: 对象类型 Name属性 第一个选项按钮 optHard 第二个选项按钮 optSoft 列表框 lboxSystems 第三个选项按钮 optMale 第四个选项按钮 optFemale 第一个复选框 chkIBM 第二个复选框 chkNote 第三个复选框 chkMac 复合框 cboxWhereUsed 文字框 txtPercent 旋转按钮 spPercent 第一个命令按钮 cmdOK 第二个命令按钮 cmdCancel 图像 picImage 10.5.3 设置其它的控件属性 放置在Info Survey窗体上的控件也是对象,这些对象中的每一个都有它们自己的属性和方法。 在前一节中,您更改了所有对象的Name属性,这些将在后面的VBA过程里引用。控件的属性可以在 自定义窗体设计时设定,也可以在运行时设置(也就是说,在VBA过程执行的时候)。 我们现在来给所选控件设置一些属性。单击窗体上的控件,定位到属性窗口上希望设置的属性 并在属性名称右边输入新值。例如,设置控件lboxSystems的ControlTipText属性,单击窗体Info 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 244 共 612 页 Survey上的列表框控件并在属性窗口里找到ControlTipText属性,在其右边一列里输入当用户将鼠标 移动到该列表框上面就会显示的文本:仅选择一项。 1. 如下表所示更改对象属性: 对象名称 属性 更改为 lboxSystems ControlTipText 仅选择一项 spPercent Max 100 spPercent Min 0 OK button Accelerator O Cancel button Accelerator C picImage PictureSizeMode 0 - fmPictureSizeModeClip Accelerator属性指明对象名称中的哪一个字母可以用在键盘快捷组合里来激活该控件。该特定 的字母在该对象的标题里将以带下划线显示。例如,显示该窗体后,将可以使用组合键Alt+O快速选 择OK按钮。 Info Survey窗体对象的其它属性将在VBA过程里直接设置。 10.5.4 准备工作表以存储自定义窗体数据 用户在自定义窗体上选择了合适的选项并单击“确定”按钮之后,所选的数据将会转移到工作 表中。然而,在此之前,先得准备一个工作表来接收这些数据,并且给用户一个易于操作的界面来 启动窗体。按照下述步骤来准备工作表: 1. 激活Excel窗口。 2. 双击工作簿Chap10.xls的Sheet1标签,并且输入新的名称:Info Survey。 3. 输入列标题,如图10-13所示。 4. 选择K列和第一行,并且将它们的背景色改为您喜欢的颜色(使用“格式”工具栏上的“填充颜色” 按钮)。 从工作表里启动自定义窗体的最简单的方法是通过单击一个按钮,接下来的步骤介绍在工作 表Info Survey里添加“调查”按钮。 5. 选择“视图”|“工具栏”,并选择“窗体”。 6. 单击“窗体”工具栏上的“按钮”控件,在单元格K2里放置一个按钮。当出现“指定宏”对话 框时,在“宏名”框里面输入DoSurvey,并且单击“确定”。稍后将编写该过程。 7. 当返回到工作表时,被指定宏DoSurvey的按钮(按钮1)应该仍然选中,给它输入一个新名称:调 查。如果该按钮没有被选定,那么在其上单击右键选中它,从快捷菜单中选择“编辑文字”并输 入“调查”作为其新名称。要退出编辑模式,可以单击按钮之外的任何地方。 8. 保存对Chap10.xls所作的改变。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 245 图10-13:“调查”按钮将会启动Info Survey窗体当用户单击窗体上的“确定”按钮时,窗体数据将会放 置到该工作表中 10.5.5 显示自定义窗体 每个用户窗体都有Show方法,可以给用户显示该窗体。在下面的例子里,您将准备DoSurvey 过程。回想前一节里已经将该过程指定给了Info Survey工作表的“调查”按钮。 1. 在VBE编辑器窗口,选择工程CustomForms(Chap10.xls),并且选择“插入”|“模块”。 2. 在属性窗口,将新模块的名称改为ShowSurvey。 3. 输入下面的过程来显示自定义窗体: Sub DoSurvey() InfoSurvey.Show End Sub 注意,Show方法的前面是窗体对象的名称,正如出现在窗体文件夹(InfoSurvey)里面的那样。 4. 保存Chap10.xls的变化。 5. 切换到Excel窗口,并单击“调查”按钮,窗体Info Survey将出现。 注意:如果单击“调查”按钮后出现错误信息,则没有按上一节中步骤6那样给该按钮指定需要 的宏。要更正该错误,可以单击“确定”按钮对消息作出响应。然后在“调查”按钮上单击右键, 并选择快捷菜单上的“指定宏”,单击“指定宏”对话框里的DoSurvey宏,并单击“确定”按钮。 现在,可以单击“调查”按钮来显示窗体。 6. 通过单击窗体右上角的关闭按钮(x),关闭Info Survey窗体。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 246 共 612 页 10.5.6 设置Tab键顺序 用户可以使用鼠标或者Tab键在窗体上移动,因为许多用户倾向于使用键盘在窗体上移动,所以 决定窗体上每个控件的激活顺序是很重要的。下列步骤设置Info Survey窗体上控件的Tab键顺序: 1. 在工程浏览器窗口里的窗体文件夹里,双击InfoSurvey窗体。 2. 选择“视图”|“Tab键顺序”,出现“Tab键顺序”对话框,该对话框按控件添加的顺序显示窗体 InfoSurvey上的所有控件名称。对话框的右边有一些按钮,允许向上或者向下移动所选的控件。 要移动某个控件,可以单击其名称并且点击上移或者下移按钮,直到该控件出现在想要的位置。 3. 按照图10-14那样重新排列Info Survey窗体上的控件。 4. 单击“确定”关闭“Tab键顺序”对话框。 5. 返回到Excel界面,并且单击“调查”按钮。 6. 按Tab键向前移动,按Shift+Tab键向后移动。 7. 关闭InfoSurvey窗体。 如果想要更改控件激活的顺序,那么重新打开“Tab键顺序”对话框,并作适当的更改。 图10-14:Tab键顺序对话框让您决定按Tab键时控件激活的顺序 10.5.7 理解窗体和控件事件 除了有属性和方法之外,每个窗体和控件都有一套预定义事件。事件是指一类操作,例如单击 鼠标、按键、从列表里选择一项或者改变列表框里可用的项目列表。事件可以由用户或者系统触发。 编写事件过程,可以指定窗体或控件如何响应事件。 当设计一个自定义窗体时,应该预想和规划运行时(当窗体使用时)能够发生的事件。最常见的事 件是单击事件,每当单击一个命令按钮时,就会触发某个事件过程响应按钮的单击事件。窗体本身 可以响应20多种独立的事件,包括Click(单击)、DblClick(双击)、Activate(激活)、Initialize(初始化) 和Resize(调整大小)。 表10—2列出了各种窗体控件可以识别的事件。如果某个控件不能识别某个事件,那么表格相应 单元格便显示“N”,否则为空白。花几分钟熟悉一下事件名称吧,例如,看看表格里的AddControl 事件,一眼就可以看出,该事件只对三个对象可用:框架、多页和窗体本身。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 247 NN 表10—2:窗体和控件事件 Event name UserForm Label TextBox ComboBox ListBox CheckBox OptionButton ToggleButton Frame CommandButton TabStrip MultiPage ScrollBar SpinButton Image RefEdit Activate NNNNNNNNNNNNNNN AddControl NNNNNNN NN NNNN AfterUpdate NN NNNN N BeforeDragOver BeforeDropOrPaste BeforeUpdate NN NNNN N Change NN NN N Click NN DblClick NN Deactivate N NNNNNNNNNNNNN DropButtonClick NN NNNNNNNNNNN Enter NN N Error Initialize NNNNNNNNNNNNNNN Exit NN N KeyDown NN KeyPress NN KeyUp NN Layout NNNNNNN NN NNNN MouseDown NN MouseMove NN MouseUp NN QueryClose NNNNNNNNNNNNNNN RemoveControl NNNNNNN NN NNNN Resize NNNNNNNNNNNNNNN Scoll NNNNNNN NN NNN SpinDown NNNNNNNNNNNNN NN SpinUp NNNNNNNNNNNNN NN Terminate NNNNNNNNNNNNNNN 创建的每个窗体都有一个窗体模块用来存储VBA事件过程。可以通过以下几种方式来访问窗体 模块编写事件过程或者找到某个控件可识别的事件: • 双击某个控件 • 在控件上单击右键,并选择快捷菜单上的“查看代码” • 单击工程浏览窗口的“查看代码”按钮 • 双击用户窗体上任何未用的区域 执行以上任一操作都会导致打开窗体的代码窗口。图10-15显示了通过双击窗体上的命令按钮 而激活的代码窗口。注意,Microsoft Visual Basic标题栏显示的标题:Chap10.xls – (UserForm1(代 码))。窗体模块包含一个“通用”部分,也包含放置于窗体上每个控件的单个部分。通用部分用来声 明窗体变量或者常量。 可以通过单击右上角复合框右边的向下箭头来进入预期的部分。该复合框被称为过程框,它显 示左手边复合框所选的控件可以识别的所有事件过程。已经编写了过程的事件显示为粗体。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 248 共 612 页 图10-15:过程框列出了命令按钮控件可用的事件过程 10.5.8 编写VBA过程响应窗体和控件事件 在用户能够使用自定义窗体完成特定的任务之前,通常必须编写一些VBA过程。正如前面提及 的,VBE编辑器创建的每个窗体都有一个模块以存储该窗体使用的过程。 在显示自定义窗体之前,可能需要设置控件的初始值。为用户窗体编写一个Initialize事件过程, 可以给控件设置初始值、或者默认值,每次显示窗体时控件都会拥有这些值。 Initialize事件发生在窗体被装载但显示在屏幕之前。假设想要Info Survey窗体显示以下初始设 置: 1. 在“主要的兴趣”框架里,选择了“硬件”按钮。 2. 下面的列表框包含了对应于“硬件”选项按钮的项目。 3. “计算机类型”中没有选取任何复选框。 4. 标签“用于哪里”下面的复合框显示第一条可用的项目,并且用户不能给该复合框添加项目。 5. 在旋转按钮旁边的文字框显示初始值零(0)。 6. 图像控件显示与所选“硬件”或“软件”选项按钮相关的图片。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 249 10.5.9 编写过程来初始化窗体 1. 在工程浏览器窗口,双击InfoSurvey窗体。 2. 双击窗体背景,打开活动窗体的代码窗口。 当双击窗体或控件时,会自动打开带有所单击控件的Click事件的代码窗口并且可以编辑。 在过程定义(图10-15)时,VBA自动在关键字Sub之前添加了关键字Private。私有过程只能 从当前窗体模块里调用,换句话说,在当前工程的其它模块里的过程不能调用该私有过程。 在代码窗口的顶部,有两个复合框,左边的复合框显示所有的窗体对象名称;右边的复合框 则显示所选控件能够识别的所有事件。 3. 单击过程框右边的向下箭头,并且选择Initialize事件,VBA将在代码窗口显示UserForm_Initialize 过程: Private Sub UserForm_Initialize() End Sub 4. 在关键字Private Sub和End Sub之间输入窗体的初始设置,完整的UserForm_Initialize过程如下 所示: Private Sub UserForm_Initialize() '选择硬件选项 optHard.Value = True '关闭软件选项和所有复选框 optSoft.Value = False chkIBM.Value = False chkNote.Value = False chkMac.Value = False '在文字框中显示0 txtPercent.Value = 0 '调用过程来填充使用硬件选项时的列表框 Call ListHardware '填充组合框 With Me.cboxWhereUsed .AddItem "家里" .AddItem "工作" .AddItem "学校" .AddItem "工作/家里" .AddItem "家里/学校" .AddItem "工作/家里/学校” End With '选择复合框中的第一个元素 Me.cboxWhereUsed.ListIndex = 0 '选择列表框中的第一个元素 Me.lboxSystems.ListIndex = 0 '上载硬件选项的图片文件 Me.picImage.Picture = LoadPicture("C:\cd.bmp") End Sub 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 250 共 612 页 可以使用关键字Me代替窗体的实际名称来简化事件过程代码,例如,除了使用语句: InfoSurvey.cboxWhereUsed.ListIndex = 0 也可以使用下面的语句以节省输入时间: Me.cboxWhereUsed.ListIndex = 0 特别是当窗体名称很长时,这种技术很有用。注意,列表框的第一个元素的索引号为0,因 此,如果想要选择列表里的第二个元素,必须设置其ListIndex属性为1。 该过程结束时给图像控件上载图片,确保该指定的图片文件可以在指定的文件夹里找到。如 果没有该文件,那么输入想显示的图片文件的完整路径。 过程UserForm_Initialize调用外部过程(ListHardware)用硬件成员来填充它的列表框控件。 5. 激活ShowSurvey模块并且输入过程ListHardware的代码,如下所示: Sub ListHardware() With InfoSurvey.lboxSystems .AddItem "CD-ROM Drive" .AddItem "Printer" .AddItem "Fax" .AddItem "Network" .AddItem "Joystick" .AddItem "Sound Card" .AddItem "Graphics Card" .AddItem "Modem" .AddItem "Monitor" .AddItem "Mouse" .AddItem "Zip Drive" .AddItem "Scanner" End With End Sub 现在已经准备好了UserForm_Initialize过程和ListHardware过程,那么可以运行窗体查看结 果了。 6. 在工作表Info Survey中使用“调查”按钮来启动窗体。 显示窗体后,用户可以选择合适的选项或者单击“取消”按钮。当用户单击软件选项按钮时, 下面的列表框应该显示不同的项目,同时,图像控件应该上载一个不同的图片。下一节将解释如何 编写这些事件。 10.5.10 编写过程填充列表框控件 在上一节,准备了ListHardware过程用硬件元素来填充lboxSystems列表框,可以使用同样的方 法将软件元素装载到该列表框。 1. 激活ShowSurvey模块,并输入过程ListSoftware代码,如下所示: Sub ListSoftware() With InfoSurvey.lboxSystems .AddItem "Spreadsheets" .AddItem "Databases" .AddItem "CAD Systems" .AddItem "Word Processing" 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 251 .AddItem "Finance Programs" .AddItem "Games" .AddItem "Accounting Programs" .AddItem "Desktop Publishing" .AddItem "Imaging Software" .AddItem "Personal Information Managers" End With End Sub 10.5.11 编写过程控制选项按钮 1. 激活窗体InfoSurvey,并双击位于“主要的兴趣”框架里的软件选项按钮。 2. 当出现带有optSoft_Click过程构架的代码窗口时,选中该代码并按下Delete键。 3. 点击右上角的复合框下拉箭头,并且选择Change事件过程,VBA将会自动输入optSoft_Change 过程的开头和结尾。 4. 输入optSoft_Change过程的代码,如下所示: Private Sub optSoft_Change() Me.lboxSystems.Clear Call ListSoftware Me.lboxSystems.ListIndex = 0 Me.picImage.Picture = LoadPicture("C:\Books.bmp") End Sub 过程optSoft_Change开始时,使用Clear方法将列表框lboxSystems的当前项目列表清除, 下一条语句调用ListSoftware过程用软件项目来填充列表框,换句话说,当用户单击软件按钮时, 程序将硬件项目从列表框中清除,然后添加软件项目。如果在添加新成员之前没有清除列表框, 这些新项目就会附加在当前列表后面。语句Me.lboxSystems.ListIndex = 0选择列表里的第一项。 该过程里的最后一条语句为图像控件上载图片文件,确保用计算机中有效图片的完整路径代替。 因为用户在选择软件按钮之后,还可能要重新选择硬件按钮,所以,必须给optHard选项按 钮创建一个类似的Change事件过程。 5. 在optSoft_Change过程下面,输入下面的过程optHard_Change: Private Sub optHard_Change() Me.lboxSystems.Clear Call ListHardware Me.lboxSystems.ListIndex = 0 Me.picImage.Picture = LoadPicture("C:\cd.bmp") End Sub 6. 单击工作表Info Survey里的“调查”按钮启动窗体并检查结果。当单击软件选项按钮时,应该 在下面的列表框里看到软件项目,同时,图像控件也应该显示了相应的图片。单击硬件选项按钮 后,列表框应该显示适当的硬件项目,同时,图像控件应该显示一张不同的图片。 7. 单击窗体右上角的“关闭”按钮,关闭该窗体。 10.5.12 编写过程同步文字框和旋转按钮 Info Survey窗体在旋转按钮前面有个文字框,用户可以直接在文字框里输入或者使用旋转按钮, 在文字框里表明硬件或者软件项目使用的百分率。文字框的初始值为0,假设用户输入了10,现在要 用旋转按钮将它增加到15。要产生这个动作,该文字框和旋转按钮必须同步。每个对象需要有一个学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 252 共 612 页 单独的Change事件过程。 1. 在旋转按钮上单击右键,并选择快捷菜单上的查看代码。 2. 输入过程spPercent_Change过程,如下所示: Private Sub spPercent_Change() txtPercent.Value = spPercent.Value End Sub 使用旋转按钮将会导致文字框中的数值增加或减少。 3. 输入下面的过程txtPercent_Change: Private Sub txtPercent_Change() Dim entry As String On Error Resume Next entry = Me.txtPercent.Value If entry > 100 Then entry = 0 Me.txtPercent.Value = entry End If spPercent.Value = txtPercent.Value End Sub 过程txtPercent_Change确保只有从0到100之间的数值可以输入在文字框里,该过程使用了 On Error Resume Next语句来忽略数据输入错误。如果用户在文字框中输入了一个非数字数据 (或者大于100的数字),VBA将会将文字框的值重新设置为0。每次按下旋转按钮,文字框里的数 字将会增加或者减少1。 10.5.13 编写过程关闭用户窗体 显示窗体之后,用户可能需要通过按Esc键或者单击“取消”按钮来取消窗体。可以准备一个使 用Hide方法的简单过程,让窗体从屏幕上消失。 1. 双击“取消”按钮,并且输入下面的过程cmdCancel_Click: Private Sub butCancel_Click() Me.Hide End Sub 方法Hide将对象隐藏,但是不从内存里清除它。这样,当用户看不到窗体时,VBA过程仍 然可以在屏幕后台使用该窗体的对象和属性。 使用Unload方法可以将窗体从屏幕上卸载并且从内存里清除: Unload Me 当卸载窗体后,所有相关的内存都会被收回,用户不能再和窗体相交互,窗体的对象也不能再 被VBA过程访问,除非使用Load语句再次将窗体放置于内存里。 10.5.14 转移窗体数据到工作表 当用户点击“确定”按钮时,窗体的选择应该写入工作表中,用户就可以通过单击“取消”按 钮随时退出窗体。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 253 1. 双击“确定”按钮,并输入过程cmdOK_Click,如下所示: Private Sub butOK_Click() Me.Hide r = Application.CountA(Range("A:A")) Range("A1").Offset(r + 1, 0) = Me.lboxSystems.Value If Me.optHard.Value = True Then Range("A1").Offset(r + 1, 1) = "*" End If If Me.optSoft.Value = True Then Range("A1").Offset(r + 1, 2) = "*" End If If Me.chkIBM.Value = True Then Range("A1").Offset(r + 1, 3) = "*" End If If Me.chkNote.Value = True Then Range("A1").Offset(r + 1, 4) = "*" End If If Me.chkMac.Value = True Then Range("A1").Offset(r + 1, 5) = "*" End If Range("A1").Offset(r + 1, 6) = Me.cboxWhereUsed.Value Range("A1").Offset(r + 1, 7) = Me.txtPercent.Value If Me.optMale.Value = True Then Range("A1").Offset(r + 1, 8) = "*" End If If Me.optFemale.Value = True Then Range("A1").Offset(r + 1, 9) = "*" End If Unload Me End Sub 过程butOK_Click通过隐藏用户窗体开始。语句: r = Application.CountA(Range("A:A")) 使用了VBA函数CountA来计算A列里含有数据的单元格数目,函数的结果被赋给变量r。下一句: Range("A1").Offset(r + 1, 0) = Me.lboxSystems.Value 将列表框选择的项目输入到A列最后一个使用了的单元格的下一个单元格(r+1)中。接下来,有几 个条件语句。第一个告诉VBA当硬件选项按钮被选中时,在B列适当的单元格里输入一个星号。B列 是位于A列的右边一列,因此,在Offset方法第二个参数的位置为1。第二条If语句,当用户选择软件 选项按钮时,就在C列输入星号。类似的指令记录复选框的实际数值。 在G列,将会输入“用于哪里”复合框里所选的项目。H列显示“使用的百分数(%)”文字框里 的数字,而I列和J列则分别显示提交调查的人员的性别。 10.5.15 使用Info Survey应用程序 现在,您的应用程序已经准备好做最后的测试了。 1. 切换到Excel界面中的Info Survey工作表,并单击“调查”按钮。 2. 当窗体出现时,选择适当的选项,并单击“确定”按钮。 3. 激活窗体几次,每次选择不同的选项。 4. 保存变化到Chap10.xls中。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 254 共 612 页 10.6 应用程序示例 2:学生和考试 近年来,许多Windows应用程序越来越依赖于那些将各种控件组合在一起的对话框,“选项”对 话框就是很好的例子。使用选项卡,可以在一个对话框里提供应用到不同控件的很多设置。对话框 使用选项卡非常容易且便于使用。当给更高级的VBA应用程序设计自定义窗体时,可以利用工具箱 里可用的两种特殊的Tab控件:多页控件和TabStrip控件。本章接下来的应用程序示例2使用这些控件 和其它的高级控件(例如RefEdit和Calendar)来跟踪学生和他们的考试成绩。 10.6.1 使用多页控件和TabStrip控件 图10-16所示的自定义窗体中的主要对象是一个由两页组成的多页控件。第一页含有文字框和 复合框来收集学生信息,例如社会保险号码(SSN:Social Security Number)、名和姓、学习年限和 专业。窗体顶部有一组选项按钮充许指定学生状况:新入学或者已入学。如果该学生的数据还没有 输入到工作表中,那么该学生的状况就是新入学。当窗体第一次装载时,“新入学”选项按钮就会自 动被选上。当想要检查或者更新已有学生的数据时,单击“已入学”选项按钮将会显示RefEdit控件, 用来使用学生姓名填充列表框控件,如同在工作表中输入一样。 1. 在当前工程里插入一个新用户窗体,并重命名为Students。 2. 将窗体的Caption属性改为“学生和考试”。 3. 单击工具箱上的“多页”控件,然后在窗体的左上方单击并向下向右拖曳鼠标来画一个大的框架。 4. 在Page1选项卡上单击右键并选择快捷菜单上的“重命名”,在“题注”文字框里面输入“学生”, 并在“加速键”字段输入S。用同样的方法,重命名第二页为“考试”,以及输入加速键X。 5. 单击“学生”选项卡,使用工具箱上的控件,在该页面中按照下面的原则添加如图10-16所示 的所有控件: „ “学生状况”框架包含两个选项按钮,分别设置它们的Caption属性为“新入学”和“已入学”, 再将它们的Name属性设置为optNew和optActive。 „ 使用标签标示文字框,标签的Caption属性分别为SSN、姓和名。更改文字框的Name属性为 txtSSN、txtLast和txtFirst。 „ 标示复合框的标签的Caption属性设置为年限和专业,复合框的Name属性设置为cboxYear和 cboxMajor。要让用户只能选择一个选项,可以给每个复合框设置下面的属性:MatchRequired 为True,MatchEntry为1-fmMatchEntryComplete。 „ 在RefEdit控件上面放置标签“姓名区域”,其Name属性为lblNames,设置RefEdit控件的Name 属性为refNames。 „ 设置列表框控件的Name属性为lboxStudents,设置ColumnCount属性为2用来显示两列数据。 „ 设置命令按钮的Caption属性为“确定”和“取消”,并且设置它们的Name属性为cmdOK和 cmdCancel。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 255 图10-16:多页控件可以包含两页或者多页,每页显示不同的控件 多页控件的第二页(图10-17)用来记录和显示与考试相关的信息。该页包含两个对象。标签控 件的临时标题为“姓名”,在运行时,该标签将显示前一页选定的学生的姓名。 该页上的第二个控件是TabStrip控件,它包含了带有考试科目名称的四个选项卡。尽管在本练习 中,选项卡显示在控件的顶部,但是VBA同样也允许将选项卡的方位设置在底部、左边或者右边。 当从一个选项卡切换到另一个选项卡时,可以看到相同的控件,只是显示在每个控件中的数据改变 了。 1. 单击多页控件上的“考试”选项卡。 2. 单击工具箱上的“标签”控件,并单击“考试”选项卡的左上角,拖曳鼠标画一个足够放下姓名 的方框。 3. 在属性窗口,设置该标签的Name属性为lblWho,Caption属性为“姓名”,设 置 Font属性为Arial、 Bold和14磅。 4. 单击工具箱上的TabStrip控件,在紧接标签控件的下面单击,并拖曳鼠标直到达到想要的大小和 形状(参见图10-17)。默认情况下,TabStrip控件将会显示两页:Tab1和Tab2。要添加新的tab 页,首先单击TabStrip控件内部选择该对象,再次单击,直到TabStrip对象周围的虚框显示为粗 体,单击右键,并选择快捷菜单上的“新建页”,VBA就会添加Tab3,使用相同的方法添加Tab4。 在每页上单击右键,并且选择快捷菜单上的“重命名”,参见图10-17给每页设置名称。注意, 每个选项卡的名称有一个字母带有下划线,它允许用户从键盘访问该选项卡页,例如按下Alt+E 将激活“英语”页。 5. 单击“英语”选项卡页并开始画下面的控件: • 如果工具箱上仅包含标题为“控件”的一页,那么回到本章的前面标题为“创建用户窗体的 工具”的部分,找到如何添加“日历”控件到工具箱上的方法,然后将“日历”控件放置到 窗体中的TabStrip控件里,如图10-17所示。设置日历控件的ShowDateSelectors属性为学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 256 共 612 页 True。 • 添加“输入/改变成绩”标签和一个复合框控件,设置复合框的Name属性为cboxGrade。 • 添加两个标签,Caption属性分别设置为“成绩”和“日期”,再添加两个标签并将它们的Name 属性分别设置为lblGrade和lblDate,同时更改这两个标签的SpecialEffect属性为 3-fmSpecialEffectEtched。 技巧10—6:设置TabStrip控件 设置TabStrip控件的最好方法是首先添加它到窗体中,然后再放置其它的控件在里面。然而,如果已经有控 件在窗体里,那么可以在它们上面画TabStrip控件,然后使用“移至底层”命令,将TabStrip控件放到Z-顺序的 底层。 图10-17:外面的框架包含一个多页控件,而里面的框架则包含TabStrip控件 “学生和考试”自定义窗体允许输入新的数据,或者像输入到在工作表里一样显示数据。图10 -18显示了本窗体使用的工作表。 1. 准备如图10-18所示的工作表。 2. 在工作表里添加按钮“显示窗体”,并给它指定宏DoStudents。 3. 在VBE编辑器屏幕中,在当前工程里添加一个新模块,并设置模块的Name属性为InfoStudents。 4. 在InfoStudents模块里输入下面的过程DoStudents: Sub DoStudents() Students.Show End Sub 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 257 图10-18:Students and Exams应用程序的协助工作表 5. 返回到工作表中并单击按钮“显示窗体”,测试DoStudents过程。 6. 单击窗体右上角的“关闭”按钮,关闭窗体。 10.6.2 为“学生和考试”自定义窗体编写VBA过程 “学生和考试”自定义窗体包含很多VBA过程,如下面的代码所示。这些过程的代码必须输入 在窗体模块里面,双击窗体背景,激活窗体模块。 1. 从代码窗口顶部左上角的复合框里选择“(通用)”,右边的过程选择复合框应该显示“(声明)”, 输入下面的变量声明: '声明 Dim r As Integer Dim nr As Integer Dim indexPlus As Integer Dim YesNo As Integer 2. 输入过程UserForm_Initialize的代码来设定窗体的初始设置: Private Sub UserForm_Initialize() '选择多页控件的第一页 '页码编号从 0 开始 Me.MultiPage1.Value = 0 '选择“新入学”选项按钮 optNew.Value = True '启动时先隐藏三个控件 lblNames.Visible = False refNames.Visible = False lboxStudents.Visible = False '填充年限复合框 With Me.cboxYear .AddItem "1" .AddItem "2" .AddItem "3" .AddItem "4" End With '填充专业复合框 With Me.cboxMajor .AddItem "英语" 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 258 共 612 页 .AddItem "化学" .AddItem "数学" .AddItem "语言学" .AddItem "计算机学" End With '填充成绩复合框 With Me.cboxGrade .AddItem "A" .AddItem "B" .AddItem "C" .AddItem "D" .AddItem "F" End With '在 lblDate 标签里显示日期 Me.lblDate.Caption = Me.Calendar1.Value '激活 TabStrip 控件的第一页 Me.TabStrip1.Value = 0 '激活 SSN 文字框 Me.txtSSN.SetFocus End Sub 3. 输入两个过程来控制选项按钮(optNew_Click和optActive_Click) Private Sub optNew_Click() lblNames.Visible = False refNames.Visible = False lboxStudents.Visible = False Me.MultiPage1(1).Enabled = False If lboxStudents.RowSource < > "" Then Me.txtSSN.Text = "" Me.txtLast.Text = "" Me.txtFirst.Text = "" Me.cboxYear.Text = "" Me.cboxMajor.Text = "" Me.txtSSN.SetFocus End If Me.txtSSN.SetFocus End Sub Private Sub optActive_Click() lblNames.Visible = True refNames.Visible = True refNames.SetFocus If lboxStudents.RowSource < > "" Then lboxStudents.Visible = True Call lboxStudents_Change End If End Sub 4. 输入过程lboxStudents_Change和refNames_Change的代码,它们控制着“学生”页上的RefEdit 控件和列表框控件: Private Sub lboxStudents_Change() indexPlus = lboxStudents.ListIndex + 3 With ActiveWorkbook.Worksheets("Sheet2") Me.txtSSN.Text = Range("A" & indexPlus).Value Me.txtLast.Text = Range("B" & indexPlus).Value Me.txtFirst.Text = Range("C" & indexPlus).Value Me.cboxYear.Text = Range("D" & indexPlus).Value 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 259 Me.cboxMajor.Text = Range("E" & indexPlus).Value Call TabStrip1_Change Me.MultiPage1(1).Enabled = True End With End Sub Private Sub refNames_Change() lboxStudents.RowSource = refNames.Value lboxStudents.ListIndex = 0 lboxStudents.Visible = True Call lboxStudents_Change End Sub 5. 输入代码来控制命令按钮“确定”(cmdOK_Click)和“取消”(cmdCancel_Click): Private Sub cmdOK_Click() If Me.optNew.Value = True Then Me.Hide ActiveWorkbook.Sheets("Sheet2").Select r = ActiveSheet.UsedRange.Rows.Count nr = r + 1 Range("A" & nr).Value = Me.txtSSN.Text Range("B" & nr).Value = Me.txtLast.Text Range("C" & nr).Value = Me.txtFirst.Text Range("D" & nr).Value = Me.cboxYear.Text Range("E" & nr).Value = Me.cboxMajor.Text Me.txtSSN.Text = "" Me.txtLast.Text = "" Me.txtFirst.Text = "" Me.cboxYear.Text = "" Me.cboxMajor.Text = "" Me.txtSSN.SetFocus '显示窗体 Me.Show Else MsgBox "这个控件目前是不可用的." End If End Sub Private Sub cmdCancel_Click() Unload Me Set Students = Nothing End Sub 6. 输入过程cboxGrade_Click来控制位于“考试”页上的成绩复合框: Private Sub cboxGrade_Click() YesNo = MsgBox("输入工作表中的成绩?", _ vbYesNo, "修改成绩") If YesNo = 6 Then Me.lblGrade.Caption = cboxGrade.Value Select Case TabStrip1.Value Case 0 Range("F" & indexPlus).Value = Me.lblGrade.Caption Case 1 Range("H" & indexPlus).Value = Me.lblGrade.Caption Case 2 Range("J" & indexPlus).Value = Me.lblGrade.Caption Case 3 Range("L" & indexPlus).Value = Me.lblGrade.Caption End Select cboxGrade.Value = "" End If 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 260 共 612 页 End Sub 7. 输入过程Calendar1_Click,如下所示: Private Sub Calendar1_Click() YesNo = MsgBox("输入工作表中的日期?", vbYesNo, _ "修改日期") If YesNo = 6 Then Me.lblDate.Caption = Calendar1.Value Select Case TabStrip1.Value Case 0 Range("G" & indexPlus).Value = Me.lblDate.Caption Case 1 Range("I" & indexPlus).Value = Me.lblDate.Caption Case 2 Range("K" & indexPlus).Value = Me.lblDate.Caption Case 3 Range("M" & indexPlus).Value = Me.lblDate.Caption End Select End If End Sub 8. 输入过程TabStrip1_Change和MultiPage1_Change,如下所示: Private Sub TabStrip1_Change() indexPlus = lboxStudents.ListIndex + 3 With ActiveWorkbook.Worksheets("Sheet2") Select Case TabStrip1.Value Case 0 '英语 Me.lblGrade.Caption = Range("F" & indexPlus).Value Me.lblDate.Caption = Range("G" & indexPlus).Value Case 1 '法语 Me.lblGrade.Caption = Range("H" & indexPlus).Value Me.lblDate.Caption = Range("I" & indexPlus).Value Case 2 '数学 Me.lblGrade.Caption = Range("J" & indexPlus).Value Me.lblDate.Caption = Range("K" & indexPlus).Value Case 3 '物理 Me.lblGrade.Caption = Range("L" & indexPlus).Value Me.lblDate.Caption = Range("M" & indexPlus).Value End Select End With End Sub Private Sub MultiPage1_Change() Me.lblWho.Caption = Me.txtLast.Value & ", " _ & Me.txtFirst.Value Call TabStrip1_Change End Sub 10.6.3 使用“学生和考试”自定义窗体 现在已经准备好了所有必须的VBA过程,我们来看看该窗体是如何响应用户操作的: 1. 切换到Excel窗口,并激活Sheet2。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 261 2. 单击“显示窗体”按钮。 单击“显示窗体”运行过程DoStudents,该过程显示“学生和考试”自定义窗体。 在窗体 出现在屏幕上之前,VBA执行过程UserForm_Initialize里面的每条语句。结果显示如图10-19。 图10-19:工作表里的“显示窗体”按钮让您快速访问“学生和考试”自定义窗体来查看或者输入数据 当窗体装载时,只有符合选定的选项按钮的控件才会显示 窗体显示后,就可以输入新学生并单击“确定”将学生的数据转移到工作表中。当点击“确定” 按钮时,就会执行cmdOK_Click过程。注意,不能输入新学生参加的考试,因为多页控件的第二页(考 试)此时不可用。一旦新学生的数据被写入工作表,该窗体就会重新显示,您可以继续输入数据,或 者可以单击“取消”将窗体从屏幕上清除。当单击“取消”按钮时,就会执行过程cmdCancel_Click。 3. 使用“学生和考试”自定义窗体输入两位新学生的数据。任何时候,都可以单击“已入学”选项 按钮,从已有学生中上传数据。当单击“已入学”选项按钮时,标签“姓名区域”和RefEdit控 件就变为可见。 4. 单击“已入学”选项按钮,然后单击RefEdit控件的减号按钮。 5. 选择工作表里的姓名区域,如图10-20所示。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 262 共 612 页 图10-20:使用RefEdit控件,可以选择包含想要使用数据的特定单元格区域 使用RefEdit控件,可以选择工作表里的单元格区域。在本示例中,选择了包含学生姓和名的单 元格。选择有效数据很重要,开始单击“姓”列标下面的单元格(B3)并且向下向右拖曳鼠标以至包 括学生的名。注意,当使用RefEdit控件时,窗体临时被隐藏。所选择的单元格区域出现在RefEdit 控件上。单击RefEdit控件上的减号按钮返回到窗体中。当返回窗体时,过程refNames_Change正在 运行,该过程使用单元格区域地址,将学生姓名填充到列表框控件里。 该过程的最后一条语句调用lboxStudents_Change过程,确保窗体的文字框和复合框与列表框里 所选学生姓名同步。 列表框显示已有学生的姓名,被选学生的数据显示在左边的文字框和复合框里面(参见图10- 21)。 图10-21:窗体上的列表框是通过RefEdit控件将存储在工作表里的数据填充的 6. 单击列表框里的任意姓名,并查看该学生的数据。 第 10 章 9B 对话框和自定义窗体 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 263 7. 单击列表框里的任意姓名,然后单击“考试”选项卡页。 “考试”页也显示了被选学生(参见图10-22)的姓名。TabStrip控件显示考试科目,如果被选学 生参加过任何考试,当单击适当的科目页时,考试的日期和分数就会显示出来。 图10-22:“考试”页显示被选学生和科目的考试日期和分数. 可以通过提供的复合框和日历控件输入或者更改学生的成绩和考试日期。VBA将询问是否修改 数据(回顾Calendar1_Click和cboxGrade_Click过程的VBA代码),在以“确定”回应对话框后,所选 的数据或者成绩就会写入到工作表中相应的列(参见图10-23)。 TabStrip1_Change过程确保当单击科目选项卡页时,VBA会从适当的工作表单元格中显示考试 成绩和考试日期。MultiPage1_Change过程则确保当单击“考试”页时,lblWho标签显示列表框里 当前所选学生的姓和名。 图10-23:工作表F到M列里的数据是通过“学生和考试”用户窗体上的“考试”选项卡页输入的 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 264 共 612 页 本章小结和下一章内容简介 现在您已到了这个相对比较长的章节的结尾,那么您已经具有必要的技术来设计有用的窗体。 我们来简单总结一下您在本章学习的内容。可以从自己的VBA过程里显示内置对话框。对于需要用 户输入的自定义VBA应用程序,就需要创建一个自定义窗体。通过设置tab键顺序,确保用户可以在 窗体上按逻辑顺序移动。在窗体模块里面编写VBA过程,让窗体响应用户操作。通过使用属性窗口 或者编写UserForm_Initialize过程,给控件设置初始值。确保有过程将数据转移到工作表。在下一章 里,当您开发集合和自定义对象时,将获得更多实用的自定义窗体的经验。 第11章 自定义集合和类模块 在第九章,您学习了如果通过使用自动控制(Automation) 控制另一个应用程序的对象。回想一下,在创建了对 Microsoft Word 10.0 Object Library 的引用之后,您就能够通过调用其对 象、属性和方法间接地控制 Word 应用程序…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 266 共 612 页 在第九章,您学习了如果通过使用自动控制(Automation)控制另一个应用程序的对象。回想一下, 在创建了对Microsoft Word 10.0 Object Library的引用之后,您就能够通过调用其对象、属性和方法 间接地控制Word应用程序。您也学习了如何使用自动控制从Microsoft Outlook获取联系地址列表到 Excel电子表格中。好消息是不必局限于使用Excel的内置对象或者其它应用程序的对象,VBA允许 创建自己的对象和对象集合,以及它们的方法和属性。在本章,您将学习如何使用集合,包括如何 声明自定义集合对象。也将学习如何使用类模块来创建用户定义的对象。 在探究这些理论和实用的示例之前,我们来过一下一些本章将用到的术语: 集合(Collection)—— 一个包含一组相关对象的对象 类(Class)—— 对象的定义,包含其名称、属性、方法和事件。类充当一种对象模板,在运行时, 由此创建对象实例。 实例(Instance)—— 属于类的一个特定的对象称为类的实例。当创建一个实例时,也就创建了 一个新对象,它拥有类定义的属性和方法。 类模块(Class Module)—— 包含类定义的模块,包括它的属性和方法定义。 模块——模块含有 Sub(子过程)和 Function(函数)过程,可为其它 VBA 过程使用,并且和任何 对象没有特别的关系。 窗体模块——包含由用户窗体或者其控件触发的事件过程使用的 VBA 代码。窗体模块是一种类 模块。 事件—— 一种可以为对象识别的操作,例如鼠标单击或者按键,可以为其定义相应的操作。事 件可以由用户行为、或者 VBA 语句或者系统触发。 事件过程—— 一个可以自动执行的过程,是对用户或程序代码引发的或者系统触发的事件的响 应。 11.1 使用集合 一组相类似的对象称为集合。例如,在Excel里,所有打开了的工作簿属于Workbooks集合,而 某个具体工作簿里面的所有工作表都是Worksheets集合里的成员。在Word里,所有打开的文档都属 于Documents集合,而一个文档里的每个段落都是Paragraphs集合的成员。集合是包含其它对象的 对象。 无论您想要使用什么集合,都可以做下面的事情: • 使用索引值可以引用集合里的特定对象,例如,要指向Worksheets集合里的第二个对象,可使 用下面的任一语句: Worksheets(2).Select 或者 Worksheets("Sheet2").Select • 使用Count属性可以知道集合里的成员数,例如,当在立即窗口里输入语句: 第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 267 ?Worksheets.Count VBA将会返回当前工作簿里的工作表总数。 • 使用Add方法可以在集合里插入新的项目,例如,当在立即窗口里输入语句: Worksheets.Add VBA将会在当前工作簿里面插入一个新的工作表,这时,Worksheets集合里多了一个成员。 • 使用For Each…Next循环可以遍历集合里的每个对象。 假设打开了一个工作簿,该工作簿包含五个工作表,它们的名称为: “Daily wages”、 “Weekly wages”、“Bonuses”、“Yearly salary”和“Monthly wages”。 使用下面的过程将名称里包含“wages”的工作表删除: Sub DeleteSheets() Dim ws As Worksheet Application.DisplayAlerts = False For Each ws In Worksheets If InStr(ws.Name, "wages") Then ws.Delete End If Next End Sub 当编写自己的VBA过程时,您可能会碰到这样一种情况,即没有方便的内置集合来处理任务。 解决办法就是创建一个自定义集合。在第七章,您就已经知道如何通过使用动态或静态数组来处理 多个数据。因为,集合有允许添加、删除和计算其成员的内置属性和方法,所以使用集合比使用数 组容易得多。 11.1.1 声明自定义集合 要创建一个用户定义的集合,应该先声明一个Collection类型的对象变量,该变量在Dim语句里 和关键字New一起声明,如下所示: Dim 集合名称 As New Collection 11.1.2 给自定义集合添加对象 在声明Collection对象后,就可以使用Add方法往集合里插入新成员了。用来组成该集合的对象 不需要是同样的数据类型。Add方法如下所示: object.Add item, key, before, after 只需要明确object和item,object是集合名称,与使用在Collection对象声明中的名称相同;item 是要添加到集合里的对象。 尽管其它的参数是可选的,但是它们也很有用。集合里的成员自动从1开始分配号码,了解这个 很重要。然而,它们也可以分配一个唯一的键值。除了通过索引号(1、2、3,等等)访问某个特定的 成员之外,也可以在对象添加到集合中时给该对象分配一个键值。例如,如果创建一个自定义工作 表集合,那么可以使用工作表名称作为键;要鉴别学生或者员工集合里的单个人员,就可以使用社学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 268 共 612 页 会保险号码(SSN)作为他们的键。 如果想要指定对象在集合里面的位置,那么就应该使用before或者after参数(不能同时使用它 们)。参数before在该对象之前添加新对象,而参数after则在该对象之后添加新的对象。 接下来的过程GetComments声明了一个叫做colNotes的自定义集合,该过程提示输入作者姓名, 然后在活动工作簿里遍历所有的工作表以找到该作者的批注。只有某个特定作者输入的批注被添加 到自定义集合。过程给第一个批注分配一个键值,然后将剩余的批注添加到集合里,每次都将它们 置于最后添加的批注之前(注意before参数的使用)。如果集合至少有一个批注,那么过程会在一个信 息框显示由参数key确定的批注内容。注意,参数key如何用来引用集合里的成员。然后,过程将集 合里的所有批注的文本打印到立即窗口。文本函数(Mid和Len)用来仅获取批注内容,不带作者姓名。 接下来,通过Count属性返回工作簿里的批注总数和自定义集合里的批注总数。 在试验过程GetComments之前,我们先按照以下步骤创建一个工作簿: 1. 打开一个新工作簿并保存为Chap11.xls。 2. 在工作表Sheet1的任意单元格上单击右键,并从快捷菜单上选择“插入批注”,随意输入一些文 本。在批注框之外的任意地方单击,退出批注编辑模式。使用相同的技术在工作表Sheet2再输 入两个批注,给每个批注输入不同的文本。在当前工作簿里添加一个新工作表(Sheet4),并添加 一个批注。现在应该在三个工作表里共有四个批注。 3. 选择“工具”|“选项”并且单击“常规”选项卡,用户名文本框里面应该显示的是您的名字, 删除您自己的名字,并输入Joan Simth,单击“确定”。现在,在工作表Sheet2和Sheet4的任意 地方各输入一个批注,这些批注应该会自动地印上Joan Smith的名字。当输入完批注后,回到“选 项”对话框,并将“常规”选项卡上的用户名改回您自己的名字。 4. 切换到VBE编辑器,并将VBA工程重命名为ObjColClass。 5. 在当前工程里添加一个新模块,并重命名为MyCollection。 6. 输入过程GetComments,如下所示: Sub GetComments() Dim sht As Worksheet Dim colNotes As New Collection Dim myNote As Comment Dim I As Integer Dim t As Integer Dim fullName As String fullName = InputBox("输入作者的全名:") For Each sht In ThisWorkbook.Worksheets sht.Select I = ActiveSheet.Comments.Count For Each myNote In ActiveSheet.Comments If myNote.Author = fullName Then MsgBox myNote.Text If colNotes.Count = 0 Then colNotes.Add Item:=myNote, key:="first" Else colNotes.Add Item:=myNote, Before:=1 End If End If Next t = t + I Next 第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 269 If colNotes.Count <> 0 Then MsgBox colNotes("first").Text MsgBox "工作簿中的总批注数: " & t & Chr(13) & _ "集合中的总批注数:" & colNotes.Count Debug.Print "Comments by " & fullName For Each myNote In colNotes Debug.Print Mid(myNote.Text, Len(myNote.Author) + 2, _ Len(myNote.Text)) Next End Sub 7. 运行过程GetComments并且查看结果。 11.1.3 从自定义集合中移除对象 从自定义集合里移除成员和添加成员一样简单。使用Remove方法可以移除对象,如下所示: object.Remove item object是自定义集合的名称,含有想要移除的对象;item是想要从集合里移除的对象。 我们来修改在上节中准备的过程GetComments,演示从集合里移除成员的过程。在该过程的结 尾,我们将当前集合colNotes里成员的内容一个一个地显示出来,并且询问用户是否将该成员从该 集合中移除。 1. 将下面的声明添加到过程GetComments的声明部分: Dim response Dim myId As Integer 第一条语句声明了一个叫做response的变量,您将使用该变量来存储Msgbox函数的结果。 第二条语句声明变量myld来存储集合对象的索引号。 2. 定位到过程GetComments中的下面的语句: For Each myNote In colNotes 在上面的语句之后,添加下述语句: myId = 1 3. 定位到过程GetComments中的下面的语句: Debug.Print Mid(myNote.Text, Len(myNote.Author) + 2, _ Len(myNote.Text)) 在该语句后,输入下面的代码块: response = MsgBox("移除这个批注?" & Chr(13) _ & Chr(13) & myNote.Text, vbYesNo + vbQuestion) If response = 6 Then colNotes.Remove Index:=myId Else myId = myId + 1 End If 4. 在该过程的结尾处输入下面的语句: Debug.Print "下面的批注保留在集合里:" For Each myNote in colNotes Debug.Print Mid(myNote.Text, Len(myNote.Author) + 2, _ Len(myNote.Text)) 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 270 共 612 页 Next 5. 运行过程GetComments,并且将显示在信息框里的部分批注删除。 修改后的过程GetComments2可以在随书附带的CD里找到。该过程将指定的批注从自定义集合 里清除,但是不会从工作表里删除批注。 技巧11—1:集合重新编号 当移除对象后,集合会自动重新编号,因此,可以使用1作为Index参数,来删除自定义集合里的所有对象, 如下所示: Do While myCollection.Count >0 myCollection.Remove Index:=1 Loop 11.2 插入:模块还是类模块? 在VBE编辑器的“插入”菜单里有两个可利用的模块命令:模块和类模块,在本章的开头已对 它们进行过定义。到目前为止,您已经使用过标准模块来创建Sub过程和Function过程。您将在本章 中第一次使用类模块来创建自定义对象并定义其属性和方法。 11.2.1 创建自定义对象 创建一个新的、非标准的VBA对象,需要在工程里插入类模块并且在模块里添加代码。然而, 在做此之前,您需要对什么是类要有一个基本的了解。 如果您回头看一下本章的开头,那么就会知道类是一种对象模板。一个经常使用的比喻就是将 类比作一个甜饼切割机,就像一个甜饼切割机决定了某个甜饼将是什么样一样,类的定义决定了某 个对象应该是什么样以及它的行为。在实际使用一个对象类之前,必须先创建一个类的新实例。对 象实例就是这些甜饼,每个实例都有它的类定义的特点(属性和方法),正如您可以使用相同的甜饼切 割机制作许多甜饼一样,您可以创建一个类的多个实例。可以独立改变相同类中的任意其它实例中 的类的每个实例的属性。 类模块允许定义自己的自定义类,连同自定义的属性和方法。回忆一下,属性定义对象的某个 特征,例如外形、位置、颜色、标题,等等;方法是对象可以执行的操作。通过在类模块里编写属 性过程,可以给自定义对象创建属性。对象的方法也可以在类模块里通过编写Sub子过程或者 Function函数过程来创建。 在类模块里创建对象后,可以像使用其它的内置对象一样使用它。也可以将对象类导出给其它 能使用VBA的应用程序使用。 11.3 创建类 本章的剩余部分将演示创建和使用一个称作CEmployee的自定义对象的过程。这个对象代表一 个员工,CEmployee对象将具有属性,例如Id、FirstName、LastName和Salary。它也有一个方法, 可以修改当前的薪水。 第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 271 1. 在工程浏览器窗口选择ObjColClass(Chap11.xls),并选择“插入”|“类模块”。 2. 选择工程浏览器窗口里的类模块,并使用属性窗口将其重命名为CEmployee。 技巧11—2:给类模块命名 每次创建一个新的类模块时,给它一个有意义的名称。将类模块命名为想要在您的VBA过程中使用的名称。 给类选择的名称应该易于理解并且明确对象类要代表的“东西”。作为一个规则,对象类名称前面一般有一个大 写字母“C”。 11.3.1 变量声明 在添加和重命名类模块后,下一步就是声明变量,用来包含要存储在该对象里的数据。想要存 储在对象中的每个数据项都应该赋给一个变量。类的变量被称为数据成员,并且使用关键字Private 声明,该关键字确保这些变量只在该类模块里面可用。使用关键字Private代替熟悉的Dim语句隐藏 该数据成员并且避免应用程序的其它部分引用它,只有定义这些变量的类模块中的过程才可以修改 这些变量的值。 因为这些变量的名称也作为属性名称,所以要给对象数据成员使用有意义的名称。习惯使用m_ 作为变量名称的前缀,以表明它们是类的数据成员。 1. 在类模块CEmployee的顶部输入下面的声明: Option Explicit '声明 Private m_LastName As String Private m_FirstName As String Private m_Salary As Currency Private m_Id As String 注意,每个数据成员变量的名称都以前缀“m_”开始。 11.3.2 定义类的属性 使用关键字Private声明变量保证变量不能从该对象的外部直接访问,这意味着该类模块以外的 VBA过程不能设置或者读取存储在这些变量里的数据。要让VBA应用程序的其它部分也能够设置或 者读取员工数据,就必须在CEmployee类模块里添加专门的属性过程。这里有三种属性过程: z Property Let——这类过程允许应用程序的其它部分设置属性值。 z Property Get——这类过程允许应用程序的其它部分获得或者读取属性值。 z Property Set——当设置对对象的引用时,这类过程用来代替Property Let。 属性过程在一个对象属性需要设置或者读取的时候被执行。Property Get过程可以与Property Let过程拥有相同的名称。应该给对象的每个可能被VBA应用程序的其它部分访问的属性创建属性过 程。 三种属性语句中最容易理解的是Property Get过程,我们通过仔细研究Property Get LastName 过程来检查一下属性过程的语法。 属性过程包含下面的部分: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 272 共 612 页 过程声明行指定属性的名称和数据类型: Property Get LastName ( ) As String LastName是属性名称,As String决定该属性返回值的数据类型。 赋值语句与在函数过程里使用的相类似: LastName = m_LastName LastName是属性名称,而m_LastName是数据成员变量,包含想要获取或者设置的属性 值。m_LastName变量应该在类模块的顶部使用关键字Private定义。 如果获取的数据是一个计算的结果,那么可以包含适当的VBA语句: Property Get Royalty() Royalty = (Sales * Percent)-Advance End Property z End Property关键字表明属性过程结束。 技巧11—3:从属性过程立即退出 正如关键字Exit Sub和Exit Function允许提前退出sub子过程或者函数过程一样,Exit Property关键字同样 也给出了立即退出属性过程的方法。程序将会继续执行Property Get,Property Let或者Property Set过程下面 的语句。 11.3.3 创建Property Get过程 CEmployee类对象有四个属性需要给当前VBA工程里的其它模块的VBA过程使用,使用对象 CEmployee时,肯定想要获得员工的ID、姓和名以及当前薪水的信息。 1. 在类模块CEmployee里紧接着声明部分输入下面的Property Get过程: Property Get Id( ) As String Id = m_Id End Property Property Get LastName( ) As String LastName = m_LastName End Property Property Get FirstName( ) As String FirstName = m_FirstName End Property Property Get Salary( ) As Currency Salary = m_Salary End Property 每种员工必须的信息都要求一个单独的Property Get过程,上面的每个Property Get过程返回当 前属性的值。注意,Property Get过程和函数过程非常类似。就像函数过程,Property Get过程包含 一个赋值语句。回忆一下第四章的内容,要从函数过程返回值,必须将该值赋给函数名称。 11.3.4 创建Property Let过程 除了使用Property Get过程来获取存储在数据成员(私有变量)里的值之外,必须准备相应的 Property Let过程,以允许其它过程在需要时可以更改这些变量的值。然而,如果存储在私有变量的 值只读,则不必定义Property Let过程。假设不希望用户更改员工ID,只要不给它编写Property Let第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 273 过程,就可以使它为只读。因此,类模块CEmployee将只有三个属性(LastName、First- Name和 Salary),每个属性需要一个单独的Property Let过程。 1. 在类模块CEmployee里输入下面的Property Let过程: Property Let LastName(L As String) m_LastName = L End Property Property Let FirstName(F As String) m_FirstName = F End Property Property Let Salary(ByVal dollar As Currency) m_Salary = dollar End Property Property Let过程至少需要一个参数来指定想要赋给属性的值,该参数可以按值传递(参见 Property Let Salary过程里的关键字ByVal)或者按引用传递(ByRef是默认的)。如果需要重新熟悉一下 这些关键字的意思,那么参阅第四章里的“按引用或者按值传递参数”部分。传递给Property Let过 程的参数数据类型必须和同名称的Property Get 或者Set过程返回值的数据类型一致。注意, Property Let过程拥有与上节中准备的Property Get过程相同的名称。通过忽略Id属性的Property Let 过程,将创建一个只读的ID属性,只能读取而不能设置该属性。 技巧11—4:定义属性过程的作用域 可以将关键字Public、Private或Static放在属性过程名称的前面来确定它的作用域。例如: 要创建一个可以从所有模块的过程里访问的Property Get过程,使用下面的语句格式: Public Property Get FirstName( ) As String 要使Property Get过程只能从声明它的模块中的其它过程访问,使用下面的语句格式: Private Property Get FirstName( ) As String 在过程间调用时,要保留Property Get过程的本地变量,可以使用下面的语句格式: Static Property Get FirstName( ) As String 如果没有明确使用Public或者Private,属性过程默认为公共的。同样,如果没有使用关键字Static, 那么 本地变量在过程间调用时就不会被保留。 11.3.5 创建类的方法 除了属性之外,对象通常还一个或者多个方法。方法是该对象可以执行的操作,允许操作存储 在类对象里的数据,方法使用Sub子过程或者函数过程创建。要让方法在类模块之外可用,就需要在 Sub子过程或函数过程定义前使用关键字Public。您在本章中创建的对象CEmployee有一个方法,可 以计算新的薪水。假设员工的薪水可以按照一个确定的百分比或者数量来增加或者减少。 1. 在CEmployee类模块里输入下面的函数过程CalcNewSalary: Public Function CalcNewSalary(choice As Integer, _ curSalary As Currency, amount As Long) As Currency Select Case choice Case 1 ' 通过百分比 CalcNewSalary =curSalary +((curSalary + amount)/100) 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 274 共 612 页 Case 2 ' 通过数量 CalcNewSalary = curSalary + amount End Select End Function 在类模块里使用关键字Public定义的函数CalcNewSalary作为类CEmployee的一个方法。要计算 一个新的薪水,类模块之外的VBA过程必须提供三个参数:choice、CurSalary和amount。参数choice 明确计算类型,假设想要按5个百分点或者5美元增加员工的薪水,选择1将通过指定的百分比增加薪 水,而选择2将添加指定的数量到当前薪水中。参数CurSalary是员工的当前薪水数字,而amount决 定了薪水的改变量。 技巧11—5:关于类的方法 „ 只有在类模块之外可以访问的方法应该声明为Public,所有其它的应该为Private。 „ 方法执行一些类中包含的数据的操作。 „ 如果方法需要返回值,那么就编写一个函数过程,否则创建一个Sub子过程。 11.3.6 创建一个类的实例 在类模块里输入完所有必需的Property Get、Property Let、Sub子过程和函数过程后,就可以 创建一个类的新实例了,该实例称为对象。在能够创建对象之前,对象变量必须在一个标准模块里 声明,以存储对该对象的引用。如果该类模块的名称为CEmployee,该类的新实例就可以使用下面 的语句创建: Dim emp As New CEmployee 变量emp代表对CEmployee类的一个对象的引用。 当使用关键字New声明对象变量时,VBA就会创建该对象并为它分配内存;然而,该对象并没 有实例化,直到在过程代码里通过赋值给它的属性或者执行它的方法引用该对象。 同样可以通过声明一个对象变量为所定义的对象类数据类型来创建该对象的一个实例。例如: Dim emp As CEmployee Set emp = New CEmployee 如果在Dim语句里不使用关键字New(如上所示),那么VBA就不会分配内存给自定义对象,直至 程序真正需要它。 11.3.7 类模块里的事件过程 事件基本上是一个对象可以识别的操作。自定义类仅识别两种事件:Initialize和Terminate,这 两个事件分别在类的实例创建和消毁时触发。 Initialize事件在从类创建对象时产生(参见上一节关于“创建类的实例”的介绍)。在CEmployee 类的例子里,Initialize事件在代码里第一次使用变量emp时也会引发。因为Initialize事件里的语句将 是该对象在任何属性被赋值之前或任何方法被执行之前第一个要执行的,所以Initialize事件是一个执 行类创建的对象初始化的好地方。 第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 275 回忆一下,在类模块CEmployee里,Id是只读的,可以使用Initialize事件给m_Id变量赋给一个唯 一的五位数字。 1. 在类模块CEmployee里输入下面的过程Class_Initialize: Private Sub Class_Initialize() Randomize m_Id = Int((99999 - 10000) * Rnd + 10000) End Sub Class_Initialize过程通过给变量m_Id赋一个唯一的五位数初始化CEmployee对象。使用下面的 公式,可以产生一个介于起始值10000和结束值99999之间的随机数: =Int((结束值–起始值)*Rnd +起始值) Class_Initialize过程也使用了Randomize语句来初始化随机数发生器。可以搜索在线帮助,获得 更多关于使用Rnd和Int函数以及Randomize语句的信息。 Terminate事件发生在释放该对象的引用时,这是一个执行任何必要的清理任务的好地方。 Class_Terminate过程使用下面的语法: Private Sub Class_Terminate() [这里放置清理代码] End Sub 使用下面的语法,将对象变量从对象中释放: Set objectVariable = Nothing 当设置对象变量为Nothing时,Terminate事件就发生了,此时,任何位于该事件里的代码就会被 执行。 11.3.8 创建用户界面 如果您跳过了前面的章节,那么可能得返回去学习,因为,执行自定义的CEmployee对象需要 设计一个自定义窗体。 1. 在工程浏览窗口选中当前的VBA工程,并选择“插入”|“用户窗体”。 2. 按照图11-1所示准备好该窗体: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 276 共 612 页 图11-1:本窗体演示了自定义对象CEmployee的使用 3. 为窗体和它的控件设置下面的属性: 对象 属性 设置 窗体 Name Salaries Caption 雇员和薪水 标签1 Caption 姓 标签“姓”下面的文字框 Name txtLastName 标签2 Caption 名 标签“名”下面的文字框 Name txtFirstName 标签3 Caption 薪水 标签“薪水”下面的文字框 Name txtSalary 框架1 Caption 修改薪水 框架“修改薪水”下面的文字 框 Name txtRaise 选项按钮1 Name optPercent Caption 百分比(%) 选项按钮2 Name optAmount Caption 数量($) 框架2 Caption 改变薪水的人为 第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 277 选项按钮3 Name optHighlighted Caption 所选雇员 选项按钮4 Name optAll Caption 所有雇员 列表框 Name lboxPeople Height 91.45 Width 180.75 命令按钮1 Name cmdSave Caption 保存 命令按钮2 Name cmdClose Caption 关闭 命令按钮3 Name cmdUpdate Caption 更新薪水 命令按钮4 Name cmdDelete Caption 删除雇员 命令按钮5 Name cmdEmployeeList Caption 更新列表 4. 准备一个数据输入工作表,如图11-2所示: 图11-2:在窗体“雇员和薪水”上输入的数据将会转移到这个工作表中 5. 切换到VBE编辑器窗口,双击窗体背景以激活窗体模块。 6. 在窗体代码窗口顶部输入下面的声明: Option Explicit Dim emp As New CEmployee Dim CEmployees As New Collection Dim index As Integer Dim ws As Worksheet Dim extract As String Dim cell As Range 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 278 共 612 页 Dim lastRow As Integer Dim empLoc As Integer Dim startRow As Integer Dim endRow As Integer Dim choice As Integer Dim amount As Long 第一条语句声明变量emp为类CEmployee的一个新实例,第二条语句声明了一个自定义集 合,集合CEmployees将会用来存储员工数据。这里声明的其它变量将会用于窗体上各种控件 的VBA过程里面。 7. 输入下面的UserForm_Initialize过程来激活或者禁用窗体上的控件: Private Sub UserForm_Initialize() txtLastName.SetFocus cmdEmployeeList.Visible = False lboxPeople.Enabled = False Frame1.Enabled = False txtRaise.Value = "" optPercent.Value = False optAmount.Value = False txtRaise.Enabled = False optPercent.Enabled = False optAmount.Enabled = False Frame2.Enabled = False optHighlighted.Enabled = False optAll.Enabled = False cmdUpdate.Enabled = False cmdDelete.Enabled = False End Sub 当窗体被装载时,UserForm_Initialize过程里所输入的语句只会激活所需要的控件(见图11-3)。 第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 279 图11-3:当窗体第一次被装载时,UserForm_Initialize过程禁用了某些控件 8. 输入下面的过程cmdSave_Click,将输入在窗体中的数据转移到工作表中: Private Sub cmdSave_Click() If txtLastName.Value = "" Or txtFirstName.Value = "" Or _ txtSalary.Value = "" Then MsgBox "输入姓、名和薪水." txtLastName.SetFocus Exit Sub End If If Not IsNumeric(txtSalary) Then MsgBox "您必须为薪水输入一个值." txtSalary.SetFocus Exit Sub End If If txtSalary < 0 Then MsgBox "薪水不能是一个负数." Exit Sub End If Worksheets("Salaries").Select index = ActiveSheet.UsedRange.Rows.Count + 1 lboxPeople.Enabled = True '设置和输入数据到CEmployees集合 With emp Cells(index, 1).Formula = emp.Id .LastName = txtLastName Cells(index, 2).Formula = emp.LastName .FirstName = txtFirstName 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 280 共 612 页 Cells(index, 3).Formula = emp.FirstName .Salary = CCur(txtSalary) If .Salary = 0 Then Exit Sub Cells(index, 4).Formula = emp.Salary CEmployees.Add emp End With ‘从文字框中删除数据 txtLastName = "" txtFirstName = "" txtSalary = "" ‘启用隐藏的控件 cmdEmployeeList.Value = True cmdUpdate.Enabled = True cmdDelete.Enabled = True Frame1.Enabled = True txtRaise.Enabled = True optPercent.Enabled = True optAmount.Enabled = True Frame2.Enabled = True optHighlighted.Enabled = True optAll.Enabled = True txtLastName.SetFocus End Sub cmdSave_Click过程以验证用户在姓、名和薪水文字框中的输入有效性开始,如果用户输 入了正确的数据,VBA将当前工作表里的第一个空白行行号赋给变量Index,以便数据输入。下 一条语句激活窗体的列表框控件。 当程序到达With emp结构时,CEmployee类的一个新实例便被创建。属性LastName, FirstName和Salary的设置基于相应文字框里输入的数据,而Id属性则是由Class_Initialize事件 过程里的随机数语句产生的数字设置的。VBA每次看到对实例化的对象emp的引用时,它就会 调用位于类模块里的适当的Property Let过程。 本章的最后一节将演示如何一步一步地跟踪过程的运行,准确地查看什么时候运行属性过 程。设置完对象的属性值后,VBA将员工数据转移到工作表里。With emp结构里面的最后一条 语句将用户定义的对象emp添加到一个称为CEmployee的自定义集合里。 接下来,VBA将窗体文字框里的当前输入清除并且激活在UserForm_Initialize过程里关闭 的命令按钮。注意,本代码块的第一条指令:cmdEmployeeList.Value = True,该语句导致附 于“更新列表”命令按钮自动执行cmdEmployeeList_Click过程(顺便说一下,这是唯一用户从 未见到的控件)。该过程的代码如下所示。 9. 输入cmdEmployeeList_Click过程,如下所示: Private Sub cmdEmployeeList_Click() lboxPeople.Clear For Each emp In CEmployees lboxPeople.AddItem emp.Id & ", " & _ emp.LastName & ", " & emp.FirstName & ", $" & _ Format(emp.Salary, "0.00") Next emp End Sub cmdEmployeeList_Click过程附加在命令按钮Update List中,该按钮由cmdSave_Click过第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 281 程控制,并且导致新的员工数据添加到列表框控件里。cmdEmployeeList_Click过程以清除列 表框的内容开始,然后用存储在自定义集合CEmployees中的成员填充列表框。 图11-4:列表框控件显示雇员数据,它们被输入在自定义集合中 10. 输入下面的过程cmdClose_Click: Private Sub cmdClose_Click() Unload Me End Sub cmdClose_Click过程将用户窗体从屏幕上清除,并结束使用雇员的自定义集合。当再次运 行窗体时,输入的雇员将会成为新的CEmployees集合的成员。 11. 输入下面的过程cmdDelete_Click: Private Sub cmdDelete_Click() ' 确保在列表框中选中了某雇员 If lboxPeople.ListIndex > -1 Then MsgBox "所选项目号: " & lboxPeople.ListIndex extract = CEmployees.Item(lboxPeople.ListIndex + 1).Id MsgBox extract Call FindId MsgBox empLoc Range("A" & empLoc).Delete (3) MsgBox "在CEmployees集合中共有" & CEmployees.Count & _ " 项. " CEmployees.Remove lboxPeople.ListIndex + 1 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 282 共 612 页 MsgBox "CEmployees集合现在有" & _ CEmployees.Count & " 项." cmdEmployeeList.Value = True If CEmployees.Count = 0 Then Call UserForm_Initialize End If Else MsgBox "单击您想移除的项." End If End Sub 过程cmdDelete_Click可以从自定义集合CEmployees里面清除某员工。要删除一名员工, 必须单击列表框里的适当项目。当单击一个列表项,cmdEmployeeList_Click过程就自动执行。 该过程确保更新列表框的内容,雇员将同时从集合和列表框里删除。如果列表框里面只有一个 雇员,那么VBA将调用过程UserForm_Initialize在清除最后一个雇员工后将某些控件禁用。 cmdDelete_Click过程里有几个MsgBox语句,让在删除时检查列表框控件里的内容。除了从自 定义集合里删除雇员工之外,过程cmdDelete_Click也必须从工作表的相应行删除雇员的信息。 使用函数FindId可以很方便地在工作表里找到员工数据(该过程的代码见下面的第12步),该函 数将要删除的行号返回到过程cmdDelete_Click。 12. 输入下面的函数过程: Private Function FindId() Set ws = ActiveWorkbook.Sheets("Salaries") startRow = ActiveSheet.UsedRange.Rows.Count + _ 1 - CEmployees.Count endRow = ActiveSheet.UsedRange.Rows.Count For Each cell In ws.Range(Cells(startRow, 1), _ Cells(endRow, 1)) If cell.Value = extract Then empLoc = cell.Row FindId = empLoc Exit Function End If Next End Function 函数过程FindId将含有窗体列表框中当前选择的员工数据的行号返回到主调过程。工作表 中的数据搜索基于变量extract的内容,它存储唯一的雇员号码。雇员ID的搜索被限制在工作表 的第一列,并从集合的第一个成员放置的行开始搜索,这样使搜索更快一些。您不要在工作表 整个使用的区域里搜索。想想如果不只一次地使用窗体,但是,自定义集合的内容不会包含前 面输入的员工。 13. 输入下面的过程cmdUpdate_Click: Private Sub cmdUpdate_Click() If optHighlighted = False And optAll = False Then MsgBox "单击'所选雇员' 或 " _ & " '所有雇员' 选项按钮." Exit Sub End If If Not IsNumeric(txtRaise) Then MsgBox "这个字段需要一个数字." 第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 283 txtRaise.SetFocus Exit Sub End If If optHighlighted = True And _ lboxPeople.ListIndex = -1 Then MsgBox "单击雇员的名字." Exit Sub End If If lboxPeople.ListIndex <> -1 And _ optHighlighted = True And _ optAmount.Value = True And _ txtRaise.Value <> "" Then extract = CEmployees.Item(lboxPeople.ListIndex + 1).Id MsgBox extract Call FindId MsgBox empLoc choice = 2 amount = txtRaise CEmployees.Item(lboxPeople.ListIndex + 1).Salary = _ emp.CalcNewSalary(choice, _ CEmployees.Item(lboxPeople.ListIndex + 1).Salary, amount) Range("D" & empLoc).Formula = CEmployees. _ Item(lboxPeople.ListIndex + 1).Salary cmdEmployeeList.Value = True ElseIf lboxPeople.ListIndex <> -1 And _ optHighlighted = True And _ optPercent.Value = True And _ txtRaise.Value <> "" Then extract = CEmployees.Item(lboxPeople.ListIndex + 1).Id MsgBox extract Call FindId MsgBox empLoc CEmployees.Item(lboxPeople.ListIndex + 1).Salary = _ CEmployees.Item(lboxPeople.ListIndex + 1).Salary + _ (CEmployees.Item(lboxPeople.ListIndex + 1).Salary * _ txtRaise / 100) Range("D" & empLoc).Formula = CEmployees. _ Item(lboxPeople.ListIndex + 1).Salary cmdEmployeeList.Value = True ElseIf optAll = True And _ optPercent.Value = True And _ txtRaise.Value <> "" Then For Each emp In CEmployees emp.Salary = emp.Salary + ((emp.Salary * txtRaise) _ / 100) extract = emp.Id MsgBox extract Call FindId MsgBox empLoc Range("D" & empLoc).Formula = emp.Salary Next emp cmdEmployeeList.Value = True ElseIf optAll = True And _ optAmount.Value = True And _ txtRaise.Value <> "" Then For Each emp In CEmployees emp.Salary = emp.Salary + txtRaise extract = emp.Id MsgBox extract 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 284 共 612 页 Call FindId MsgBox empLoc Range("D" & empLoc).Formula = emp.Salary Next emp cmdEmployeeList.Value = True Else MsgBox "输入数据或者选择一个选项." End If End Sub 使用过程cmdUpdate_Click,可以通过指定百分比或者数量来修改薪水,可以为选定的雇员或者 列表框和集合里列出的所有雇员更新薪水。cmdUpdate_Click过程检查用户是否选择了适当的选项按 钮,然后在文字框里输入增加的数字。取决于选择了哪个选项按钮,可以给某个雇员或者所有员工 按照百分比或数量更新的薪水数。薪水的更改也会反映在工作表里。图11-15显示James Nolan按 百分之十增加了薪水。在文字框里面输入负数,可以按照指定的百分比或者数量减少薪水。 图11-5:可以按照指定的百分比或者数量增加或者减少雇员的薪水 14. 选择“插入”|“模块”在当前工程里插入一个标准模块,重命名该模块为WorkAndPay,输入 下面的过程来显示“雇员和薪水”窗体: Sub ClassDemo( ) Salaries.Show End Sub 15. 运行过程ClassDemo来使用自定义类。 也可以单击窗体的背景并按下F5键来运行“Salaries”窗体,或者也可以在工作表里放置一个按第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 285 钮,并将过程ClassDemo指定给该按钮(参见第十章将按钮放在工作表里)。 11.3.9 观察VBA过程的执行 为了帮助您理解代码运行时会发生什么,以及自定义对象如何工作,我们来逐步运行过程 cmdSave_Click。本练习也简单地介绍了调试技术,该技术将在第十三章里详细介绍。 1. 在工程浏览窗口,选择“Salaries”窗体,并单击窗口顶部的查看代码按钮。 2. 出现Salaries代码窗口时,从代码窗口左上角的复合框里选择过程cmdSave。 3. 在下面的代码行的左边框上单击,设置一个断点: If txtLastName.Value = "" Or txtFirstName.Value = "" Or _ txtSalary.Value = "" Then 图11-6:边框上的红色圆圈代表断点当VBA遇到带断点的语句时,它就会自动切换到代码窗口并且以白 字红底的显示该文本 4. 在工程浏览窗口,选中模块WorkAndPay并单击查看代码按钮。 5. 将光标放在过程ClassDemo里的任意位置,并且按下F5键,或者选择“运行”|“运行子过程/ 用户窗体”。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 286 共 612 页 6. 当窗体出现时,在姓、名和薪水文字框里输入数据,然后单击窗体的“保存”按钮。现在VBA 应该切换到代码窗口,因为它碰到了过程cmdSave_Click第一行中的断点。 图11-7:当VBA在运行过程遇到断点时,它会切换到代码窗口,并在中断过程的语句的左边边框上显示 一个黄色箭头 7. 通过按F8键逐句运行代码。 VB运行当前语句,接着自动向前移动到下一句然后停止执行。当前语句的边框上显示了黄色箭 头,并且为黄色底色。不断地按F8键逐句执行该过程。当VBA遇到With emp语句时,它会切换到过 程Class_Initialize。 第 11 章 10B 自定义集合和类模块 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 287 图11-8:当VBA遇到对对象变量emp的引用时,它就会去执行过程Class_Initialize在它执行完该过程里 的语句之后,VBA会返回到过程cmdSave_Click里面 当VBA遇到语句Cells(Index, 1).Formula = emp.ID时,它就会去执行类模块CEmployee里的过 程Property Get Id。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 288 共 612 页 图11-9:对自定义对象属性的读取要通过过程Property Get来实现 8. 使用F8键,跟踪过程cmdSave_Click的执行,直到结束。 当VBA遇到过程的结束(End Sub)时,黄色加亮将会关闭。这时候,单击屏幕底部WIndows 任务栏上的Excel按钮返回到当前窗体。输入一个新员工的数据,然后单击“保存”。当VBA显 示代码窗口时,选择“调试”|“清除所有断点”。现在按F5键运行完过程剩余的代码而不需单 步执行。 技巧11—6:VBA调试工具 VB提供了许多调试工具,帮助您分析应用程序如何操作,以及找到过程里的错误源。参见第十三章关于这 些工具使用的详细介绍。 本章小结和下一章内容简介 在本章里,您学习了在VBA过程里如何创建和使用自己的对象和集合。您使用了类模块来创建 一个用户定义(自定义)对象。您已经看到了如何使用Property Get和Property Let过程定义自定义对象 的属性。您也学习了如何为自定义对象编写方法。接下来,您看到了如何通过创建一个自定义窗体 使得类模块为用户可用。最后,您学习了如何通过逐句执行代码来分析VBA程序。在下一章,您将 学习如何通过自定义菜单和工具条让VBA程序为终端用户所用。 第12章 使用VBA创建自定义菜单和工 具栏 用户肯定期望在任何 Windows 应用程序里都有一个容易的 方法来选择命令和选项,因此,当您编写 VBA 过程提供对某个特 定的工作表自动化的解决方案时,应该花些时间添加功能让应用 程序更快并易于使用…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 290 共 612 页 用户肯定期望在任何Windows应用程序里都有一个容易的方法来选择命令和选项,因此,当您 编写VBA过程提供对某个特定的工作表自动化的解决方案时,应该花些时间添加功能让应用程序更 快并易于使用。用户界面最为期望的功能就是自定义菜单和工具栏,当VBA程序由很多个过程组成 时尤为重要。简单地在内置菜单或者自定义工具栏上创建一个控件,就可以为某个命令提供一个快 捷访问。 本章将教您如何编程处理菜单和工具栏。 12.1 工具栏 术语“工具栏”既指工具栏又指菜单栏,工具栏给用户提供了对应用程序命令快捷方便的访问。 可以通过“自定义”对话框(参见图12-1)轻松创建和修改工具栏。访问该对话框的方法之一是选择 “工具”|“自定义”,也可以选择“视图”|“工具栏”|“自定义”,或者在任意工具栏上单击右键, 然后从快捷菜单上选择“自定义”。工具栏可以包括按钮、菜单,或者两者都有。菜单栏位于应用程 序窗口的顶部(就在标题栏下方),它是一种特殊的工具栏。除了命令之外,菜单栏也可以包含图片, 允许用户快速将命令和工具栏上相应的按钮联系起来。例如,“文件”菜单里的“新建”和“打开” 命令都在命令的左边显示了图片,这些相同的图片也可以在Excel“常用”工具栏里面找到。 图12-1:使用“自定义”对话框,可以手动定制菜单和工具栏 “自定义”对话框包含三个选项卡:工具栏、命令和选项。使用“工具栏”选项卡页,可以创 建一个新工具栏、更改现有工具栏的名称、清除工具栏或者重新设置工具栏。“命令”选项卡页允许 将新的命令拖曳到活动菜单里或者任何可见的工具栏中。单击某个类别后,可以看到它里面可用的 命令列表。“选项”选项卡页让您通过设置图标大小、显示关于工具栏的屏幕提示以及选择动画来设 置个性化的菜单和工具栏。如果需要重新看一下如何通过“自定义”对话框操作菜单和工具栏,可 以看看在线帮助。本章侧重于编写VBA语句和过程以获取对应用程序的菜单和工具栏的完全控制。 第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 291 12.2 使用CommandBar对象 CommandBars是代表当前应用程序中所有工具栏对象的集合,该集合里的每个对象称为 CommandBar。术语“CommandBar”用来代表菜单栏、快捷菜单或者工具栏。 因为CommandBar对象可以代表各种工具(工具栏、菜单栏、快捷菜单),所以该对象有个专门的 Type属性,可以用来返回工具栏的特定类型,如表12—1所示。 表12—1 集合CommandBars里的CommandBar对象的类型 对象类型 索引 常量 工具栏 0 msoBarTypeNormal 菜单栏 1 msoBarTypeMenuBar 快捷菜单 2 msoBarTypePopup 1. 打开一个新工作簿并保存为Chap12.xls。 2. 切换到VBE编辑器屏幕。 3. 在工程浏览窗口选择当前VBA工程Chap12.xls ,并重命名为CustomTools。 4. 添加一个新模块到CustomTools工程中。 5. 输入过程MyToolBars,如下所示: Sub MyToolBars( ) Dim bar As CommandBar Dim r As Integer r = 1 ActiveSheet.Range(“A1”).Formula = “List of Toolbars” For Each bar In CommandBars If bar.Type = msoBarTypeNormal Then With Worksheets("Sheet1").Range("A1") .Offset(r, 0) = bar.Name .Offset(r, 1) = bar.Index End With r = r + 1 End If Next Set bar = Nothing End Sub 上面的过程搜索CommandBars集合,并且只选择Type属性为msoBarTypeNormal的工具。如果 CommandBars集合里面的某个元素是工具栏,那么VBA就会将它的名称输入到活动工作表的第一 列,B列将存储该对象的索引号。 修改上面的过程,让它输入集合CommandBars里所有对象(工具栏、菜单栏、快捷菜单)的名称 到工作表中,使用表12—1作为参考。 可以使用工具栏的名称或者索引号来引用CommandBars集合里的某个特定的工具栏。 1. 在立即窗口里输入下面的语句: ?CommandBars(1).Name 当按下回车键后,VBA就会返回CommandBars集合里第一个元素的名称。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 292 共 612 页 2. 在立即窗口输入下面的语句: ?CommandBars("Circular Reference").Type VBA返回0,这是工具栏的索引号码(参见表12—1)。 3. 要计算CommandBars集合里可用工具的总数,可以使用Count属性。在立即窗口里输入下面的 语句: ?CommandBars.Count 12.2.1 创建自定义工具栏 要创建自定义工具栏、菜单栏或者快捷菜单,使用CommandBars对象的Add方法。 假设您想要创建一个称作“预算计划”的新工具栏,要调用的Add方法如下所示: CommandBars.Add(Name, Position, MenuBar, Temporary) 可选参数Name是想要分配给新命令条的名称,如果不指定该名称,VBA会分配一个通用的名称, 例如“自定义 1”。 Position参数决定新命令条在屏幕上出现的位置(参见表12—2)。 表12—2:CommandBar对象的位置常量 位置常量 索引值 描述 msoBarLeft 0 命令栏停靠在应用程序窗口的左边 msoBarRight 2 命令栏停靠在应用程序窗口的右边 msoBarTop 1 命令栏停靠在应用程序窗口的顶部 msoBarBottom 3 命令栏停靠在应用程序窗口的底部 msoBarFloating 4 命令栏悬浮在屏幕上(非停靠) msoBarPopup 5 命令栏是一个快捷菜单 msoBarMenuBar 6 命令栏取代系统菜单栏(仅用于Macintosh) 参数MenuBar是一个逻辑值(True或False),它决定新命令条是否取代活动菜单栏,如果想要取 代活动菜单栏则输入True,否则使用False。 参数Temporary是一个逻辑值(True或False),决定何时删除命令条。使用True,当Excel程序关 闭时命令条会自动删除。使用False,则退出该程序时,工具栏不会被删除。 可以在立即窗口里练习创建工具栏。 1. 在立即窗口里面输入下面的语句,注意要将完整的语句写在一行里: set newToolbar = CommandBars.Add("预算计划", msoBarRight, False, True) 当按下回车键时,VBA就会在集合CommandBars里面添加一个名为“预算计划”的新工具 栏。切换到Excel应用软件窗口并且选择“视图”|“工具栏”,Excel显示可用的工具栏列表,包 括刚才已创建的那个(参见图12-2)。 第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 293 图12-2:在Excel内置工具栏列表里添加了一个自定义工具栏 2. 切换回VBE编辑器窗口,并在立即窗口里输入下面的语句: CommandBars("Budget Plans").Visible = True 切换到Excel应用程序窗口查看该工具栏。 “预算计划”工具栏出现在垂直滚动条的右边。 回想在创建该工具栏时,使用常量msoBarRight来决定其位置。 3. 现在关闭Excel应用软件,然后重新打开它并且查看“预算计划”工具栏是否仍然出现在应用软 件窗口的右边。因为在Add方法最后一个参数的位置使用了逻辑值True,“预算计划”工具栏应 该已经不在了。 在试图创建新工具栏之前,检查某个指定名称的工具栏是否已经存在于集合CommandBars里是 个好习惯。,倘若不存在具有相同名称的工具栏,下面的过程将创建“预算计划”工具栏。在工程学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 294 共 612 页 CustomTools(Chap12.xls)的代码窗口里面输入这个过程,并运行两次。第二次执行该过程时,您将 看到一条信息,提醒您该工具栏已经存在。 Sub MakeToolBar() Dim bar As CommandBar Dim flagExists As Boolean flagExists = False For Each bar In CommandBars If bar.Name = "Budget Plans" Then flagExists = True MsgBox "使用这个名称的工具栏已经存在." Exit For End If Next bar If Not flagExists Then Set bar = CommandBars.Add("Budget Plans", _ msoBarBottom, False, True) CommandBars("Budget Plans").Visible = True End If Set bar = Nothing End Sub 12.2.2 删除自定义工具栏 如果创建了工具栏但不想保留它,那么可以去掉它而不用关闭Excel应用程序,只要使用Delete 方法就可以了。例如,要删除“预算计划”工具栏,可以在立即窗口里输入下面的语句: CommandBars("Budget Plans").Delete 注意,不能删除内置工具栏。 12.2.3 使用CommandBar的属性 CommandBar对象有许多属性,下面在立即窗口里面使用该对象的一些属性。 1. 使用立即窗口来创建一个名为“我的报告”的工具栏: set myBar= CommandBars.Add("我的报告", msoBarBottom, False) 2. 使用下面的语句来确定某个工具栏是否是内置工具栏: ?CommandBars("我的报告").BuiltIn 3. 输入下面的语句可以确定新工具栏在CommandBars集合里的索引号: ?CommandBars("我的报告").Index 当将Visible属性设置为True时,该工具栏将显示在屏幕上;而设置Visible属性为False时则可以 隐藏该工具栏。 12.2.4 使用CommandBar控件 一个空的工具栏并不能做什么,要让工具栏有用,就需要将想要的控件放置在上面并给它们指 定合适的VBA过程。有三种类型的命令条控件,如下表所示。 第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 295 表12—3:可以放置在工具栏上的控件类型 对象名称 描述 CommandBarButton 该对象代表工具栏按钮和菜单选项。当单击按钮或者选择菜单选项时, 就会执行相应的VBA过程。 CommandBarPopup 该对象代表弹出控件,单击时显示菜单或者子菜单。 CommandBarComboBox 该对象代表文字框、列表框或者复合框(例如,“格式”工具栏上的“字 体”和“字号”控件,或者“常用”工具栏上的“缩放”控件)。 CommandBar对象的重要属性之一是Controls属性,该属性返回某指定工具栏上所有控件的集 合。 1. 在立即窗口里试验下面的语句: ?CommandBars(1).Controls. Count 当按下回车键时,VBA就会返回工作表菜单栏上所有可用控件的总数。 2. 输入下面的语句来返回工作表菜单栏上第一个控件的名称: ?CommandBars(1).Controls(1).Caption VBA返回第一个控件的名称:文件(&F)。字母F前面的字符&表明该菜单选项可以通过在键盘中 按下Alt+F键来执行。 3. 输入下面的语句来执行一个指定的选项: CommandBars(1).Controls(1).Execute Execute方法激活该指定的控件,“文件”菜单应该打开了。 4. 在当前工程的代码窗口里输入下面的过程ControlList,将活动菜单栏上所有控件的名称写入立 即窗口: Sub ControlList() Dim bar As CommandBar Dim ctrl As CommandBarControl Set bar = CommandBars(1) Debug.Print bar.Name & ": " & bar.Controls.Count For Each ctrl In bar.Controls Debug.Print ctrl.Caption Next End Sub 5. 运行上面的过程后,查看立即窗口,将看到下面的列表: Worksheet Menu Bar:10 文件(&F) 编辑(&E) 视图(&V) 插入(&I) 格式(&O) 工具(&T) 数据(&D) 操作(&C) 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 296 共 612 页 窗口(&W) 帮助(&H) 12.2.5 添加控件到CommandBar 要运行预期的VBA过程,那么可以添加一个内置的或者自定义控件到内置工具栏。如果愿意, 也可以添加控件到自定义工具栏。无论添加内置控件还是自定义控件到内置工具栏或者自定义工具 栏,总是要使用Add方法,其语法如下: CommandBar.Controls.Add(Type, Id, Parameter, Before, Temporary) CommandBar是想要添加控件的那个对象。 Type是一个常量,决定要添加的自定义控件的类型,可以选择下面的类型之一: msoControlButton 1 msoControlPopup 10 msoControlEdit 2 msoControlDropDown 3 msoControlComboBox 4 Id是个整数,指定想要添加的内置控件编号。 Parameter用来给VBA过程发送信息,或者存储关于该控件的信息。 Before参数是控件的索引号,新控件将添加在该控件之前,如果忽略,那么VBA将在该命令条 的结尾处添加控件。 Temporart参数是一个逻辑值(True或False),决定控件什么时候被删除。设置该参数为True的话, 将导致在应用软件关闭时自动删除该控件。 1. 在代码窗口里输入过程AddBarAndControls,如下所示: Sub AddBarAndControls( ) With Application.CommandBars.Add("测试", , False, True) .Visible = True .Position = msoBarBottom With .Controls.Add(msoControlButton) .Caption = "控件列表" .FaceId = 4 .OnAction = "ControlList" End With End With End Sub 该过程创建了一个名为“测试”的新工具栏,并将它放在应用程序窗口的底部。接下来,Add 方法在其上放置一个名为“控件列表”的按钮,并用打印机图标以识别。当用户单击该按钮时,之 前已准备好的过程ControlList就会被执行。 12.2.6 理解和使用控件属性 放置在工具栏上的控件有许多属性可供读取或者设置。要知道某个控件是内置的或者自定义, 那么可以使用BuiltIn属性,如果返回值为True,那么该控件就是内置控件;所有用户定义的控件将返第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 297 回值False。如果Enabled属性的值为True,那么该控件就是活动的,并且可以响应鼠标单击。非活 动控件的Enabled属性被设置为False。不用说,所有控件都拥有Caption属性,可以用来发现或者设 置控件的标题。 以 CommandBarComboBox 对象为代表的组合类型控件具有一些特殊的属性,例如 DropDownLines、DropDownWidth、List、ListCount、ListIndex以及Text。这些属性的说明参见表 12—4。 表12—4:CommandBarComboBox对象的一些属性 属性 描述 DropDownLines 返回或者设置当用户单击组合框下拉箭头时显示的项目数量。 DropDownWidth 返回或者设置组合框控件的宽度,以像素为单位。 List(Index) 返回或者设置由Index指定的列表项目的值(列表里第一个项目的索引号为 0)。 ListCount 返回列表里的项目数。 ListIndex 返回或者设置列表里所选项目。 Text 返回或者设置显示在复合框控件的文字框部分的文本。 1. 在代码窗口中输入过程MyCombo,如下所示: Sub MyCombo() Dim cbo As CommandBarControl Set cbo = CommandBars(4).Controls.Add(Type:=4, Before:=1) With cbo .AddItem Text:="Row", Index:=1 .AddItem Text:="Column", Index:=2 .Caption = "Insert Row/Column" .DropDownLines = 2 .DropDownWidth = 80 End With End Sub 过程MyCombo创建了一个复合框控件(Type:=4表明msoControlComboBox)并将其放置在内置 的“ 格 式 ”工 具 栏 (该工具栏是CommandBars集合里的第四个CommandBar对象)的最前面。接下来, 在复合框控件中添加两个项目。该过程也设置了复合框控件的标题和宽度。 2. 切换到Excel窗口检查“格式”工具栏里的第一个控件。 3. 返回到VBE编辑器窗口。 4. 在立即窗口里输入下面的语句从“格式”工具栏里删除由MyCombo过程添加的复合框控件: CommandBars(4).Controls(1).Delete 当按下回车键后,VBA就删除了“格式”工具栏里的第一个控件。 由于有放置在它们上面的图像,因此这些出现在工具栏上的按钮都很好辨认。如果工具栏上的 控件是一个CommandBarButton对象, FaceId属性将返回或者设置按钮上图标的ID编号。大多数情 况下, 图标的ID编号(FaceId)和控件的ID属性是相同的。使用CopyFace方法可以将图标图片复制到 Windows的剪切板上。 接下来的Images过程将出现在“标准”工具栏上的按钮列表写到电子表格上。除了按钮名称,学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 298 共 612 页 同时也显示了它的图标。因为不能复制当前禁用的图标的图像(参见“标准”工具栏上的“撤销”和 “恢复”按钮),所以当VBA试图复制该按钮的图标至剪切板时,就会遇到错误。Images过程利用 On Error GoTo ErrorHandler语句捕获该错误。采用这种方式,当VBA遇到错误时,就会跳到 ErrorHandler:标志处并且执行该标志下面的指令。最后一条语句Resume Next会让VBA回到刚才导 致该错误的指令的下面一条语句,并且该过程会继续直到“标准”工具栏上所有的按钮都被检查了 一遍。您将在下一章学习更多有关错误捕捉的知识。 图12-3:“标准”工具栏上的图标列表,可以修改Images过程来列出任何工具栏上的完整按钮列表和它 们的图标 Sub Images() Dim i As Integer Dim total As Integer Dim buttonId As Integer Dim buttonName As String Dim myControl As CommandBarControl Dim bar As CommandBar On Error GoTo ErrorHandler Workbooks.Add 第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 299 Range("A1").Select With ActiveCell .Value = "图像" .Offset(0, 1) = "索引值" .Offset(0, 2) = "名称" .Offset(0, 3) = "FaceId" End With Set bar = CommandBars(3) total = bar.Controls.Count With bar For i = 1 To total buttonName = .Controls(i).Caption buttonId = .Controls(i).ID Set myControl = CommandBars.FindControl(ID:=buttonId) myControl.CopyFace ' 这里可能发生错误 ActiveCell.Offset(1, 0).Select ActiveSheet.Paste With ActiveCell .Offset(0, 1).Value = buttonId .Offset(0, 2).Value = buttonName .Offset(0, 3).Value = myControl.FaceId End With Next i Columns("C:C").EntireColumn.AutoFit Exit Sub ErrorHandler: Set myControl = CommandBars(3).Controls.Add With myControl .FaceId = buttonId .CopyFace .Delete (False) End With Resume Next End With End Sub 12.2.7 控件方法 控件拥有很多相关的方法,这些方法允许进行一些操作,例如移动、复制和删除控件。假设想 复制“格式”工具栏上的“粗体”按钮到“常用”工具栏中: 1. 在立即窗口里输入下面的三条语句: set myBar = CommandBars(3) set myControl = CommandBars(4).Controls(3) myControl.Copy Bar:=myBar, Before:=1 2. 切换到Excel应用程序窗口,应该能看到“常用”工具栏“新建”按钮的左边有一个“粗体”按 钮。 3. 切换到VBE编辑器屏幕,并在立即窗口里输入下面的语句从“常用”工具栏上删除该“粗体” 按钮: CommandBars(3).Controls(1).Delete 将Copy方法改成Move方法,就可以将“粗体”按钮从“格式”工具栏移动到“常用”工具栏, 可以自己试验一下。使用Reset方法,可以将工具栏恢复为缺省设置。当做完移动和复制按钮的练习学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 300 共 612 页 后,在立即窗口里输入下面的语句: CommandBars(3).Reset CommandBars(4).Reset 如果某个控件是一个复合框(CommandBarComboBox),那么可以使用AddItem方法添加新项目 到下拉列表中。如果要从列表中删除项目,就可以使用RemoveItem方法。我们花上几分钟,在立即 窗口里练习一下这些方法。 1. 激活您先前准备的MyCombo过程,运行该过程在“格式”工具栏上放置一个自定义复合框控件。 2. 在立即窗口里输入下面的语句: set myBar = CommandBars(4) set myControl = CommandBars(4).Controls(1) myControl.RemoveItem(1) myControl.AddItem "Cells", 1 3. 切换到Excel窗口,并且查看“格式”工具栏上该自定义复合框控件里可用的项目。 4. 返回到VBE编辑器窗口,在立即窗口里面输入下面的语句并且按回车键,以重新设置“格式” 工具栏: CommandBars(4).Reset 12.3 使用菜单 就像工具栏一样,菜单也是CommandBar对象。有两类菜单:内置菜单和快捷菜单。内置菜单 出现在应用程序窗口的顶部、标题栏下方。在Excel 2002里,有两种内置菜单:工作表菜单栏和图 表菜单栏。 当工作表处于活动状态时,显示工作表菜单(参见图12-4)并列出几个主要的菜单。每个主菜单 组选项都和某些特定的任务相关联,可以在工作表或者工作簿上执行。例如,“格式”菜单包含的选 项允许将各种各样的格式应用到工作表。有些菜单选项在子菜单里组合了一些更详细的选项(参见图 12-5)。 图12-4:Excel中内置的“工作表菜单栏” 第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 301 图12-5:选择右边带三角形的菜单选项将展开带有更多选项的子菜单 当用户正在处理图表工作表或者选择了内嵌于工作表里的图表时,“工作表菜单栏”就会被“图 表菜单栏”取代(见图12-6)。同一时间应用程序窗口上只能显示一个菜单栏。 图12-6:Excel内置的“图表菜单栏” 当在某个对象上单击右键或者按下Shift+F10键时,就会出现快捷菜单。Excel 2002 有50个以 上的快捷菜单。快捷菜单包含一些经常使用的命令,例如,当在工作表的任何单元格中单击右键时, 单元格快捷菜单就会出现( 参见图12-7)。当在工作表标签上单击右键时,就会显示工作表标签的标 准菜单(参见图12-8)。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 302 共 612 页 图12-7:当右键单击任何单元格时出现的快捷菜单 图12-8:当右键单击工作表标签时出现的快捷菜单 菜单栏和工具栏一样,用同样的对象——CommandBar来表示。使用Control对象引用菜单、菜 单选项、子菜单或快捷菜单,Control对象的类型由适当的常量决定,使用常量msoControlPopup引 用菜单,msoControlButton常量引用菜单选项,而msoBarPopup常量则引用快捷菜单。您将在下一 节里学习如何使用这些常量。 12.3.1 菜单编程 使用VBA,您可以进行一些操作,例如创建一个新菜单栏、添加新菜单到内置菜单栏、激活内 置的或者自定义菜单栏、删除用户定义(自定义)菜单栏、重设内置菜单、检查某个菜单栏是否是内置 的或者自定义的,等等。 1. 在立即窗口里输入下面的语句,返回当前活动菜单栏的名称: ?CommandBars.ActiveMenuBar.Name 当按下回车键时,VBA就会返回活动菜单栏的名称:Worksheet Menu Bar。 菜单栏上的每个菜单都有一个标题,标题可以通过属Caption性和Id属性返回或者设置。 第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 303 2. 在本章的模块里面输入下面的过程,来返回内置工作表菜单栏上“格式”菜单的ID: Sub Return_ID() Dim myControl As Object Set myControl = CommandBars("Worksheet menu bar").Controls("格式(&O)") Debug.Print myControl.Caption & " Id is " & myControl.Id End Sub 如果想让上面的过程更灵活一些,可以如下修改Set语句,让用户可以返回“工作表菜单栏”上 其它菜单的ID: Set myControl = CommandBars("Worksheet menu bar").Controls (InputBox("输入菜单名(例 如: 格式(&O)):")) 3. 运行过程Return_Id,然后切换到立即窗口查看其结果。 4. 在立即窗口里输入下面的语句,可以创建一个名为“其他”的自定义菜单,并将它放置在内置 的工作表菜单栏上: CommandBars("Worksheet menu bar").Controls. Add(Type:=msoControlPopup, before:=10).Caption = "其他(&R)" 当按下回车键并切换回Excel应用程序窗口时,工作表菜单栏将会在“帮助”菜单前显示自 定义菜单。如果没有将上面的语句在一行里输入,它就不会起作用。 现在“其他”菜单是空的,下一步演示如何添加菜单命令。 5. 在立即窗口里的一行中输入下面的语句,给自定义菜单里添加自定义命令(选项): CommandBars("Worksheet menu bar").Controls(" 其他(&R)"). Controls.Add(Type:=msoControlButton, before:=1) _ .Caption = "网格线" 当按下回车键并切换到Excel应用程序菜单,然后选择“其他”,将看到命令“网格线”。如 果没有将上面的语句在一行里输入,它就不会起作用。 下一步将要求给自定义菜单选项指定一个当用户选择该菜单时就会执行的适当的VBA过 程。 6. 在当前工程的代码窗口里输入下面的过程来打开或者关闭网格线的显示: Sub GridOnOff( ) ActiveWindow.DisplayGridlines = Not ActiveWindow.DisplayGridlines End Sub 7. 在立即窗口里的一行中输入下面的代码,将过程GridOnOff指定给自定义菜单选项: CommandBars("Worksheet menu bar").Controls("其他(&R)").Controls ("网格线").OnAction = "GridOnOff" 当按下回车键时,VBA就会将过程GridOnOff指定给“网格线”菜单项。如果没有将上面的 语句在一行里输入,它就不会起作用。 当切换到Excel应用程序窗口并且选择“其他”|“网格线”时,如果网格线显示被关闭时, VBA就会打开网格线显示,反之亦然。 将菜单项的Enabled属性设置为False可以临时禁用该菜单项。一个被禁用的菜单项其名称 将变为灰色,并且单击它时不会有任何反应。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 304 共 612 页 8. 在立即窗口里在同一行中输入下面的语句可以禁用“其他”菜单里的“网格线”命令: CommandBars("Worksheet menu bar").Controls("其他(&R)").Controls ("网格线").Enabled = False 当按下回车键时,VBA就会将Gridlines菜单项禁用。如果没有将上面的语句输入在一行中, 它就不会起作用。当切换到Excel应用程序窗口并且选择“其他”时,“网格线”选项不再可用。 9. 要激活“其他”菜单里的“网格线”命令,可以在立即窗口里将逻辑值False用True取代: CommandBars("Worksheet menu bar").Controls("其他(&R)"). Controls ("网格线").Enabled = True 注意,内置菜单上的每个单独的选项都是和与它相似的命令成组组织在一起,组和组之间 用一条水平线分割(参见图12-9)。使用BeginGroup方法可以在菜单项之间添加这样一条线。 图12-9:每个菜单用横线分成好几个部分 10. 在立即窗口里输入下面的语句,可以在“窗口”菜单的“隐藏”命令上面添加一条横线: CommandBars("Worksheet menu bar").Controls(" 窗口(&W)").Controls (" 隐藏 (&H)").BeginGroup = True 当按下回车键后,VBA就会在“窗口”菜单的“隐藏”项上面添加一条横线。如果没有将 上面的语句输入在一行中,它就不会起作用。当切换到Excel应用程序窗口并且选择“窗口”, 就会看到“隐藏”和“取消隐藏”命令在两条横线之间。上面的那条就是添加的。 当某个菜单项被选中时,该选项左边可能会出现一个复选标记,例如在“视图”菜单(参见 图12-9)里,“编辑栏”和“状态栏”左边的复选标记表明这些选项当前是有效的。 第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 305 11. 要表明“其他”菜单里的自定义“网格线”选项被选中,可以修改过程GridOnOff,如下所示: Sub GridOnOff() Dim Other As Object Set Other = CommandBars("Worksheet menu bar").Controls("Other") ActiveWindow.DisplayGridlines = Not ActiveWindow.DisplayGridlines If ActiveWindow.DisplayGridlines = True Then Other.Controls("Gridlines").State = msoButtonDown Else Other.Controls("Gridlines").State = msoButtonUp End If End Sub 运行该过程,然后切换到Excel窗口,并且选择“其他”|“网格线”。如果活动工作表显示 网格线,那么它们现在就会被关闭。再次选择“其他”|“网格线”。 12. 在立即窗口里输入下面的语句,以删除内置工作表菜单栏里的自定义菜单: CommandBars("Worksheet menu bar").Controls("Other").Delete 当删除某个自定义菜单时,放置在该菜单里面所有的菜单项都会自动被删除。自定义菜单 以及其选项一旦被删除,就不能恢复它们。 12.3.2 创建子菜单 在名称右边包含一个黑色三角的菜单项都会显示一个包含额外命令的子菜单。假设想在“工具” 菜单里添加一个子菜单。 1. 在立即窗口里的一行中输入下面的语句,给“工具”菜单添加一个子菜单: CommandBars("Worksheet menu bar").Controls("工具(&T)").Controls.Add(Type:=msoContr olPopup, Before:=1) _ .Caption = "我的菜单" 当按下回车键后,上面的指令会在“工具”菜单(工作表菜单栏)的顶部放置一个名为“我的 子菜单”的自定义子菜单。如果没有将上面的语句输入在一行中,它就不会起作用。 2. 在立即窗口的一行里输入下面的指令,可以在子菜单里添加自定义命令: CommandBars("Worksheet menu bar").Controls("工具(&T)").Controls("我的子菜单").Controls _ .Add(Type:=msoControlButton, Before:=1).Caption = "选项1" 当按下回车键后,上面的指令会在“工具”菜单里的“我的子菜单”中放置“选项1”命令。 如果没有将上面的语句输入在一行中,它就不会起作用。您可以使用相同的技术在子菜单里添 加更多的菜单项。 下面的Colors过程将在内置的“格式”菜单里添加自定义子菜单“颜色”,并且在里面放置四个 选项:红、绿、蓝和黑。使用这些选项,可以更改所选工作表单元格或者单元格区域里的文本颜色。 接下来的过程将应用适当的颜色格式。 Sub Colors() Dim myMenu As Object Dim mySubMenu As Object Set myMenu = CommandBars("Worksheet menu bar").Controls("Format") With myMenu 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 306 共 612 页 .Controls.Add(Type:=msoControlPopup, Before:=2).Caption = "Colors" End With Set mySubMenu = myMenu.Controls("Colors") With mySubMenu .Controls.Add(Type:=msoControlButton).Caption = "Red" .Controls.Add(Type:=msoControlButton).Caption = "Green" .Controls.Add(Type:=msoControlButton).Caption = "Blue" .Controls.Add(Type:=msoControlButton).Caption = "Black" .Controls("Red").OnAction = "ColorRed" .Controls("Green").OnAction = "ColorGreen" .Controls("Blue").OnAction = "ColorBlue" .Controls("Black").OnAction = "ColorBlack" End With End Sub Sub ColorRed() ActiveCell.Font.Color = RGB(255, 0, 0) End Sub Sub ColorGreen() ActiveCell.Font.Color = RGB(0, 255, 0) End Sub Sub ColorBlue() ActiveCell.Font.Color = RGB(0, 0, 255) End Sub Sub ColorBlack() ActiveCell.Font.Color = RGB(0, 0, 0) End Sub 12.3.3 修改内置快捷菜单 Excel提供了60个快捷菜单,带有不同的经常使用的菜单项。当在Excel应用程序窗口的某个对 象上单击右键时,就会显示快捷菜单。使用VBA,可以返回快捷菜单的准确编号,还有它们的名称。 1. 在当前工程的模块里输入过程ShortcutMenus,如下所示: Sub ShortcutMenus() Dim myBar As CommandBar Dim counter As Integer For Each myBar In CommandBars If myBar.Type = msoBarTypePopup Then counter = counter + 1 Debug.Print counter & ": " & myBar.Name End If Next End Sub 注意,使用常量msoBarTypePopup来确定CommandBars集合里的快捷菜单,使用常量 msoBarTypeMenuBar,可以返回内置菜单的名称,msoBarTypeNormal返回工具栏的名称。当运行 过程ShortcutMenus后,所有快捷菜单的名称就会打印在立即窗口里,如下所列出的内容。 Excel 2002 的内置快捷菜单: Excel 2003 的内置快捷菜单(译者添加): 1: Query and Pivot 1:PivotChart Menu 2: PivotChart Menu 2:Workbook tabs 3: Workbook tabs 3:Cell 4: Cell 4:Column 5: Column 5:Row 第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 307 6: Row 6:Cell 7: Cell 7:Column 8: Column 8:Row 9: Row 9:Ply 10: Ply 10:XLM Cell 11: XLM Cell 11:Document 12: Document 12:Desktop 13: Desktop 13:Nondefault Drag and Drop p 14: Nondefault Drag and Drop 14:AutoFill 15: AutoFill 15:Button 16: Button 16:Dialog 17: Dialog 17:Series 18: Series 18:Plot Area 19: Plot Area 19:Floor and Walls 20: Floor and Walls 20:Trendline 21: Trendline 21:Chart 22: Chart 22:Format Data Series 23: Format Data Series 23:Format Axis 24: Format Axis 24:Format Legend Entry 25: Format Legend Entry 25:Formula Bar 26: Formula Bar 26:PivotTable Context Menu 27: PivotTable Context Menu 27:Query 28: Query 28:Query Layout 29: Query Layout 29:AutoCalculate 30: AutoCalculate 30:Object/Plot 31: Object/Plot 31:Title Bar (Charting) 32: Title Bar (Charting) 32:Layout 33: Layout 33:Pivot Chart Popup 34: Pivot Chart Popup 34:Phonetic Information 35: Phonetic Information 35:Auto Sum 36: Auto Sum 36:Paste Special Dropdown 37: Paste Special Dropdown 37:Find Format 38: Find Format 38:Replace Format 39: Replace Format 39:List Range Popup 40: Shapes 40:List Range Layout Popup 41: Inactive Chart 41:XML Range Popup 42: Excel Control 42:List Range Layout Popup 43: Curve 43:Shapes 44: Curve Node 44:Inactive Chart 45: Curve Segment 45:Excel Control 46: Pictures Context Menu 46:Curve 47: OLE Object 47:Curve Node 48: ActiveX Control 48:Curve Segment 49: WordArt Context Menu 49:Pictures Context Menu 50: Rotate Mode 50:OLE Object 51: Connector 51:ActiveX Control 52: Script Anchor Popup 52:WordArt Context Menu 53: Canvas Popup 53:Rotate Mode 54: Organization Chart Popup 54:Connector 55: Diagram 55:Script Anchor Popup 56: Add Command 56:Canvas Popup 57: Built-in Menus 57:Organization Chart Popup 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 308 共 612 页 58: System 58:Diagram 59: Layout 59:Select 60: Select 60:Layout 61:Add Command 62: Built-in Menus 现在,您已经知道了Excel快捷菜单的准确名称,可以容易地添加其它经常用到的命令到这些菜 单中的任意菜单中。尽管从工具栏上单击打印图标或者选择“文件”|“打印”来打印工作表都是很 容易的,但是可能还是想将“打印”命令添加到在工作表标签上单击右键时所出现的快捷菜单中。 我们来看看如何添加该选项到Ply菜单上去。 2. 输入如下所示的过程AddToPlyMenu: Sub AddToPlyMenu() With Application.CommandBars("Ply") .Reset .Controls.Add(Type:=msoControlButton, Before:=2).Caption = _ "打印…..." .Controls("打印...").OnAction = "PrintSheet" End With End Sub 上面的过程所使用的Reset方法避免当多次运行该过程时,将同样的选项放置到该快捷菜单上。 3. 运行过程AddToPlyMenu,然后返回到代码窗口,并且输入下面的过程,当从该快捷菜单上选择 “打印”选项时,就会执行该过程: Sub PrintSheet() Application.Dialogs(xlDialogPrint).Show End Sub 4. 切换到Excel应用程序窗口,并且在任何工作表标签上单击右键,选择“打印”选项,应该可以 看到当使用其它内置工具打印时出现的相同的对话框。 图12-10:自定义选项可以添加到内置快捷菜单上(参见“打印”选项通过过程AddToPlyMenu里被添加) 12.3.4 创建快捷菜单 1. 在当前VBA工程的代码窗口里输入过程Create_ShortMenu,如下所示: Sub Create_ShortMenu() Dim sm As Object Set sm = Application.CommandBars.Add("信息", msoBarPopup) 第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 309 With sm .Controls.Add(Type:=msoControlButton).Caption = "操作系统" With .Controls("操作系统") .FaceId = 1954 .OnAction = "OpSystem" End With .Controls.Add(Type:=msoControlButton).Caption = "总内存" With .Controls("总内存") .FaceId = 1977 .OnAction = "TotalMemory" End With .Controls.Add(Type:=msoControlButton).Caption = "已用内存" With .Controls("已用内存") .FaceId = 2081 .OnAction = "UsedMemory" End With .Controls.Add(Type:=msoControlButton).Caption = "自由内存" With .Controls("自由内存") .FaceId = 2153 .OnAction = "FreeMemory" End With End With End Sub 上面的过程创建了一个名为“信息”的自定义快捷菜单,并给它添加了四个命令。注意, 每个命令都指定了一个图标。当从该快捷菜单选择一个命令时,就会执行下面的步骤2中相应 过程。 2. 输入下面的可以被Create_ShortMenu过程调用的过程: Sub FreeMemory( ) MsgBox Application.MemoryFree & "字节", , "自由内存" End Sub Sub OpSystem( ) MsgBox Application.OperatingSystem, , "操作系统" End Sub Sub TotalMemory( ) MsgBox Application.MemoryTotal, , "总内存" End Sub Sub UsedMemory( ) MsgBox Application.MemoryUsed, , "已用内存" End Sub 要将名为“信息”的自定义快捷菜单显示在屏幕上,可以使用ShowPopup方法,如步骤3所示。 3. 在立即窗口里输入下面的语句: CommandBars("信息").ShowPopup 0, 0 CommandBar对象的ShowPopup方法接受两个可选参数(x, y),决定快捷菜单在屏幕上的 位置。在上面的例子里,“信息”快捷菜单将出现在屏幕的左上角。 假设您正在设计一个自定义窗体,并想当用户右键单击一个命令按钮时显示一个快捷菜单: 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 310 共 612 页 1. 从VBE编辑器菜单上,选择“插入”|“用户窗体”。 2. 使用工具箱上的命令按钮控件,在空白用户窗体的任意位置放置一个按钮。 3. 通过单击工程浏览窗口中的“查看代码”按钮,切换到该窗体的代码窗口。 4. 在UserForm1(代码)窗口里输入下面的过程: Private Sub CommandButton1_MouseDown(ByVal Button _ As Integer, _ ByVal Shift As Integer, _ ByVal X As Single, _ ByVal Y As Single) If Button = 2 Then Call Show_ShortMenu Else MsgBox "您必须右击该按钮." End If End Sub 当用户右键单击放置在窗体上的这个命令按钮时,该过程就会调用过程Show_ShortMenu。单 击鼠标按钮时VBA执行两个事件过程来响应。当单击鼠标按钮时,VBA会执行MouseDown事件过程, 当释放鼠标按钮时,MouseUp事件则会发生。 MouseDown和MouseUp事件过程需要下面的参数: • 参数object确定对象。在本例中,它是放置在窗体上的命令按钮的名称。 • 参数Button是整数值,确定按下的是哪个鼠标按键。 Button参数值 意义 1 鼠标左键 2 鼠标右键 3 鼠标中键 • 参数Shift确定当事件发生时,用户是否按住了Shift、Ctrl或者Alt键。 Shift参数值 意义 1 Shift键 2 Ctrl键 3 Shift和Ctrl键 4 Alt键 5 Alt和Shift键 6 Alt和Ctrl键 7 Alt、Shift和Ctrl键 5. 在当前工程模块里输入过程Show_ShortMenu的代码: Sub Show_ShortMenu() Dim shortMenu As Object Set shortMenu = Application.CommandBars("信息") With shortMenu .ShowPopup End With End Sub 注意,本过程使用的ShowPopup方法没有使用决定快捷菜单在屏幕上显示位置的可选参第 12 章 11B 使用 VBA 创建自定义菜单和工具栏 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 311 数,因此,菜单将出现在鼠标单击的位置(见图12-11)。 6. 要删除名为“信息”的快捷菜单,在代码窗口输入并运行下面的过程Delete_ShortMenu: Sub Delete_ShortMenu() Application.CommandBars("信息").Delete End Sub 图12-11:当右键单击一个对象时出现一个自定义快捷菜单 本章小结和下一章内容简介 在本章,您学习了如何使用VBA修改内置菜单和工具栏,如何创建和显示自己的工具栏、菜单 和快捷菜单。当使用菜单和工具栏时,使用了CommandBar对象的各种属性和方法。您学习了三种 类型的CommandBar对象:Normal、MenuBar和Popup。使用立即窗口,您试验单个的语句来演示 如何创建自己的工具栏和控件。 下一章将介绍错误捕捉和调试。换句话说,您将学习当程序不正确工作时,该做些什么。 第13章 调试VBA过程和处理错误 错误很容易就会悄悄混入 VBA 过程。事实上,无论您多么仔 细,所有的 VBA 过程第一次就能全部正确地运行是极其少见的, 总有一些错过的或者没有考虑到的…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 314 共 1 6 2 页 错误很容易就会悄悄混入VBA过程。事实上,无论您多么仔细,所有的VBA过程第一次就能全 部正确地运行是极其少见的,总有一些错过的或者没有考虑到的。在第二章中,您就已经知道VBA 中有三种类型的错误:语法错误、逻辑错误和运行时错误。本章将给介绍许多内置工具,您会发现 它们在分析过程的代码和定位错误源的过程中是有用的。 13.1 测试VBA过程 迄今为止,在本书中您已经创建和执行了很多示例过程和函数。因为这些过程序大多数都相当 短,所以找错误并不是非常困难。然而,当编写更长且更复杂的过程时,查找错误源就更乏味和费 时了。幸运的是,VBE编辑器提供了一套方便的工具,使得追踪VBA问题的过程更简单、更快捷且 更顺利。Bug是电脑程序中的错误,而调试则是定位和解决这些错误的过程。调试可以找到过程为什 么不工作的原因并找到使过程工作的方法。您可以通过逐语句执行过程代码或者检查变量值来达到 目的。 按照下面的原则来测试VBA程序: • 如果想要分析过程,通过按F8键或者选择“调试”|“逐语句”,一句一句地执行代码。 • 如果怀疑过程的某个地方可能会发生错误,那么可以使用断点。 • 如果想监视过程中某个特定变量或者表达式的值,那么可以添加一个监视表达式。 • 如果讨厌在冗长的过程代码中拉动滚动条到感兴趣的部分,那么可以设置一个书签来快速跳到 需要的地方。 每条原则在本章中都有实用的例子进行演示。 13.2 终止过程 您知道如何终止一个VBA过程吗?如果想到了按Esc键,那么您对了。如果在运行程序时突然按 下Esc键,那么VBA就会中断程序的运行,并显示如图13-1显示的信息。然而除了按Esc键这个在 很多情况下都很有力且可靠的方法,VBA还提供了很多其它的方法来中断过程,进入所谓的中断模 式: • 按Ctrl+Break • 设置一个或多个断点 • 插入Stop语句 • 添加监视表达式 当VBA过程的执行被临时挂起时便发生中断。VBA会从过程的执行中记住所有变量和语句的值, 当用户从工具栏单击“运行子过程/用户窗体”(或者“运行”菜单上的相同名称),或者单击对话框(图 13-1)上面的“继续”按钮,可以恢复运行。 第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 315 图13-1:在运行VBA过程时,如果按下Esc键或者Ctrl+Break键,就会出现这个信息 图13-1显示的错误对话框通知该过程已被中断,下面的按钮是可用的: 按钮 内容 继续 单击该按钮可以恢复代码执行。如果遇到错误该按钮将变灰。 结束 如果这次不想排除故障则单击该按钮,VBA将终止代码执行。 调试 单击该按钮进入中断模式,代码窗口将出现,并且VBA会加亮过程执行时停止处的 代码行。您可以检查、调试、中断或者逐句执行代码。 帮助 单击该按钮查看在线帮助,解释导致该错误信息的原因。 技巧13—1:防止用户干预 通过将下面的语句加入到过程代码中去,可以防止用户中断程序: Application.EnableCancelKey = xlDisabled 在程序运行时,当用户按下Esc键或者Ctrl+Break时,不会发生任何情况。应用程序对象的EnableCancelKey 属性禁用了这些键。 13.2.1 使用断点 如果或多或少可以预料到程序代码的问题,那么应该在该位置(给定的行)暂停代码执行。要设置 断点,简单点就是当光标位于目标代码行时按下F9键。当过程运行中VBA到达此处时,立即会显示 代码窗口。这时,可以通过按F8或者选择“调试”|“逐语句”来一行一行地运行代码。 我们来看看下面一个方案,了解它是如何工作的。假设在过程ChangeCode的执行中下面的代 码行会出问题: ActiveCell.Formula = "=VLookup(RC[1],Codes.xls!R1C1:R6C2,2)" 1. 准备好如图13-2和13—3所示的电子表格。保存图13-2所示的数据为Chap13.xls,图13-3 所示的数据为Codes.xls。关闭Codes.xls文件。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 316 共 612 页 图13-2:本文件中输入在D列的编号将会在过程ChangeCode中被显示于图13-3中的编号所代替 图13-3:过程ChangeCode使用该编号表进行查找 2. 激活文件Chap13.xls,切换到VBE编辑器窗口。 3. 使用属性窗口重新命名VBAProject(Chap13.xls)为Debugging。 4. 插入一个模块到Debugging (Chap13.xls)工程中并将其Name属性改为Breaks。 5. 输入过程ChangeCode的代码,如下所示: Sub ChangeCode() Workbooks.Open FileName:="C:\Codes.xls" ‘将此处文件路径改为你的真实路径 Windows("Chap13.xls").Activate Columns("D:D").Select Selection.Insert Shift:=xlToRight 第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 317 Range("D1").Select ActiveCell.Formula = "Code" Columns("D:D").Select Selection.SpecialCells(xlBlanks).Select ActiveCell.Formula = "=VLookup(RC[1],Codes.xls!R1C1:R6C2,2)" Selection.FillDown With Columns("D:D") .EntireColumn.AutoFit .Select End With Selection.Copy Selection.PasteSpecial Paste:=xlValues Rows("1:1").Select With Selection .HorizontalAlignment = xlCenter .VerticalAlignment = xlBottom .Orientation = xlHorizontal End With Workbooks("Codes.xls").Close End Sub 6. 在过程ChangeCode里,单击下面语句行的任意地方: ActiveCell.Formula = "=VLookup(RC[1],Codes.xls!R1C1:R6C2,2)" 7. 按下F9键(或者选择“调试”|“切换断点”)在光标所处行设置一个断点。设置断点的另外一种 方法是单击想要暂停程序的代码左侧的页边。设置断点后,VBA在页边的空白处显示一个红色 的圈,同时,带断点的代码行显示为红底白字(见图13-4)。断点的颜色可以通过“选项”对话 框(“工具”菜单)的“编辑器格式”选项卡页上更改。 图13-4:设置了断点的代码行显示了在“选项”对话框里“编辑器格式”选项卡中设定的颜色 8. 运行过程ChangeCode。当运行该过程时,VBA将执行所有的语句,直到它遇到该断点。一旦 遇到断点,代码便暂停,并且显示代码窗口(见图13-5)。VBA在导致过程暂停的语句左边的页 边上显示一个黄色箭头。同时,该语句出现在一个黄色底色的框里面。错误和框表明当前语句 或者将要被执行的语句。如果当前语句也包含一个断点,那么页边将重叠显示它们(圆圈和箭头)。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 318 共 612 页 图13-5:当VBA遇到断点时,就会显示代码窗口并且指示当前语句 9. 按F8键,或者选择“调试”|“逐语句”。 10. 重复几次步骤9。 11. 按F5键(或者选择“运行子过程/用户窗体)来继续运行该过程,而不必逐语句运行。 当结束该过程的运行后,VBA不会自动删除断点。注意带有VLookup函数的代码行还是加亮的。 在本例中,您只设置了一个断点。VBA允许在一个过程里设置任意多个断点。这样,就可以随 心所欲地暂停和继续过程的执行。在过程暂停时,您可以分析程序代码和检查变量的值,也可以通 过在立即窗口里输入语句进行各种各样的测试。 12. 通过选择“调试”|“清除所有断点”,或者按下Ctrl+Shift+F9,可以清除断点。 所有断点都被清除了。如果在某个过程里面设置了多个断点,并且想要只清除其中的一个或几 个,那么可以单击想要清除断点的代码行并且按下F9键(或者选择“调试”|“切换断点”)。当断点 不再需要时,应该清除它们。当关闭文件时,所有断点将自动被清除。 技巧13—2:什么时候使用断点 如果怀疑过程根本就没有执行过某段代码块,那么考虑设置断点。 13.2.2 在中断模式下使用立即窗口 一旦程序执行被挂起,当代码窗口出现时,可以激活立即窗口并且输入VBA指令,例如,查明 哪一个单元格是当前单元格或者活动工作表的名称。也可以使用立即窗口来更改变量的内容,以便 改正可能导致错误的值。到现在,您应该已经是个使用立即窗口的专家了。图13-6显示了暂停的过 程ChangeCode和在中断模式下,向VBA询问的立即窗口。 在中断模式下,可以很快地查找代码窗口里光标所在处的变量的内容。只要在运行的过程中简 单地将光标移动到变量上,就可以知道该变量的内容了。例如图13-7里所示的VarValue过程,断点第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 319 设置在第二次出现的Workbooks.Add语句上。 图13-6:当代码执行被暂停时,通过在立即窗口里输入适当的语句,就可以找到很多问题的答案 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 320 共 612 页 图13-7:在中断模式下,可以将鼠标指向某个变量,找到该变量的值 当VBA遇到该语句时,代码窗口(中断模式)就会出现。因为VBA已经执行了那条语句,将当前活 动工作簿名称存储存到变量myName中,所以将鼠标停留在该变量名称上时,就可以快速找到该变 量的值。变量的名称及其当前值出现在一个框里面。要同时显示过程里使用的多个变量的值,就应 该使用当地窗口,本章的后面将会介绍。 13.2.3 使用Stop语句 有时候不可能马上测试程序,如果设置了断点,然后关闭该文件,那么Excel就会清除断点,而 下次准备测试程序时,将不得不再次设置断点。如果需要推迟测试工作,那么可以使用不同的方式。 简单地在需要暂停程序的地方插入一个Stop语句。图13-8显示了For…Next循环之前的Stop语句。 当VBA遇到Stop语句时,它就会暂停过程StopExample的执行,屏幕会显示中断模式下的代码窗口。 尽管Stop语句与设置断点具有完全一样的效果,但是,它有一个缺点——所有Stop语句都会保留在 程序里直到清除它们。当不再需要暂停程序时,必须查找并清除所有的Stop语句。 第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 321 图13-8:你可以在VBA过程代码的任何地方插入Stop语句当程序到达Stop语句时就会暂停,并出现加亮 显示该行的代码窗口 技巧13—3:在中断模式下使用代码窗口 在中断模式下,可以改变代码、添加新语句、每次执行一行语句、跳过代码行、设置下一条语句、使用立 即窗口、以及更多。当VBA处于中断模式时,“调试”菜单上所有的选项都是可用的。可以通过按下Esc键、 Ctrl+Break、或者通过设置断点进入中断模式。 当在中断模式下工作时,如果更改某些代码,VBA将会显示如下错误信息:“该操作将重新设置工程,继续 吗?” 提示重新设置工程,可以单击“确定”,终止程序的执行并继续编辑代码,或者单击“取消”,删除新的 变化并从中断点继续运行代码。要查看该错误,可以将程序进入中断模式,然后更改变量的声明,当按下F5键 恢复代码执行时,VBA就会提示重新设置工程。 13.2.4 添加监视表达式 程序中的许多错误是由变量获得未预期的值导致的。如果某个过程使用了一个在不同的地方值 会改变的变量,可能想要停止程序查看该变量的当前值。VBA提供了一个特别的监视窗口,允许在 过程运行时密切注视变量或者表达式。 进行下面的操作,给过程添加监视表达式: 1. 在代码窗口,选择想要监视的变量。 2. 选择“调试”|“添加监视”。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 322 共 612 页 屏幕上会显示“添加监视”对话框,如图13-9所示。 “添加监视”对话框包含三部分,描述如下: 表达式 显示过程中加亮变量的名称。如果打开“添加监视”对话框时没有选择变量名称, 那么在“表达式”文字框中需要输想要监视的变量名称。 上下文 在该节,应该指明包含该变量的过程名称和该过程所在的模块名称。 监视类型 指定如何监视该变量。如果选择“监视表达式”选项按钮,那么在中断模式下能够 在监视窗口里查看该变量的值。当选择“当监视值为真时中断”选项按钮,那么当 该变量值为真(非零)时VBA将自动停止过程。最后一个选项按钮“当监视值改变时 中断”,每当该变量或表达式的值改变时,过程就会停止 图13-9:“添加监视”对话框允许定义在VBA过程运行时监视的条件 可以在运行过程之前或者在过程执行中断之后添加监视表达式。 断点和监视表达式之间的区别是断点总是将过程停止在某个特定的位置,而监视表达式则是当 指定的情况(监视值为真中断或者监视值改变时中断)满足时停止过程。当不确定变量在哪儿改变时, 监视是极其有用的。可以简单地添加一个监视断点在某个变量上并正常运行过程,而不必在大量的 代码里逐语句运行来找到变量在哪里获取指定的值。我们来看看这是如何实现的。 1. 准备如图13-10所示的过程。 第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 323 图13-10:使用监视窗口 过程WhatDate使用For…Next循环来计算将来x天后的日期。如果运行该过程,不会得到任何结 果,除非在过程代码里插入下面的指令: MsgBox “在“ & x & “天后,将是“ & NewDate 然而,这次不想一天一天地显示每一个日期。假如想要当变量x等于211时停止该程序,换句话 说,想要知道从现在起211天后的日期。要得到答案,可以在过程中插入下面的语句: If x = 211 Then MsgBox "在" & x & "天后将是" & NewDate 假设想不输入任何新语句就得到结果,如何做呢?如果给过程添加了监视表达式,当指定的条 件满足时,VBA就会停止For…Next循环,然后就可以查看想要的变量值。 1. 选择“调试”|“添加监视”。 2. 在“表达式”文字框里输入下面的表达式:x=211。 3. 在“上下文”部分,从“过程”下拉列表里选择“WhatDate”,从“模块”下拉列表里选择“Breaks”。 4. 在“监视类型”部分,选择“当监视值为真时中断”选项按钮。 5. 点击“确定”,关闭“添加监视”对话框。现在您已经添加了第一个监视表达式。 6. 在代码窗口,将光标放在变量curDate内部的任意地方。 7. 选择“调试”|“添加监视”,并单击“确定”,设置缺省的监视类型。 8. 在代码窗口,将光标放在变量newDate内部的任意地方。 9. 选择“调试”|“添加监视”,并单击确定,设置缺省的监视类型。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 324 共 612 页 执行完上面的步骤后,过程WhatDate包含了下面的三个监视: x =211 当监视值为真时中断 curDate 监视表达式 newDate 监视表达式 10. 将光标放在过程WhatDate代码的任意地方,并且按下F5键,在x=211时VBA停止过程(见图13 -10) 注意,变量x在“监视窗口”的值和在“添加监视”对话框里指定的值一样。另外,监视窗口显 示了变量curDate和newDate的值。该过程处于中断模式,可以按F5键继续或者可以问另一个问题: 277天后是哪一天?下一步将示范如何做。 11. 选择“调试”|“编辑监视”,然后输入下面的表达式:x=227。可以通过双击监视窗口里的表达 式,快速显示“编辑监视”对话框 12. 单击“确定”关闭“编辑监视”对话框。注意,现在监视窗口显示表达式的新值。x现在的值为 False。 13. 按下F5键,当x值为227时过程再次停止。curDate的值相同,但是变量newDate现在有了一个 新的值——现在之后277天的日期。可以再次改变表达式的值,或者结束该过程。 14. 按下F5键结束该过程。 当过程正在运行并且监视表达式有一个值时,监视窗口就会显示该监视表达式的值。如果在过 程运行结束后打开监视窗口,那么将看到<溢出上下文>,而不是变量的值。换句话说,当监视表达 式溢出上下文时,它没有值。 13.2.5 清除监视表达式 1.在监视窗口里,单击想要清除的表达式并且按下Delete键。清除先前练习中定义的所有监视表 达式。 13.2.6 使用快速监视 如果想查看一个表达式的值,但是还没有定义监视表达式,那么可以使用快速监视(见图13-11)。 图13-11:“快速监视”对话框显示VBA过程里所选表达式的值 可以通过下面的方法访问“快速监视”对话框: - 在中断模式下,将光标放在要监视的变量名称或者表达式内部。 第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 325 - 选择“调试”|“快速监视”,或者按下Shift+F9。 “快速监视”对话框上面有“添加”按钮,允许添加表达式到“监视窗口”。 确保过程WhatDate里没有包含任何监视表达式,参见前一节有关如何从监视窗口清除监视表达 式的内容。现在我们通过例子来看看如何利用“快速监视”。 1. 在过程WhatDate里,将插入点(光标)放在变量x处。 2. 选择“调试”|“添加监视”。 3. 输入下面的表达式:x = 50。 4. 选择“当监视值为真时中断”选项按钮,并单击“确定”。 5. 运行过程WhatDate。 当x等于50时VBA将中断过程的执行,注意,监视窗口里没有变量newDate和curDate。想要查 看这些变量的值,那么可以将光标放在代码窗口里相应变量名称上,或者也可以调用快速监视窗口。 6. 在代码窗口里,将鼠标光标放在变量newDate内部并按下Shift+F9。快速监视窗口就会显示该表 达式名称和其当前值。 7. 单击“取消”返回到代码窗口。 8. 在代码窗口中,将鼠标光标放在变量curDate内部并按下Shift+F9。现在,快速监视窗口就会显 示变量curDate的值。 9. 单击“取消”返回到代码窗口。 10. 按下F5键继续运行该过程。 13.2.7 使用本地窗口和调用堆栈对话框 如果VBA过程执行期间想观察所有声明的变量和它们的当前值,那么确保在运行该过程前选择 “视图”|“本地窗口”。在中断模式下时,VBA就会在本地窗口里显示一系列的变量和它们相应的数 值(参见图13-12)。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 326 共 612 页 图13-12:本地窗口显示当前VBA过程里所有声明的变量和它们的当前值 本地窗口包含三列,表达式列显示在当前过程里声明的变量名称。第一行显示前面带加号的模 块名称,当单击该加号时,就可以查看是否有在模块级声明的变量。类模块将显示系统变量Me。在 本地窗口,全局变量和被其它工程使用的变量不会显示。 第二列显示变量的当前值,在这列中可以通过单击并输入新值来更改变量的值,更改了数值后, 按下回车键来记录该变化。也可以在更改变量值后,按Tab键、Shift+Tab键或者向上或向下箭头,或 者也可以单击本地窗口的其它任意地方。第三列显示每个声明了的变量的类型。 想要在本地窗口里观察变量的值,按下面的操作: 1. 选择“视图”|“本地窗口”。 2. 单击过程WhatDate里的任意地方,并按F8键,将过程置于中断模式。本地窗口显示了当前模块 和本地变量的名称以及它们的初始值。 3. 按几下F8键,观察本地窗口。 4. 按F5键继续运行该过程。 本地窗口也包含一个带有三个点的按钮,该按钮将打开“调用堆栈”对话框(参见图13-13), 显示所有调用的活动过程清单。调用的活动过程是指已经开始但是还没有完成的过程。也可以通过 选择“视图”|“调用堆栈”来激活“调用堆栈”对话框,该选项只在中断模式下可用的。 第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 327 图13-13:“调用堆栈”对话框显示了开始但未完成的过程列表 “调用堆栈”对话框在追踪嵌套的程序时特别有用。回想一下,被嵌套的过程是指一个被另一 个过程所调用的过程。如果一个过程调用另一个过程,该被调用的过程名称就会自动添加到“调用 堆栈”对话框里的调用列表中。当VBA执行完该被调过程后,该过程名称就会从“调用堆栈”对话 框里自动清除。可以使用“调用堆栈”对话框上的“显示”按钮,显示调用下一个过程的语句。 13.3 逐句运行VBA过程 逐句运行代码意思是每次只运行一条语句,这样可以检查每个过程里的每一条语句。想要从头 开始逐句运行过程,可以将插入点置于过程代码的任意地方,并且选择“调试”|“逐语句”,或者按 下F8键。“调试”菜单包含好几个选项供在逐步模式下执行过程(参见图13-14)。 图13-14:“调试”菜单提供了许多逐语句执行VBA过程的命令 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 328 共 612 页 当每次运行过程中的一条语句时,VBA将会执行每条语句,直到它碰到关键字End Sub。如果不 希望VBA逐句运行,那么随时可以按下F5键来运行过程中剩余的代码,而不必逐步运行。 13.3.1 逐句运行过程 1. 将插入点置于想要追踪的过程代码中的任意地方。 2. 按下F8键或者选择“调试”|“逐语句”。VBA就会执行当前语句,并且自动跳到下一句并中断 执行。在中断模式下,可以激活立即窗口、监视窗口或者本地窗口,查看某特定语句中变量和 表达式的值。并且,如果正在逐语句执行的过程调用了其它过程,那么也可以激活“调用堆栈” 窗口来查看当前哪些过程是活动的。 3. 再次按下F8键,执行被选中的语句。执行完该语句后,VBA会选中下一条语句,并且该过程将 再次中断。 4. 按下F8键继续逐语句执行该过程,或者按F5键一下就执行完剩余的代码。也可以选择“运行” |“重新设置”来终止该过程的执行,而不执行剩下的语句。 当逐过程运行(Shift+F8)时,VBA将一次执行一个过程,好像里面只有一条语句一样。如果某过 程调用了其它过程并且不想逐语句执行这些已经测试和调试过的过程,或者只想侧重于尚未被调试 的新代码,那么该选项特别有用。 13.3.2 逐过程执行 假设过程MyProcedure的当前语句调用过程SpecialMsg。如果选择“调试”|“逐过程”(Shift+F8), 而“非调试”|“逐语句”(F8),那么VBA就会快速地执行过程SpecialMsg里面的所有语句,并且选 择主调过程MyProcedure里的下一条语句。在执行SpecialMsg过程期间,VBA将继续显示带有当前 过程的代码窗口。 1. 在当前模块里输入下面的过程: Sub MyProcedure() Dim myName As String Workbooks.Add myName = ActiveWorkbook.Name ‘ 选择逐过程来避免逐语句运行被调过程SpecialMsg SpecialMsg myName Workbooks(myName).Close End Sub Sub SpecialMsg(n As String) If n = "Book2" Then MsgBox "您必须更改名称." End If End Sub 2. 在下面的语句处添加一个断点: SpecialMsg myName 3. 将插入点置于过程MyProcedure代码中的任意处,并按下F5键运行它。VBA到达断点时将中止 执行。 4. 按下Shift+F8,或者选择“调试”|“逐过程”。VBA将会快速的运行过程SpecialMsg并且跳到紧 挨着调用过程SpecialMsg的语句下面的那条语句。 第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 329 5. 按下F5键无间断地完成过程的运行。 当不想分析被调过程中单个的语句时,逐过程执行是非常有用的。 “调试”菜单上的另外一个命令“跳出(Ctrl+Shift+F8)”用于当步入了某个过程,然后决定不继 续逐步执行它时。当选择该选项时,VBA就会一步执行完该过程里剩余的语句,然后继续去激活主 调过程中的下一条语句。 在逐步运行过程期间,可以在逐语句、逐过程和跳出选项之间切换,选择哪个取决于此时想要 分析的代码段。 “调试”菜单中“运行到光标处”(Ctrl+F8)命令让过程运行,直到碰到所选中的行。如果想要 在执行一个大循环之前停止,或者想要跳过一个被调过程时,该命令非常有用。 假设想要执行过程MyProcedure到调用过程SpecialMsg的那一行。 1. 单击语句SpecialMsg myName的内部。 2. 选择“调试”|“运行到光标处”。当到达指定行时,VBA将停止执行。 3. 按下Shift+F8以逐过程运行过程SpecialMsg。 4. 按下F5键无间断地执行完剩下的代码。 13.3.3 设置下一条语句 有时,也许想要重新运行过程中前面的几行代码,或者想要跳过一段将导致问题的代码。每遇 到这种情况,可以使用“调试”菜单里的“设置下一条语句”选项。当中断过程的执行时,可以随 意恢复任何语句。VBA将会跳过所选语句和中断处语句之间的语句。假设在过程MyProcedure(参见 上一节该过程的代码)中,已经在调用过程SpecialMsg的语句处设置了断点。要跳过过程SpecialMsg 的执行,可以将光标置于语句Workbooks(myName)内,关闭并按下Ctrl+F9(或者选择“调试”|“设 置下一条语句”)。除非中断了过程的执行,否则不能使用“设置下一条语句”选项。 技巧13—4:跳过代码行 尽管跳过代码行在VBA过程调试中非常有用,但是要小心使用。当使用下一条语句选项时,告诉VBA这是 想要执行的下一条语句。中间的所有代码行将被忽略,这意味着期间有很多本预期要发生的事情并没有发生, 可能导致意想不到的错误。 13.3.4 显示下一条语句 如果不肯定过程的执行会从哪里继续,那么可以选择“调试”|“显示下一条语句”,这样VBA 就会将光标放置到下次将运行的代码行中。当正在观察其它的过程,不确定会执行哪条代码时,该 命令尤其有用。“显示下一条语句”选项仅在中断模式下可用。 13.3.5 终止和重新设置VBA过程 任何时候在逐步过程代码时,可以: • 按下F5键无间断地执行剩余指令 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 330 共 612 页 • 选择“运行”|“重新设置”来结束过程,而不执行剩下的语句 当重新设置过程时,所有变量将丢失它们的当前值。数字型变量恢复为其初始值0,变长字符串 变量初始化为0长度字符串(””),而固定长度的字符串用ASCII码0代表的字符或者Chr(0)填充,Variant 型变量初始化为Empty,对象变量则设置为Nothing。 13.4 理解和使用条件编译 当第一次运行某个过程时,VB会将所使用的VBA语句转换为计算机能够理解的机器码,该过程 被称为编译(compiling)。也可以选择“调试”|“编译”(当前VBA工程名称),在运行该过程之前执行 整个VBA工程的编译。 使用条件编译,可以告诉VBA在编译或者运行时包括或者忽略某些代码块。根据设置的条件, 过程可能会有不同的行为。例如,条件编译用来编译一个将会运行于不同平台(Windows 或者 Macintosh、Win16或者Win32)上的应用程序。条件编译对于本地化使用于不同语言的应用软件也是 很有用的。在条件编译时排除的程序代码将从最终文件中忽略,因此,它对文件大小或程序执行没 有影响。 要激活条件编译,应该使用称作指令(directives)的特殊表达式。首先,需要使用#Const指令声 明一个布尔值(True或者False)常量。接下来,在#If . . .Then... #Else指令中检查该常量。需要进行条 件编译的代码部分必须包括在这些指令中。注意,关键字If和Else前面都带有一个数字符号(#)。 如果一部分代码将要运行,那么该条件常量的值必须设置为真(-1),否则为假(0)。 在模块的声明部分声明条件常量,例如: #Const User = True 声明名为User的条件常量。 在下面的过程中,当名为verPolish的条件常量为True时,数据就显示为Polish语。过程WhatDate 调用函数DayOfWeek,它基于提供的日前返回星期名称。要用English语言编译该程序,所需要做的 就是将该条件常量改为False,然后VBA就会跳到#Else指令后面的指令块中。 1. 在当前VBA工程中插入一个新模块,并重命名为Conditional。 2. 输入下面的过程和函数: ‘声明一个条件编译常量 #Const verPolish = True Sub WhatDay() Dim dayNr As Integer #If verPolish = True Then dayNr = WeekDay(InputBox(“Wpisz date, np. 01/01/2000”)) MsgBox “To bedzie “ & DayOfWeek(dayNr) & “.” #Else WeekdayName #End If End Sub Function DayOfWeek(dayNr As Integer) As String DayOfWeek = Choose(dayNr, “niedziela”, “poniedzialek”, “wtorek”, _ “sroda”, “czwartek”, “piatek”, “sobota”) End Function 第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 331 Function WeekdayName() As String Select Case WeekDay(InputBox(“Enter date, e.g. 01/01/2000”)) Case 1 WeekdayName = “Sunday” Case 2 WeekdayName = “Monday” Case 3 WeekdayName = “Tuesday” Case 4 WeekdayName = “Wednesday” Case 5 WeekdayName = “Thursday” Case 6 WeekdayName = “Friday” Case 7 WeekdayName = “Saturday” End Select MsgBox “It will be “ & WeekdayName & “.” End Function 3. 运行过程WhatDay。因为条件常量(verPolish)在模块顶端已被设置为True了,所以,VBA将运 行Polish的过程WhatDay。它用Polish语询问用户输入日期并且将结果用Polish语显示。要运行 代码的English版,需要将常量verPolish设置为False,然后重新运行该过程。 除了在模块顶部声明条件编译常量之外,也可以选择“工具”|(Debugging)属性(参见图13-15)。 使用这个属性窗口时,在“条件编译参数”文本框里输入下面的内容,以激活过程WhatDay的English 版: verPolish = 0 如果还有更多的条件编译常量,每个常量之间必须用冒号(:)分割开。 4. 将模块顶部的#Const verPolish指令变为注释,并在如图13-15所示的“属性”对话框里输入 条件编译常量。然后运行过程WhatDay,看看程序的Else部分是如何为讲英语的用户所执行的。 图13-15:条件编译常量可以在模块顶部也可以在属性窗口声明,但是不能同时在两个地方声明 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 332 共 612 页 13.5 使用书签导航 在分析或回顾VBA过程时,经常会发现自己跳进了某些代码区域。使用内置的书签功能,可以 轻易地标示想要在它们之间导航的地方。 使用下面的步骤来设置书签: 1. 单击想要定义为书签的语句的任意地方。 2. 选择“编辑”|“书签”|“切换书签”(或者单击“编辑”工具栏上的“切换书签”按钮——参 见图13-16)。VBA将在语句左边的边界上放置一个蓝色的圆角矩形。 图13-16::可以使用书签在经常要使用的部分之间切换 一旦设置了两个或两个以上的书签,就可以通过选择“编辑”|“书签”|“下一书签”,或者简 单地单击“编辑”工具栏上的“下一书签”按钮,在有标志的代码之间切换。也可以在代码窗口的 任意地方单击右键,然后选择快捷菜单上的“下一书签”。要到前面的书签,则选择“上一书签”。 随时都可以通过选择“编辑”|“书签”|“清除所有书签”,或者单击“编辑”工具栏上的“清 除所有书签”按钮来清除书签。要清除单个书签,那么只要单击书签语句的任意地方然后选择“编 辑”|“书签”|“切换书签”,或者单击编辑工具栏上的“切换书签”按钮。 13.6 捕捉错误 没有人第一次就编写出没有错误的程序。当创建VBA过程时,必须决定程序如何应对错误。许 多意想不到错误在运行时发生,例如,过程可能要试图给一个工作簿的名称与一个已经打开的工作 簿名称相同。运行时错误经常不是被程序员发现,而是被试图做一些程序员没有预测到的事情的用第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 333 户发现。如果程序运行错误发生了,那么VBA将显示一个错误信息,并且程序终止。大多情况下VBA 显示的错误信息对用户来说很隐秘。通过在VBA过程里加入错误处理代码,可以避免用户看到一些 运行时错误。这样,当VBA碰到错误时,就会显示一个更友好且更易理解的错误信息,可能指导用 户如何去改正错误,而不是简单的显示一个缺省的错误信息。 如何在VBA过程里实行错误处理呢?第一步,要将On Error语句放到程序里。该语句告诉VBA 当程序运行时发生错误应该做什么,换句话说,VBA使用On Error 语句来激活错误处理程序以捕捉 运行时错误。取决于过程的类型,可以通过以下任何方式退出错误陷阱:Exit Sub、Exit Function、 Exit Property、End Sub、End Function或者End Property。应该给每个过程写一个错误处理程序。 On Error语句可以按下面的方式之一使用: On Error GoTo 标签 指定一个标签,当错误发生时跳到该标签。该标签标示错误处理程序的 开始。错误处理是在应用程序中用来捕捉错误并作出响应的程序。该标 签必须和On Error语句出现在同一过程里面。 On Error Resume Next 当运行时错误发生时,VBA将忽略该导致错误的代码行,不显示错误信 息,但是从下一行开始继续运行程序。 On Error GoTo 0 关闭程序里的错误捕捉。当VBA运行该语句后,错误会被发现,但是不 会在程序里捕捉错误。 技巧13—5:是错误(Error)还是失误(Mistake)? 在编程中,错误与失误并非相同的事情。失误,比如错误拼写、漏掉语句、放错地方的引号或逗号、或者 给变量赋予了不匹配的值,通过适当的测试和调试可以从程序中消除失误。尽管程序代码没有任何失误,但这 并不意味着不会发生错误。错误是指一个事件或者操作没有按预期工作的结果。例如,如果VBA程序需要访问 硬盘上某个特定的文件,但是某人删除了该文件或者将该文件移到其它位置,无论如何总会得到一个错误。错 误阻止过程去完成具体的任务。 下面显示的过程Archive使用了错误处理程序(见过程的底部)。该过程使用内置的SaveCopyAs 方法保存当前工作簿的副本到一文件,而没有在内存中修改这个打开的工作簿。 1. 在当前工程里插入一个新模块,并重命名为Traps。 2. 输入过程Archive,如下所示: Sub Archive() Dim folderName As String Dim DriveA As String Dim BackupName As String Dim Response As Integer Application.DisplayAlerts = False On Error GoTo DiskProblem folderName = ActiveWorkbook.Path If folderName = "" Then MsgBox "您不能复制这个文件. " & Chr(13) _ & "这个文件没有被保存.", _ vbInformation, "文件档案" Else With ActiveWorkbook If Not .Saved Then .Save 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 334 共 612 页 DriveA = "A:" MsgBox "放入磁盘到驱动器 " & DriveA & _ "并单击确定.", , "复制到" & DriveA BackupName = DriveA & .Name .SaveCopyAs Filename:=BackupName MsgBox .Name & " 被复制到的磁盘在驱动器 " & _ DriveA, , "档案结束" End With End If GoTo ProcEnd DiskProblem: Response = MsgBox("在驱动器A中没有磁盘 " & Chr(13) _ & "在驱动器" & DriveA & " 中的磁盘没有格式化 ", _ vbRetryCancel, "检查磁盘驱动器") If Response = 4 Then Resume 0 Else Exit Sub End If ProcEnd: Application.DisplayAlerts = True End Sub 声明完变量后,过程Archive的语句Application.DisplayAlerts = False 确保过程运行时,VBA不 会显示自己的警告和信息。下一条语句, On Error GoTo DiskProblem,指定了当错误发生时要跳 到的标签。保存的活动工作簿的路径名称存储在变量folderName中。 VBA找不到该工作簿路径时,就会假设该文件没有保存并且显示相应的信息。接下来,VBA跳 到End If之后的语句处,并执行指令 GoTo ProcEnd,指向在End Sub关键字之前的ProcEnd标签。 注意,标签带有一个冒号。VBA执行语句Application.DisplayAlerts = True,恢复系统的内置警告和 信息。因为没有要执行的语句了,所以过程结束。 如果活动工作簿的路径不是空字符串,那么VBA就会检查该工作簿最近的更改是否已保存。如 果没有,VBA使用语句If Not .Saved Then .Save来保存活动工作簿。Saved是Workbook对象的VBA 属性。接下来,VBA将软盘驱动名称”A:”存储到变量DriveA中并显示信息提示用户插入磁盘到指定的 驱动器。然后将磁盘名称和活动工作簿名称合并在一起,并且存储到一个叫BackupName的变量中。 正如您所知,当往磁盘拷贝文件时,所有的事情都可能出错。例如,软驱可能是空的、或者软 盘未格式化或已经满了。当VBA检测到一个错误时,它就会跳到以标签DiskProblem开始的代码行中, 并且会显示相应的信息。如果用户单击了信息框上的“重试”按钮(值为4),那么VBA就执行语句 Resume 0,该语句就会将VBA送到导致错误的语句那里(.SaveCopyAs FileName: = BackupName), 然后VBA会再次执行它。如果用户单击消息框上的“取消”按钮,VBA就会执行语句Exit Sub,过程 结束。 如果软驱里面的A盘没有问题,那么VBA就会复制活动工作簿到该软盘,并且信息框会通知用户 复制操作已成功。 3. 运行几次过程Archive,每次响应不同的选项,确保测试尽可能多的可能性。使用您在本章中学 习的不同的调试技术。 第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 335 技巧13—6:程序测试 您对所编写的代码负责,这意味着在发布程序给其他人测试之前,自己先测试它。毕竟,您了解它应该如 何工作。有些程序员认为测试他们自己的代码是一种降格的事情,特别是当他们在一个有专门测试部门的组织 中工作时。不要犯这种错误。程序员级别的测试过程是非常重要的,如同编写代码本身一样。在你自己测试完 过程后,应该给用户们去测试。用户会问你的问题,如:程序能产生预期的结果吗?用起来容易并且有趣吗? 符合标准习惯吗?再有,将整个应用软件交给某个不懂使用该种应用软件的人,请他使用并试图打破它。 我们来看看另一个示例程序,下面显示的过程OpenToRead演示了Resume Next和Error语句以 及Err对象的使用。 Sub OpenToRead() Dim myFile As String Dim myChar As String Dim myText As String Dim FileExists As Boolean FileExists = True On Error GoTo ErrorHandler myFile = InputBox("Enter the name of file you want to open:") Open myFile For Input As #1 If FileExists Then Do While Not EOF(1) '遍历文件 myChar = Input(1, #1) '获取一个字符 myText = myText + myChar '存储在变量myText中 Loop Debug.Print myText '打印到立即窗口 '. 关闭文件 – 注释掉该指令会导致错误52 Close #1 End If Exit Sub ErrorHandler: FileExists = False Select Case Err.Number Case 71 MsgBox "该磁盘驱动器是空的." Case 53 MsgBox "在指定驱动器中没有发现文件." Case 75 Exit Sub Case Else MsgBox "错误" & Err.Number & " :" & Error(Err.Number) Exit Sub End Select Resume Next End Sub 过程OpenToRead的目的是一字节一字节地读取用户提供的文本文件内容(在第八章里介绍了操 作文件)。当用户输入了一个文件名时,各种各样的错误可能发生。例如,文件名可能是错误的、或 者用户可能试图从软盘上打开文件,而这时软驱里并没有软盘、或者试图打开一个已经打开了的文 件。 要捕捉这些错误,过程OpenToRead结尾处的错误处理程序使用了Err对象的Number属性。Err学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 336 共 612 页 对象包含有关运行时错误的信息。如果过程运行时错误发生了,Err.Number语句就会返回错误编号。 如果错误71、53或者75发生了,VBA就会显示写在Select…Case代码块里的友好信息并且进行 到语句Resume Next,它 会 将 VBA发送到导致错误的代码行下面的一行。如果是其它(意想不到)的错 误发生了,那么VBA就会返回错误编号(Err.Number)和错误描述(Error(Err.Number))。 在过程的开始处,变量FileExists被设置为真,这样,如果该程序没遇到错误,所有在If FileExists Then代码块里的指令就会被执行。然而,如果VBA遇到了错误,那么变量FileExists的值就会被设置 为假(参见标签ErrorHandler下面的错误处理程序的第一行语句)。这样,VBA在试图读取导致错误打 开的文件时就不会产生另一错误。如果注释掉语句Close #1,那么VBA在下次试图打开同一文件时, 就会遭遇错误。 注意ErrorHandler之前的语句Exit Sub。将Exit Sub语句放在错误处理程序的上面,您不会希望 如果没有错误发生时还执行该错误处理程序。 我们来进行下面的练习,测试过程OpenToRead并更好的理解错误捕捉: 1. 用记事本准备一个名叫C:\Vacation.txt文本文件,输入任何文本。 2. 逐语句执行过程OpenToRead四次,每次提供下面的信息之一: • C:\Vacation.txt文件的名称 • 在C:盘上不存在的文件名 • A:盘上的任意文件,但软驱是空的 • 注释掉语句Close #1,并输入文件名C:\Vacation.txt 技巧13—7:错误:制造错误来测试错误处理程序 可以故意制造一些错误来测试程序里的错误陷阱: „ 通过使用下面的语法产生内置错误:Error error_number。例如,要显示当除数为0时发生的错误,可以在 立即窗口里输入下面的语句: Error 11 当按下回车键后,VBA就会显示错误信息: 运行时错误”11” 除数为零 „ 要检查所产生的错误的意义,使用下面的语法:Error(error_number)。例如,想要知道编号为7的错误的 意思,可以在立即窗口里输入下面的指令: ?Error(7) 当按回车键后,VBA会返回该错误的描述:内存溢出。 本章小结和下一章内容简介 在本章中,您学习了如何测试VBA过程以确保它们按计划执行。您使用断点和监视逐步运行程 序来调试代码。您学习了如何在中断模式下使用立即窗口。您知道了本地窗口如何能帮助您检测变 量的值,以及“调用堆栈”对话框如何能在复杂的程序里帮助您追踪代码运行在哪里。您已经学习 了在编译时指定想要包括或排除的过程代码。最后,您学习了如何使用错误处理程序来捕捉错误。第 13 章 12B 调试 VBA 过程和处理错误 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 337 通过使用内置的调试工具,您可以快速地指出程序的问题所在。试着多花一些时间来熟悉这些工具, 掌握调试艺术可以节省许多时间并避免错误。 通过完成第一章到第十三章的学习,您已经获得了扎扎实实的VBA知识,很可能,您应该开始 自己的Excel自动化工程了。本章结束了使用Excel 2002 VBA的中级级别,VBA提供了许多更高级的 功能,这将在本书的剩余章节里介绍。 第14章 Excel2002 事件编程 当用户单击工作表单元格时如何禁用内置的快捷菜单?如何 在工作簿打开或者关闭之前显示一个自定义信息?如何验证输入 在单元格或者单元格区域里的数据…… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 340 共 612 页 当用户单击工作表单元格时如何禁用内置的快捷菜单?如何在工作簿打开或者关闭之前显示一 个自定义信息?如何验证输入在单元格或者单元格区域里的数据?要彻底控制Excel,必须学习如何 响应事件。学习如何进行事件编程可以在Excel应用程序中执行自已的功能。关于该主题所要学习的 第一件事情就是什么是事件,这里有个简单的定义: 事件是发生了什么 无需说,对象发生的事件是Excel的一部分,然而,一旦您学习了Excel中的事件,将发现更容 易去理解发生在Word或者其它任何Microsoft Office应用程序的对象事件。事件是由对象认可的行为。 现在您知道了什么是事件,接下来您需要知道事件可以被一个应用程序用户(例如您自己)、另 一 个程序或者系统本身触发。因此,如何能够触发事件呢?假设右键单击一个工作表单元格,该具体 操作将显示一个内置的工作表单元格快捷菜单,允许快速访问与工作表单元格相关的常用命令。但 是,万一在某种情况下该内置响应不合适呢?您可能想要完全不接受在工作表中单击右键,或者可 能想要当用户右键单击任何单元格时,单元格快捷菜单上出现一个自定义菜单。您可以使用VBA来 编写代码来反应发生的事件。 Excel提供了许多可以响应的事件,下面的对象可以响应事件: • 工作表(Worksheet) • 图表工作表(Chart Sheet) • 查询表(Query Table) • 工作簿(Workbook) • 应用程序(Application) 通过编写事件过程,您可以决定当特定的事件发生时应该发生什么。 14.1 事件过程介绍 事件过程(event procedure)是一种特殊的VBA过程,用来对特定的事件作出反应。该过程包含 处理具体事件的VBA代码。有些事件只需要简单的一行代码,然而其它事件可能更复杂。事件过程 有名称,按下面的方式创建: 对象名称_事件名称() 在事件名称后面的括号里,可以放置需要发送到过程里的参数。程序员不能更改事件过程名称。 在编写事件过程对Excel事件作出反应之前,您需要知道: • 想要响应的具体对象和事件的名称 响应事件的对象在代码窗口的过程(参见图14-1)下拉列表里显示了一系列事件,也可以使用对 象浏览器找到事件名称(参见图14-2)。 • 应该放置代码的地方 有些事件在标准模块里编写代码,其它则存储在类模块里。然而,工作簿、图表工作表和工作第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 341 表事件对任何打开的工作表或者工作簿都是可用的。要给一个内嵌的图表、查询表或者应用程序对 象创建事件过程,那么必须首先在类模块里使用With Events关键字创建一个新对象。 图14-1:可以在代码窗口里找到事件名称 图14-2:可以在对象浏览器里找到事件名称 14.2 激活和禁用事件 可以使用Application对象的EnableEvents属性来激活或者禁用事件。如果编写了VBA过程但是 不想某特定的事件发生,那么就将EnableEvents属性设置为False。例如,为了避免在运行过程学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 342 共 612 页 EnterData(参见下面的代码)触发Workbook_BeforeClose事件,那么在调用Workbook对象的Close 方法之前,设置EnableEvents属性为False。在程序结束之前,将EnableEvents属性设置回True,确 保事件可用。 1. 打开一个新工作簿并保存为DisableEvents.xls。 2. 切换到VBE编辑器屏幕,双击工程浏览器窗口的ThisWorkbook,并在出现的代码窗口里输入 Workbook_BeforeSave事件过程。 Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, _ Cancel As Boolean) If MsgBox("您想复制" & vbCrLf _ & "这个工作表到" & vbCrLf _ & "一个新工作簿中吗?", vbYesNo) = vbYes Then Sheets(ActiveSheet.Name).Copy End If End Sub 3. 选择“插入”|“模块”添加一个标准模块到当前VBA工程中,并且输入下面显示的过程: Sub EnterData() With ActiveSheet.Range("A1:B1") .Font.Color = vbRed .Value = 15 End With Application.EnableEvents = False ActiveWorkbook.Save Application.EnableEvents = True End Sub 4. 切换到Excel应用程序窗口,并选择“文件”|“保存”。这时将触发Workbook_BeforeSave事件, 单击“是”响应该信息框,Excel会打开一个带有当前工作表副本的新工作簿。 5. 激活DisableEvents工作簿,并选择“工具”|“宏”|“宏”。在该对话框上,单击“EnterData” 然后“执行”。注意,当运行EnterData过程时,没有被提示在保存之前复制工作表,这表明 Workbook_BeforeSave事件没有运行。 14.3 事件顺序 事件发生以响应具体的动作并且按预定义的顺序发生。下表演示了打开新工作簿、往工作簿里 添加新工作表以及关闭工作簿时事件的顺序。 动作 对象 事件顺序 打开一个新工作簿 Workbook NewWorkbook, WindowDeactivate, WorkbookDeactivate, WorkbookActivate, WindowActivate 往工作簿添加一个新工 作表 Workbook WorkbookNewSheet, SheetDeactivate, SheetActivate 关闭工作簿 Workbook WorkbookBeforeClose, WindowDeactivate, WorkbookDeactivate, WorkbookActivate, WindowActivate 第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 343 14.4 工作表事件 工作表对象响应诸如工作表激活(activate)和失活(deactivate)事件、计算工作表事件、工作表更 改事件和双击或右击工作表事件。本节介绍一些工作表对象能够响应的事件。 事件名称 Activate 事件描述 示例1 当用户激活工作表时,该事件 发生。 Dim shtName As String ‘在模块顶部声明 Private Sub Worksheet_Activate() shtName = ActiveSheet.Name Range("B2").Select End Sub 示例过程中,每次该工作表被激活时,选择单元格B2。 示例1 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel对象文件夹。双 击Sheet2(Sheet2),并且在Sheet2(代码)窗口输入示例过程。接下来,切换到Excel窗口并激活 Sheet2。注意,当Sheet2被激活时,被选择的总是单元格B2。 事件名称 Deactivate 事件描述 示例2 当用户激活一个不同的工作表 时,该事件发生。 Private Sub Worksheet_Deactivate() MsgBox "您离开了工作表" & _ shtName & "." & vbCrLf & _ "您切换到工作表" & _ ActiveSheet.Name & "." End Sub 示例过程中,当Sheet2不是当前工作表时,显示一个信息框。 示例2 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel对象文件夹,双 击Sheet2 (Sheet2),然后在Sheet2(代码)窗口输入示例过程。接下来,切换到Excel窗口并激活 Sheet2。在示例1里创建的Worksheet_Activate过程将会运行,Excel会选中单元格B2并且将工作表 名称存储于在Sheet2代码模块顶部声明的全局变量shtName里。现在,单击当前工作簿里的其它工 作表,注意,Excel将显示离开的工作表名称和切换到的工作表名称。 事件名称 SelectionChange 事件描述 示例3 当用户选择工作表单元格时, 该事件发生。 Private Sub Worksheet_SelectionChange(ByVal Target As Excel.Range) On Error Resume Next Dim myRange As Range Set myRange = Intersect(Range("A1:A10"), Target) If Not myRange Is Nothing Then MsgBox "不允许数据输入或编辑." End If End Sub 示例过程中,当用户选择myRange中的任何单元格时,显示一个信息框。 示例3 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并且打开Microsoft Excel对象文件夹,学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 344 共 612 页 双击Sheet3 (Sheet3),并在Sheet3(代码)窗口里输入该示例过程。然后切换到Excel窗口并激活 Sheet3。单击给定区域A1:A10中的任何单元格。注意,无论何时单击限定区域内的单元格,Exce 都l将显示一个信息。 事件名称 Change 事件描述 示例4 当用户更改单元格内容时,该 事件发生 Private Sub Worksheet_Change(ByVal Target _ As Excel.Range) Application.EnableEvents = False Target = UCase(Target) Columns(Target.Column).AutoFit Application.EnableEvents = True End Sub 示例过程中,将输入到单元格中的内容变为大写,并且该列列宽将自动调整以适应文本长短。 示例4 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并且打开Microsoft Excel对象文件夹, 双击Sheet1 (Sheet1),并在Sheet1(代码)窗口里输入该示例程序。然后切换到Excel窗口并激活 Sheet1。在任意单元格里输入任何文本,注意,一旦按下回车键,Excel就会将该文本变为大写,然 后该列自动调整以适应文本长度。 事件名称 Calculate 事件描述 示例5 当用户重新计算工作表时,该 事件发生。 Private Sub Worksheet_Calculate() MsgBox "工作表被重新计算." End Sub 一旦工作表重新计算,示例过程就会显示一条信息。 示例5 – 试验:在当前工作簿里添加一个新工作表,本练习假定Excel将放置Sheet4在工作簿里。 在Sheet4的单元格A2里输入1,B2里输入2。在单元格C2里输入下面的公式:= A2 + B2。在VBE编 辑器窗口,激活工程浏览器窗口并且打开Microsoft Excel对象文件夹,双击Sheet4 (Sheet4),并且 输入如上所示的Worksheet_Calculate事件过程。切换到Excel窗口并激活Sheet4,在B2单元格里输 入任何数字。注意,当退出编辑模式后,Worksheet_Calculate事件就被触发了,也就看到了一个自 定义的信息。 事件名称 BeforeDoubleClick 事件描述 示例6 当用户双击工作表单元格时, 该事件发生。 Private Sub Worksheet_BeforeDoubleClick(ByVal _ Target As Range, Cancel As Boolean) If Target.Address = Range("$C$9") Then MsgBox "请不要双击." Cancel = True Else MsgBox "您可以编辑该单元格." End If End Sub 用户双击单元格C9时示例过程不允许在单元格内编辑。 第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 345 示例6 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并且打开Microsoft Excel对象文件夹, 双击Sheet2 (Sheet2),并在Sheet2(代码)窗口里输入该示例过程。然后切换到Excel窗口并激活 Sheet2。当双击单元格C9时,该事件过程取消了内置的Excel行为,不允许用户在单元格内部直接 编辑数据。然而,用户可以通过单击编辑栏或者按下F2键绕过该限制。当编写一些事件过程来限制 访问某些功能时,应编写一些额外的代码禁止一些工作区。 事件名称 BeforeRightClick 事件描述 示例7 当用户右键单击工作表单元 格时,该事件发生。 Private Sub Worksheet_BeforeRightClick(ByVal _ Target As Range, Cancel As Boolean) With Application.CommandBars("Cell") .Reset If Target.Rows.Count > 1 Or _ Target.Columns.Count > 1 Then With .Controls.Add(Type:=msoControlButton, _ before:=1, temporary:=True) .Caption = "Print..." .OnAction = "PrintMe" End With End If End With End Sub Sub PrintMe() Application.Dialogs(xlDialogPrint).Show arg12:=1 End Sub 示例过程中,当用户在工作表中选择多于一个单元格时,就会在单元格快捷菜单上添加一个Print选 项。 示例7 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel对象文件夹,双 击Sheet2 (Sheet2),并在Sheet2(代码)窗口里输入该示例过程。在当前工程里添加一个新模块,并 且输入PrintMe 过程,如上所示。当用户从快捷菜单上选择Print 选项时,该过程就会被 Worksheet_BeforeRightClick事件调用。注意,对话框的Show方法后面带了个名为arg12:=1的参数。 该参数让“打印”对话框显示时,勾选“所选区域”选项按钮。在适当的模块里输入完两个过程后, 切换到Excel窗口并激活Sheet2。在任何单个单元格上单击右键,注意,这时出现的快捷菜单为缺省 选项。现在,另作一次选择,这次包括多个单元格并在所选区域上单击右键,将看到“Print…”选 项出现在第一个菜单位置。单击“Print”选项,注意,打印对话框显示的是“所选区域”,而不是缺 省的“所选工作表”。 注意:参见第十章中使用快捷菜单的更多信息。另外,参见图10-3关于如何查找Excel的内置 参数列表的信息。 事件名称 FollowHyperlink 事件描述 示例8 当用户单击Excel工作表中的 链接时,该事件发生。 Private Sub Worksheet_FollowHyperlink(ByVal _ Target As Hyperlink) Target.AddToFavorites End Sub 示例过程将用户单击的链接添加到IE的收藏夹里。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 346 共 612 页 示例8 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel对象文件夹,双 击Sheet1 (Sheet1),并在Sheet1(代码)窗口里输入该示例过程。切换到Excel窗口,并在任何单元格 里输入一个网址,比如www.wordware.com,然后回车。现在,单击该链接激活该网址,当IE窗口出 现后,打开收藏菜单,就会发现Wordware网址已经被添加到该菜单里了。 事件名称 PivotTableUpdate 事件描述 示例9 工作表中的数据透视表报告被 更新后,发生该事件。它是 Excel 2002的新事件。参数 Target指定所选数据透视表报 告。 pivTbl是个变量,指向类模块里 使用WithEvents关键字声明的 工作表的对象。 Private Sub pivTbl_PivotTableUpdate( _ ByVal Target As PivotTable) MsgBox Target.Name & _ " 报告已更新." & vbCrLf _ & "数据透视表报告位于单元格区域:" & _ Target.DataBodyRange.Address End Sub 示例过程显示一条信息,描述被更新的数据透视表报告的名称和在工作表上报告占用的单元格区域 地址。 示例9 – 试验:打开位于本书附带光盘上的文件PivotReport_2.xls,单击数据透视表上的任意单 元格,并且单击数据透视表工具栏上的“更新数据”按钮。图14-3和14—4演示了如何创建 PivotTableUpdate事件的事件句柄。 图14-3:必须使用类模块来捕捉PivotTableUpdate事件该类模块可以是任何有效的模块名称对象变量名 称pivTbl可以是任何有效的变量名称 第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 347 图14-4:在能够捕捉PivotTableUpdate事件之前,必须在标准模块里创建一个类模块的实例,并且将 Worksheet对象赋给新对象的pivTbl属性 14.5 工作簿事件 当用户执行一些操作,诸如打开、激活、离开、打印、保存以及关闭工作簿时,发生Workbook 对象事件。工作簿事件不会在标准VBA模块里创建,想要编写响应特定工作簿事件的代码,需要双 击VBE编辑器里工程浏览器中的ThisWorkbook。在出现的代码窗口里,打开对象下拉列表并选择 Workbook对象,在过程下拉列表里选择想要的事件。被选择的事件过程将出现在代码窗口。例如: Private Sub Workbook_Open() 在此放置事件处理代码 End Sub 本节描述Workbook对象一些可用的事件。 事件名称 Activate 事件描述 示例10 当用户激活工作簿时,该事件 发生。当用户从其它应用程序 切换到Excel工作簿时,不会触 发该事件。 Private Sub Workbook_Activate() MsgBox "这个工作簿包含" & _ ThisWorkbook.Sheets.Count & "个工作表." End Sub 当用户激活包含Workbook_Activate事件过程的工作簿时,示例过程显示该工作簿含有的工作表数。 示例10 – 试验:在VBE编辑器窗口中,激活工程浏览器窗口并打开Microsoft Excel对象文件夹, 双击ThisWorkbook并且在ThisWorkbook(代码)窗口里输入示例过程。然后,切换到Excel窗口并且 打开一个新工作簿。切换到输入了Workbook_Activate过程的工作簿,这时,Excel将显示该工作簿 里的工作表总数。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 348 共 612 页 事件名称 Deactivate 事件描述 示例11 当用户激活Excel的其它工作 簿时,该事件发生。当用户切 换到其它应用程序时,不会发 生该事件。 Private Sub Workbook_Deactivate() For Each cell In _ ActiveSheet.UsedRange If Not IsEmpty(cell) Then Debug.Print cell.Address & _ ":" & cell.Value End If Next End Sub 当用户激活其它工作簿时,示例过程将在立即窗口里打印当前工作簿里包含内容的单元格地址和数 值。 示例11 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel对象文件夹。 双击ThisWorkbook,然后在ThisWorkbook(代码)窗口中输入示例过程。切换到Excel窗口,并且在 活动工作表里输入一些数据。然后,激活某个不同的工作簿。该动作将触发Workbook_Deactiate事 件过程。切换到VBE编辑器窗口,并打开立即窗口察看。 事件名称 Open 事件描述 示例12 当用户打开工作簿时,该事件 发生。 Private Sub Workbook_Open() ActiveSheet.Range("A1").Value = Format _ (Now(), "mm/dd/yyyy") End Sub 当工作簿被打开时,示例过程在单元格A1里放置当前日期。 示例12 – 试验:打开一个新工作簿,在VBE编辑器窗口,激活工程浏览器窗口,并打开Microsoft Excel对象文件夹。双击ThisWorkbook,并在ThisWorkbook(代码)窗口输入示例过程,保存并且关 闭该工作簿。当再次打开该工作簿时,当前日期就会被放置在当前活动工作表的单元格A1里。 事件名称 BeforeSave 事件描述 示例13 该事件发生在工作簿被保存之 前。参数SaveAsUI是只读的, 指向“保存”对话框。如果该 工作簿并没有被保存过,那么 SaveAsUI参数的值就是True, 否则为False。 Private Sub Workbook_BeforeSave(ByVal _ SaveAsUI As Boolean, Cancel As Boolean) If SaveAsUI = True And _ ThisWorkbook.Path = vbNullString Then MsgBox "这个文档仍然还没有" _ & "被保存." & vbCrLf _ & "将显示.另存为对话框" ElseIf SaveAsUI = True Then MsgBox "您不允许使用" _ & "保存选项. " Cancel = True End If End Sub 如果该工作簿没有被保存过,那么示例过程将显示“另存为”对话框。如果该文件没有被保存过, 那么该工作簿的路径名将为NULL字符串(vbNullString)。过程不允许用户将该工作簿保存为一个不同第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 349 的名称;另存为操作将通过设置参数Cancel为True而中断。用户需要选择“保存”选项来保存该工 作簿。 示例13 – 试验:打开一个新工作簿,在VBE编辑器窗口,激活工程浏览器窗口并在新添加的工 程中打开Microsoft Excel对象文件夹。双击ThisWorkbook并且在ThisWorkbook(代码)窗口里输入示 例过程。单击“If SaveASUI…”语句旁边的页边以放置一个断点。切换到Excel窗口,并且在任意 单元格输入一些数据。单击工具栏上“保存”按钮。Workbook_BeforeSave事件过程将会被激活。 执行If SaveAsUI后面的语句。在“另存为”对话框里输入SaveEvent.xls作为该工作簿的名称。在保 存(并命名)该工作簿之后,对该工作簿作一些更改,然后选择“文件”|“另存为”。这次ElseIf子句 将会被执行,并且不允许通过使用“另存为”选项来保存该工作簿。 事件名称 BeforePrint 事件描述 示例8 该工作簿被打印之前以及打 印对话框出现之前,该事件发 生。 Private Sub Workbook_BeforePrint(Cancel _ As Boolean) Dim response As Integer response = MsgBox("您想" & vbCrLf & _ "在脚注中打印工作簿的全名吗?", _ vbYesNo) If response = vbYes Then ActiveSheet.PageSetup.LeftFooter = _ ThisWorkbook.FullName Else ActiveSheet.PageSetup.LeftFooter = "" End If End Sub 在打印之前,如果用户单击了信息框的“是”按钮,那么示例过程会将工作簿全名放入文档的脚注 里。 示例14 – 试验:打开一个新工作簿,在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel对象文件夹。双击ThisWorkbook并且在ThisWorkbook(代码)窗口里输入示例过程。然后,切 换到Excel窗口,并激活任意工作表。在任意单元格里输入想要的任意内容。当按下工具栏上的“打 印预览”按钮时,Excel将会询问是否想要将工作簿名称和路径放入到脚注里。 事件名称 BeforeClose 事件描述 示例8 该事件发生在工作簿关闭之 前,并且在系统询问用户是 否保存变化之前。 Private Sub Workbook_BeforeClose(Cancel _ As Boolean) If MsgBox("您想在关闭前改变" & vbCrLf _ & " 工作簿的属性吗?", _ vbYesNo) = vbYes Then Application.Dialogs(xlDialogProperties).Show End If End Sub 如果用户对信息框响应“是”时,示例过程将显示“属性”对话框。 示例15 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel对象文件夹。 双击ThisWorkbook并且在ThisWorkbook(代码)窗口里输入示例过程程序。然后,切换到Excel窗口,学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 350 共 612 页 并关闭包含BeforeClose事件过程的工作簿。在关闭之前,您将看到一个信息框,询问是否要察看“属 性”对话框。在察看和修改工作簿属性之后,该过程关闭工作簿。如果有些改变仍未保存,那么还 有机会去保存该工作簿、取消更改或者干脆终止该关闭操作。 事件名称 AddInInstall 事件描述 示例8 当用户安装该工作簿为一个加 载宏时,该事件发生。 Private Sub Workbook_AddinInstall() MsgBox "要创建一个日历, " & vbCrLf _ & "输入CalendarMaker " & vbCrLf _ & "在宏对话框中." End Sub 请参考下面详细的说明来触发Workbook_AddinInstall事件过程。 示例16 – 试验: 1. 打开一个新工作簿。 2. 切换到VBE编辑器,激活工程浏览器窗口,并打开Microsoft Excel对象文件夹。 3. 双击ThisWorkbook并在ThisWorkbook(代码)窗口里输入示例16和示例17的程序。 4. 在当前VBA工程里插入一个新模块,并且输入过程CalendarMaker的代码,该代码显示于示例 17之后。 5. 切换到Excel窗口,并选择“文件”|“属性”,在“属性”对话框里输入下面的内容: 标题:日历生成器 备注:在Excel电子表格中创建一个月日历。当加亮该加载宏名称时,上面的信息将出现在加载 宏对话框里面。 6. 单击“确定”以退出“属性”对话框。 7. 选择“文件”|“另存为”并且将该工作簿保存为Calendar.xls。 8. 现在,选择“文件”|“另存为”,将Calendar.xls保存为一个加载宏。从“另存为”类型下拉列 表中,选择“Microsoft Excel加载宏”,输入一个新的文件名称(CalendarMaker.xla),然后单击 “保存”。 9. 关闭Calendar.xls工作簿。 10. 打开一个新工作簿。 11. 选择“工具”|“加载宏”。使用“浏览”按钮将CalendarMaker加入到加载宏列表中,勾选列表 框里的“日历生成器”加载宏。当单击“确定”后,Workbook_AddInInstall过程就会被触发, 单击信息框上的“确定”按钮。 12. 想要创建一个日历,可以选择“工具”|“宏”|“宏”,在“宏名”文本框里输入“CalendarMaker” 然后单击“执行”,您将会被询问输入月和年。输入月和年(例如,October 2002)并单击确定, 将出现一个日历页,如图14-5所示。 第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 351 图14-5:由过程CalendarMaker创建的月历 13. 想要触发示例17中演示的AddInUninstall事件过程,可以选择“工具”|“加载宏”,并清除“日 历生成器”加载宏前面的选择。 事件名称 AddInUninstall 事件描述 示例17 当用户卸载一个加载宏时,引 发该事件 Private Sub Workbook_AddinUninstall() MsgBox "CalendarMaker " & vbCrLf _ & "加载宏被卸载." End Sub 一旦该工作簿作为一个加载宏被卸载后,示例过程将显示一个信息。 示例17 – 试验:参见示例16 下面的过程CalendarMaker可在CodeLibrarian 加载宏里获得,这里用来演示Workbook对象的 AddInInstall和AddInUninstall事件的使用(参见示例16和17)。 Sub CalendarMaker() Dim MyInput As String Dim StartDay As Date Dim DayofWeek As Integer, CurYear As Integer, CurMonth As Integer 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 352 共 612 页 Dim FinalDay As Long Dim cell As Range Dim RowCell As Integer, ColCell As Integer Dim x As Integer '保护工作表,以避免之前的日历带来的错误 ActiveSheet.Protect DrawingObjects:=False, Contents:=False, _ Scenarios:=False '制作日历时避免屏幕闪烁 Application.ScreenUpdating = False '设置错误捕捉 On Error GoTo MyErrorTrap '清除a1:g14单元格区域的内容 Range("a1:g14").Clear ' 使用输入框从用户获得年和月,并赋给变量MyInput MyInput = InputBox("Type in Month and year for Calendar ") ' 许用户单击取消按钮以结束宏 If MyInput = "" Then Exit Sub '获取输入年月的日期值 StartDay = DateValue(MyInput) '检查是否是有效日期,但不是当月第一天 ' 如不是,则设置StartDay为当月第一天 If Day(StartDay) <> 1 Then StartDay = DateValue(Month(StartDay) & "/1/" & _ Year(StartDay)) End If '准备年月的单元格,年月为完整拼写 Range("a1").NumberFormat = "mmmm yyyy" ' 将年月于A1:G1区域跨列居中显示,并设置合适的字号、粗体和行高 With Range("a1:g1") .HorizontalAlignment = xlCenterAcrossSelection .VerticalAlignment = xlCenter .Font.Size = 18 .Font.Bold = True .RowHeight = 35 End With '设置A2:G2区域为星期标志,设置为居中、大小、行高和粗体 With Range("a2:g2") .ColumnWidth = 11 .VerticalAlignment = xlCenter .HorizontalAlignment = xlCenter .VerticalAlignment = xlCenter .Orientation = xlHorizontal .Font.Size = 12 .Font.Bold = True .RowHeight = 20 End With ' 在a2:g2中输入星期日期 Range("a2") = "Sunday" Range("b2") = "Monday" Range("c2") = "Tuesday" Range("d2") = "Wednesday" Range("e2") = "Thursday" Range("f2") = "Friday" 第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 353 Range("g2") = "Saturday" '准备A3:G7区域为日期并设置左对齐和上对齐、大小、行高和粗体 With Range("a3:g8") .HorizontalAlignment = xlLeft .VerticalAlignment = xlTop .Font.Size = 18 .Font.Bold = True .RowHeight = 21 End With '在单元格A1里输入完整的年月 Range("a1").Value = Application.Text(MyInput, "mmmm yyyy") '设置变量,并获取该月第一天的星期 DayofWeek = Weekday(StartDay) '设置变量分别获取年和月 CurYear = Year(StartDay) CurMonth = Month(StartDay) '设置变量并计算下个月的第一天 FinalDay = DateSerial(CurYear, CurMonth + 1, 1) '基于DayofWeek,在选定月份第一天的单元格位置放置“1” Select Case DayofWeek Case 1 Range("a3").Value = 1 Case 2 Range("b3").Value = 1 Case 3 Range("c3").Value = 1 Case 4 Range("d3").Value = 1 Case 5 Range("e3").Value = 1 Case 6 Range("f3").Value = 1 Case 7 Range("g3").Value = 1 End Select '在单元格区域A3:G8里面循环,在单元格“1”之后的每个单元格增加1 For Each cell In Range("a3:g8") RowCell = cell.Row ColCell = cell.Column '如果1在第一列 If cell.Column = 1 And cell.Row = 3 Then '如果当前单元格并非第一列 ElseIf cell.Column <> 1 Then If cell.Offset(0, -1).Value >= 1 Then cell.Value = cell.Offset(0, -1).Value + 1 '当遇到当月的最后一天时,停止 If cell.Value > (FinalDay - StartDay) Then cell.Value = "" '当日历显示了正确的数字时,退出循环 Exit For End If End If '仅当当前单元格不在第三行,而在第一列时 ElseIf cell.Row > 3 And cell.Column = 1 Then 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 354 共 612 页 cell.Value = cell.Offset(-1, 6).Value + 1 '当遇到当月的最后一天时,停止 If cell.Value > (FinalDay - StartDay) Then cell.Value = "" '日历显示了正确的数字时,退出循环 Exit For End If End If Next '创建输入单元格、居中、自动换行,并在每日周围设置边框 For x = 0 To 5 Range("A4").Offset(x * 2, 0).EntireRow.Insert With Range("A4:G4").Offset(x * 2, 0) .RowHeight = 65 .HorizontalAlignment = xlCenter .VerticalAlignment = xlTop .WrapText = True .Font.Size = 10 .Font.Bold = False '解开这些区域的锁定,以供将来工作表受保护后输入文本 .Locked = False End With '在日期周围设置边框 With Range("A3").Offset(x * 2, 0).Resize(2, _ 7).Borders(xlLeft) .Weight = xlThick .ColorIndex = xlAutomatic End With With Range("A3").Offset(x * 2, 0).Resize(2, _ 7).Borders(xlRight) .Weight = xlThick .ColorIndex = xlAutomatic End With Range("A3").Offset(x * 2, 0).Resize(2, 7).BorderAround _ Weight:=xlThick, ColorIndex:=xlAutomatic Next If Range("A13").Value = "" Then Range("A13").Offset(0, 0) _ .Resize(2, 8).EntireRow.Delete '关闭网格线 ActiveWindow.DisplayGridlines = False '保护工作表,防止覆盖日期 ActiveSheet.Protect DrawingObjects:=True, Contents:=True, _ Scenarios:=True '调整窗口大小以显示完整日历 ActiveWindow.WindowState = xlMaximized ActiveWindow.ScrollRow = 1 '许屏幕重绘日历显示 Application.ScreenUpdating = True '这里放置退出Sub来避免进入错误捕捉,除非发现错误 Exit Sub '错误导致信息框指明问题,提供新的输入框并恢复至导致错误的代码行 MyErrorTrap: MsgBox "您没有正确地输入年和月." _ 第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 355 & Chr(13) & "正确地拼写月" _ & " (或者使用3个字母的缩写)" _ & Chr(13) & "和代表年的4个数字" MyInput = InputBox("为日历输入月和年") If MyInput = "" Then Exit Sub Resume End Sub 事件名称 NewSheet 事件描述 示例18 当用户在工作簿中新建一个 工作表后,该事件发生。 Private Sub Workbook_NewSheet(ByVal Sh As Object) If MsgBox("您想放置" & vbCrLf _ & "新工作表" & vbCrLf _ & "在工作簿的开头吗?", vbYesNo) = vbYes Then Sh.Move before:=ThisWorkbook.Sheets(1) Else Sh.Move After:=ThisWorkbook.Sheets( _ ThisWorkbook.Sheets.Count) MsgBox Sh.Name & _ " 现在是工作簿的最后一个工作表." End If End Sub 当用户在弹出的信息框上单击“是”,示例过程就会将新建的工作表置于工作簿的开始,否则在结尾。 示例18 – 试验:打开一个新工作簿,在VBE编辑器窗口,激活工程浏览器窗口并打开新添加的 工程下的Microsoft Excel对象文件夹。双击ThisWorkbook并在ThisWorkbook(代码)窗口输入示例程 序。切换到Excel窗口,并且在工作表标签的任意地方单击右键,从快捷菜单上选择“插入”,选择 想要插入的工作表类型,并单击“确定”。Excel将会问新建的工作表放置到什么地方。 下面是Excel工作表可以响应的事件列表。 事件名称 描述 SheetActivate 当用户激活工作簿里的任何工作表时发生该事件。SheetActivate 事件同样也可以发生在应用程序级别,当激活任意打开的工作簿 里的任意工作表时,都会激活该事件。 SheetDeactivate 当用户激活工作簿里一个不同的工作表时,触发该事件。 SheetSelectionChange 当用户选择不同单元格区域时,触发该事件。该事件发生在该工 作簿里的每个工作表上。 SheetChange 当用户更改单元格内容时,该事件发生。 SheetCalculate 用户重新计算工作表时,该事件发生。 SheetBeforeDoubleClick 当用户双击工作表上的单元格时,触发该事件。 SheetBeforeRightClick 当用户在工作表单元格上单击右键时,触发该事件。 事件名称 WindowActivate 事件描述 示例19 当用户激活任何显示工作簿的 Private Sub Workbook_WindowActivate(ByVal _ Wn As Window) 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 356 共 612 页 窗口时,该事件发生。 Wn.GridlineColor = vbYellow End Sub 当用户激活包含Workbook_WindowActivate程序的工作簿时,示例过程将更改工作表的网格线颜色 为黄色。 示例19 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel对象文件夹。 双击ThisWorkbook,并在ThisWorkbook(代码窗口)输入示例过程。切换到Excel窗口,打开一个新 的工作簿。使用“窗口”菜单将Excel工作簿设置为竖直并排排列。当激活含有示例过程的工作簿时, 网格线应该会变为黄色。 事件名称 WindowDeactivate 事件描述 示例20 当用户离开工作簿窗口时,触 发该事件。 Private Sub Workbook_WindowDeactivate(ByVal _ Wn As Window) Wn.GridlineColor = vbRed End Sub 当用户从包含Workbook_WindowActivate过程代码的工作簿切换到另一个工作簿时,示例过程将工 作表的网格线设置为红色。 示例20 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel文件夹,双击 ThisWorkbook并在ThisWorkbook(代码)窗口中输入示例程序。切换到Excel窗口,打开一个新的工 作簿。使用“窗口”菜单将所有Excel工作簿设置为竖直排列。当从包含Workbook_WindowDeactivate 事件代码的工作簿切换到空工作簿时,该含有Workbook_WindowDeactivate事件代码的工作簿中的 工作表网格线会变为红色。 事件名称 WindowResize 事件描述 示例21 当用户打开窗口、调整窗口大 小、最大化或者最小化窗口时, 触发该事件。 Private Sub Workbook_WindowResize(ByVal Wn As Window) If Wn.WindowState <> xlMaximized Then Wn.Left = 0 Wn.Top = 0 End If End Sub 当用户调整窗口大小时,示例过程将工作簿窗口移至屏幕的左上角。 示例21 – 试验:在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel对象文件夹, 双击ThisWorkbook并在ThisWorkbook(代码)窗口输入示例过程。切换到Excel窗口并且单击还原按 钮,通过拖曳窗口内部边框改变当前活动窗口大小。一旦完成大小调整操作,该工作簿窗口会自动 跳到屏幕的左上角。 下表描述了Excel 2002里增加的工作簿事件。 事件名称 描述 PivotTableOpenConnection 当数据透视表报告打开对其数据源的连接时发生该事件。该事件 要求在类模块里面使用WithEvents关键字声明一个Application或 者Workbook类型的对象。(参见“图表事件”和“应用程序对象 识别的事件”部分有关该关键字使用的示例) 第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 357 PivotTableCloseConnection 当数据透视表报告关闭对其数据源的连接后发生该事件。该事件 要求在类模块里面使用WithEvents关键字声明一个Application或 者Workbook类型的对象。(参见“图表事件”和“应用程序对象 识别的事件”部分有关该关键字使用的示例) SheetPivotTableUpdate 需要下面的两个参数: Sh – 被选择的工作表 Target – 被选择的数据透视表 报告 当数据透视表报告被更新后发生该事件。该事件要求在类模块里 使用关键字WithEvents声明一个Application或者Workbook类型 的对象(参见示例 9,需要使用关键字WithEvents设置事件处理的 相关信息)。该事件处理可以在附带CD里的PivotReport.xls里找 到。 Private Sub App_SheetPivotTableUpdate( _ ByVal Sh As Object, ByVal Target As PivotTable) MsgBox "数据透视表已更新." End Sub 14.6 图表事件 正如您所知,可以创建一个内嵌在工作表里的图表,也可以创建一个单独的图表工作表。在本 节,您将学习如何控制图表事件,而不管决定将图表放在哪里。在试验图表事件之前,先做以下几 件事情: 1. 打开一个新的Excel工作簿,并且保存为ChartEvents.xls。 2. 输入如图14-6所示的示例数据。 3. 选择单元格区域A1:D4,并单击“常用”工具栏上的“图表向导”按钮。 4. 准备一个柱形图,如图14-6所示,并将其内嵌于工作表内。 5. 使用相同的数据,在一个单独的图表工作表里创建一个折线图(参见图14-7)。 6. 将图表工作表名称改为“销售分析图”。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 358 共 612 页 图14-6:内嵌于工作表的柱形图 第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 359 图14-7:置于图表工作表里的折线图 下表列出了Chart对象的事件。在该表格里演示的示例过程应该在刚才放置在图表工作表里的折 线图上试验(参见图14-7)。内嵌于工作表里的图表的事件需要特别的设置,本章稍候将讲解。 1. 在VBE编辑器窗口,激活工程浏览器窗口并打开Microsoft Excel对象文件夹。 2. 双击图表对象Chart1(销售分析图)。 3. 在代码窗口,输入事件过程,如下表所示。 4. 激活图表工作表,并执行一些触发事件过程的操作。例如,单击图表标题后,应该触发 Chart_MouseDown和Chart_Select事件。 事件名称 描述 Activate 当用户激活图表工作表时,该事件发生。 Private Sub Chart_Activate() MsgBox "您已经激活了这个图表工作表." End Sub Deactivate 当用户离开该图表时,该事件发生。 Private Sub Chart_Deactivate() MsgBox "好像您想离开这个图表工作表." End Sub Select 当用户选择了某个图表元素时,该事件发生。 Private Sub Chart_Select(ByVal ElementID As Long, _ ByVal Arg1 As Long, ByVal Arg2 As Long) If Arg1 <> 0 And Arg2 <> 0 Then MsgBox ElementID & ", " & Arg1 & ", " & Arg2 End If 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 360 共 612 页 If ElementID = 4 Then MsgBox "您选择了图表标题." ElseIf ElementID = 24 Then MsgBox "您选择了图例." ElseIf ElementID = 12 Then MsgBox "您选择了图例关键字." ElseIf ElementID = 13 Then MsgBox "您选择了图例条目." End If End Sub ElementId返回一个代表所选图表元素的常数。参数Arg1和Arg2用于某些相关的图表元素上。例如, 图表坐标轴(ElementId=21)可以指定是主坐标轴(Arg1=0)或者次坐标轴(Arg1=1),而坐标轴的类型则 由参数Arg2指定,它可以是下面三个值之一: 0 – 分类轴,1 – 数值轴,或者3 – 系列轴 SeriesChange 当用户更改图表数据点时,该事件发生。Object对象应该在类模块 里使用关键字WithEvents进行声明。 Calculate 当用户选择新数据或者改变图表数据时,该事件发生。 Private Sub Chart_Calculate() MsgBox "电子表格中的数据" & vbCrLf _ & "已改变.您的图表已更新." End Sub Resize 当用户改变图表大小时,该事件发生。Chart对象应该在类模块里 面使用WithEvents关键字来声明。 DragOver 当用户拖曳数据到该图表时,该事件发生。Chart对象应该在类模 块里面使用WithEvents关键字来声明。 DragPlot 当用户拖曳单元格区域到图表时,该事件发生。Chart对象应该在 类模块里面使用WithEvents关键字来声明。 BeforeDoubleClick 当用户双击图表时,引发该事件 BeforeRightClick 当用户右键单击图表时,该事件发生。 Private Sub Chart_BeforeRightClick(Cancel As Boolean) Cancel = True End Sub 当将参数Cancel设置为True后,就不能访问图表区域的快捷菜单了。 MouseDown 当光标在图表上并按下鼠标时,该事件发生。 Private Sub Chart_MouseDown(ByVal Button As Long, _ ByVal Shift As Long, ByVal x As Long, ByVal y As _ Long) If Button = 1 Then MsgBox "您按下了鼠标左键. " ElseIf Button = 2 Then MsgBox "您按下了鼠标右键. " Else MsgBox "您按下了鼠标中键. " End If End Sub 第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 361 Button参数决定哪个鼠标键被按下(MouseDown事件),或者释放(MouseUp事件): 1 – 鼠标左键,2 – 鼠标右键,4 – 鼠标中键 Shift参数指定Shift键、Ctrl键和Alt键的状态: 1- 选择了Shift键、2 – 选择了Ctrl键、以及4 – 选择了Alt键 参数x, y 分别指定鼠标指针坐标。 MouseMove 当鼠标指针坐标在图表之上变化时,该事件发生。 MouseUp 当鼠标按键在图表之上释放时,该事件发生。 14.6.1 内嵌图表事件 想要捕捉工作表内嵌图表的事件,首先要在类模块里使用关键字WithEvents创建一个新对象。 我们按照下面的步骤来看看如何实现它: 1. 激活VBE编辑器窗口。 2. 在工程浏览器里,选择VBAProject(ChartEvents.xls)。 3. 选择“插入”|“类模块”。 4. 在“类模块”文件夹里,您将看到一个名为Class1的模块。 5. 在属性窗口,将Class1重命名为clsChart。 6. 在类模块的代码窗口,声明一个对象变量,它将代表产生事件的图表对象。 Public WithEvents xlChart As Excel.Chart 关键字Public将使对象变量xlChart可用于当前VBA工程里的所有模块。使用WithEvents关 键字声明一个对象变量,将公开该被定义对象类型的所有事件。 输入上面的声明之后,对象变量xlChart就会添加到代码窗口左上角的对象下拉列表中,而 与该对象变量相对应的事件就会出现在代码窗口右上角的过程下拉列表里面。 7. 打开对象下拉列表框并选择变量xlChart名称,现在代码窗口将出现xlChart_Activate事件过程的 构架: Private Sub xlChart_Activate() End Sub 8. 在该事件过程里添加VBA代码。在本练习中,我们将添加一条语句来显示一个信息框。添加完 这条语句后,VBA过程应该像这样: Private Sub xlChart_Activate() MsgBox "您已经激活了一个内嵌于下面工作表中的图表:" & _ ActiveSheet.Name End Sub 输入完事件过程后,仍然需要通知VB,您打算使用它。 9. 在工程浏览器窗口,双击ThisWorkbook对象,并在[ThisWorkbook(代码)]窗口中输入下面的语 句来创建一个名为clsChart的新实例: Dim myChart As New clsChart 上面显示的指令声明一个名为myChart的对象变量,该变量将指向位于类模块clsChart里的对象 xlChart。关键字New告诉VBA去创建指定对象的新实例。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 362 共 612 页 10. 在ThisWorkbook(代码)窗口输入下面的过程来初始化对象变量myChart: Sub InitializeChart() '使用Chart对象连接类模块和它的对象 Set myChart.xlChart = _ Worksheets(1).ChartObjects(1).Chart End Sub 11. 运行InitializeChart过程。运行该过程之后,输入在类模块里的事件过程就会被相应的事件触发。 12. 激活Excel窗口,并且单击内嵌图表。这次,在第七步输入的xlChart_Activate事件过程将会被触 发。 13. 现在,可以在类模块里给内嵌图表输入其它的事件过程了。 14.7 可为Application对象识别的事件 如果想要你的事件过程无论在哪个当前活动的Excel工作簿上都能执行,那么需要创建 Application对象的事件过程。Application对象的事件过程具有全局性,这意味着只要Excel应用程序 是打开的,那么过程代码就会执行以响应某个事件。 Application对象的事件列在下面的表格中。和内嵌图表类似,Application对象的事件过程要求在 类模块里使用关键字WithEvents创建一个新的对象。表格中演示的事件过程示例应该在类模块里面 输入。在VBE编辑器窗口选择“插入”|“类模块”,在属性窗口重命名类模块为clsApplication,在 [clsApplication(代码)]窗口使用WithEvents关键字声明一个对象变量来代表Application对象,如下所 示: Public WithEvents App As Application 在声明语句下面,输入下面的事件过程,如表格所示:App_NewWorkbook 、 App_WorkbookOpen 、 App_WorkbookBeforeSave 、 App_WorkbookBeforeClose 、 App_Sheet- SelectionChange和 App_WindowActivate。在类模块中输入完这些过程的代码后,插入一个标准模 块到当前VBA工程里(选择“插入”|“模块”)。在标准模块里,创建一个类clsApplication的新实例, 并且将位于类模块clsApplication里的对象和代表Application对象的对象变量App连接起来,如下所 示: Dim DoThis As New clsApplication Public Sub InitializeAppEvents () Set DoThis.App = Application End Sub 现在将鼠标光标置于过程InitializeAppEvents 里并且按下F5 键运行它。运行过程 InitializeAppEvents的结果是类模块的对象App将会指向Excel应用程序。从现在开始,当某个具体事 件发生时,您已经输入在类模块里的事件过程代码就会被执行。如果不想响应Application对象产生 的事件,可以通过在一个标准模块里输入下面的过程(然后运行它)来中断对象和它的变量之间的连 接: Public Sub CancelAppEvents() Set DoThis.App = Nothing End Sub 当设置对象变量为Nothing时,实际上释放了内存,并且断开了对象变量和该变量指向的对象之 间的连接。当运行过程CancelAppEvents后,写在类模块里面的事件过程代码在某个事件发生就不第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 363 会自动执行。 注意:所有在本表格里演示的事件过程都需要在类模块里使用关键字WithEvents声明一个对象 变量。 事件名称 描述 NewWorkbook 当用户创建一个新工作簿时,该事件发生。 Private Sub App_NewWorkbook(ByVal _ Wb As Workbook) Application.DisplayAlerts = False If Wb.Sheets.Count = 3 Then Sheets(Array(2, 3)).Delete End If Application.DisplayAlerts = True End Sub WorkbookOpen 当用户打开一个工作簿时,该事件发生。 Private Sub App_WorkbookOpen(ByVal Wb As Workbook) If Wb.FileFormat = xlCSV Then If MsgBox("您想保存" & vbCrLf _ & " 这个文件作为一个工作簿吗?", vbYesNo, _ "原始文件格式: " _ & "逗号分隔符文件") = vbYes Then Wb.SaveAs FileFormat:=xlWorkbookNormal End If End If End Sub WorkbookActivate 当用户将焦点移到一个开启的工作簿时,该事件发生。 WorkbookDeactivate 当用户将焦点从一个开启的工作簿移开时,该事件发生。 WorkbookNewSheet 当用户在一个打开的工作簿上添加一个新工作表时,该事件发生。 WorkbookBeforeSave 一个打开的工作簿被保存之前,该事件发生。 Private Sub App_WorkbookBeforeSave(ByVal _ Wb As Workbook, _ ByVal SaveAsUI As Boolean, _ Cancel As Boolean) If Wb.Path <> vbNullString Then ActiveWindow.Caption = Wb.FullName & _ " [上一次保存的时间: " & Time & "]" End If End Sub WorkbookBeforePrint 一个打开的工作簿被打印之前,该事件发生。 Private Sub App_WorkbookBeforePrint(ByVal _ Wb As Workbook, Cancel As Boolean) Wb.PrintOut Copies:=2 End Sub WorkbookBeforeClose 一个打开的工作簿被关闭之前,该事件发生。 Private Sub App_WorkbookBeforeClose( ByVal _ Wb As Workbook, Cancel As Boolean) Dim r As Integer Sheets.Add r = 1 For Each p In Wb.BuiltinDocumentProperties On Error GoTo ErrorHandle 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 364 共 612 页 Cells(r, 1).Value = p.Name & " = " & _ ActiveWorkbook. _ BuiltinDocumentProperties _ .Item(p.Name).Value r = r + 1 Next Exit Sub ErrorHandle: Cells(r, 1).Value = p.Name Resume Next End Sub WorkbookAddInInstall 用户安装一个作为加载宏的工作簿时,该事件发生。 WorkbookAddInUninstall 用户卸载一个作为加载宏的工作簿时,该事件发生。 SheetActivate 当用户激活一个打开工作簿中某个工作表时,该事件发生。 SheetDeactivate 当用户离开一个打开工作簿中某个工作表时,该事件发生。 SheetSelectionChange 当用户改变一个打开工作簿中工作表上的所选区域时,该事件发 生。 Private Sub App_SheetSelectionChange( _ ByVal Sh As Object, ByVal Target As Range) If Selection.Count > 1 Or _ (Selection.Count < 2 And _ IsEmpty(Target.Value)) Then Application.StatusBar = Target.Address Else Application.StatusBar = Target.Address & _ "(" & Target.Value & ")" End If End Sub SheetChange 当用户在一个打开的工作簿里改变单元格的内容时,该事件发生。 SheetCalculate 当用户重新计算某个开启的工作簿里的工作表时,该事件发生。 SheetBeforeDoubleClick 当用户双击一个打开的工作簿里工作表单元格时,该事件发生。 SheetBeforeRightClick 当用户在一个打开的工作簿里右击工作表单元格时,该事件发生。 WindowActivate 当用户转移焦点到一个打开的窗口时,该事件发生。 Private Sub App_WindowActivate(ByVal _ Wb As Workbook, ByVal Wn As Window) Wn.DisplayFormulas = True End Sub WindowDeactivate 当用户将焦点从打开的窗口移走时,该事件发生。 WindowResize 当用户调整一个打开的窗口的大小时,该事件发生。 WorkbookPivotTableClose- Connection (Excel 2002的新事件) 在数据透视表报告连接被关闭后,该事件发生。 WorkbookPivotTableOpen- Connection (Excel 2002的新事件) 在数据透视表报告连接被打开后,该事件发生。 14.8 查询表事件 查询表(query table)是Excel工作表里的一个表,代表从外部数据源获取的数据,例如SQL服务第 14 章 13BExcel2002 事件编程 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 365 器数据库、Access数据库、网页、或者文本文件。查询表用QueryTable对象代表。Excel为QueryTable 对象提供了两个事件:BeforeRefresh和AfterRefresh。想要试验一下本章后面的表中演示的示例过 程,那么执行下面的这些操作。本练习假设您的计算机上安装了Access以及其示例Northwind数据库。 1. 在Excel应用程序窗口,选择“数据”|“导入外部数据”,并且选择“新建数据库查询”以创建 一个新的数据库查询。 2. 在“选择数据源”对话框里,选择<新数据源>,并单击“确定”按钮。 3. 在“创建新数据源”对话框里,输入SampleDb作为数据源名称。 4. 在“创建新数据源”对话框上,从第二步旁边的下拉列表里,选择Microsoft Access driver (*.mdb)。 5. 单击“连接”按钮。 6. 在“ODBC Microsoft Access安装”对话框上单击“选择”按钮。 7. 在“选择数据库”对话框中,找到文件Northwind.mdb。该文件通常可以在C:\Program Files\Microsoft Office\Office\Samples文件夹中找到。 8. 选择该文件并且单击“确定”按钮关闭“选择数据库”对话框。 9. 再单击“确定”按钮退出“ODBC Microsoft Access安装”对话框。 10. 在“创建新数据源”对话框的第四步,在下拉列表框里选择“类别”。 11. 单击“确定”按钮关闭“创建新数据源”对话框。 12. 在“选择数据源”对话框上,数据源名称SampleDb现在应该被加亮了,单击“确定”按钮。 13. 在“查询向导 – 选择列”对话框里,单击“>”按钮,将“类别”表中所有的字段移到查询框 的列中。 14. 单击“下一步”按钮,直到看到“查询向导 – 完成”对话框。 15. 在“查询向导 – 完成”对话框上,确保“将数据返回到Microsoft Excel”选项按钮已选中,然 后单击“完成”按钮。 16. 在“导入数据”对话框,当前电子表格单元格被选择,在当前工作表中单击单元格A1改变单元 格引用并单击“确定”按钮关闭这个对话框。 完成上面的步骤后,Northwind数据库里“类别”表中的数据应该被放置在当前工作表里了。使 用了相当多的步骤来重新获得这些数据。在下章中,您将学习如何编程创建查询表。 想要给QueryTable对象编写事件过程,必须创建一个类模块并且使用WithEvents关键字声明一 个QueryTable对象。 1. 插入一个类模块到当前VBA工程中并重命名为clsQryTbl。 2. 在[clsQryTbl(代码)]窗口,输入下面的语句: Public WithEvents qrytbl As QueryTable 当使用WithEvents关键字声明新对象qrytbl后,它就会出现在类模块的对象下拉列表中 3. 在[clsQryTbl(代码)]窗口,输入在下面的表格中所示的两个事件过程:QryTbl_BeforeRefresh和 QryTbl_AfterRefresh。在能够触发这些事件过程之前,必须将在类模块里声明的对象(qrytbl)和 某个特定的QueryTable对象连接起来 4. 在当前VBA工程中插入一个标准模块,并输入下面的代码: Public Sub Auto_Open() 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 366 共 612 页 ' 使用Query对象连接类模块和它的对象 Set sampleQry.qrytbl = ActiveSheet.QueryTables(1) End Sub 上面的程序创建了QueryTable类(clsQryTbl)的一个新实例,并且将它和活动工作表里的第 一个查询表连接起来。当打开该工作簿时,Auto_Open过程会自动执行,因此不必手动运行它, 以确保当数据被刷新时,查询事件将会被触发。 5. 运行第四步输入的Auto_Open过程,在运行完该初始化过程后,在类模块里声明的对象就会指 向特定的QueryTable对象 6. 在放置从Access里导入的“类别”表的工作表里,更改某个类别名称。选择查询表中的任意单 元格,并且单击“外部数据”工具栏上的“刷新数据”按钮,或者选择“数据”|“刷新数据”。 这次,事件过程qrytbl_BeforeRefresh将会被触发,您将看到一个自定义信息框。如果单击“是”, 该数据将会被数据库里已存在的数据刷新掉,您更改过的数据将会被覆盖。 事件名称 描述 BeforeRefresh 在查询表被刷新数据之前,该事件发生。 Private Sub qryTbl_BeforeRefresh(Cancel As Boolean) Response = MsgBox("您确定想 " _ & " 现在刷新吗?", vbYesNoCancel) If Response = vbNo Then Cancel = True End Sub AfterRefresh 在查询完成或者被取消后,该事件发生。如果查询成功完成,则参 数Success为True。 Private Sub qryTbl_AfterRefresh(ByVal Success As Boolean) If Success Then MsgBox "数据已被刷新." Else MsgBox "查询失败." End If End Sub 本章小结和下一章内容简介 在本章中,您获得了Excel中的事件和事件编程的经验,不管您是否计划给他人创建电子表格应 用程序去使用,还是简单地将您的日常工作自动化,这些技术都是极其有价值的。 Excel提供了许多可以响应的事件。通过编写事件过程,您可以更改对象响应事件的方式。当一 个指定的事件发生时,您的事件过程可以简单为一条语句,仅仅显示一条自定义信息;也可以复杂 到包括一些判断语句和其它允许改变程序流程的编程结构。当某个事件发生时,VBA将会直接运行 适当的事件过程,而不是按标准的内置方式进行响应。您已经学习了一些编写在标准模块里的事件 过程(工作簿、工作表、图表工作表),而其它的事件过程(内嵌图表、应用程序、查询表)则需要在类 模块里使用WithEvents关键字创建一个新对象。您也学习了使用EnableEvents属性激活或者禁用事 件。 在下章中,您将学习如何在Excel VB环境下编写VBA过程来使用Microsoft Access数据库。 第15章 在Excel里使用Access 在第九章,您已经学习了从 Excel 里通过自动控制(允许一个 应用程序控制另一个应用程序的对象)来控制 Microsoft Word 和 Outlook …… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 368 共 1 6 2 页 在第九章,您已经学习了从Excel里通过自动控制(允许一个应用程序控制另一个应用程序的对象) 来控制Microsoft Word和Outlook。本章介绍如何通过使用下面的方法编程从Excel里使用Access以及 获取Access数据到电子表格中:Automation、DAO(Data Access Objects)以及ADO(ActiveX Data Objects)。在学习如何使用Excel VBA在Access数据库里执行各种任务以及在Access数据库里获取 和存储数据前,先简单地介绍一下Microsoft Access用来编程对其对象访问的数据访问方法。 15.1 对象库 Microsoft Access数据库由各种类型的对象组成,这些对象存储在不同的对象库中,用来使用 VBA语言显示、存储或者管理数据。在本章,您将从下面列出的几个库里访问对象、属性和方法。 Microsoft Access 10.0对象库提供了用来显示数据和处理Microsoft Access 2002应用程序的对 象。这个库存储在MSACC10.OLB文件里,可以在C:\Program Files\Microsoft Office\Office文件夹里 找到。在“引用”对话框上设置了对该库的引用之后(将在下节介绍),将能够在对象浏览器里访问该 库的对象、属性和方法(参见图15-1)。 图15-1:Access库 Microsoft Access DAO 3.6对象库提供了数据访问对象(DAO),允许决定数据库的结构和使用 VBA操作数据。该库存储在DAO360.DLL文件里,可以在C:\Program Files\Common Files\Microsoft Shared\DAO文件夹里找到。在“引用”对话框上设置了对该库的引用之后(将在下节介绍),将能够第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 369 在对象浏览器里面访问该库的对象、属性和方法(参见图15-2)。 图15-2:DAO库 Microsoft ActiveX Data Objects 2.5(ADO)提供了ActiveX数据对象(ADO)并允许使用OLEDB提 供者访问和操作数据。ADO对象使得在Access数据库里与数据源建立链接以及读取、插入、修改和 删除数据成为可能。该库存储在MSADO15.DLL 里,可以在C:\Program Files\Common Files\system\ado文件夹里找到。在“引用”对话框上设置了对该库的引用之后,将能够在对象浏览 器里访问该库的对象、属性和方法(参见图15-3)。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 370 共 612 页 图15-3:ADODB库 Microsoft ADO Ext. 2.5 for DDL(动态数据链接) and Security(ADOX)存储可以定义数据库结构 和安全的对象。例如,可以定义表、索引和关系、以及创建和修改用户和用户组帐户。 该库存储在MSADOX.DLL里并且可以在C:\Program Files\Common Files\System\ado文件夹里 找到。在“引用”对话框上设置了对该库的引用之后,将能够在对象浏览器里面访问该库的对象、 属性和方法(参见图15-4)。 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 371 图15-4:ADOX库 Microsoft Jet and Replication Objects 2.6库(JRO)包含用于数据库复制里的对象。该库存储在 MSJRO.DLL里,可以在C:\Program Files\Common Files\System\ado文件夹里可以找到。在“引用” 对话框上设置了对该库的引用之后,将能够在对象浏览器里面访问该库的对象、属性和方法(参见图 15-5)。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 372 共 612 页 图15-5:JRO库 VBA对象库提供了很多VBA对象、函数和方法来访问文件系统、处理日期和时间函数、进行数 学和财务计算、与用户交互动、转换数据和读取文本文件。该库存储在位于C:\Program Files\Common Files\Microsoft Shared\VBA\VBA6文件夹中的VBE6.DLL文件里。当安装Microsoft Excel 2002时, 就会自动设置对该库的引用。该库在Office 2002所有的应用程序中共享(参见图15-6)。 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 373 图15-6:VBA库 15.1.1 建立对对象库的引用 要操作Access 2002里的对象,首先要创建对Microsoft Access 10.0对象库的引用(如果您使用 Microsoft Office 2000,则选择Microsoft Access 9.00 Object Library)。 1. 在VBE编辑器窗口中,选择“工具”|“引用”以打开“引用”对话框。该对话框列出了计算机 上所有可用的类型库。 2. 在列表中找到Microsoft Access 10.0 Object Library,并选中。 3. 关闭“引用”对话框。 一旦创建了对Access类型库的引用,就可以使用对象浏览器来查看该应用程序的对象,属性和 方法(参见上节的图15-1)。 使用“引用”对话框创建对其它将在本章练习中访问的对象库的引用。您可以在上节找到库列 表。您可以忽略对Microsoft Jet and Replication Objects 2.6 Library(JRO)的引用,因为这里不会用 到它。如果您对数据库复制有兴趣,那么可以找到很多涉及到该主题的Microsoft Access编程的书, 包括我的一本由Wordware Publishing出版的书《Learn Microsoft Access 2000 Programming by Example》(ISBN 1—55622—770—1)。 技巧15—1:创建对Microsoft Access对象库引用的好处 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 374 共 612 页 当设置对Access对象库的引用后,可以: „ 在对象浏览器里查找Microsoft Access的对象、属性和方法。 „ 可以在VBA过程里直接运行Access函数。 „ 可以声明该应用程序的Application类型的对象变量,而不必声明为普通的Object类型。声明对象变量如Dim objAccess As Access.Application(早期绑定)要比将其声明为Dim objAccess As Object(后期绑定)要快。 „ 可以在VBA代码里使用Microsoft Access内置常量。 „ VBA过程将运行得更快。 15.2 连接到Access 本章中的示例程序使用了多种连接到Microsoft Access的方法,本节将详细介绍每种方法。可以 使用下面的三种方法之一来建立对Microsoft Access的连接: „ 自动控制 „ Data Access Objects(DAO) „ ActiveX Data Objects(ADO) 要访问数据库里的数据,需要打开它。如何打开某个具体的数据库,很大程度上取决于使用了 哪种建立数据库连接的方法。 15.2.1 使用自动控制连接到Microsoft Access数据库 当通过自动控制从Excel(或者其它应用软件)里使用Access时,需要采取下面的步骤: 1. 设置对Microsoft Access 10.0 Object Library的引用(参见本章前面的“建立对对象库的引用”) 2. 声明一个对象变量,代表Access Application对象。 Dim objAccess As Access.Application 在该声明中,objAccess为变量名称,而Access.Application使用提供该对象的VB对象库名称来 限定该对象变量。 3. 返回对Application对象的引用,并且将该引用赋给对象变量。使用CreateObject函数、GetObject 函数或者关键字New来返回对Application对象的引用。使用Set语句将引用赋值到对象变量。 Dim objAccess As Object Set objAccess = CreateObject(“Access.Application.10”) 当对象没有当前实例时,使用CreateObject函数返回对Application对象的引用。如果 Microsoft Access已经运行,那么新的实例开启并创建指定的对象。 Dim objAccess As Object Set objAccess = GetObject(, “Access.Application.10”) 或者 Set objAccess = GetObject(“C:\Program Files\ _ & “Microsoft Office\Office\Samples\Northwind.mdb”) 使用GenObject函数返回对Application对象的引用,以使用Access的当前实例,或者开启Access 并打开一个文件(更多信息参见技巧15—2)。 Dim objAccess As New Access.Application 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 375 上面的语句使用关键字New声明了一个对象变量,返回对Application对象的引用,并且将该引 用赋给对象变量,所有操作一步完成。 也可以使用两步来声明一个对象变量,这样会对该对象有更多的控制: Dim objAccess As Access.Application Set objAccess = New Access.Application „ 当使用关键字New声明对象变量时,Access应用程序并不会打开,直到开始在VBA代码里真正 使用该对象变量。 „ 当使用关键字New声明Application对象变量时,就会自动创建Access的一个新实例,不需要使 用CreateObject函数。 „ 使用关键字New创建Application对象的新实例比使用CreateObject函数更快。 因为计算机上可能安装了多个版本的Access,所以需要在GetObject函数或者CreateObject函数 的参数里包括版本号。下面列出了最后四个Access版本: Microsoft Access 2003 Access.Application.11 (译者加) Microsoft Access 2002 Access.Application.10 Microsoft Access 2000 Access.Application.9 Microsoft Access 97 Access.Application.8 Microsoft Access 95 Access.Application.7 一旦使用第三步所述的方法之一创建了Application 类的新实例,就可以通过 OpenCurrentDatabase或者NewCurrentDatabase方法来打开一个数据库或者创建一个新数据库。可 以使用CloseCurrentDatabase方法关闭通过自动控制打开的Access数据库。 技巧15—2:GetObject函数的参数 GetObject函数的第一个参数 – Pathname – 是可选的,当想要使用某个特定文件里的对象时使用。第二 个参数 – Class – 是必需的,它指定哪个应用程序创建该对象以及对象的类型。当第一个参数为可选的而第二 个参数为必需的时,就必须在第一个参数的位置放置一个逗号,如下所示: Dim objAccess As Object Set objAccess = GetObject(, Access.Application.10”) 因为GetObject函数的第一个参数(Pathname)被忽略了,所以就返回对Access Application类的现存实例的 引用。 Dim objAccess As Object Set objAccess = GetObject(“C:\Program Files\ & “Microsoft Office\Office\Samples\Northwind.mdb”) 当GetObject函数的第一个参数是数据库文件的名称,那么就会使用该特定的数据库激活或者创建Access Application类的一个新实例。 现在您知道了如何创建代表Application对象的对象变量,那么就来看看一个从Excel VBA过程里 直接打开Access数据库的程序示例。 下面显示的过程AccessViaAutomation将打开随Access发布的示例Northwind数据库。如果可 用,该过程将使用Access自动控制服务器的当前实例。如果Access没有运行,将发生运行时错误, 并且该对象变量将被设置为Nothing。可以通过在程序里放置On Error Resume Next 语句捕捉错误。学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 376 共 612 页 因此,如果Access没有运行,新的Access实例就会被打开。本例子使用关键字New来启动Access 的新实例。正如前面所提到的,除了使用New关键字创建一个新的对象实例之外,也可以使用 CreateObject()函数来启动自动控制服务器的新实例,如下所示: Set objAccess = GetObject(, “Access.Application.10”) If objAccess Is Nothing Then Set objAccess = CreateObject(“Access.Application.10”) End If 当使用自动控制启动Access时,将在任务栏上看到Access程序图标。Access Application对象的 Visible属性被设置为False,想要恢复该应用程序窗口,将其Visible属性设置为True。 在使用时,对象要消耗内存和系统资源。要释放这些资源,那么每次使用完时总应该关闭该对 象。下面演示的示例过程首先使用CloseCurrentDatabase方法关闭Northwind数据库。接下来,使用 Quit方法关闭Access应用程序对象。在关闭对象后,也应该将对象变量设置为关键字Nothing以释放 该变量使用的内存资源。 可以将对象变量声明为模块级而不是过程级变量,以避免关闭Access实例。 在这样的环境下,对数据库的连接就会保持打开,直到关闭该自动控制的控制源(Excel)或者在 VBA代码里使用Quit方法关闭它。 Sub AccessViaAutomation() Dim objAccess As Access.Application Dim strPath As String On Error Resume Next Set objAccess = GetObject(, "Access.Application.9") If objAccess Is Nothing Then ' 获取对Access Application对象的引用 Set objAccess = New Access.Application End If strPath = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\northwind.mdb" ' 打开 Northwind数据库 With objAccess .OpenCurrentDatabase strPath If MsgBox("您想使Access应用程序 " & vbCrLf _ & "可见吗?", vbYesNo, _ "Display Access") = vbYes Then .Visible = True MsgBox "注意,Access Application图标" _ & "现在显示在Windows任务栏中." End If '关闭数据库并退出Access .CloseCurrentDatabase .Quit End With Set objAccess = Nothing End Sub 使用F8键逐语句运行上面的过程。 技巧15—3:打开被保护的Access数据库 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 377 如果Access数据库用密码进行了保护,那么会提示用户输入正确的密码。必须使用Data Access Objects (DAO)或者ActiveX Data Access (ADO)编程来打开密码保护的Access数据库。下面的示例使用了Access对象的 DBEngine属性来确定该数据库的密码。要让该过程工作,就必须创建对Microsoft DAO 3.6对象库的引用,如本 章开头所述。 Sub OpenSecuredDB() Static objAccess As Access.Application Dim db As DAO.Database Dim strDb As String strDb = "C:\Program Files\Microsoft Office\" & "Office\Samples\ _ Northwind.mdb" Set objAccess = New Access.Application Set db = objAccess.DBEngine.OpenDatabase(Name:=strDb, Options:=False, _ ReadOnly:=False, Connect:=";PWD=test") With objAccess .Visible = True .OpenCurrentDatabase strDb End With db.Close Set db = Nothing End Sub 15.2.2 使用DAO连接到Microsoft Access数据库 要使用数据访问对象(DAO)连接到Access数据库,必须首先在“引用”对话框里创建对Microsoft Data Access Objects 3.6 Library的引用(参见本章前面“建立对对象库的引用”一节)。下面演示的 示例过程DAOOpenJetDatabase,使用DBEngine对象的OpenDatabase方法来打开Northwind数据 库,并通知用户该数据库已被打开。DBEngine对象可以初始化称为Microsoft Jet Engine的标准数据 库引擎并打开一个数据库文件(.mdb)。过程使用Close方法关闭数据库文件。 Sub DAOOpenJetDatabase() Dim db As DAO.Database Set db = DBEngine.OpenDatabase _ ("C:\Program Files\Microsoft Office\Office\Samples\Northwind.mdb") MsgBox "Northwind数据库已打开." db.Close MsgBox "Northwind数据库已关闭." End Sub 15.2.3 使用ADO连接到Microsoft Access数据库 最新的并建议使用建立对Access数据库连接的方法是使用ActiveX数据对象(ADO),必须先设置 对Microsoft ActiveX Data Objects 2.5 Library或者更高版本的引用。示例过程ADOOpenJetDatabase 使用Connection对象连接到Northwind数据库,该对象通过Open方法打开。注意,Open方法需要一 个包含数据提供者名称(本例中为Microsoft.Jet.OLEDB.4.0)和数据源名称(本例中为要打开的数据库学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 378 共 612 页 文件完整名称)的连接字符串参数: con.Open _ "Provider=Microsoft.Jet.OLEDB.4.0;" _ & "Data Source=C:\Program Files\Microsoft Office\" _ & "Office\Samples\NorthWind.mdb;" 在建立对Northwind数据库的连接之后,可以使用Recordset对象来访问其数据。Recordset对象 用来在记录级操作数据,由记录(行)和字段(列)组成。要获得一组记录,需要使用Open方法打开 Recordset。该方法需要指定信息,例如Recordset记录源: rst.Open "SELECT * FROM Customers " & _ "WHERE City = 'London'", con, _ adOpenForwardOnly, adLockReadOnly 记录源可以是返回记录的数据库表名称、或查询、或SQL语句。在指定记录源后,还需要指明 数据库连接(con)和两个常数,一个定义光标类型(adOpenForwardOnly),另一个为锁定类型 (adLockReadOnly)。常 数adOpenForwardOnly告诉VBA创建只能向前翻的记录集,按记录的顺序滚 动。第二个常数adLockReadOnly指定在编辑时放置在记录上的锁定类型,该记录为只读,意味着不 能改变数据。过程的下一部分使用For…Each…Next循环遍历Recordset并将第一条记录的内容打印 到立即窗口: For Each fld In rst.Fields Debug.Print fld.Name & "=" & fld.Value & vbCr Next 在获取第一条记录的数据后,使用了Close方法关闭Recordset,另一个Close方法关闭对Access 数据库的链接: rst.Close con.Close ADOOpenJetDatabase过程如下: Sub ADOOpenJetDatabase() Dim con As New ADODB.Connection Dim rst As New ADODB.Recordset Dim fld As ADODB.Field '连接数据库 con.Open _ "Provider=Microsoft.Jet.OLEDB.4.0;" _ & "Data Source=C:\Program Files\Microsoft Office\" _ & "Office\Samples\NorthWind.mdb;" '基于SQL语句打开Recordset rst.Open "SELECT * FROM Customers " & _ "WHERE City = 'London'", con, _ adOpenForwardOnly, adLockReadOnly '在调试窗口打印第一条记录中字段的值 For Each fld In rst.Fields Debug.Print fld.Name & "=" & fld.Value & vbCr Next '关闭Recordset以及与Access的连接 rst.Close con.Close '销毁对象变量以释放资源 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 379 Set rst = Nothing Set con = Nothing End Sub 15.3 从Excel执行Access任务 从Excel连接到Access后,就可以执行Access应用程序的不同任务。本节具体演示如何使用VBA 代码来: „ 创建新的Access数据库 „ 打开已存在的数据库表 „ 创建全新的数据库表 „ 打开数据库报表 „ 运行Access函数 15.3.1 创建新的Access数据库 如果想要通过编程将Excel数据传送到一个新的Access数据库里面,那么需要使用VBA代码从头 开始创建一个数据库。下面的过程演示了如何使用数据访问对象(DAO)来建立和Access的连接。 Workspace对象的CreateDatabase方法在C盘的根目录下创建一个名为ExcelDump.mdb的新数据 库。然后,Database对象的CreateTableDef方法用来创建一个名为tblStates的表。在表能够添加到 数据库之前,必须先创建一个字段并附加在该表上。该过程创建了三个文本字段(dbText),每个分别 可以储存2、25和25个字符。每个字段创建后,使用Append方法将这些字段添加到TableDef对象的 Fields集合里。字段一旦创建并添加到表之后,就会使用Append方法将表本身添加到数据库。因为 名为“C:\ExcelDump.mdb”的数据库可能已经存在于指定的目录下,该过程包括了一个错误处理程序 来删除现有文件,以确保数据库创建过程继续。因为其它错误也可能发生,Else子句包括了显示错 误和该错误描述的语句,并允许退出过程。 Sub NewDB_DAO() Dim db As DAO.Database Dim tbl As DAO.TableDef Dim strDb As String Dim strTbl As String On Error GoTo Error_CreateDb_DAO strDb = "C:\ExcelDump.mdb" strTbl = "tblStates" '创建名为ExcelDump的新数据库 Set db = CreateDatabase(strDb, dbLangGeneral) ' 创建名为tblStates的新表 Set tbl = db.CreateTableDef(strTbl) '创建字段并添加它们到Fields集合 With tbl .Fields.Append .CreateField("StateId", dbText, 2) .Fields.Append .CreateField("StateName", dbText, 25) .Fields.Append .CreateField("StateCapital", dbText, 25) End With '添加表对象到TableDefs db.TableDefs.Append tbl 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 380 共 612 页 ' 关闭数据库 db.Close Set db = Nothing MsgBox "有一个新数据库在您的硬盘中. " & vbCrLf _ & "这个数据库文件包含一个表" & strDb & vbCrLf _ & "名为" & strTbl & "." & vbCrLf _ & "在激活这个数据库之前,关闭Excel应用程序." Exit_CreateDb_DAO: Exit Sub Error_CreateDb_DAO: If Err.Number = 3204 Then ' 如果数据库文件已存在则删除 Kill "C:\Exceldump.mdb" Resume Else MsgBox Err.Number & ": " & Err.Description Resume Exit_CreateDb_DAO End If End Sub 图15-7:通过Excel VBA过程创建的Access数据库表 15.3.2 打开Microsoft Access窗体 可以从Excel里打开Access窗体,也可以创建新窗体。下面的示例使用自动控制连接到Access, 一旦链接建立后,就使用OpenCurrentDatabase方法来打开示例Northwind数据库。接着,使用 DoCmd对象的OpenForm方法打开“客户”窗体,该窗体在普通视图(acNormal)下打开。如果在设 计视图里打开显示窗体,那么可以使用acDesign常数代替。DoCmd对象的Restore方法确保该窗体 显示在屏幕上而不是最小化。Access Application对象(objAccess)的Visible属性必须设置为True,以 确保窗体可见。注意,Access.Application对象变量(objAccess)在模块顶部声明。为了让该过程正确 地运行,必须建立对Microsoft Access Objcet Library的引用。图15-8显示了被打开的“客户”窗体。 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 381 ‘在模块顶部声明 Dim objAccess As Access.Application Sub DisplayAccessForm() Dim strDb As String Dim strFrm As String strDb = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\Northwind.mdb" strFrm = "客户" Set objAccess = New Access.Application With objAccess .OpenCurrentDatabase(strDb) .DoCmd.OpenForm strFrm, acNormal .DoCmd.Restore .Visible = True End With End Sub 图15-8:通过Excel VBA过程可以打开的Access窗体 如果您还想深入编程,那么从Excel VBA过程里创建一个全新的Access窗体,如下所示: ‘ 在模块顶部声明 Dim obAccess As Access.Application Sub CreateAccessForm() Dim myForm As Form Dim myDb As String Dim myCtrl As Control Dim strFrmName As String On Error GoTo Error_CreateForm myDb = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\Northwind.mdb" 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 382 共 612 页 strFrmName = "frmCustomForm" Set obAccess = New Access.Application obAccess.OpenCurrentDatabase myDb Set myForm = obAccess.CreateForm myForm.Caption = "本窗体通过Excel创建" myForm.RecordSource = "雇员" obAccess.DoCmd.Save , strFrmName '在窗体中创建一个标签和文字框 Set myCtrl = CreateControl(FormName:=strFrmName, _ ControlType:=acLabel, _ Left:=1000, Top:=1000) myCtrl.Caption = "姓:" myCtrl.SizeToFit Set myCtrl = CreateControl(FormName:=strFrmName, _ ControlType:=acTextBox, _ Parent:="", _ ColumnName:="LastName", _ Left:=2200, Top:=1000) With obAccess With .DoCmd .Save , strFrmName .Close acForm, strFrmName End With .CloseCurrentDatabase .Quit End With Set obAccess = Nothing MsgBox "在NorthWind数据库中,现在有 " & vbCrLf _ & "一个新的窗体,名为" & strFrmName & "." & vbCrLf _ & "在打开NorthWind数据库之前关闭Excel " & vbCrLf _ & "来察看这个窗体." ErrorHandler: Exit Sub Error_CreateForm: MsgBox Err & " :" & Err.Description Resume ErrorHandler End Sub 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 383 图15-9:Access窗体可以由Excel VBA过程创建(参见上面的CreateAccessForm过程代码) 15.3.3 打开Microsoft Access报表 可以从Excel里打开Access报表。下面的过程演示如何直接从Excel里显示已经存在的Access报 表。 ‘在模块顶部声明 Dim objAccess As Access.Application Sub DisplayAccessReport() Dim strDb As String Dim strRpt As String strDb = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\Northwind.mdb" strRpt = "各类产品" Set objAccess = New Access.Application With objAccess .OpenCurrentDatabase (strDb) .DoCmd.OpenReport strRpt, acViewPreview .DoCmd.Maximize .Visible = True End With End Sub 下面的过程更通用,因为它允许在任意Access数据库里显示任意的Access报表。注意,该过程 接受两个字符串参数:Access数据库名称和报表名称。 Sub DisplayAccessReport2(strDb As String, strRpt As String) Set objAccess = New Access.Application With objAccess .OpenCurrentDatabase (strDb) 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 384 共 612 页 .DoCmd.OpenReport strRpt, acViewPreview .DoCmd.Maximize .Visible = True End With End Sub 可以从立即窗口或者从如下所示的一个子过程里运行DisplayAccessReport2过程: „ 从立即窗口里运行DisplayAccessReport2过程 ‘在立即窗口里的一行中输入下面的语句: Call DisplayAccessReport2("C:\Program Files\Microsoft Office\Office\Samples\Northwind.mdb", " 按金额汇总销售额") „ 从一个子过程中运行DisplayAccessReport2过程: '在代码窗口中输入下面的过程 Sub ShowReport() Dim strDb As String Dim strRpt As String strDb = InputBox("输入数据库名(完整路径): ") strRpt = InputBox("输入报表名:") Call DisplayAccessReport2(strDb, strRpt) End Sub 图15-10:通过Excel VBA过程可以打开Access报表 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 385 15.3.4 运行Microsoft Access查询 接下来的两个示例过程将演示如何从Excel VBA过程里运行Access查询。在Access用户界面最 常用的查询类型是选择查询和参数查询。两个示例过程都使用Range对象的CopyFromRecordset方 法将查询到的数据放置到Excel工作表。通过ADO建立与数据库的连接。 ADOX对象库(参见本章前面的图15-4)允许访问数据库结构、安全和存储在数据库里面的过程。 该库中最上层的对象是Catalog对象,代表整个数据库。该对象包含一些数据库元素,例如表、字段、 索引、视图和存储的过程。使用Catalog对象的Create方法,可以创建一个新的数据库,例如: Dim cat As ADOX.Catalog Set cat = New ADOX.Catalog cat.Create "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=C:\ExcelDump2.mdb;" 上面的例子示范如何使用ActiveX Data Objects创建新的数据库。回想一下,在本章前面使用 DAO创建了一个叫做NewDB_DA的新数据库。 示例过程RunAccessQuery,首先创建一个可以指向Catalog对象的对象变量cat。接着,Catalog 对象的ActiveConnection属性定义建立数据库连接的方法: Set cat = New ADOX.Catalog cat.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & dbPath ADODB对象库里的Command对象(参见本章前面的图15-3)指定为了从数据源获取数据而想 要执行的命令。我们的过程偿试访问数据库里某特定的查询。 Set cmd = cat.Views(strQryName).Command Views集合是ADOX对象库的一部分,包含某特定目录的所有View对象。视图是筛选后的一组记 录,或者由其它表或者视图创建的虚拟表。获得对数据库里需要的查询的访问后,就可以按下面的 方式运行查询: Set rst = cmd.Execute Command对象的Execute方法允许激活某个特定的查询、SQL语句、或者存储的过程。然后, 返回的一组记录会通过使用Set关键字被赋给Recordset类型的对象变量。创建该组记录后,这些记 录就会通过使用CopyFromRecordset方法放置到Excel工作表中。 15.3.5 运行选择查询 Sub RunAccessQuery(strQryName As String) ' 运行本过程前,必须建立对所需对象库的引用 Dim cat As ADOX.Catalog Dim cmd As ADODB.Command Dim rst As ADODB.Recordset Dim i As Integer Dim dbPath As String dbPath = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\Northwind.mdb" Set cat = New ADOX.Catalog cat.ActiveConnection = _ 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 386 共 612 页 "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & dbPath Set cmd = cat.Views(strQryName).Command Set rst = cmd.Execute Sheets(2).Select For i = 0 To rst.Fields.Count - 1 Cells(1, i + 1).Value = rst.Fields(i).Name Next With ActiveSheet .Range("A2").CopyFromRecordset rst .Range(Cells(1, 1), _ Cells(1, rst.Fields.Count)).Font.Bold = True .Range("A1").Select End With Selection.CurrentRegion.Columns.AutoFit rst.Close Set cmd = Nothing Set cat = Nothing End Sub 想要运行上述过程的话,可以在立即窗口里面输入下述语句并回车: RunAccessQuery("当前产品列表") 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 387 图15-11:从Excel VBA过程运行Access查询的结果被放在了一个工作表里了 15.3.6 运行参数查询 可以运行Access 参数查询并将其结果放置于Excel 电子表格里面。例如,过程 RunAccessParamQuery运行Access数据库的Employee Sales by Country参数查询,并取得开始于 7/1/96结束于7/30/96期间的记录。Employee Sales by Country查询要求定义开始和结束日期的两个 参数。 应该使用Command对象的Paramenters集合定义这些参数: cmd.Parameters("[Beginning Date]") = StartDate cmd.Parameters("[Ending Date]") = EndDate 设置参数后,使用下面的语句执行查询: Set rst = cmd.Execute 该查询返回的记录会被赋给Recordset类型的对象变量,然后使用CopyFromRecordset方法复制 到工作表(参见本章后面更多关于使用这种方法的信息)。 Sub RunAccessParamQuery() '运行这个过程之前必须设置对所需对象库的引用 Dim cat As ADOX.Catalog Dim cmd As ADODB.Command Dim rst As ADODB.Recordset Dim i As Integer Dim dbPath As String Dim StartDate As String Dim EndDate As String dbPath = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\Northwind.mdb" StartDate = "7/1/96" EndDate = "7/31/96" Set cat = New ADOX.Catalog cat.ActiveConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & dbPath Set cmd = cat.Procedures("Employee Sales by Country").Command cmd.Parameters("[Beginning Date]") = StartDate cmd.Parameters("[Ending Date]") = EndDate Set rst = cmd.Execute Sheets(1).Select For i = 0 To rst.Fields.Count - 1 Cells(1, i + 1).Value = rst.Fields(i).Name Next With ActiveSheet .Range("A2").CopyFromRecordset rst .Range(Cells(1, 1), Cells(1, rst.Fields.Count)).Font. _ Bold = True .Range("A1").Select End With Selection.CurrentRegion.Columns.AutoFit rst.Close Set cmd = Nothing Set cat = Nothing End Sub 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 388 共 612 页 (译者:本人运行上述过程不成功,将Parameters参数改为cmd.Parameters(0) = StartDate, cmd.Parameters(1) = EndDate才运行成功。另外过程给定的日期范围查询结果为空,可以更改查询 日期范围。) 15.3.7 调用Microsoft Access函数 您可以通过自动控制从Excel里运行Access内置函数。下面的过程调用EuroConvert函数将1000 西班牙比赛塔转变为欧元。EuroConvery函数使用欧盟确定的固定汇率。 Sub RunAccessFunction() Dim objAccess As Object On Error Resume Next Set objAccess = GetObject(, "Access.Application") '如果没有Access实例打开则创建一个新实例 If objAccess Is Nothing Then Set objAccess = CreateObject("Access.Application") End If MsgBox "您将获得 " & _ objAccess.EuroConvert(1000, "ESP", "EUR") & _ "欧元. " Set objAccess = Nothing End Sub 15.4 获取Microsoft Access数据到Excel工作表 有很多种方法获取外部数据到Excel中,本节介绍下面不同的技术将Access数据导入Excel工作 表: • 使用GetRows方法 • 使用CopyFromRecordset方法 • 使用TransferSpreadsheet方法 • 使用OpenDatabase方法 • 创建一个文本文件 • 创建一个查询表 15.4.1 使用GetRows方法获取数据 可以使用GetRows方法将Access数据放置于Excel工作表中。该方法返回一个二维数组,第一个 下标是一个代表字段的数字,而第二个下标则是代表记录的数字。记录和字段数从0开始。 通过在VBA过程里使用Data Access Objects(DAO)返回数据到Excel工作表。下面的示例过程演 示了如何运行Northwind数据库里的Invoices查询,并返回记录到Excel工作表。为了确保该过程正确 地工作,必须首先建立对Microsoft Access 3.6 Object Library的引用。参考本章前面关于创建对对象 库的引用的介绍。 打开Access数据库后,GetData_withDAO2过程示范使用下面的语句运行Invoices查询: 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 389 Set qdf = db.QueryDefs("Invoices") Microsoft Access 3.6 Object Library里的QueryDefs对象代表一个选择或者操作查询。选择查询 从一个或者多个表或者查询里返回数据,而操作查询允许修改数据(使用操作查询可以添加、修改或 者删除记录)。 执行查询后,过程将查询返回的记录通过OpenRecordset方法放置到Recordset类型对象变量 上,如下所示: Set rst = qdf.OpenRecordset 接下来,通过RecordCount方法获取记录数并放置在变量countR上。注意,为了获得正确的记 录数,记录指针必须通过使用MoveLast方法移动到记录集里的最后一条记录。 rst.MoveLast countR = rst.RecordCount 接着,过程提示用户输入要返回到工作表的记录数。您可以单击输入对话框上的“取消”按钮 就此取消,或者输入记录数目获取数据。如果输入的数字大于记录数,过程将获取全部记录。在获 取记录之前,必须使用方法MoveFirst将记录指针移动到第一条记录。如果忘了这样做,那么记录指 针会停留在最后一条记录上,并且将只能获取一条记录。然后,该过程继续执行,激活Get Records 工作表并清除当前范围的内容。首先,通过使用Recordset对象的GetRows方法,记录将返回到一个 二维数组的Variant类型变量。接着,过程在数组的两维中循环将记录从单元格A2开始放置到工作表 中。完成后,另一个循环将在工作表第一行里放置字段名称,并且将每列设置为自动适应列宽,以 正确显示数据。 Sub GetData_withDAO2() Dim db As DAO.Database Dim qdf As DAO.QueryDef Dim rst As DAO.Recordset Dim recArray As Variant Dim i As Integer Dim j As Integer Dim strPath As String Dim a As Variant Dim countR As Long Dim strShtName As String strPath = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\northwind.mdb" strShtName = "Returned records" Set db = OpenDatabase(strPath) Set qdf = db.QueryDefs("Invoices") Set rst = qdf.OpenRecordset rst.MoveLast countR = rst.RecordCount a = InputBox("这个记录集包含" & _ countR & " 条记录." & vbCrLf _ & "输入返回的记录数: ", _ "获取记录数") If a = "" Or a = 0 Then Exit Sub If a > countR Then a = countR MsgBox "您输入的记录太大." & vbCrLf _ 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 390 共 612 页 & "将返回所有的记录." End If Workbooks.Add ActiveWorkbook.Worksheets(1).Name = strShtName rst.MoveFirst With Worksheets(strShtName).Range("A1") .CurrentRegion.Clear recArray = rst.GetRows(a) For i = 0 To UBound(recArray, 2) For j = 0 To UBound(recArray, 1) .Offset(i + 1, j) = recArray(j, i) Next j Next i For j = 0 To rst.Fields.Count - 1 .Offset(0, j) = rst.Fields(j).Name .Offset(0, j).EntireColumn.AutoFit Next j End With db.Close End Sub 15.4.2 使用CopyFromRecordset方法获取数据 想要将整个记录集导入工作表,可以使用Range对象的CopyFromRecordset方法。该方法接受 三个参数:Data、MaxRows和MaxColumns。只有第一个参数Data是必需的,该参数可以是Recordset 对象。可选参数MaxRows 和MaxColumns 允许指定应该返回的记录数(MaxRows) 和字段数 (MaxColumns)。如果忽略MaxRows参数,那么所有返回的记录将会复制到工作表;如果忽略 MaxColumns参数,那么将获取所有的字段。下面示范的过程GetProducts使用ADO对象建立与 Northwind数据库的连接。为了让该过程正确地工作,必须先创建对Microsoft ActiveX Data Objects 2.6 Library的引用(参见本章前面有关创建对对象库引用的介绍)。 Sub GetProducts() Dim conn As New ADODB.Connection Dim rst As ADODB.Recordset Dim strPath As String Dim j As Integer strPath = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\Northwind.mdb" conn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" _ & "Data Source=" & strPath & ";" conn.CursorLocation = adUseClient '从所有产品表中的记录创建记录集 Set rst = conn.Execute(CommandText:="Products", _ Options:=adCmdTable) '从第一个记录开始 Rst.MoveFirst '转移数据到Excel '获取第一个字段名 With Worksheets("Sheet3").Range("A1") .CurrentRegion.Clear For j = 0 To rst.Fields.Count - 1 .Offset(0, j) = rst.Fields(j).Name Next j 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 391 .Offset(1, 0).CopyFromRecordset rst .CurrentRegion.Columns.AutoFit End With rst.Close conn.Close End Sub 上面的过程从Northwind数据库的“产品”表中复制所有的记录到Excel工作表。如果想要复制 某一些记录,那么可以使用MaxRows参数,如下所示: .Offset(1, 0).CopyFromRecordset rst, 5 该语句告诉VBA仅复制5条记录。Offset方法使得输入到电子表格里的记录从电子表格当前行的 第二行开始。 想要仅将两个表字段的所有记录发送到工作表,可以使用下面的语句: .Offset(1, 0).CopyFromRecordset rst, , 2 该语句告诉VBA从开始两列复制所有数据。在rst和数字2之间的逗号是个占位符,忽略了 MaxRows参数。 15.4.3 使用TransferSpreadsheet方法获取数据 使用TransferSpreadsheet方法在当前Access数据库(.mdb)或者Access项目(.adp)和电子表格文 件之间导入或者导出数据是可能的,也可以将Excel电子表格里的数据连接到当前Access数据库。对 于连接的电子表格,当Access仍然允许从Excel程序里完全访问时,可以使用Access来查看和编辑 电子表格数据。 在VB里TransferSpreadsheet方法执行TransferSpreadsheet操作,其语法如下: DoCmd.TransferSpreadsheet [transfertype][, spreadsheettype], _ tablename, filename [, hasfieldnames][, range] 参数transfertype可以是以下常数之一:acImport(缺省设置)、acExport或者acLink。这些常数定 义数据是否导入、导出或者连接到数据库。 参数spreadsheettype可能是下面的常数之一: 0 acSpreadsheetTypeExcel3 (缺省设置) 6 acSpreadsheetTypeExcel4 5 acSpreadsheetTypeExcel5 5 acSpreadsheetTypeExcel7 8 acSpreadsheetTypeExcel8 8 acSpreadsheetTypeExcel9 2 acSpreadsheetTypeLotusWK1 3 acSpreadsheetTypeLotusWK3 7 acSpreadsheetTypeLotusWK4 不难猜到spreadsheettype参数指定电子表格名称和版本号。 tablename参数是一个字符串表达式,指定想要往里面导入电子表格数据、或者从里面导出电子 表格数据、或者连接电子表格数据的Access表的名称。除了表名称之外,也需要指定想要导出数据 到电子表格的选择查询名称。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 392 共 612 页 hasfieldnames参数是一个逻辑值True(-1)或者False(0)。True表明工作表第一行包含字段名称; False则表示第一行包含普通数据。缺省设置为False(第一行里没有字段名称)。 参数range是一个字符串表达式,指定工作表中的单元格区域或者区域名称。该参数仅用于导入。 如果忽略range参数,那么整个电子表格将会被导入。如果想要导出,就将该参数空在那里,除非需 要指定该工作表名称。 下面示范的ExportData示例过程使用TransferSpreadsheet方法从Northwind数据库里的“运货 商”表中导出数据到Shippers.xls电子表格中。注意,该过程使用自动控制来建立对Access的连接。 建立连接后,使用OpenCurrentDatabase方法打开Northwind数据库。运行完ExportData过程后,打 开C:\Shippers.xls文件查看获取的数据。 ‘ 在模块顶部声明 Dim objAccess As Access.Application Sub ExportData() Set objAccess = CreateObject("Access.Application") objAccess.OpenCurrentDatabase filepath:= _ "C:\Program Files\Microsoft Office\Office\" _ & "Samples\Northwind.mdb" objAccess.DoCmd.TransferSpreadsheet _ TransferType:=acExport, _ SpreadsheetType:=acSpreadsheetTypeExcel9, _ TableName:="Shippers", _ Filename:="C:\Shippers.xls", _ HasFieldNames:=True, _ Range:="Sheet1" objAccess.Quit Set objAccess = Nothing End Sub 图15-12:使用TransferSpreadsheet方法可以将Access表里的数据导出到Excel电子表格里(参见上面的 ExportData过程) 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 393 15.4.4 使用OpenDatabase方法 Excel 2002提供了一个操作数据库的新方法——OpenDatabase方法,它应用于Workbooks集 合,是将数据库数据导入Excel电子表格最容易的方法。该方法要求指定想要打开的数据库文件名称。 下面的示例过程打开位于C:\Program Files\Microsoft Office\Office10\Samples文件夹里的Northwind 数据库。当运行该过程时,Excel显示一个对话框,列出了该数据库里的所有表和查询(参见图15- 13)。从列表里选择后,就会打开一个全新的工作簿,该工作簿中带有从所选表或者查询里获取的数 据的工作表。 Sub OpenAccessDatabase() Workbooks.OpenDatabase _ Filename:="C:\Program Files\Microsoft Office\" _ & "Office10\Samples\Northwind.mdb" End Sub 图15-13:使用带一个参数(数据库文件名称)的OpenDatabase方法可以从一个列表框里选择一个表或者 查询 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 394 共 612 页 图15-14:使 用 Excel 2002里新增的OpenDatabase方法可以很容易地将存储在表或者查询里的数据库数 据导入Excel工作簿 OpenDatabase方法有四个可选参数可以进一步限定想要获取的数据: OpenDatabase方法的可选 参数 数据类型 描述 CommandText Variant SQL查询字符串,参见下面使用该参数的示例。 CommandType Variant 查询的命令类型,可用的命令类型有:Deault、SQL和表。 BackgroundQuery Variant 查询的背景,可以是下面的常数之一:PivotCache或者 QueryTable。 ImportDataAs Variant 指定查询的格式。使用xlQueryTable报表创建一个查询表, 或者xlPivotTableReport报表来创建一个数据透视表。 下面的示例过程演示了如何使用带可选参数的OpenDatabase方法,该过程从获取的雇员记录创 建一个数据透视表。当运行该过程时,Excel就会基于提供的查询字符串显示一个可用字段的列表。 可以拖曳一个或者多个字段到该透视表中以创建数据透视报告。图15-15显示了按国家分类的雇员 Id字段。 Sub CountCustomersByCountry() Workbooks.OpenDatabase _ Filename:="C:\Program Files\Microsoft Office\" _ & "Office10\Samples\Northwind.mdb", _ CommandText:="Select * from 雇员", _ BackgroundQuery:=PivotTable, _ 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 395 ImportDataAs:=xlPivotTableReport End Sub (译者注:Excel 2003+Access 2002运行该过程有问题:BackgroundQuery: = PivotTable。 此 处有矛盾,上面的参数解释说该参数为PivotCache或者QueryTable,而这里却是PivotTable,但这三 个参数均导致错误。搜索对象浏览器说该参数为布尔类型。译者将该参数改为-1,1,2,10,True, False等运行,结果没有区别。) 图15-15:使用OpenDatabase方法的可选参数,可以指定获取数据库的数据为一个特定的格式,例如数 据透视报告或者查询表报告 15.4.5 从Microsoft Access数据创建文本文件 可以使用Excel的VBA过程从Access数据创建一个以逗号或者tab分隔的文本文件。文本文件对 于传输大量数据到电子表格特别有用。下面的示例过程演示了如何从一个ADO记录集创建一个以tab 分隔的文本文件。为了确保该过程正确地运行,必须创建对Microsoft ActiveX Data Objects 2.6 Library的引用。参考第八章中关于操作文本文件的详细信息。运行该过程后,在Excel里打开 C:\ProductsOver50.txt。 Sub CreateTextFile() Dim strPath As String Dim conn As New ADODB.Connection Dim rst As ADODB.Recordset Dim strData As String Dim strHeader As String Dim strSQL As String Dim f As Object 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 396 共 612 页 strPath = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\Northwind.mdb" conn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" _ & "Data Source=" & strPath & ";" conn.CursorLocation = adUseClient strSQL = "SELECT * FROM 产品 WHERE 单价 > 50" Set rst = conn.Execute(CommandText:=strSQL, Options:=adCmdText) '将记录集保存为以Tab分隔的文件 strData = rst.GetString(StringFormat:=adClipString, _ ColumnDelimeter:=vbTab, _ RowDelimeter:=vbCr, _ nullExpr:=vbNullString) Open "C:\ProductsOver50.txt" For Output As #1 For Each f In rst.Fields strHeader = strHeader + f.Name & vbTab Next Print #1, strHeader Print #1, strData Close #1 End Sub 在第八章,您学习了如何使用FileSystemObject操作文本文件。下面的过程演示了如何使用该对 象来创建一个名为ProductsOver100.txt的文本文件: Sub CreateTextFile2() Dim strPath As String Dim conn As New ADODB.Connection Dim rst As ADODB.Recordset Dim strData As String Dim strHeader As String Dim strSQL As String Dim fso As Object Dim myFile As Object Dim f As Object Set fso = CreateObject("Scripting.FileSystemObject") Set myFile = fso.CreateTextFile("C:\ProductsOver100.txt", True) strPath = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\Northwind.mdb" conn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" _ & "Data Source=" & strPath & ";" conn.CursorLocation = adUseClient strSQL = "SELECT * FROM 产品 WHERE 单价 > 100" Set rst = conn.Execute(CommandText:=strSQL, Options:=adCmdText) '将记录集保存为以tab分隔的文件 strData = rst.GetString(StringFormat:=adClipString, _ ColumnDelimeter:=vbTab, _ RowDelimeter:=vbCr, _ nullExpr:=vbNullString) For Each f In rst.Fields strHeader = strHeader + f.Name & vbTab Next With myFile .WriteLine strHeader .WriteLine strData .Close End With End Sub 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 397 图15-16:因为在Excel中可以很容易打开文本文件,所以可以使用它在Access和Excel之间传输数据 15.4.6 从Microsoft Access数据创建查询表 如果想要在Excel中使用来自外部的数据源,而且知道将使用的数据会经常改变,那么需要创建 一个查询表。查询表是Excel工作表里的一种特殊的表,它连接外部数据源,例如Access数据库、SQL 服务器、网页或者文本文件。用户可以轻易地刷新查询表来获取最新的信息。Excel提供了专门的菜 单选项来获取外部数据源中的数据:只要选择“数据”|“导入外部数据”,并选择“新建数据库查询”。 通过查询外部数据库,可以带来一些正好满足要求的数据。例如,不必将所有的产品信息都导入电 子表格中来检查,只要在获取数据之前明确数据必须达到的条件就行。因此,可以只获取单价大于 20美元的产品,而不是从Access导入所有的产品。 在VBA里,可以使用QueryTable对象访问外部数据。每个QueryTable对象代表从外部数据源, 例如SQL服务器或者Access数据库,返回的数据所创建的工作表表格。要编程创建一个查询,可以 使用QueryTabes集合对象的Add方法。该方法需要三个参数。本节结尾处的示例过程使用下面的语 句在活动工作表上创建一个查询表: Set myQryTable = ActiveSheet.QueryTables.Add(strConn, Dest, strSQL) strConn是为第一个参数——Connection提供数值的变量,它是必需的参数,为Variant数据类型, 指定查询表的数据源。 Dest是为第二个参数——Destination提供数值的变量,这是个必需的参数,为Range数据类型, 指定在哪个单元格放置查询表结果。 strSQL是为第三个参数——SQL提供数值的变量,这是个必需的参数,为字符串数据类型,定 义从查询返回的数据。 当使用Add方法创建查询时,该查询直到调用Refresh方法时才会运行。该方法接受一个参数—学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 398 共 612 页 —BackgroundQuery。这是一个Variant数据类型的可选参数,允许决定是否在建立了对数据库的连 接以及查询被提交(True)后将控制返回给过程,或者在查询已经运行并且工作表已经获取了所有数据 (False)才将控制返回给过程。 接下来的过程CreateQueryTable仅仅从Northwind数据库的产品表中获取产品单价大于20的产 品。注意,该过程仅在所有相关记录都被获取之后,才将控制交回过程。RefreshStyle方法决定数据 如何插入到工作表中。下面的常数可供使用: • xlOverwriteCells – 现存的单元格会被新数据覆盖。 • xlInsertDeleteCells – 插入或者删除单元格以容纳新数据。 • xlInsertEntireRows – 插入整行以容纳新数据。 Sub CreateQueryTable() Dim myQryTable As Object Dim myDb As String Dim strConn As String Dim Dest As Range Dim strSQL As String myDb = "C:\Program Files\Microsoft Office\Office\" _ & "Samples\Northwind.mdb" strConn = "OLEDB;Provider=Microsoft.Jet.OLEDB.4.0;" _ & "Data Source=" & myDb & ";" Set Dest = Worksheets(1).Range("A1") strSQL = "SELECT * FROM 产品 WHERE 单价>20" Set myQryTable = ActiveSheet.QueryTables.Add(strConn, _ Dest, strSQL) With myQryTable .RefreshStyle = xlInsertEntireRows .Refresh False End With End Sub 图15-17:使用QueryTable对象可以在Excel里分析来自外部数据源例如Access数据库的数据 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 399 15.5 在Excel里使用Microsoft Access数据 使用上面介绍的方法从Access数据库获取数据之后,可以使用许多Excel内置的工具来分析数 据。基于获取的信息来创建一些图表经常是很有用的。 15.5.1 用Access数据创建内嵌图表 使用VBA,可以轻松的基于从Access数据库获取的数据创建图表。下面显示的ChartData过程使 用从Northwind数据里获取的数据创建了一个内嵌图表,该图表由Charts集合的Add方法创建。图表 的数据源由Range对象提供,CurrentRegion方法返回单元格A1周围的所有非空单元格。过程的剩余 部分则通过设置图表的各种属性来设置图表格式。图表代码段录制在一个单独的宏里,然后移到该 VBA过程里并作一些修改以设置一些图表属性。 Sub ChartData() Dim db As DAO.database Dim qd As DAO.QueryDef Dim rs As DAO.Recordset Dim mySheet As Worksheet Dim recArray As Variant Dim i As Integer Dim j As Integer Dim pathDb As String Dim qdName As String pathDb = "C:\Program Files\Microsoft Office\" _ & "Office\Samples\northwind.mdb" qdName = "Category Sales for 1997" Set db = OpenDatabase(pathDb) Set qd = db.QueryDefs(qdName) Set rs = qd.OpenRecordset Set mySheet = Worksheets("Sheet2") With mySheet.Range("A1") .CurrentRegion.Clear recArray = rs.GetRows(rs.RecordCount) For i = 0 To UBound(recArray, 2) For j = 0 To UBound(recArray, 1) .Offset(i + 1, j) = recArray(j, i) Next j Next i For j = 0 To rs.Fields.Count - 1 .Offset(0, j) = rs.Fields(j).Name .Offset(0, j).EntireColumn.AutoFit Next j End With mySheet.Activate Charts.Add ActiveChart.ChartType = xl3DColumnClustered ActiveChart.SetSourceData _ Source:=mySheet.Cells(1, 1).CurrentRegion, PlotBy:=xlRows ActiveChart.Location Where:=xlLocationAsObject, Name:=mySheet.Name With ActiveChart .HasTitle = True .ChartTitle.Characters.Text = qdName .Axes(xlCategory).HasTitle = True .Axes(xlCategory).AxisTitle.Characters.Text = "" .Axes(xlSeries).HasTitle = False 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 400 共 612 页 .Axes(xlValue).HasTitle = True .Axes(xlValue).AxisTitle.Characters.Text = mySheet.Range("B1") _ & "($)" .Axes(xlValue).AxisTitle.Orientation = xlUpward End With db.Close End Sub 过程ChartData的运行结果显示如图15-18。 图15-18:可以使用VBA编程基于从Access表、查询或者SQL语句获取的数据创建内嵌图表 15.6 传输Excel电子表格到Access数据库 世界上许多大的数据库都是从电子表格开始的。当需要从电子表格创建一个数据库应用程序时, 可以求助于缓慢呆滞的手动方法来传输数据,也可以使用新学的VBA编程技巧自动将电子表格变为 数据库表。一旦在数据库格式中,Excel数据就可以使用在高级的公司范围的报告上使用,或者作为 一个独立的应用程序使用(无须多言,后面的需求就是要拥有数据库应用程序的设计技巧)。本章剩余 的部分将演示如何将Excel电子表格连接和导入到Access数据库。在移动Excel数据到Access之前, 应该尽可能地整理好数据,以便于顺利地进行数据传输。记住,电子表格里要传输的每一行都会成 为表中的一个记录,而每一列都会作为表的字段。因此,您计划传输到Access中的电子表格的第一 行应该包含字段名称,数据列中间应该没有空白,换句话说,数据应该是连续的。如果要传输的数 据有大量的列,那么应该先打印出数据并检查确保以后没有意外。如果数据的第一列为字段名称, 那么建议使用内置的Transpose函数先将数据转置,以确保数据是从上到下而不是从左到右。顺利导 入数据的关键是使电子表格尽可能像数据库表。 15.7 将Excel电子表格连接到Microsoft Access数据库 可以使用TransferSpreadsheet方法将Excel电子表格连接到Access数据库(参见本章“使用 TransferSpreadsheet方法获取数据”一节中有关该方法的详细介绍)。下面的示例过程将图15-19 显示的电子表格连接到Northwind数据库。在使用OpenCurrentDatabase方法打开Access数据库之 后,该过程从Chap15.xls电子表格文件的mySheet工作表中位于单元格(A1:D7)区域里,使用Access 第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 401 DoCmd对象的TransferSpreadsheet方法创建了一个名为ExcelSheet的连接表。注意,DoCmd语句 中的参数-1表明电子表格的第一行包含列标题。接着,该过程在编辑模式下打开该连接表,因此用 户可以添加或者修改数据。如果在添加一条或者多条记录后,要返回Excel,那么您将注意到在连接 的Access表里作的改变将在Excel里立即可用。 图15-19:VBA过程LinkExcel_ToAccess将该电子表格连接到Access的Northwind数据库 Sub LinkExcel_ToAccess() Dim objAccess As Access.Application Dim strName As String strName = "Linked_ExcelSheet" Set objAccess = New Access.Application With objAccess .OpenCurrentDatabase "C:\Program Files\Microsoft Office\" _ & "Office\Samples\Northwind.mdb" .DoCmd.TransferSpreadsheet acLink, acSpreadsheetTypeExcel9, _ strName, _ "C:\Chap15.xls", _ -1, "mySheet!A1:D7" .DoCmd.OpenTable strName, acViewNormal, acEdit End With End Sub 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 402 共 612 页 图15-20:Excel电子表格可以被连接到Access数据库 15.8 将Excel电子表格导入Access数据库 在前一节,您学习了如何将Excel电子表格连接到Access数据库。导入电子表格也是一样简单, 您甚至可以使用用于连接的相同的VBA过程,只需作一些细微的改变:将常数acLink改为acImport。 下面的过程将图15-19里显示的电子表格(参见前一节)导入到Northwind数据库。 Sub ImportExcel_ToAccess() Dim objAccess As Access.Application Dim strName As String strName = "Imported_ExcelSheet" Set objAccess = New Access.Application With objAccess .OpenCurrentDatabase "C:\Program Files\Microsoft Office\" _ & "Office\Samples\Northwind.mdb" .DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel9, _ strName, _ "C:\Chap15.xls", _ -1, "mySheet!A1:D7" .DoCmd.OpenTable strName, acViewNormal, acEdit End With End Sub 15.9 在Access表中放置Excel数据 除了连接或者内嵌Excel电子表格外,假设想要从头开始创建一个Access表并且装载电子表格里 面的数据呢?使用一些您已经在本书里获得的编程技巧,可以轻松完成该任务。我们来看看一个VBA第15章 14B在Excel里使用Access 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 403 过程,基于图15-19显示的Excel电子表格动态地创建一个Access表。注意,该过程使用ActiveX Data Objects(ADO)和MicrosoftJet.OLEDB.4.0提供者连接到Access数据库。建立连接后,该过程使用 ADOX对象库里的Catalog和Table对象创建一个新Access表。接着,对应电子表格列名称的字段被 添加到该表中。注意,每个文本字段明确了它可以接受的最大字符数。如果电子表格单元格的长度 大于该字段大小,那么错误处理程序将显示Access内置的信息,提示该错误并结束过程。 该过程的最后一个任务是数据传输操作。要执行该任务,该过程打开了Access表的Recordset 对象。因为需要添加记录到该表,所以该过程使用了一个adOpenKeyset指针类型。现在该表已打开, 该过程使用For…Next循环在Excel数据行中循环,将在每个单元格中找到的信息放置到相应的表字 段中。注意,使用Recordset对象的AddNew方法往Access表里添加新的记录。从每行的所有单元格 中复制数据后,该过程使用Recordset对象的Update方法来保存表记录。 Sub AccessTbl_From_ExcelData() Dim conn As ADODB.Connection Dim cat As ADOX.Catalog Dim myTbl As ADOX.Table Dim rstAccess As ADODB.Recordset Dim rowCount As Integer Dim i As Integer On Error GoTo ErrorHandler '使用ADO连接到Access Set conn = New ADODB.Connection conn.Open "Provider = Microsoft.Jet.OLEDB.4.0;" & _ "Data Source = C:\Program Files\Microsoft Office\Office\Samples\Northwind.mdb;" '创建一个空Access表 Set cat = New Catalog cat.ActiveConnection = conn Set myTbl = New ADOX.Table myTbl.Name = "TableFromExcel" cat.Tables.Append myTbl '添加字段(列)到表中 With myTbl.Columns .Append "学校代号", adVarWChar, 7 .Append "设备类型", adVarWChar, 15 .Append "系列号", adVarWChar, 15 .Append "生产厂商", adVarWChar, 20 End With Set cat = Nothing MsgBox "已创建了表结构." ' 基于新创建的Access表打开记录集 Set rstAccess = New ADODB.Recordset With rstAccess .ActiveConnection = conn .CursorType = adOpenKeyset .LockType = adLockOptimistic .Open myTbl.Name End With ' 从Excel电子表格区域中转移数据 With Worksheets("mySheet") rowCount = Range("A2:D7").Rows.Count For i = 2 To rowCount + 1 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 404 共 612 页 With rstAccess .AddNew ' 添加一个新记录到Access表中 .Fields("School No") = Cells(i, 1).Text .Fields("Equipment Type") = Cells(i, 2).Value .Fields("Serial Number") = Cells(i, 3).Value .Fields("Manufacturer") = Cells(i, 4).Value .Update '更新表记录 End With Next i End With ' 关闭Recordset和Connection对象并从内存中移除 rstAccess.Close conn.Close Set rstAccess = Nothing Set conn = Nothing AccessTbl_From_ExcelDataExit: Exit Sub ErrorHandler: MsgBox Err.Number & ": " & Err.Description Resume AccessTbl_From_ExcelDataExit End Sub 本章小结和下一章内容简介 本章提供了多个将Excel数据放入Access中以及从Access获取数据到工作表中的示例。您学习 了如何从Excel VBA过程中控制Access应用程序,执行一些任务,诸如打开Access窗体和报表、创 建新窗体、运行选择和参数查询以及调用Access内置函数。此外,本章示范了一些创建文本文件、 查询表和图表的技术。您也学习了如何通过使用链接、导入和动态Access表,将Excel数据放置到 Access数据库里。 在下章中,您将学习如何使用Excel创建、查看和分析Internet数据。 第16章 Excel和Internet 我们这几年已经见证了 Internet 的飞速发展,这让我们获取 散布在世界各地的大量知识成为可能。感谢 Internet,我们有了 触手可及的数据库,它们涵盖了各行各业和各个知识领域、词典 和百科全书、股票查询、地图、天气预报和大量存储在在无数网 络服务器上的其它类型的信息 …… 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 406 共 612 页 我们这几年已经见证了Internet的飞速发展,这让我们获取散布在世界各地的大量知识成为可 能。感谢Internet,我们有了触手可及的数据库,它们涵盖了各行各业和各个知识领域、词典和百科 全书、股票查询、地图、天气预报和大量存储在在无数网络服务器上的其它类型的信息。很多情况 下,从网页上获取的信息成为计算机程序进一步分析的主题。由于其结构(行和列),因此Excel 2002 是处理Internet上表数据的极佳工具。使用Excel,您能容易地创建、发布、检查和分析数据。 本章演示了Excel 2002里从网页获取数据和发布Excel电子表格到网页上的可利用的内置工具。 这里,您将发现许多VBA语句,可以使用自己编写的VBA过程来获取和发布数据。为了尽可能多的 学习到本章的知识,您应该连接到因特网(调制解调器、电话线或网线、因特网服务器账号、以及网 页浏览器,例如Internet Explorer 5.0或更高版本)。 (译者注:本章的内容及示例在WinXP+Excel 2003中进行,因此一些截图可能与原文不同或省 略。同时,根据原文内容进行翻译,所以译文的叙述可能与实际操作有所差异。) 16.1 使用VBA创建超链接 和Office中的其它应用程序一样,Excel 2002也可以在电子表格里创建超链接。单击一个含有超 链接的单元格之后,可以打开一个位于网络服务器、局域网或者因特网上的文件。可以直接在用户 界面中使用“插入”|“超链接”选项(图16-1)或者使用VBA编程来创建超链接。 图16-1:在Excel里使用对话框插入超链接 在VBA里面,每个超链接由Hyperlink对象表示,想要创建一个到网页的超链接,可以使用 Hyperlinks集合的Add方法。该方法如下所示: Expression.Hyperlinks.Add(Anchor, Address, [SubAddress], [ScreenTip], [TextToDisplay]) 方括号里的参数为可选的。Expression表示想要放置超链接的工作表或者单元格区域;Anchor 是要单击的对象,它既可以是一个Range对象也可以是Shape对象;Address指向本地网络或者网页; SubAddress 是 Excel 文件里的单元格区域名称。ScreenTip 可以在屏幕上显示一条信息;第 16 章 15BExcel 和 Internet 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 407 TextToDisplay是为指定的超链接在电子表格单元格里显示的适当的名称。 我们来看看如何通过VBA过程在工作表单元格里放置一个超链接。单击该超链接时,将带您到 雅虎站点。 1. 打开一个新工作簿。 2. 切换到VBE编辑器屏幕,并插入一个新模块到当前VBA工程中。 3. 在代码窗口里,输入下面显示的FollowMe过程代码。 4. 激活在第三步中所创建的过程。 Sub FollowMe() Dim myRange As Range Set myRange = Sheets(1).Range("A1") myRange.Hyperlinks.Add _ Anchor:=myRange, _ Address:="http://search.yahoo.com/bin/search", _ ScreenTip:="搜索Yahoo", _ TextToDisplay:="单击这里" End Sub 当运行FollowMe过程后,第一个工作表的单元格A1将会包含一个超链接“单击这里”,屏幕提 示为“搜索Yahoo”(参见图16-2)。如果现在想链接到因特网,单击该超链接将激活网页浏览器并 装载雅虎搜索引擎(图16-3)。 图16-2:通过VBA过程将该超链接放置到工作表里 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 408 共 612 页 图16-3:单击放置在工作表单元格上的超链接可以激活雅虎搜索引擎主页 如果不愿意在工作表里面放置超链接但是用户仍然可以从Excel工作表里面直接到达需要的网 页,那么可以使用FollowHyperlink方法。该方法允许打开需要的网页而不必在工作表里放置超链接 对象。该方法的格式如下所示: Expression.FollowHyperlink(Address, [SubAddress], [NewWindow], _ [AddHistory], [ExtraInfo], [Method], [HeaderInfo]) 同样,方括号里的参数是可选的。Expression返回一个Workbook对象;Address是想要激活的 网页地址;SubAddress是超链接地址指向对象的一部分,可以是Excel工作表的单元格区域; NewWindow表明是否需要在新窗口里面显示文档或页面,缺省设置为False;下一个参数 AddHistory,现在不需要使用。它保留给将来使用;ExtraInfo提供额外的信息,允许跳到文档或者 网页上的指定位置,例如,在这里可以指定需要搜索的文本;Method指明附加额外信息(ExtraInfo) 的方法,它可以是下面的常数之一:msoMethodGet或者msoMethodPost:当使用msoMethodGet 时,ExtraInfo是附加到URL地址的字符串;当使用msoMethodPost时,ExtraInfo则作为一个字符串 或者字节数组;最后一个可选参数HeaderInfo是一个字符串,为HTTP请求指定标题信息,其缺省值 为空字符串。 我们来看看如何在VBA过程里使用FollowHyperlink方法,本过程的目的是使用AltaVisa搜索引擎 查找输入到工作表单元格里的任意文本。 第 16 章 15BExcel 和 Internet 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 409 1. 在VBE编辑器窗口,激活工程浏览器窗口,并双击Microsoft Excel对象文件夹里的对象 Sheet2(Sheet2)。 2. 在代码窗口里,输入如下所示的Worksheet_BeforeDoubleClick过程(回顾第十四章关于在Excel 里创建和使用事件过程的内容) Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, _ Cancel As Boolean) Dim strSearch As String strSearch = Sheets(2).Range("C3").Formula If Target = Range("C3") Then Cancel = True ActiveWorkbook.FollowHyperlink _ Address:="http://www.altavista.com/cgi-bin/query", _ ExtraInfo:="q=" & strSearch, _ Method:=msoMethodGet End If End Sub 3. 现在切换到Excel应用程序窗口,并在Sheet2的单元格C3里输入任何想要查找信息的词语或术 语(参见图16-4)。 4. 确保已与Internet相连接。 5. 双击单元格C3,该操作导致在单元格C3里输入的文本会被发送到AltaVisa搜索引擎,屏幕上应 该会显示查找特定条件主题的索引(图16-5)。 图16-4:Excel工作表可以用来将搜索参数发送到任何Internet搜索引擎(参见上面第二步的Worksheet_ BeforeDoubleClick过程) 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 410 共 612 页 图16-5:从Excel工作表打开的网页列出了基于工作表单元格中输入的数据为条件而找到的主题 16.2 使用VBA创建和发布HTML文件 和以前的版本一样,Excel 2002也可以将文件保存为HTML格式(Hypertext Markup Language 超文本标识语言)。该格式就像它的标准.xls格式一样,也能够为Excel所识别。当保存Excel文件为 HTML格式时,您可以使用Internet浏览器例如IE或者Netscape Navigator来查看电子表格。当保存工 作簿或者其中一部分为HTML时,Excel保存原始工作簿的选项。正因为这样,所以用户既可以使用 浏览器也可以在Excel应用程序窗口内部来查看该文件。 Excel 2002能够保存数据和图表为交互式网页。当保存整个工作簿或者其中一部分为HTML格式 时,您可以选择创建一个静态的或者交互式的HTML文档并指定文件的保存地址。也可以直接将文件 保存到网页、网络服务器或者本地计算机。 不需要进行任何其它处理,只要使用用户界面(文件|另存为网页),您就可以将整个工作簿或者 工作簿的一部分放置到网页中,因此用户可以交互式地使用该信息或者仅查看数据。关于如何将整 个工作簿或者任意工作表(或者它们的成员之一——例如图表、数据透视表)放置到网页上的详细说明 可以在Excel在线帮助里找到。因为本书是关于编程的,所以我们仅侧重于介绍通过VBA代码执行这 些任务的方法。 Excel 2002 的VBA对象库提供了发布工作表到网页的对象。要使用编程的方法创建和发布第 16 章 15BExcel 和 Internet 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 411 Excel文件为HTML格式,您应该熟悉PublishObject对象和PublishObjects集合。 图16-6:从“文件”菜单上单击“另存为网页”后就会出现“另存为”对话框,可以将工作簿保存为网 页 图16-7:点击“另存为”对话框(参见图16-6)上的“发布”按钮后,出现“发布为网页”对话框 PublishObject代表保存在网页中的工作表元素,而PublishObjects是某个指定工作簿中所有 PublishObject对象的集合。可以使用Add方法添加工作表元素到PublishObjects集合里,该方法将会学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 412 共 612 页 创建一个对象,代表保存为网页的特定工作表元素。Add方法的格式如下: expression.Add(SourceType, Filename, [Sheet], [Source], [HtmlType], [DivID], [Title]) 方括号里的参数是可选的。Expression返回属于PublishObjects集合的一个对象。参数 SourceType使用下面的常量之一来指定对象源: 常数 描述 xlSourceAutoFilter 自动筛选单元格区域 xlSourceChart 图表 xlSourcePivotTable 数据透视报告 xlSourcePrintArea 打印区域 xlSourceQuery 查询表(外部数据区域) xlSourceRange 单元格区域 xlSourceSheet 整个工作表 参数Filename是指定对象源(SourceType)保存地址的字符串,它可以是URL(Unified Resource Locator统一资源定位符)或者本地或网络文件的路径。参数Sheet是保存为网页的工作表名称。参数 Source 是确定对象源的唯一名称,该参数取决于SourceType 参数。当SourceType 参数是 xlSourceRange常数时,Source是单元格区域或者应用到单元格区域的名称;如果参数SourceType 是常数,例如xlSourceChart、xlSourcePivotTable或者xlSourceQuery,那么Source则指定图表名称、 数据透视报告名称或者查询表名称。参数HTMLType指是否所选的工作表元素保存为交互式Office网 页组件或者静态的文本和图像。它可以是下面的常数之一: 常数 描述 xlHTMLCalc 使用电子表格组件。 该组件使得在Internet浏览器上直接查看、分析和计算电子表格数据成为 可能。该组件也允许更改文字、单元格、行和列的格式。 xlHTMLChart 使用图表组件。 该组件允许在浏览器里创建交互式图表。 xlHTMLList 使用数据透视表组件。 该组件允许在浏览器上重新排列、筛选和汇总信息。该组件也能够从电子 表格或者数据库(例如Assess、SQL服务器或者OLAP服务器)里显示数据。 xlHTMLStatic(缺省值) 使用静态(非交互式)HTML,仅可查看。 发布在HTML文档上的数据不可更改。 注意:Office Web Components允许在Internet浏览器上使用Excel分析功能。 参数DivID是用于HTML DIV标记上唯一的识别符,用来识别网页上的项目。参数Title是网页标 题。 我们在学习如何从VBA过程里使用Add方法之前,需要学习如何使用PublishObject的Publish方 法。该方法将允许发布某个特定文档的元素或者元素集合到网页上。该方法比较简单,如下所示: expression.Publish([Create]) 第 16 章 15BExcel 和 Internet 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 413 Expression是一个表达式,返回PublishObject对象或者PublishObjects集合。可选参数Create, 仅和PublishObject一起使用。如果HTML文件已经存在,那么设置该参数为True以覆盖该文件;设 置该参数为False在文件结尾处插入项目。如果该文件并不存在,那么不管参数Create的值是什么, 都会创建一个新HTML文件。 现在已经介绍了创建和发布Excel工作簿为HTML格式要使用的VBA对象和方法,那么可以开始 编程了。在接下来的练习里,将创建两个VBA过程,第一个将创建和发布一个带有内嵌图表的Excel 工作表为静态HTML;第二个过程将演示该相同的工作表如何能制作为一个交互式的网页。 1. 创建一个工作表和图表,如图16-8所示。 2. 保存该工作簿为PublishExample.xls。 3. 激活VBE编辑器窗口,并且插入一个新模块到当前VBA工程。 4. 在代码窗口,输入如下所示的两个过程。第一个过程PublishOnWeb将内嵌图表的工作表发布到 网页上,作为一个静态的HTML。第二个过程CreateHTMLFile则调用PublishOnWeb过程并且提 供两个需要的参数:要发布的工作簿名称和数据保存的位置HTML文件的名称。 ' 下面的过程将发布一个带内嵌图表的工作表作为静态的HTML Sub PublishOnWeb(strSheetName As String, strFileName As String) Dim objPub As Excel.PublishObject Set objPub = ThisWorkbook.PublishObjects.Add( _ SourceType:=xlSourceSheet, _ Filename:=strFileName, Sheet:=strSheetName, _ HtmlType:=xlHtmlStatic, Title:="Calls Analysis") objPub.Publish True End Sub Sub CreateHTMLFile() Call PublishOnWeb("Help Desk", "C:\WorksheetWithChart.htm") End Sub 5. 输入完两个过程后,运行名为CreateHTMLFile的过程。当该过程完成时,您将看到一个名为 C:\WorksheetWithChart.htm的新文件。同时,也有一个名为WorksheetWithChart_files的文件 夹,存储一些附加文件。 6. 在Windows文件浏览器里,双击第五步创建的文件C:\WorksheetWithChart.htm。该操作将导致 发布的工作表出现在Internet浏览器里(参见图16-9)。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 414 共 612 页 图16-8:使用“文件”菜单里的“另存为网页”命令或者从VBA过程程序可以将类似这个带内嵌图表的 工作表放置到网页上 第 16 章 15BExcel 和 Internet 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 415 图16-9:发布为静态(非交互式)网页的Excel工作表 要交互式地发布如图16-8所示的内嵌图表的工作表,执行下面的操作: 1. 在VBE编辑器窗口,插入一个新模块到当前VBA工程中。 2. 在代码窗口,输入InterHTML过程,如下所示: Sub InterHTML() Dim strSheetName As String strSheetName = "Help Desk" ' 确保没选择图表 Range("A1").Select ActiveWorkbook.PublishObjects _ .Add(xlSourceChart, "C:\Inter_WorksheetWithChart.htm", _ strSheetName, "Chart 1", xlHtmlChart).Publish (True) End Sub 3. 运行刚创建的过程。 4. 在Windows文件浏览器里,双击C:\Inter_WorksheetWithChart.htm,这将导致Internet浏览器被 激活并显示交互式图表(参见图16-10)。该网页也包含一个交互式工作表,为图表提供数据。 5. 更改B列里的任何数值,并查看图表上的变化。 注意:想要交互式地使用数据,计算机上必须安装了Microsoft IE 4.01或者更高版本以及学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 416 共 612 页 Microsoft Office Web Components。 图16-10:交互式发布的Excel工作表可以在浏览器上直接修改 16.2.1 网络服务器——存储和打开工作簿 可以通过使用VBA语句保存或打开存储在网络服务器上的工作簿,然而,在能够存储存工作簿 到网络服务器之前,必须在该服务器上运行FrontPage Server Extensions。 下面的语句将一个工作簿保存到网站中: ActiveWorkbook.SaveAs “http://www.yourWebSite.com/TestWkb.xls” 下面的语句打开存储在网站上的工作簿: Workbooks.Open(“http://www.yourWebSite.com/TestWkb.xls”) 16.3 Web查询 如果打算从某个网页获取数据并在Excel里使用和分析它们,那么可以打开Excel的数据菜单, 并选择“导入外部数据”|“新建Web查询”。Web查询允许直接从网络获取数据到Excel里面。将数 据放置到工作表后,可以使用Excel工具来进行数据分析。使用web查询,可以导入一个单个的表、 一系列表或者某特定网站包含的所有文本。 第 16 章 15BExcel 和 Internet 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 417 技巧16—1:准备使用Web查询 Excel 2002带有几个内置的网络查询,它们安装在C:\Program Files\Microsoft Office\Office10\ Queries文 件夹里面,并且可以通过Excel“文件”菜单上的“打开”命令来装载。这些查询的名称为: MSN MoneyCentral Investor Currency Rates.iqy MSN MoneyCentral Investor Major Indicies.iqy MSN MoneyCentral Investor Stock Quotes.iqy 如果您的C:\Program Files\Microsoft Office\Office10\ Queries文件夹是空的,那么需要更新当前的Excel安 装(使用控制面板上的“添加/删除程序”对话框)并且表明需要安装这些功能。 图16-11:使用内置的web查询MSN MoneyCentral Investor Stock Quotes.iqy,在激活查询后出现的对 话框中输入TDK,从网络中获取TDK公司的股价 想要运行web查询,就必须已与Internet连接。 Web查询可以是静态的也可以是动态的。静态查询总是返回相同的数据,而动态查询则允许用 户指定不同的参数来减少网页返回的数据。Web查询存储在后缀名为.iqy的文本文件里。.iqy文件的 内容可以使用任何文本编辑器(例如Windows记事本)打开后查看。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 418 共 612 页 图16-12:用户界面中的这个对话框允许创建web查询,而无须知道任何编程知识 图16-13:这个web查询文件引用您想获取数据的页面并指定参数来定义数据如何导入以及web服务器的 任何特定的指令 .iqy文件包含下面的部分: 部分名称 描述/示例 查询类型 (可选部分) 当使用版本部分时,设置为WEB: WEB 查询版本 (可选部分) 允许设置网络查询的版本号,例如: 1 第 16 章 15BExcel 和 Internet 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 419 URL (必需的) 将要获取数据的网页的URL,例如: http://investor.msn.com/external/excel/quotes.asp http://www.um.lublin.pl/oswiata/sz_pdst.htm POST参数 (可选部分) 可以使用POST或者GET方法向网络服务器发送参数。本部分用来通过POST 方法发送参数。这些参数需要分别输入在不同行上,如下例所示: http://www.xe.net/cgi-bin/ucc/convert From=USD&Amount=1&To=CAD From、Amount和To是参数名称,而等号(=)后面的值是参数设置。参数用&符 号来分隔。 注意:当使用GET方法发送参数时,使用问号将参数附加在URL地址之后,如 下所示: http://uk.finance.yahoo.com/quote?symbols=met Symbols是一个参数名称,而met则是一个想要从指定URL地址获取数据的股 票符号(一个参数值)。 16.3.1 使用VBA创建和运行Web查询 在前一节里,您学习了可以通过使用菜单选项或者在一个文本编辑器例如记事本里输入特殊的 指令来创建Web查询。创建Web查询的第三种方法是使用VBA语句。 想要编程创建web查询,那么使用QueryTables集合的Add方法。该集合属于Worksheet对象并 包含指定工作表中所有QueryTable对象。Add方法返回一个QueryTable对象,代表一个新查询。该 方法的格式如下: expression.Add(Connection, Destination, [Sql]) Expression是一个返回QueryTable对象的表达式,参数Connection指定该查询表的数据源,数 据源可以是下面之一: „ 带有格式为“URL;.”的网页地址的字符串,例如: "URL;http://www.nycenet.edu/distschweb/searchresult.asp" „ 表明指向现有网络查询文件(.iqy)路径的字符串,使用“ FINDER;.”格式。 例如: "FINDER;C:\Program Files\Microsoft Office\Office\Queries\ _ MSN MoneyCentral Investor Currency Rates.iqy" „ 包含OLE DB或者ODBC连接的字符串。ODBC连接字符串形式为“ODBC;.”, 例如: "ODBC;DSN=MyNorthwind;UID=NorthUser;PWD=UserPass;Database=Northwind" „ ADO或者DAO记录集对象。Excel将保留该记录集,直到查询表被删除或者该连接被更改。查 询表的结果不可编辑。 „ 表示文本文件路径字符串,格式为“TEXT;.”。例如: “TEXT;C:\myTextFile.txt” 参数destination是查询表目标区域(查询表结果放置的地方)左上角的单元格。该单元格必须在包 含expression里使用的QueryTable对象的工作表中。当使用QueryTable对象作为数据源时,不需要 可选参数Sql。 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 420 共 612 页 下面显示的示例过程在当前活动工作簿里创建了一个新的web查询,从网页获取的数据将放置在 一个工作表里作为静态文本。 1. 打开一个新工作簿,并保存为MyWebQueries.xls。 2. 切换到VBE编辑器窗口,并且插入一个新模块到当前VBA工程(MyWebQueries)中。 3. 在代码窗口,输入Manhattan_Schools过程,它从网页上获取曼哈顿的NYC学校列表。 Sub Manhattan_Schools() '在当前工作簿中创建一个web查询,连接到网络,获取数据并作为静态文本粘贴到工作表 中 With ActiveSheet.QueryTables.Add(Connection:= _ "URL;http://www.nycenet.edu/dist_sch/sch/" & _ "searchresult.asp?boro=Manhattan&flag=schoolInfo2", _ Destination:=Range("a1")) .BackgroundQuery = True .WebSelectionType = xlSpecifiedTables .WebTables = "Table3" .WebFormatting = xlWebFormattingNone .Refresh BackgroundQuery:=False .SaveData = True End With End Sub 4. 运行Manhattan_Schools过程。在程序的执行时,会发生下面的任务:(a)建立对指定网页的链 接,(b)获取网页数据, 以及(c)将数据放置到工作表。 5. 当激活上面的过程时,当前工作表将显示曼哈顿学校的名称和地址(参见图16-14)。注意,该 工作表不含有任何超链接,因为我们在过程代码里,将QueryTable的WebFormatting属性设置 为xlWebFormattingNone了。该属性决定当导入页到查询表时,从网页应用中所应用的格式, 可以使用下面的常量之一:xlWebFormattingAll、xlWebFormattingNone(缺省设置)、或者 xlWebFormattingRTF。QueryTable对象的BackgroundQuery设置为True时,允许在从网页获取 数据时可以执行其它工作表操作。WebSelectionType属性决定是否导入整个网页、网页上所有 表格、还是网页上的某个特定表格到查询表中。WebSelectionType属性可以是下面的常量之一: xlAllTables、xlEntirePage或者xlSpecifiedTables。 6. 当将网页导入到查询表时,WebTables属性明确一个逗号分隔的表格名称列表或者表格索引号。 7. 从网页获取数据后,为了在工作表里显示数据,必须使用QueryTable对象的Refresh方法。如果 在过程代码里忽略了该方法,从网页获取的数据将不可见。 8. 通过设置SaveData属性为True,将从网页获取的表格与工作簿一起保存。 图16-14:在VBA过程中使用web查询从网页中获取数据。(译者注:由于该网站已改版,因此 没能获取与原图相同的数据,读者可试试其它网站,主要是理解和掌握其方法)(本图略) 9. 在工作表里放置web查询的数据区域中的任意地方单击右键,然后从快捷菜单上选择“编辑查 询”。您将看到“编辑web查询”对话框,单击该对话框工具栏上的“选项”按钮可以访问“Web 查询选项”对话框,如图16-15所示。注意,在格式区域选取了选项按钮“无”,该选项按钮代 表过程代码中的WebFormatting属性的xlWebFormattingNone的设置。 第 16 章 15BExcel 和 Internet 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 421 图16-15:“Web查询选项”对话框 16.3.2 带参数的Web查询 为了从网页获取数据,经常需要指定参数。要在网络查询里向网络服务器发送参数,那么需要 在检查某个具体网络服务器使用哪种方法后,使用POST或者GET方法。可以使用下面的方法找到该 信息: 1. 激活浏览器并且输入想要获取数据的网页地址。例如,输入: 2. http://www.xe.net/ucc/ 学习 Excel VBA 与 XML、ASP 的协同应用 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 422 共 612 页 图16-16:这个网络可以将一种货币类型转换为另一种类型 3. 从浏览器的“查看”菜单里选择“源文件”,该网页源代码就会出现在记事本里面(参见图16- 17)。 4. 在记事本里,选择“编辑”菜单的“查找”并且输入“POST”作为要查找的文本。如果给网络 服务器发送参数使用的是POST方法,那么文本POST就应该出现,如下所示。POST方法后面 有提供数据的网络服务器URL地址。 现在,您知道了用于发送参数的方法后,还需要知道如何调用这些参数。 第 16 章 15BExcel 和 Internet 微软技术社区联盟成员,Excel 教程和软件下载中心,全球领先的 Excel 门户,学习 Office 技术的最佳社区: Excel Home (http:// www.excelhome.net) 423 图16-17:可以通过选择浏览器菜单中的“查看”|“源文件”检查某特定网页的源代码,源代码将出现 在记事本中 5. 在记事本中,选择“编辑”菜单上的“查找”并且输入“Name”作为查找字符串。在单词name 之后,您应该看到引号里的text,这个text就是第一个参数的名称。在单词“value=”后面,您 应该可以看到参数的当前值。例如:
在上面的HTML语句中,单词“Amount”是参数的名称,“1”是该参数的当前值。参数值 也可以是HTML